Compare commits

...

188 Commits

Author SHA1 Message Date
Victor Vieux 0d078b6581 fix -link parsing 2013-11-26 00:09:45 -08:00
Victor Vieux 14d9f04e89 fix docker run on an unknown image 2013-11-25 23:28:48 -08:00
Solomon Hykes db28e839e0 Merge pull request #2857 from shykes/0.7-release
0.7 release
2013-11-25 22:14:33 -08:00
Solomon Hykes de30ffb2c3 Add 0.7.0 Changelog entry, with notable high-level features since 0.6.0. 2013-11-26 06:06:22 +00:00
Solomon Hykes 5c5ee194cb Bump version file to 0.7.0 2013-11-26 06:06:22 +00:00
Solomon Hykes b6dd67c707 Merge pull request #2868 from jamtur01/ubtfixes
Tidied up Ubuntu installation documentation
2013-11-25 22:05:36 -08:00
Michael Crosby 740958dda7 Merge pull request #2867 from shykes/more-random-names
Add more moods and inventor names to random name generator
2013-11-25 22:04:29 -08:00
Solomon Hykes c38386d876 Remove non-ascii characters from name generator 2013-11-26 06:02:37 +00:00
Victor Vieux 4267fb66ef Merge pull request #2866 from tianon/binaries-docs
Update "Binaries" installation language to point to distro packages first
2013-11-25 22:00:20 -08:00
James Turnbull a74b512540 Tidied up Ubuntu installation documentation 2013-11-26 00:58:17 -05:00
Solomon Hykes 60809a4f72 Add more moods to random name generator 2013-11-26 05:55:48 +00:00
Tianon Gravi 65fcc81b42 Update "Binaries" installation language to point to distro packages first
Also, added a minor consistency update to our usage of "wget".
2013-11-25 22:53:50 -07:00
Michael Crosby 06cf8fee1b Merge pull request #2865 from tianon/arch-docs
A couple tiny docs consistency fixes
2013-11-25 21:53:16 -08:00
Tianon Gravi c92dab0eb4 Update Fedora placeholder to include the same "community" and "pre-1.0" warnings so we don't forget them 2013-11-25 22:48:51 -07:00
Tianon Gravi 6ad5b2bcf4 Update Arch Linux instructions to include warnings at the top like other "community" platforms 2013-11-25 22:47:15 -07:00
Tianon Gravi 77f1362c64 Merge pull request #2864 from tianon/gentoo-docs
Update the Gentoo installation docs for 0.7+
2013-11-25 21:42:14 -08:00
Tianon Gravi 4049359bee Update the Gentoo installation docs for 0.7+ 2013-11-25 22:40:24 -07:00
Michael Crosby 7daefc9d3f Merge pull request #2862 from shykes/integration-tests-vfs
Improve integration tests with vfs driver
2013-11-25 21:37:35 -08:00
Victor Vieux d4c32b9015 Merge pull request #2854 from dotcloud/copy_dockerinit
create a copy of dockerinit on new runtime to handle upgrades
2013-11-25 21:31:33 -08:00
Solomon Hykes 8bd6127ab3 Merge pull request #2815 from tianon/hack-make-tgz
Add tgz bundlescript
2013-11-25 21:29:14 -08:00
Solomon Hykes 2302293244 Fix a race condition in the integration tests 2013-11-26 05:28:00 +00:00
Tianon Gravi fd7ff6411d Merge pull request #2863 from jamtur01/instsort
Installation docs update
2013-11-25 21:19:30 -08:00
James Turnbull 59f76bf1c7 Installation docs update
* Updated TOC sort order
* Fixed some grammar in Rackspace doc
* Updated titles to remove Linux where not needed
2013-11-26 00:16:30 -05:00
Solomon Hykes 02cb7f45fa Fix a race condition in TestInterruptedRegister 2013-11-26 05:05:15 +00:00
Solomon Hykes a937313747 Always use the 'vfs' storage driver in integration tests. To test other drivers, we need a dedicated driver validation suite. 2013-11-26 05:05:15 +00:00
Solomon Hykes fb3d60f27a Move all graph tests into integration, because they now rely on the underlying graph driver, which currently cannot be mocked. 2013-11-26 05:05:15 +00:00
Tianon Gravi 5ff74e268d Merge pull request #2860 from jamtur01/fedora2
Updated Fedora docs with simple placeholder
2013-11-25 20:59:15 -08:00
Solomon Hykes 09b7b55e2c Merge pull request #2861 from crosbymichael/fix-time-race-archive
Set consistent time for sample dir in archive test
2013-11-25 20:24:16 -08:00
Victor Vieux 110c4f2043 create a copy of dockerinit 2013-11-25 20:21:54 -08:00
Michael Crosby 0d1b5d7676 Set consistent time for sample dir in archive test 2013-11-25 19:58:14 -08:00
James Turnbull 5242a49f3f Updated Fedora docs
* Added warning to Fedora docs
2013-11-25 22:51:38 -05:00
Michael Crosby 2586c042ae Merge pull request #2810 from dotcloud/use_utc_time
Use UTC for time
2013-11-25 19:11:59 -08:00
Solomon Hykes 688e86c625 Merge pull request #2858 from jamtur01/fedora
Added Fedora skeleton
2013-11-25 19:10:50 -08:00
James Turnbull 750d2d8d07 Added Fedora skeleton 2013-11-25 22:06:18 -05:00
Guillaume J. Charmes 19df6c32c0 Merge branch 'master' into use_utc_time
Conflicts:
	image.go
2013-11-25 19:01:13 -08:00
Solomon Hykes 1d903da6fd Merge pull request #2609 from shykes/0.6.5-dm-plugin
Move aufs to a storage driver, add devicemapper and dummy drivers
2013-11-25 18:58:26 -08:00
Solomon Hykes aaefb8c07c Docs: update install pages (ubuntu and kernel requirements) to reflect the optional nature of AUFS 2013-11-26 02:45:32 +00:00
Guillaume J. Charmes b3959e69b5 Merge pull request #2837 from shykes/0.7-names
New collection of random names for 0.7
2013-11-25 18:39:36 -08:00
Solomon Hykes 43c7df946d Merge branch 'master' into 0.6.5-dm-plugin 2013-11-26 02:00:25 +00:00
Solomon Hykes 6acdf68ee1 Merge pull request #2804 from tianon/fix-hack-git-assumption
Hack: add support for compiling using make.sh without '.git'
2013-11-25 16:52:22 -08:00
Michael Crosby 487b3d8a8c Merge pull request #72 from shykes/creack-reduce-debug
Reduce debugf frequency to avoid terminal freeze
2013-11-25 15:50:07 -08:00
Andy Rothfusz 33f70f8978 Merge pull request #2725 from SvenDowideit/docker-export-doc
add 'to STDOUT' to the help and give an example
2013-11-25 14:42:57 -08:00
Andy Rothfusz 809239c0af Merge pull request #2853 from estenberg/cfe-docker-process
docker in-container process management with CFEngine
2013-11-25 14:31:52 -08:00
Eystein Måløy Stenberg 937f52aef9 docker in-container process management with CFEngine 2013-11-25 14:24:23 -08:00
Andy Rothfusz aa48acc5ec Merge pull request #2849 from jamtur01/supervisor
Added a Supervisor process management example
2013-11-25 13:50:44 -08:00
Andy Rothfusz ac70e296db Merge pull request #2836 from fj/topic/spinx⇒sphinx
Corrects misspellings of 'Sphinx'
2013-11-25 13:40:26 -08:00
Andy Rothfusz e2c3860ec3 Merge pull request #2835 from fj/topic/update-native-installation-instructions
Use requirements.txt rather than manual installation for docs
2013-11-25 13:28:18 -08:00
Andy Rothfusz 2d715bf3c0 Merge pull request #2833 from twillouer/patch-1
Better copy/paste :)
2013-11-25 13:21:50 -08:00
Victor Vieux d9e54e28e7 Merge pull request #2848 from pnasrat/713-data-races
Fix data race in TestLogEvent
2013-11-25 12:07:01 -08:00
Guillaume J. Charmes 78d2e2dc37 Reduce debugf frequency to avoid terminal freeze 2013-11-25 12:06:16 -08:00
Paul Nasrat abfdaca3f8 Fix data race in TestLogEvent
Found with -race. Improve locking on Server.
2013-11-25 14:17:58 -05:00
Guillaume J. Charmes 3a2fbcfdec Merge pull request #68 from crosbymichael/daemon-docs
Add daemon docs with selecting graph driver
2013-11-25 11:06:28 -08:00
James Turnbull ba2b36e192 Added a Supervisor process management example 2013-11-25 14:05:01 -05:00
Guillaume J. Charmes d47d49a2f9 Merge pull request #71 from crosbymichael/fix-dummy-import
Rename dummy in driver.go to vfs
2013-11-25 10:30:20 -08:00
Michael Crosby 8b0b10b6f9 Rename dummy in driver.go to vfs 2013-11-25 10:28:17 -08:00
Guillaume J. Charmes 399c71de83 Merge pull request #67 from crosbymichael/driver-flag
Change graph-driver flag to be s
2013-11-25 09:45:31 -08:00
Michael Crosby d8f4b733f2 Add daemon docs with selecting graph driver 2013-11-25 09:44:55 -08:00
Michael Crosby b4eeb6be61 Change graph-driver flag to be s 2013-11-25 09:43:32 -08:00
Guillaume J. Charmes 41704d8933 Merge pull request #69 from crosbymichael/rename-dummy
Rename dummy driver to vfs
2013-11-25 09:42:50 -08:00
Michael Crosby 64dd4afed6 Merge pull request #70 from codeaholics/use-https-url-in-dockerfile
Clone LVM using https: instead of git:
2013-11-25 09:17:35 -08:00
Andy Rothfusz 5da1ed3291 Merge pull request #2730 from SvenDowideit/docker-insert-doc
Be more explicit about what insert does.
2013-11-25 09:16:08 -08:00
Danny Yates ad23745456 Clone LVM using https: instead of git:
The ports for the git protocol are not open in all corporate environments
2013-11-25 17:12:18 +00:00
Michael Crosby cee0a292d0 Rename dummy driver to vfs 2013-11-25 09:04:04 -08:00
Solomon Hykes 07180f3aa7 Add unit test for awesomeness of generated container names 2013-11-25 00:25:15 +00:00
Solomon Hykes a606474825 Add Ada Lovelace to the names generator (thanks James Turnbull) 2013-11-25 00:22:06 +00:00
Solomon Hykes 5d6ef3177b New collection of random names for 0.7: mood + famous inventor. Eg. 'sad-tesla' or 'naughty-turing' 2013-11-25 00:22:06 +00:00
William Delanoue 0a89db04fe Better copy/paste :) 2013-11-24 13:55:53 +01:00
John Feminella 1cce9f25b2 Corrects misspellings of 'Sphinx' 2013-11-23 16:21:27 -05:00
John Feminella f1b3e278b9 Suggest users install dependencies by using requirements.txt, not manually 2013-11-23 15:53:50 -05:00
Sven Dowideit e288e7763e be very explicit about docker insert - it does not insert a file into an IMAGE, it creates a new image that adds only that file to its specified parent 2013-11-23 12:48:34 +10:00
Sven Dowideit 9696ec509a add 'to STDOUT' to the help, and add a simple eg that mentions it too 2013-11-23 12:28:50 +10:00
Guillaume J. Charmes ba6dd1d8d6 Merge pull request #2827 from dotcloud/2778-fix-shell-corrupt
2778 fix shell corrupt
2013-11-22 15:46:02 -08:00
Guillaume J. Charmes c67f9b671d Remove useless New*Opt functions, singleline Opt types 2013-11-22 14:42:30 -08:00
Guillaume J. Charmes 1c8ae47770 Make a validation on links name 2013-11-22 14:33:25 -08:00
Guillaume J. Charmes d55998be81 Remove goroutine leak. Make sure termcap are reset each time. 2013-11-22 14:09:37 -08:00
Michael Crosby e69bbd239e Merge branch 'master' into 0.6.5-dm-plugin 2013-11-22 14:06:59 -08:00
Victor Vieux a26f9183bd Merge pull request #56 from shykes/creack-dm-plugin
Use variable to call functions in devmapper_wrapper + some formatting
2013-11-22 14:05:59 -08:00
Victor Vieux 944a48ec5a Merge pull request #2822 from dotcloud/better_port_help
Better port help
2013-11-22 13:59:35 -08:00
Guillaume J. Charmes 79e2b33ede Use variable to call functions in devmapper_wrapper + some formatting 2013-11-22 12:25:37 -08:00
Guillaume J. Charmes 076c0eab70 Format CmdRun 2013-11-22 12:14:34 -08:00
Guillaume J. Charmes 1f9223a7c2 Use a constant for PortSpecTemplate + display the template in the CmdRun help 2013-11-22 12:00:34 -08:00
Guillaume J. Charmes 476559458d Reformatting parseRun and partParse 2013-11-22 11:58:02 -08:00
Michael Crosby d4c8fb9ee2 Merge pull request #2769 from vbatts/none_bridge
Do not setup bridge ip if 'none' bridge is provided
2013-11-22 11:48:33 -08:00
Michael Crosby ae8c589d35 Merge pull request #2823 from dotcloud/2817-run_rm-fix
Fix docker run -rm
2013-11-22 11:35:29 -08:00
Guillaume J. Charmes 6130f2531e Merge pull request #2677 from mrallen1/fix/2627
Check for a terminal before using escapes
2013-11-22 11:30:16 -08:00
Vincent Batts ef14aaf627 fix the nil pointer panic on closing a disabled network manager
Issue #2768
2013-11-22 14:28:49 -05:00
Victor Vieux 1e7c04fcfe fix -rm 2013-11-22 11:23:48 -08:00
Mark Allen 8b0cd60019 Pass terminal setting to display utils 2013-11-22 00:05:55 -06:00
Tianon Gravi b3f5973f41 Add new tgz bundlescript to default make and release 2013-11-21 22:34:54 -07:00
Tianon Gravi 3314e005f3 Add new bundlescript to build a tgz 2013-11-21 22:34:54 -07:00
Victor Vieux a93e40a158 Merge pull request #2812 from dotcloud/bump_v0.6.7
Bump v0.6.7
2013-11-21 18:39:15 -08:00
Victor Vieux cb48ecc9dc Merge pull request #2812 from dotcloud/bump_v0.6.7
Bump v0.6.7
2013-11-21 18:21:19 -08:00
Victor Vieux 53f1bf0f99 Bump version to v0.6.7 2013-11-21 18:03:41 -08:00
Victor Vieux 9dc59797e0 Merge branch 'master' into bump_v0.6.7 2013-11-21 17:40:29 -08:00
Michael Crosby 0cecc2a78c Merge branch 'master' into 0.6.5-dm-plugin
Conflicts:
	container.go
	image.go
2013-11-21 17:18:41 -08:00
Michael Crosby 437bdeee59 Merge pull request #62 from shykes/devmapper-unit-tests
Devmapper unit tests
2013-11-21 16:54:50 -08:00
Guillaume J. Charmes 806abe90ba Use UTC for time 2013-11-21 16:43:36 -08:00
Michael Crosby 25e443a3c7 Merge pull request #2798 from dotcloud/fix_state_race
Refactor State to be 100% thread safe
2013-11-21 16:39:50 -08:00
Guillaume J. Charmes 33e70864a2 Refactor State to be 100% thread safe 2013-11-21 16:34:58 -08:00
Guillaume J. Charmes bc82940a57 Forbid syscalls in tests, add 2 new unit tests 2013-11-21 16:32:16 -08:00
Michael Crosby d6e6214d37 Merge pull request #2678 from cxmcc/minor
Minor code simplification for Containers api
2013-11-21 16:27:15 -08:00
Michael Crosby 70f1bd3104 Merge pull request #2488 from viirya/fix_container_volumes_delete
Skip the volumes mounted when deleting the volumes of container.
2013-11-21 16:20:22 -08:00
Michael Crosby f7c2a00557 Merge pull request #2304 from unclejack/fix_layer_size_computation
Fix layer size computation: handle hard links correctly
2013-11-21 16:10:24 -08:00
Victor Vieux 8498b44eac Merge pull request #2731 from SvenDowideit/docker-commit-doc
make the docker commit help more copy&pasteable
2013-11-21 15:33:05 -08:00
Andy Rothfusz e2dcfc2cf7 Merge pull request #2772 from bitoiu/patch-3
Change to documentation for AWS AMI request
2013-11-21 15:15:04 -08:00
Andy Rothfusz 9b4c151142 Merge pull request #2717 from metalivedev/2342-uploadingcontext
Fix #2342. Harmonize information about ADD. Cross-link build info.
2013-11-21 15:09:47 -08:00
Andy Rothfusz 50239e0573 Merge pull request #2799 from rosenhouse/patch-1
Fix title on doc page for remote_api_client_libraries.rst
2013-11-21 14:40:33 -08:00
Guillaume J. Charmes 42c23b0f04 Merge pull request #2763 from dotcloud/use_full_id_delete
Do ont truncate ID on docker rmi
2013-11-21 14:39:38 -08:00
Guillaume J. Charmes eec91e7941 Merge pull request #2805 from pmorie/typo
Fix typo in pullImage
2013-11-21 14:35:07 -08:00
Paul Morie 3f17844b6e Fix typo in pullImage 2013-11-21 17:29:03 -05:00
Tianon Gravi efd0e13ca7 Add support for compiling using make.sh without '.git' (ie, from a Github tarball, for example) 2013-11-21 15:11:17 -07:00
Guillaume J. Charmes bcdeb37bb6 Merge pull request #2794 from dotcloud/fix_status_code_and_usage
fix status code and usage
2013-11-21 13:48:38 -08:00
Gabe Rosenhouse 362e9d6b3c Fix title on doc page for remote_api_client_libraries.rst 2013-11-21 12:45:01 -08:00
Guillaume J. Charmes c4ab498920 Merge pull request #2797 from crosbymichael/revert-exit-lock
Revert "Lock state before we modify."
2013-11-21 12:19:23 -08:00
Michael Crosby cb70eedfda Revert "Lock state before we modify."
This reverts commit d7e2fc8982.
2013-11-21 12:11:25 -08:00
Victor Vieux 75a7f4d90c Return status exit status 2 on usage, move parserun into commands.go, display usage on stderr 2013-11-21 11:43:07 -08:00
Victor Vieux da824b4a5a Merge pull request #63 from tianon/flag-help-consistency
Update a few flag help strings for consistency and clarity
2013-11-21 11:31:34 -08:00
Tianon Gravi 1ab6b8bf49 Update a few flag help strings for consistency and clarity 2013-11-21 12:30:17 -07:00
Victor Vieux eaeb969138 Merge pull request #2795 from pnasrat/docker-testmultipleattachrestart-race
Lock state before we modify.
2013-11-21 11:28:02 -08:00
Michael Crosby 253214f07d Update ImageExport after merge fail 2013-11-21 10:26:21 -08:00
Michael Crosby a2c9d2da93 Merge branch 'master' into 0.6.5-dm-plugin 2013-11-21 10:21:30 -08:00
Paul Nasrat d7e2fc8982 Lock state before we modify.
When we start a container we lock state, we should do the same in stop.

Detected via -race.
2013-11-21 08:06:02 -05:00
Victor Vieux f20c738963 Merge pull request #2793 from crosbymichael/offline-ids
Allow images to be saved and loaded by id and repository
2013-11-21 02:44:00 -08:00
Solomon Hykes df258f5861 Devmapper: test driver initialization and its interaction with libdevmapper 2013-11-21 02:17:03 +00:00
Solomon Hykes 60f728b170 Devmapper: wrap calls to os/exec for easier mocking 2013-11-21 02:16:26 +00:00
Solomon Hykes 2b7c63b1b5 devmapper: skip test which are not unit tests 2013-11-21 02:12:51 +00:00
Michael Crosby fd7ab143bf Allow images to be saved and loaded by id and repository 2013-11-20 17:28:19 -08:00
Andy Rothfusz 82cdd21a34 Merge pull request #2727 from SvenDowideit/docker-images-doc
add some common examples for docker images, and tell the user what -a filters out
2013-11-20 16:30:48 -08:00
Michael Crosby a9230af52e Merge pull request #57 from shykes/wait_on_pull_already
Wait on pull already in progress
2013-11-20 15:58:51 -08:00
Victor Vieux 2f0d18ac4a Merge pull request #59 from crosbymichael/fix-image-save-size
Fix image save size
2013-11-20 15:58:31 -08:00
Michael Crosby 6469422465 Merge pull request #58 from crosbymichael/update-aufs-tests
Add more aufs tests and implement Status
2013-11-20 15:41:59 -08:00
Michael Crosby 5306053e21 Add more aufs tests and implement Status 2013-11-20 15:41:37 -08:00
Solomon Hykes e2390318bb Devmapper: mock all calls to libdevmapper in the unit tests, and deny them by default 2013-11-20 23:39:02 +00:00
Michael Crosby 4e0c76b321 Ensure that only the layers are compressed and not mnt points 2013-11-20 15:37:26 -08:00
Solomon Hykes da514223d1 Devmapper: remove deprecated test helpers 2013-11-20 23:25:27 +00:00
Solomon Hykes 023ff36704 devmapper: fix typo 2013-11-20 23:12:19 +00:00
Michael Crosby 8fdbf46afb Fix image size calc on initial save 2013-11-20 14:51:04 -08:00
Guillaume J. Charmes d233894c25 Add devmapper struct doc 2013-11-20 14:09:46 -08:00
Victor Vieux 8a756f417e wait on pull already in progress 2013-11-20 14:04:19 -08:00
Guillaume J. Charmes a39bd65662 Remove os from devmapper 2013-11-20 13:05:17 -08:00
Guillaume J. Charmes 5690139785 Remove all syscall calls from devicemapper 2013-11-20 12:49:01 -08:00
Solomon Hykes 92f94f06ae Mock calls to system functions to facilitate unit testing 2013-11-20 20:05:10 +00:00
Michael Crosby 2bc35287a0 Merge pull request #2723 from SvenDowideit/doc-build-image
Use the work Path for docker cp CONTAINER:PATH
2013-11-20 11:10:13 -08:00
Michael Crosby 2382a0f920 Merge branch 'master' into 0.6.5-dm-plugin
Conflicts:
	server.go
2013-11-20 11:07:42 -08:00
Guillaume J. Charmes 579a5c843b Merge pull request #51 from crosbymichael/driver-specific-image
Handle image metadata when drivers are switched
2013-11-20 10:45:51 -08:00
Guillaume J. Charmes 6ebb236aa1 Merge pull request #55 from alexlarsson/dm-plugins-cleanup-graph-test
graph_test: Clean up drivers allocated in tempGraph()
2013-11-20 10:41:18 -08:00
Guillaume J. Charmes b4f7078a02 Merge pull request #2773 from dotcloud/speed_up_docker_ps
speedup docker ps
2013-11-20 10:40:03 -08:00
Guillaume J. Charmes 9e68913397 Merge pull request #2760 from dotcloud/improve_engine_test
improve tests on the engine
2013-11-20 10:38:46 -08:00
Michael Crosby 1b28cdc7f9 Handle image metadata when drivers are switched 2013-11-20 10:31:51 -08:00
Victor Vieux 304a80fcd5 Merge pull request #2747 from jpoimboe/test-fixes
Test fixes
2013-11-20 10:07:40 -08:00
Guillaume J. Charmes 04f1d4dcdb Merge pull request #2788 from jpoimboe/resize-after-start
resize pty after starting
2013-11-20 10:01:59 -08:00
Josh Poimboeuf 171d681724 resize pty after starting
Since ptyMaster is created during container start (startPty), it should
resized after starting, not before.
2013-11-20 11:40:30 -06:00
Vincent Batts 60cb5f1a34 do not setup bridge ip if bridgeNetwork is nil. This is the case when
-b='none' bridge is provided.

issue #2768
https://bugzilla.redhat.com/show_bug.cgi?id=1032094
2013-11-20 09:36:38 -05:00
Alexander Larsson cfdc284abe tags test: cleanup driver
If not we leak a devicemapper pool
2013-11-20 14:52:06 +01:00
Alexander Larsson 7192be47c5 graph_test: Clean up drivers allocated in tempGraph()
If we don't do this we leak devicemapper pools with the dm backend.
2013-11-20 14:42:32 +01:00
Michael Crosby cd4c1ac356 Merge pull request #2779 from crosbymichael/pin-python-deps
Pip python deps to a specific version
2013-11-19 21:29:09 -08:00
Michael Crosby b8af68a92b Merge pull request #1974 from dotcloud/1155-offline-image-transfer
Implement offline image transfers
2013-11-19 20:00:58 -08:00
Michael Crosby 9de4590498 Add typo in remove all. Ensure tmpDir is removed 2013-11-20 03:52:33 +00:00
Michael Crosby 0ef6fed5c7 Fix error checks for offline transfer and remove unneeded debug output 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV 383f95bba1 Adding 1.7 documentation 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV 1211065c8d Adding content type to images/(name)/get 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV 844c13bce6 Fixing build 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV 6014db4a7e Setting offline package version to 1.0 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV bf504f2afa Adding spaces to fix docs build 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV 61a8020e51 Adding version info, switching to defer for cleanup 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV 7eaa59f626 Offline Image Transfers #1155 2013-11-20 03:52:33 +00:00
Michael Crosby 2cccbbdadd Pip python deps to a specific version 2013-11-19 19:25:54 -08:00
Tianon Gravi 66beafa9f3 rename file to remove testing flags from docker binary 2013-11-19 12:41:29 -08:00
Victor Vieux 8e5ab5bfca improve tests on the engine 2013-11-19 11:47:47 -08:00
Andy Rothfusz 4f9f83d6c6 Fix #2342. Harmonize information about ADD. Cross-link build info. 2013-11-19 11:16:28 -08:00
Victor Vieux 145c2008ae speedup docker ps 2013-11-19 11:02:10 -08:00
Vitor Monteiro aeb304b37c Change to documentation for AWS AMI request
Hi guys, it just might be me, but clicking the AMI from http://cloud-images.ubuntu.com/locator/ec2/ is broken to me. So I just did it via the normal Create Instance Wizard.

I just though some people might have the same issue.

Sorry if my markdown for links is screwed up, I went by the examples, since I'm used to the `[]()` traditional one.

Cheers.
2013-11-19 17:50:38 +00:00
unclejack 78c843c8ef fix container size computation: handle hard links
This change makes docker compute container size correctly.

The old code isn't taking hard links into account. Containers could
seem like they're up to 1-1.5x larger than they really were.
2013-11-19 13:53:34 +02:00
unclejack ac821f2446 fix layer size computation: handle hard links
This change makes docker compute layer size correctly.

The old code isn't taking hard links into account. Layers could
seem like they're up to 1-1.5x larger than they really were.
2013-11-19 13:37:54 +02:00
Victor Vieux 2fe4467d73 Do ont truncate ID on docker rmi 2013-11-18 18:39:02 -08:00
Josh Poimboeuf 4b80ec9aae test: remove extra args in TestExitCode
The extra blank argument isn't needed and confuses libvirt.
2013-11-18 15:07:11 -06:00
Josh Poimboeuf fef41ef7bf test: fix TestRmi race condition 2013-11-18 15:07:06 -06:00
Josh Poimboeuf fe302fbfd2 test: 2 second timeout (not 2000) 2013-11-18 10:23:30 -06:00
Josh Poimboeuf 72d02ecdde test: skip TestCreate on Fedora due to lxc utils bug
In the dind environment running on a Fedora host, the lxc utils get
confused by the /sys/fs/cgroup/cpuacct,cpu cgroup mount and lxc-start
fails trying to access the wrong cgroup directory.
2013-11-18 10:23:30 -06:00
Josh Poimboeuf baa687bed2 test: fix TestCreateStartRestartStopStartKillRm
cat needs stdin opened, otherwise it dies immediately.
2013-11-18 10:23:30 -06:00
Josh Poimboeuf 30ea0bebce test: put each arg in a separate string
Each arg to docker run should be placed in a separate string.
Otherwise, when starting the command via exec.Cmd, the command is
interpreted as "echo test", which can't be found.
2013-11-18 10:23:06 -06:00
Sven Dowideit bc74f65068 make the docker commit help more copy&pasteable 2013-11-16 21:11:34 +10:00
Sven Dowideit 152459b727 add some common examples for docker images, and tell the user what -a filters out 2013-11-16 20:45:30 +10:00
Sven Dowideit 27159ce6ba expunge the word 'Resource' in reference to a file/dir in a CONTAINER - that way users don't wonder how its different from a Path 2013-11-16 20:15:04 +10:00
Xiuming Chen 0013aa7d9f Minor code simplification for Containers api 2013-11-13 01:29:00 -08:00
Liang-Chi Hsieh 1d7f22c0d4 use Binds key in hostConfig to detect volumes mounted from external. 2013-11-13 15:08:46 +08:00
Liang-Chi Hsieh 6a693176d6 skip the volumes mounted when deleting the volumes of container. 2013-11-13 14:58:24 +08:00
Victor Vieux 6d420407ca Merge pull request #2577 from dotcloud/bump_v0.6.6
Bump v0.6.6
2013-11-06 12:03:03 -08:00
73 changed files with 3403 additions and 1265 deletions
+51
View File
@@ -1,5 +1,55 @@
# Changelog
## 0.7.0 (2013-11-25)
#### Notable features since 0.6.0
* Storage drivers: choose from aufs, device mapper, vfs or btrfs.
* Standard Linux support: docker now runs on unmodified linux kernels and all major distributions.
* Links: compose complex software stacks by connecting containers to each other.
* Container naming: organize your containers by giving them memorable names.
* Advanced port redirects: specify port redirects per interface, or keep sensitive ports private.
* Offline transfer: push and pull images to the filesystem without losing information.
* Quality: numerous bugfixes and small usability improvements. Significant increase in test coverage.
## 0.6.7 (2013-11-21)
#### Runtime
* Improved stability, fixes some race conditons
* Skip the volumes mounted when deleting the volumes of container.
* Fix layer size computation: handle hard links correctly
* Use the work Path for docker cp CONTAINER:PATH
* Fix tmp dir never cleanup
* Speedup docker ps
* More informative error message on name collisions
* Fix nameserver regex
* Always return long id's
* Fix container restart race condition
* Keep published ports on docker stop;docker start
* Fix container networking on Fedora
* Correctly express "any address" to iptables
* Fix network setup when reconnecting to ghost container
* Prevent deletion if image is used by a running container
* Lock around read operations in graph
#### RemoteAPI
* Return full ID on docker rmi
#### Client
+ Add -tree option to images
+ Offline image transfer
* Exit with status 2 on usage error and display usage on stderr
* Do not forward SIGCHLD to container
* Use string timestamp for docker events -since
#### Other
* Update to go 1.2rc5
+ Add /etc/default/docker support to upstart
## 0.6.6 (2013-11-06)
#### Runtime
@@ -17,6 +67,7 @@
+ Prevent DNS server conflicts in CreateBridgeIface
+ Validate bind mounts on the server side
+ Use parent image config in docker build
* Fix regression in /etc/hosts
#### Client
+3 -4
View File
@@ -46,10 +46,9 @@ run apt-get install -y -q ruby1.9.3 rubygems libffi-dev
run gem install --no-rdoc --no-ri fpm
run apt-get install -y -q reprepro dpkg-sig
# Install s3cmd 1.0.1 (earlier versions don't support env variables in the config)
run apt-get install -y -q python-pip
run pip install s3cmd
run pip install python-magic
run pip install s3cmd==1.1.0-beta3
run pip install python-magic==0.4.6
run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
# Runtime dependencies
@@ -58,7 +57,7 @@ run apt-get install -y -q lxc
run apt-get install -y -q aufs-tools
# Get lvm2 source for compiling statically
run git clone git://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout v2_02_103
run git clone https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout v2_02_103
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
# note: we can't use "git clone -b" above because it requires at least git 1.7.10 to be able to use that on a tag instead of a branch and we only have 1.7.9.5
+1 -1
View File
@@ -1 +1 @@
0.6.6-dev
0.7.0
+16 -2
View File
@@ -254,7 +254,7 @@ func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
wf.Flush()
if since != 0 {
// If since, send previous events that happened after the timestamp
for _, event := range srv.events {
for _, event := range srv.GetEvents() {
if event.Time >= since {
err := sendEvent(wf, &event)
if err != nil && err.Error() == "JSON error" {
@@ -534,6 +534,18 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
return nil
}
func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
name := vars["name"]
if version > 1.0 {
w.Header().Set("Content-Type", "application/x-tar")
}
return srv.ImageExport(name, w)
}
func postImagesLoad(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
return srv.ImageLoad(r.Body)
}
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return nil
@@ -967,7 +979,7 @@ func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r *
}
if copyData.Resource == "" {
return fmt.Errorf("Resource cannot be empty")
return fmt.Errorf("Path cannot be empty")
}
if copyData.Resource[0] == '/' {
copyData.Resource = copyData.Resource[1:]
@@ -1036,6 +1048,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/images/json": getImagesJSON,
"/images/viz": getImagesViz,
"/images/search": getImagesSearch,
"/images/{name:.*}/get": getImagesGet,
"/images/{name:.*}/history": getImagesHistory,
"/images/{name:.*}/json": getImagesByName,
"/containers/ps": getContainersJSON,
@@ -1052,6 +1065,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/build": postBuild,
"/images/create": postImagesCreate,
"/images/{name:.*}/insert": postImagesInsert,
"/images/load": postImagesLoad,
"/images/{name:.*}/push": postImagesPush,
"/images/{name:.*}/tag": postImagesTag,
"/containers/create": postContainersCreate,
+18 -11
View File
@@ -71,17 +71,27 @@ func createSampleDir(t *testing.T, root string) {
{Symlink, "symlink1", "target1", 0666},
{Symlink, "symlink2", "target2", 0666},
}
now := time.Now()
for _, info := range files {
p := path.Join(root, info.path)
if info.filetype == Dir {
if err := os.MkdirAll(path.Join(root, info.path), info.permissions); err != nil {
if err := os.MkdirAll(p, info.permissions); err != nil {
t.Fatal(err)
}
} else if info.filetype == Regular {
if err := ioutil.WriteFile(path.Join(root, info.path), []byte(info.contents), info.permissions); err != nil {
if err := ioutil.WriteFile(p, []byte(info.contents), info.permissions); err != nil {
t.Fatal(err)
}
} else if info.filetype == Symlink {
if err := os.Symlink(info.contents, path.Join(root, info.path)); err != nil {
if err := os.Symlink(info.contents, p); err != nil {
t.Fatal(err)
}
}
if info.filetype != Symlink {
// Set a consistent ctime, atime for all files and dirs
if err := os.Chtimes(p, now, now); err != nil {
t.Fatal(err)
}
}
@@ -200,6 +210,9 @@ func TestChangesDirsMutated(t *testing.T) {
if err := copyDir(src, dst); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(src)
defer os.RemoveAll(dst)
mutateSampleDir(t, dst)
changes, err := ChangesDirs(dst, src)
@@ -225,8 +238,7 @@ func TestChangesDirsMutated(t *testing.T) {
{"/symlinknew", ChangeAdd},
}
i := 0
for ; i < max(len(changes), len(expectedChanges)); i++ {
for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
if i >= len(expectedChanges) {
t.Fatalf("unexpected change %s\n", changes[i].String())
}
@@ -240,14 +252,9 @@ func TestChangesDirsMutated(t *testing.T) {
} else if changes[i].Path < expectedChanges[i].Path {
t.Fatalf("unexpected change %s\n", changes[i].String())
} else {
t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
}
}
for ; i < len(expectedChanges); i++ {
}
os.RemoveAll(src)
os.RemoveAll(dst)
}
func TestApplyLayer(t *testing.T) {
+1 -1
View File
@@ -241,7 +241,7 @@ func (b *buildFile) CmdVolume(args string) error {
volume = []string{args}
}
if b.config.Volumes == nil {
b.config.Volumes = NewPathOpts()
b.config.Volumes = PathOpts{}
}
for _, v := range volume {
b.config.Volumes[v] = struct{}{}
+395 -128
View File
@@ -22,6 +22,7 @@ import (
"net/url"
"os"
"os/signal"
"path"
"path/filepath"
"reflect"
"regexp"
@@ -92,6 +93,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"insert", "Insert a file in an image"},
{"inspect", "Return low-level information on a container"},
{"kill", "Kill a running container"},
{"load", "Load an image from a tar archive"},
{"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"},
@@ -102,6 +104,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"rm", "Remove one or more containers"},
{"rmi", "Remove one or more images"},
{"run", "Run a command in a new container"},
{"save", "Save an image to a tar archive"},
{"search", "Search for an image in the docker index"},
{"start", "Start a stopped container"},
{"stop", "Stop a running container"},
@@ -117,7 +120,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
}
func (cli *DockerCli) CmdInsert(args ...string) error {
cmd := Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
cmd := cli.Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -159,7 +162,7 @@ func MkBuildContext(dockerfile string, files [][2]string) (archive.Archive, erro
}
func (cli *DockerCli) CmdBuild(args ...string) error {
cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
cmd := cli.Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
tag := cmd.String("t", "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success")
suppressOutput := cmd.Bool("q", false, "Suppress verbose build output")
noCache := cmd.Bool("no-cache", false, "Do not use cache when building the image")
@@ -257,7 +260,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
// 'docker login': login / register a user to registry service.
func (cli *DockerCli) CmdLogin(args ...string) error {
cmd := Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+auth.IndexServerAddress()+"\" is the default.")
cmd := cli.Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+auth.IndexServerAddress()+"\" is the default.")
var username, password, email string
@@ -365,7 +368,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
// 'docker wait': block until a container stops
func (cli *DockerCli) CmdWait(args ...string) error {
cmd := Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -388,7 +391,7 @@ func (cli *DockerCli) CmdWait(args ...string) error {
// 'docker version': show version information
func (cli *DockerCli) CmdVersion(args ...string) error {
cmd := Subcmd("version", "", "Show the docker version information.")
cmd := cli.Subcmd("version", "", "Show the docker version information.")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -439,7 +442,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
// 'docker info': display system-wide information.
func (cli *DockerCli) CmdInfo(args ...string) error {
cmd := Subcmd("info", "", "Display system-wide information")
cmd := cli.Subcmd("info", "", "Display system-wide information")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -495,7 +498,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
}
func (cli *DockerCli) CmdStop(args ...string) error {
cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM, and then SIGKILL after grace period)")
cmd := cli.Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM, and then SIGKILL after grace period)")
nSeconds := cmd.Int("t", 10, "Number of seconds to wait for the container to stop before killing it.")
if err := cmd.Parse(args); err != nil {
return nil
@@ -522,7 +525,7 @@ func (cli *DockerCli) CmdStop(args ...string) error {
}
func (cli *DockerCli) CmdRestart(args ...string) error {
cmd := Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
cmd := cli.Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
nSeconds := cmd.Int("t", 10, "Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10")
if err := cmd.Parse(args); err != nil {
return nil
@@ -565,7 +568,7 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
}
func (cli *DockerCli) CmdStart(args ...string) error {
cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
cmd := cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
attach := cmd.Bool("a", false, "Attach container's stdout/stderr and forward all signals to the process")
openStdin := cmd.Bool("i", false, "Attach container's stdin")
if err := cmd.Parse(args); err != nil {
@@ -577,6 +580,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
}
var cErr chan error
var tty bool
if *attach || *openStdin {
if cmd.NArg() > 1 {
return fmt.Errorf("Impossible to start and attach multiple containers at once.")
@@ -593,17 +597,13 @@ func (cli *DockerCli) CmdStart(args ...string) error {
return err
}
tty = container.Config.Tty
if !container.Config.Tty {
sigc := cli.forwardAllSignals(cmd.Arg(0))
defer utils.StopCatch(sigc)
}
if container.Config.Tty && cli.isTerminal {
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
return err
}
}
var in io.ReadCloser
v := url.Values{}
@@ -641,14 +641,20 @@ func (cli *DockerCli) CmdStart(args ...string) error {
}
return encounteredError
}
if *openStdin || *attach {
if tty && cli.isTerminal {
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
utils.Errorf("Error monitoring TTY size: %s\n", err)
}
}
return <-cErr
}
return nil
}
func (cli *DockerCli) CmdInspect(args ...string) error {
cmd := Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image")
cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -699,7 +705,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
}
func (cli *DockerCli) CmdTop(args ...string) error {
cmd := Subcmd("top", "CONTAINER [ps OPTIONS]", "Lookup the running processes of a container")
cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Lookup the running processes of a container")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -731,7 +737,7 @@ func (cli *DockerCli) CmdTop(args ...string) error {
}
func (cli *DockerCli) CmdPort(args ...string) error {
cmd := Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
cmd := cli.Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -773,7 +779,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
// 'docker rmi IMAGE' removes all images with the name IMAGE
func (cli *DockerCli) CmdRmi(args ...string) error {
cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
cmd := cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -809,7 +815,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
}
func (cli *DockerCli) CmdHistory(args ...string) error {
cmd := Subcmd("history", "[OPTIONS] IMAGE", "Show the history of an image")
cmd := cli.Subcmd("history", "[OPTIONS] IMAGE", "Show the history of an image")
quiet := cmd.Bool("q", false, "only show numeric IDs")
noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
@@ -845,7 +851,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID))
}
fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.Created, 0))))
if *noTrunc {
fmt.Fprintf(w, "%s\t", out.CreatedBy)
@@ -866,7 +872,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
}
func (cli *DockerCli) CmdRm(args ...string) error {
cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
cmd := cli.Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
v := cmd.Bool("v", false, "Remove the volumes associated to the container")
link := cmd.Bool("link", false, "Remove the specified link and not the underlying container")
@@ -900,7 +906,7 @@ func (cli *DockerCli) CmdRm(args ...string) error {
// 'docker kill NAME' kills a running container
func (cli *DockerCli) CmdKill(args ...string) error {
cmd := Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL)")
cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL)")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -922,7 +928,7 @@ func (cli *DockerCli) CmdKill(args ...string) error {
}
func (cli *DockerCli) CmdImport(args ...string) error {
cmd := Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create a new filesystem image from the contents of a tarball(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).")
cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create a new filesystem image from the contents of a tarball(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).")
if err := cmd.Parse(args); err != nil {
return nil
@@ -956,7 +962,7 @@ func (cli *DockerCli) CmdImport(args ...string) error {
}
func (cli *DockerCli) CmdPush(args ...string) error {
cmd := Subcmd("push", "NAME", "Push an image or a repository to the registry")
cmd := cli.Subcmd("push", "NAME", "Push an image or a repository to the registry")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1018,7 +1024,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
}
func (cli *DockerCli) CmdPull(args ...string) error {
cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
cmd := cli.Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
tag := cmd.String("t", "", "Download tagged image in repository")
if err := cmd.Parse(args); err != nil {
return nil
@@ -1078,9 +1084,9 @@ func (cli *DockerCli) CmdPull(args ...string) error {
}
func (cli *DockerCli) CmdImages(args ...string) error {
cmd := Subcmd("images", "[OPTIONS] [NAME]", "List images")
cmd := cli.Subcmd("images", "[OPTIONS] [NAME]", "List images")
quiet := cmd.Bool("q", false, "only show numeric IDs")
all := cmd.Bool("a", false, "show all images")
all := cmd.Bool("a", false, "show all images (by default filter out the intermediate images used to build)")
noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
flViz := cmd.Bool("viz", false, "output graph in graphviz format")
flTree := cmd.Bool("tree", false, "output graph in tree format")
@@ -1202,7 +1208,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
}
if !*quiet {
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", repo, tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", repo, tag, out.ID, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.Created, 0))))
if out.VirtualSize > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
} else {
@@ -1277,7 +1283,7 @@ func displayablePorts(ports []APIPort) string {
}
func (cli *DockerCli) CmdPs(args ...string) error {
cmd := Subcmd("ps", "[OPTIONS]", "List containers")
cmd := cli.Subcmd("ps", "[OPTIONS]", "List containers")
quiet := cmd.Bool("q", false, "Only display numeric IDs")
size := cmd.Bool("s", false, "Display sizes")
all := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.")
@@ -1344,7 +1350,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
if !*noTrunc {
out.Command = utils.Trunc(out.Command, 20)
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ","))
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ","))
if *size {
if out.SizeRootFs > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
@@ -1366,10 +1372,10 @@ func (cli *DockerCli) CmdPs(args ...string) error {
}
func (cli *DockerCli) CmdCommit(args ...string) error {
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
cmd := cli.Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
flComment := cmd.String("m", "", "Commit message")
flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"")
flConfig := cmd.String("run", "", "Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
flConfig := cmd.String("run", "", "Config automatically applied when the image is run. "+`(ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1418,7 +1424,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
}
func (cli *DockerCli) CmdEvents(args ...string) error {
cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server")
cmd := cli.Subcmd("events", "[OPTIONS]", "Get real time events from the server")
since := cmd.String("since", "", "Show previously created events and then stream.")
if err := cmd.Parse(args); err != nil {
return nil
@@ -1451,7 +1457,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
}
func (cli *DockerCli) CmdExport(args ...string) error {
cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive")
cmd := cli.Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive to STDOUT")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1468,7 +1474,7 @@ func (cli *DockerCli) CmdExport(args ...string) error {
}
func (cli *DockerCli) CmdDiff(args ...string) error {
cmd := Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem")
cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1494,7 +1500,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error {
}
func (cli *DockerCli) CmdLogs(args ...string) error {
cmd := Subcmd("logs", "CONTAINER", "Fetch the logs of a container")
cmd := cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1521,7 +1527,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
}
func (cli *DockerCli) CmdAttach(args ...string) error {
cmd := Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container")
cmd := cli.Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container")
noStdin := cmd.Bool("nostdin", false, "Do not attach stdin")
proxy := cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
if err := cmd.Parse(args); err != nil {
@@ -1543,7 +1549,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
return err
}
if !container.State.Running {
if !container.State.IsRunning() {
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
}
@@ -1576,7 +1582,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
}
func (cli *DockerCli) CmdSearch(args ...string) error {
cmd := Subcmd("search", "TERM", "Search the docker index for images")
cmd := cli.Subcmd("search", "TERM", "Search the docker index for images")
noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
trusted := cmd.Bool("trusted", false, "Only show trusted builds")
stars := cmd.Int("stars", 0, "Only displays with at least xxx stars")
@@ -1632,15 +1638,7 @@ type ports []int
// AttachOpts stores arguments to 'docker run -a', eg. which streams to attach to
type AttachOpts map[string]bool
func NewAttachOpts() AttachOpts {
return make(AttachOpts)
}
func (opts AttachOpts) String() string {
// Cast to underlying map type to avoid infinite recursion
return fmt.Sprintf("%v", map[string]bool(opts))
}
func (opts AttachOpts) String() string { return fmt.Sprintf("%v", map[string]bool(opts)) }
func (opts AttachOpts) Set(val string) error {
if val != "stdin" && val != "stdout" && val != "stderr" {
return fmt.Errorf("Unsupported stream name: %s", val)
@@ -1649,24 +1647,22 @@ func (opts AttachOpts) Set(val string) error {
return nil
}
func (opts AttachOpts) Get(val string) bool {
if res, exists := opts[val]; exists {
return res
// LinkOpts stores arguments to `docker run -link`
type LinkOpts []string
func (link *LinkOpts) String() string { return fmt.Sprintf("%v", []string(*link)) }
func (link *LinkOpts) Set(val string) error {
if _, err := parseLink(val); err != nil {
return err
}
return false
*link = append(*link, val)
return nil
}
// PathOpts stores a unique set of absolute paths
type PathOpts map[string]struct{}
func NewPathOpts() PathOpts {
return make(PathOpts)
}
func (opts PathOpts) String() string {
return fmt.Sprintf("%v", map[string]struct{}(opts))
}
func (opts PathOpts) String() string { return fmt.Sprintf("%v", map[string]struct{}(opts)) }
func (opts PathOpts) Set(val string) error {
var containerPath string
@@ -1688,7 +1684,7 @@ func (opts PathOpts) Set(val string) error {
}
func (cli *DockerCli) CmdTag(args ...string) error {
cmd := Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY[:TAG]", "Tag an image into a repository")
cmd := cli.Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY[:TAG]", "Tag an image into a repository")
force := cmd.Bool("f", false, "Force")
if err := cmd.Parse(args); err != nil {
return nil
@@ -1721,8 +1717,220 @@ func (cli *DockerCli) CmdTag(args ...string) error {
return nil
}
//FIXME Only used in tests
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
cmd := flag.NewFlagSet("run", flag.ContinueOnError)
cmd.SetOutput(ioutil.Discard)
cmd.Usage = nil
return parseRun(cmd, args, capabilities)
}
func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
var (
// FIXME: use utils.ListOpts for attach and volumes?
flAttach = AttachOpts{}
flVolumes = PathOpts{}
flLinks = LinkOpts{}
flPublish utils.ListOpts
flExpose utils.ListOpts
flEnv utils.ListOpts
flDns utils.ListOpts
flVolumesFrom utils.ListOpts
flLxcOpts utils.ListOpts
flAutoRemove = cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
flDetach = cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
flNetwork = cmd.Bool("n", true, "Enable networking for this container")
flPrivileged = cmd.Bool("privileged", false, "Give extended privileges to this container")
flPublishAll = cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
flStdin = cmd.Bool("i", false, "Keep stdin open even if not attached")
flTty = cmd.Bool("t", false, "Allocate a pseudo-tty")
flContainerIDFile = cmd.String("cidfile", "", "Write the container ID to the file")
flEntrypoint = cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
flHostname = cmd.String("h", "", "Container host name")
flMemoryString = cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
flUser = cmd.String("u", "", "Username or UID")
flWorkingDir = cmd.String("w", "", "Working directory inside the container")
flCpuShares = cmd.Int64("c", 0, "CPU shares (relative weight)")
// For documentation purpose
_ = cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
_ = cmd.String("name", "", "Assign a name to the container")
)
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
cmd.Var(&flLinks, "link", "Add link to another container (name:alias)")
cmd.Var(&flPublish, "p", fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", PortSpecTemplateFormat))
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
cmd.Var(&flEnv, "e", "Set environment variables")
cmd.Var(&flDns, "dns", "Set custom dns servers")
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
if err := cmd.Parse(args); err != nil {
return nil, nil, cmd, err
}
// Check if the kernel supports memory limit cgroup.
if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit {
*flMemoryString = ""
}
// Validate input params
if *flDetach && len(flAttach) > 0 {
return nil, nil, cmd, ErrConflictAttachDetach
}
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
return nil, nil, cmd, ErrInvalidWorikingDirectory
}
if *flDetach && *flAutoRemove {
return nil, nil, cmd, ErrConflictDetachAutoRemove
}
// If neither -d or -a are set, attach to everything by default
if len(flAttach) == 0 && !*flDetach {
if !*flDetach {
flAttach.Set("stdout")
flAttach.Set("stderr")
if *flStdin {
flAttach.Set("stdin")
}
}
}
var envs []string
for _, env := range flEnv {
arr := strings.Split(env, "=")
if len(arr) > 1 {
envs = append(envs, env)
} else {
v := os.Getenv(env)
envs = append(envs, env+"="+v)
}
}
var flMemory int64
if *flMemoryString != "" {
parsedMemory, err := utils.RAMInBytes(*flMemoryString)
if err != nil {
return nil, nil, cmd, err
}
flMemory = parsedMemory
}
var binds []string
// add any bind targets to the list of container volumes
for bind := range flVolumes {
arr := strings.Split(bind, ":")
if len(arr) > 1 {
if arr[0] == "/" {
return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'")
}
dstDir := arr[1]
flVolumes[dstDir] = struct{}{}
binds = append(binds, bind)
delete(flVolumes, bind)
}
}
var (
parsedArgs = cmd.Args()
runCmd []string
entrypoint []string
image string
)
if len(parsedArgs) >= 1 {
image = cmd.Arg(0)
}
if len(parsedArgs) > 1 {
runCmd = parsedArgs[1:]
}
if *flEntrypoint != "" {
entrypoint = []string{*flEntrypoint}
}
lxcConf, err := parseLxcConfOpts(flLxcOpts)
if err != nil {
return nil, nil, cmd, err
}
var (
domainname string
hostname = *flHostname
parts = strings.SplitN(hostname, ".", 2)
)
if len(parts) > 1 {
hostname = parts[0]
domainname = parts[1]
}
ports, portBindings, err := parsePortSpecs(flPublish)
if err != nil {
return nil, nil, cmd, err
}
// Merge in exposed ports to the map of published ports
for _, e := range flExpose {
if strings.Contains(e, ":") {
return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e)
}
p := NewPort(splitProtoPort(e))
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
}
config := &Config{
Hostname: hostname,
Domainname: domainname,
PortSpecs: nil, // Deprecated
ExposedPorts: ports,
User: *flUser,
Tty: *flTty,
NetworkDisabled: !*flNetwork,
OpenStdin: *flStdin,
Memory: flMemory,
CpuShares: *flCpuShares,
AttachStdin: flAttach["stdin"],
AttachStdout: flAttach["stdout"],
AttachStderr: flAttach["stderr"],
Env: envs,
Cmd: runCmd,
Dns: flDns,
Image: image,
Volumes: flVolumes,
VolumesFrom: strings.Join(flVolumesFrom, ","),
Entrypoint: entrypoint,
WorkingDir: *flWorkingDir,
}
hostConfig := &HostConfig{
Binds: binds,
ContainerIDFile: *flContainerIDFile,
LxcConf: lxcConf,
Privileged: *flPrivileged,
PortBindings: portBindings,
Links: flLinks,
PublishAllPorts: *flPublishAll,
}
if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
config.MemorySwap = -1
}
// When allocating stdin in attached mode, close stdin at client disconnect
if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true
}
return config, hostConfig, cmd, nil
}
func (cli *DockerCli) CmdRun(args ...string) error {
config, hostConfig, cmd, err := ParseRun(args, nil)
config, hostConfig, cmd, err := parseRun(cli.Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil)
if err != nil {
return err
}
@@ -1731,30 +1939,33 @@ func (cli *DockerCli) CmdRun(args ...string) error {
return nil
}
flRm := cmd.Lookup("rm")
autoRemove, _ := strconv.ParseBool(flRm.Value.String())
// Retrieve relevant client-side config
var (
flName = cmd.Lookup("name")
flRm = cmd.Lookup("rm")
flSigProxy = cmd.Lookup("sig-proxy")
autoRemove, _ = strconv.ParseBool(flRm.Value.String())
sigProxy, _ = strconv.ParseBool(flSigProxy.Value.String())
)
flSigProxy := cmd.Lookup("sig-proxy")
sigProxy, _ := strconv.ParseBool(flSigProxy.Value.String())
flName := cmd.Lookup("name")
// Disable sigProxy in case on TTY
if config.Tty {
sigProxy = false
}
var containerIDFile *os.File
var containerIDFile io.WriteCloser
if len(hostConfig.ContainerIDFile) > 0 {
if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
if _, err := os.Stat(hostConfig.ContainerIDFile); err == nil {
return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
}
containerIDFile, err = os.Create(hostConfig.ContainerIDFile)
if err != nil {
if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil {
return fmt.Errorf("failed to create the container ID file: %s", err)
}
defer containerIDFile.Close()
}
containerValues := url.Values{}
name := flName.Value.String()
if name != "" {
if name := flName.Value.String(); name != "" {
containerValues.Set("name", name)
}
@@ -1775,8 +1986,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
v.Set("tag", tag)
// Resolve the Repository name from fqn to endpoint + name
var endpoint string
endpoint, _, err = registry.ResolveRepositoryName(repos)
endpoint, _, err := registry.ResolveRepositoryName(repos)
if err != nil {
return err
}
@@ -1794,32 +2004,27 @@ func (cli *DockerCli) CmdRun(args ...string) error {
registryAuthHeader := []string{
base64.URLEncoding.EncodeToString(buf),
}
err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{
"X-Registry-Auth": registryAuthHeader,
})
if err != nil {
if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil {
return err
}
body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config)
if err != nil {
if body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config); err != nil {
return err
}
}
if err != nil {
} else if err != nil {
return err
}
runResult := &APIRun{}
err = json.Unmarshal(body, runResult)
if err != nil {
var runResult APIRun
if err := json.Unmarshal(body, &runResult); err != nil {
return err
}
for _, warning := range runResult.Warnings {
fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
}
if len(hostConfig.ContainerIDFile) > 0 {
if _, err = containerIDFile.WriteString(runResult.ID); err != nil {
if _, err = containerIDFile.Write([]byte(runResult.ID)); err != nil {
return fmt.Errorf("failed to write the container ID to the file: %s", err)
}
}
@@ -1830,27 +2035,38 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}
var (
wait chan struct{}
errCh chan error
waitDisplayId chan struct{}
errCh chan error
)
if !config.AttachStdout && !config.AttachStderr {
// Make this asynchrone in order to let the client write to stdin before having to read the ID
wait = make(chan struct{})
waitDisplayId = make(chan struct{})
go func() {
defer close(wait)
defer close(waitDisplayId)
fmt.Fprintf(cli.out, "%s\n", runResult.ID)
}()
}
hijacked := make(chan bool)
// We need to instanciate the chan because the select needs it. It can
// be closed but can't be uninitialized.
hijacked := make(chan io.Closer)
// Block the return until the chan gets closed
defer func() {
utils.Debugf("End of CmdRun(), Waiting for hijack to finish.")
if _, ok := <-hijacked; ok {
utils.Errorf("Hijack did not finish (chan still open)")
}
}()
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
v := url.Values{}
var (
out, stderr io.Writer
in io.ReadCloser
v = url.Values{}
)
v.Set("stream", "1")
var out, stderr io.Writer
var in io.ReadCloser
if config.AttachStdin {
v.Set("stdin", "1")
@@ -1878,7 +2094,12 @@ func (cli *DockerCli) CmdRun(args ...string) error {
// Acknowledge the hijack before starting
select {
case <-hijacked:
case closer := <-hijacked:
// Make sure that hijack gets closed when returning. (result
// in closing hijack chan and freeing server's goroutines.
if closer != nil {
defer closer.Close()
}
case err := <-errCh:
if err != nil {
utils.Debugf("Error hijack: %s", err)
@@ -1904,36 +2125,42 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}
}
// Detached mode: wait for the id to be displayed and return.
if !config.AttachStdout && !config.AttachStderr {
// Detached mode
<-wait
} else {
running, status, err := getExitCode(cli, runResult.ID)
if err != nil {
return err
}
if autoRemove {
if running {
return fmt.Errorf("Impossible to auto-remove a detached container")
}
// Wait for the process to
if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil {
return err
}
if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil {
return err
}
}
if status != 0 {
return &utils.StatusError{Status: status}
}
<-waitDisplayId
return nil
}
var status int
// Attached mode
if autoRemove {
// Autoremove: wait for the container to finish, retrieve
// the exit code and remove the container
if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil {
return err
}
if _, status, err = getExitCode(cli, runResult.ID); err != nil {
return err
}
if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil {
return err
}
} else {
// No Autoremove: Simply retrieve the exit code
if _, status, err = getExitCode(cli, runResult.ID); err != nil {
return err
}
}
if status != 0 {
return &utils.StatusError{Status: status}
}
return nil
}
func (cli *DockerCli) CmdCp(args ...string) error {
cmd := Subcmd("cp", "CONTAINER:RESOURCE HOSTPATH", "Copy files/folders from the RESOURCE to the HOSTPATH")
cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTPATH", "Copy files/folders from the PATH to the HOSTPATH")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1947,7 +2174,7 @@ func (cli *DockerCli) CmdCp(args ...string) error {
info := strings.Split(cmd.Arg(0), ":")
if len(info) != 2 {
return fmt.Errorf("Error: Resource not specified")
return fmt.Errorf("Error: Path not specified")
}
copyData.Resource = info[1]
@@ -1967,6 +2194,41 @@ func (cli *DockerCli) CmdCp(args ...string) error {
return nil
}
func (cli *DockerCli) CmdSave(args ...string) error {
cmd := cli.Subcmd("save", "IMAGE DESTINATION", "Save an image to a tar archive")
if err := cmd.Parse(args); err != nil {
return err
}
if cmd.NArg() != 1 {
cmd.Usage()
return nil
}
image := cmd.Arg(0)
if err := cli.stream("GET", "/images/"+image+"/get", nil, cli.out, nil); err != nil {
return err
}
return nil
}
func (cli *DockerCli) CmdLoad(args ...string) error {
cmd := cli.Subcmd("load", "SOURCE", "Load an image from a tar archive")
if err := cmd.Parse(args); err != nil {
return err
}
if cmd.NArg() != 0 {
cmd.Usage()
return nil
}
if err := cli.stream("POST", "/images/load", cli.in, cli.out, nil); err != nil {
return err
}
return nil
}
func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) {
var params io.Reader
if data != nil {
@@ -2078,7 +2340,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
}
if matchesContentType(resp.Header.Get("Content-Type"), "application/json") {
return utils.DisplayJSONMessagesStream(resp.Body, out)
return utils.DisplayJSONMessagesStream(resp.Body, out, cli.isTerminal)
}
if _, err := io.Copy(out, resp.Body); err != nil {
return err
@@ -2086,7 +2348,12 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
return nil
}
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan bool) error {
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error {
defer func() {
if started != nil {
close(started)
}
}()
// fixme: refactor client to support redirect
re := regexp.MustCompile("/+")
path = re.ReplaceAllString(path, "/")
@@ -2116,7 +2383,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
defer rwc.Close()
if started != nil {
started <- true
started <- rwc
}
var receiveStdout chan error
@@ -2217,12 +2484,12 @@ func (cli *DockerCli) monitorTtySize(id string) error {
return nil
}
func Subcmd(name, signature, description string) *flag.FlagSet {
func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
flags := flag.NewFlagSet(name, flag.ContinueOnError)
flags.Usage = func() {
// FIXME: use custom stdout or return error
fmt.Fprintf(os.Stdout, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
fmt.Fprintf(cli.err, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
flags.PrintDefaults()
os.Exit(2)
}
return flags
}
@@ -2267,7 +2534,7 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
if err := json.Unmarshal(body, c); err != nil {
return false, -1, err
}
return c.State.Running, c.State.ExitCode, nil
return c.State.IsRunning(), c.State.GetExitCode(), nil
}
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
+33 -245
View File
@@ -4,7 +4,6 @@ import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/graphdriver"
@@ -18,14 +17,15 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
)
type Container struct {
sync.Mutex
root string // Path to the "home" of the container, including metadata.
rootfs string // Path to the root filesystem of the container.
@@ -159,218 +159,6 @@ func NewPort(proto, port string) Port {
return Port(fmt.Sprintf("%s/%s", port, proto))
}
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
if os.Getenv("TEST") != "" {
cmd.SetOutput(ioutil.Discard)
cmd.Usage = nil
}
flHostname := cmd.String("h", "", "Container host name")
flWorkingDir := cmd.String("w", "", "Working directory inside the container")
flUser := cmd.String("u", "", "Username or UID")
flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
flAttach := NewAttachOpts()
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
flMemoryString := cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
cmd.String("name", "", "Assign a name to the container")
flPublishAll := cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
*flMemoryString = ""
}
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
var flPublish utils.ListOpts
cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)")
var flExpose utils.ListOpts
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
var flEnv utils.ListOpts
cmd.Var(&flEnv, "e", "Set environment variables")
var flDns utils.ListOpts
cmd.Var(&flDns, "dns", "Set custom dns servers")
flVolumes := NewPathOpts()
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
var flVolumesFrom utils.ListOpts
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
var flLxcOpts utils.ListOpts
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
var flLinks utils.ListOpts
cmd.Var(&flLinks, "link", "Add link to another container (name:alias)")
if err := cmd.Parse(args); err != nil {
return nil, nil, cmd, err
}
if *flDetach && len(flAttach) > 0 {
return nil, nil, cmd, ErrConflictAttachDetach
}
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
return nil, nil, cmd, ErrInvalidWorikingDirectory
}
if *flDetach && *flAutoRemove {
return nil, nil, cmd, ErrConflictDetachAutoRemove
}
// If neither -d or -a are set, attach to everything by default
if len(flAttach) == 0 && !*flDetach {
if !*flDetach {
flAttach.Set("stdout")
flAttach.Set("stderr")
if *flStdin {
flAttach.Set("stdin")
}
}
}
envs := []string{}
for _, env := range flEnv {
arr := strings.Split(env, "=")
if len(arr) > 1 {
envs = append(envs, env)
} else {
v := os.Getenv(env)
envs = append(envs, env+"="+v)
}
}
var flMemory int64
if *flMemoryString != "" {
parsedMemory, err := utils.RAMInBytes(*flMemoryString)
if err != nil {
return nil, nil, cmd, err
}
flMemory = parsedMemory
}
var binds []string
// add any bind targets to the list of container volumes
for bind := range flVolumes {
arr := strings.Split(bind, ":")
if len(arr) > 1 {
if arr[0] == "/" {
return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'")
}
dstDir := arr[1]
flVolumes[dstDir] = struct{}{}
binds = append(binds, bind)
delete(flVolumes, bind)
}
}
parsedArgs := cmd.Args()
runCmd := []string{}
entrypoint := []string{}
image := ""
if len(parsedArgs) >= 1 {
image = cmd.Arg(0)
}
if len(parsedArgs) > 1 {
runCmd = parsedArgs[1:]
}
if *flEntrypoint != "" {
entrypoint = []string{*flEntrypoint}
}
var lxcConf []KeyValuePair
lxcConf, err := parseLxcConfOpts(flLxcOpts)
if err != nil {
return nil, nil, cmd, err
}
hostname := *flHostname
domainname := ""
parts := strings.SplitN(hostname, ".", 2)
if len(parts) > 1 {
hostname = parts[0]
domainname = parts[1]
}
ports, portBindings, err := parsePortSpecs(flPublish)
if err != nil {
return nil, nil, cmd, err
}
// Merge in exposed ports to the map of published ports
for _, e := range flExpose {
if strings.Contains(e, ":") {
return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e)
}
p := NewPort(splitProtoPort(e))
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
}
config := &Config{
Hostname: hostname,
Domainname: domainname,
PortSpecs: nil, // Deprecated
ExposedPorts: ports,
User: *flUser,
Tty: *flTty,
NetworkDisabled: !*flNetwork,
OpenStdin: *flStdin,
Memory: flMemory,
CpuShares: *flCpuShares,
AttachStdin: flAttach.Get("stdin"),
AttachStdout: flAttach.Get("stdout"),
AttachStderr: flAttach.Get("stderr"),
Env: envs,
Cmd: runCmd,
Dns: flDns,
Image: image,
Volumes: flVolumes,
VolumesFrom: strings.Join(flVolumesFrom, ","),
Entrypoint: entrypoint,
WorkingDir: *flWorkingDir,
}
hostConfig := &HostConfig{
Binds: binds,
ContainerIDFile: *flContainerIDFile,
LxcConf: lxcConf,
Privileged: *flPrivileged,
PortBindings: portBindings,
Links: flLinks,
PublishAllPorts: *flPublishAll,
}
if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
config.MemorySwap = -1
}
// When allocating stdin in attached mode, close stdin at client disconnect
if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true
}
return config, hostConfig, cmd, nil
}
type PortMapping map[string]string // Deprecated
type NetworkSettings struct {
@@ -710,9 +498,10 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
}
func (container *Container) Start() (err error) {
container.State.Lock()
defer container.State.Unlock()
if container.State.Running {
container.Lock()
defer container.Unlock()
if container.State.IsRunning() {
return fmt.Errorf("The container %s is already running.", container.ID)
}
defer func() {
@@ -1046,7 +835,7 @@ func (container *Container) Start() (err error) {
}
// FIXME: save state on disk *first*, then converge
// this way disk state is used as a journal, eg. we can restore after crash etc.
container.State.setRunning(container.cmd.Process.Pid)
container.State.SetRunning(container.cmd.Process.Pid)
// Init the lock
container.waitLock = make(chan struct{})
@@ -1054,14 +843,14 @@ func (container *Container) Start() (err error) {
container.ToDisk()
go container.monitor()
defer utils.Debugf("Container running: %v", container.State.Running)
defer utils.Debugf("Container running: %v", container.State.IsRunning())
// We wait for the container to be fully running.
// Timeout after 5 seconds. In case of broken pipe, just retry.
// Note: The container can run and finish correctly before
// the end of this loop
for now := time.Now(); time.Since(now) < 5*time.Second; {
// If the container dies while waiting for it, just return
if !container.State.Running {
if !container.State.IsRunning() {
return nil
}
output, err := exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
@@ -1078,11 +867,11 @@ func (container *Container) Start() (err error) {
if strings.Contains(string(output), "RUNNING") {
return nil
}
utils.Debugf("Waiting for the container to start (running: %v): %s", container.State.Running, bytes.TrimSpace(output))
utils.Debugf("Waiting for the container to start (running: %v): %s", container.State.IsRunning(), bytes.TrimSpace(output))
time.Sleep(50 * time.Millisecond)
}
if container.State.Running {
if container.State.IsRunning() {
return ErrContainerStartTimeout
}
return ErrContainerStart
@@ -1163,11 +952,12 @@ func (container *Container) allocateNetwork() error {
return nil
}
var iface *NetworkInterface
var err error
if container.State.Ghost {
manager := container.runtime.networkManager
if manager.disabled {
var (
iface *NetworkInterface
err error
)
if container.State.IsGhost() {
if manager := container.runtime.networkManager; manager.disabled {
iface = &NetworkInterface{disabled: true}
} else {
iface = &NetworkInterface{
@@ -1203,10 +993,12 @@ func (container *Container) allocateNetwork() error {
}
}
portSpecs := make(map[Port]struct{})
bindings := make(map[Port][]PortBinding)
var (
portSpecs = make(map[Port]struct{})
bindings = make(map[Port][]PortBinding)
)
if !container.State.Ghost {
if !container.State.IsGhost() {
if container.Config.ExposedPorts != nil {
portSpecs = container.Config.ExposedPorts
}
@@ -1315,7 +1107,7 @@ func (container *Container) monitor() {
}
// Report status back
container.State.setStopped(exitCode)
container.State.SetStopped(exitCode)
// Release the lock
close(container.waitLock)
@@ -1365,10 +1157,10 @@ func (container *Container) cleanup() {
}
func (container *Container) kill(sig int) error {
container.State.Lock()
defer container.State.Unlock()
container.Lock()
defer container.Unlock()
if !container.State.Running {
if !container.State.IsRunning() {
return nil
}
@@ -1381,7 +1173,7 @@ func (container *Container) kill(sig int) error {
}
func (container *Container) Kill() error {
if !container.State.Running {
if !container.State.IsRunning() {
return nil
}
@@ -1406,7 +1198,7 @@ func (container *Container) Kill() error {
}
func (container *Container) Stop(seconds int) error {
if !container.State.Running {
if !container.State.IsRunning() {
return nil
}
@@ -1440,7 +1232,7 @@ func (container *Container) Restart(seconds int) error {
// Wait blocks until the container stops running, then returns its exit code.
func (container *Container) Wait() int {
<-container.waitLock
return container.State.ExitCode
return container.State.GetExitCode()
}
func (container *Container) Resize(h, w int) error {
@@ -1575,14 +1367,10 @@ func (container *Container) GetSize() (int64, int64) {
}
}
_, err = os.Stat(container.RootfsPath())
if err == nil {
filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error {
if fileInfo != nil {
sizeRootfs += fileInfo.Size()
}
return nil
})
if _, err = os.Stat(container.RootfsPath()); err != nil {
if sizeRootfs, err = utils.TreeSize(container.RootfsPath()); err != nil {
sizeRootfs = -1
}
}
return sizeRw, sizeRootfs
}
+11 -11
View File
@@ -25,20 +25,20 @@ func main() {
}
// FIXME: Switch d and D ? (to be more sshd like)
flVersion := flag.Bool("v", false, "Print version information and quit")
flDaemon := flag.Bool("d", false, "Daemon mode")
flDebug := flag.Bool("D", false, "Debug mode")
flDaemon := flag.Bool("d", false, "Enable daemon mode")
flDebug := flag.Bool("D", false, "Enable debug mode")
flAutoRestart := flag.Bool("r", true, "Restart previously running containers")
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking")
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
flRoot := flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime.")
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
flDns := flag.String("dns", "", "Set custom dns servers")
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge; use 'none' to disable container networking")
pidfile := flag.String("p", "/var/run/docker.pid", "Path to use for daemon PID file")
flRoot := flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime")
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS headers in the remote API")
flDns := flag.String("dns", "", "Force docker to use specific DNS servers")
flHosts := utils.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker")
flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports")
flag.Var(&flHosts, "H", "Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise")
flEnableIptables := flag.Bool("iptables", true, "Disable docker's addition of iptables rules")
flDefaultIp := flag.String("ip", "0.0.0.0", "Default IP address to use when binding container ports")
flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication")
flGraphDriver := flag.String("graph-driver", "", "For docker to use a specific graph driver")
flGraphDriver := flag.String("s", "", "Force the docker runtime to use a specific storage driver")
flag.Parse()
+1 -1
View File
@@ -9,7 +9,7 @@ run apt-get install -y python-setuptools make
run easy_install pip
#from docs/requirements.txt, but here to increase cacheability
run pip install Sphinx==1.1.3
run pip install sphinxcontrib-httpdomain==1.1.8
run pip install sphinxcontrib-httpdomain==1.1.9
add . /docs
run cd /docs; make docs
+7 -6
View File
@@ -41,11 +41,12 @@ its dependencies. There are two main ways to install this tool:
###Native Installation
* Install sphinx: `pip install sphinx`
* Mac OS X: `[sudo] pip-2.7 install sphinx`
* Install sphinx httpdomain contrib package: `pip install sphinxcontrib-httpdomain`
* Mac OS X: `[sudo] pip-2.7 install sphinxcontrib-httpdomain`
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
Install dependencies from `requirements.txt` file in your `docker/docs`
directory:
* Linux: `pip install -r docs/requirements.txt`
* Mac OS X: `[sudo] pip-2.7 -r docs/requirements.txt`
###Alternative Installation: Docker Container
@@ -136,7 +137,7 @@ Manpages
--------
* To make the manpages, run ``make man``. Please note there is a bug
in spinx 1.1.3 which makes this fail. Upgrade to the latest version
in Sphinx 1.1.3 which makes this fail. Upgrade to the latest version
of Sphinx.
* Then preview the manpage by running ``man _build/man/docker.1``,
where ``_build/man/docker.1`` is the path to the generated manfile
@@ -1171,6 +1171,53 @@ Monitor Docker's events
:statuscode 200: no error
:statuscode 500: server error
Get a tarball containing all images and tags in a repository
************************************************************
.. http:get:: /images/(name)/get
Get a tarball containing all images and metadata for the repository specified by ``name``.
**Example request**
.. sourcecode:: http
GET /images/ubuntu/get
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/x-tar
Binary data stream
:statuscode 200: no error
:statuscode 500: server error
Load a tarball with a set of images and tags into docker
********************************************************
.. http:post:: /images/load
Load a set of images and tags into the docker repository.
**Example request**
.. sourcecode:: http
POST /images/load
Tarball in body
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
:statuscode 200: no error
:statuscode 500: server error
3. Going further
================
@@ -1,4 +1,4 @@
:title: Registry API
:title: Remote API Client Libraries
:description: Various client libraries available to use with the Docker remote API
:keywords: API, Docker, index, registry, REST, documentation, clients, Python, Ruby, Javascript, Erlang, Go
+165 -25
View File
@@ -18,6 +18,38 @@ To list available commands, either run ``docker`` with no parameters or execute
...
.. _cli_daemon:
``daemon``
----------
::
Usage of docker:
-D=false: Enable debug mode
-H=[unix:///var/run/docker.sock]: Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise
-api-enable-cors=false: Enable CORS headers in the remote API
-b="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking
-d=false: Enable daemon mode
-dns="": Force docker to use specific DNS servers
-g="/var/lib/docker": Path to use as the root of the docker runtime
-icc=true: Enable inter-container communication
-ip="0.0.0.0": Default IP address to use when binding container ports
-iptables=true: Disable docker's addition of iptables rules
-p="/var/run/docker.pid": Path to use for daemon PID file
-r=true: Restart previously running containers
-s="": Force the docker runtime to use a specific storage driver
-v=false: Print version information and quit
The docker daemon is the persistent process that manages containers. Docker uses the same binary for both the
daemon and client. To run the daemon you provide the ``-d`` flag.
To force docker to use devicemapper as the storage driver, use ``docker -d -s devicemapper``
To set the dns server for all docker containers, use ``docker -d -dns 8.8.8.8``
To run the daemon with debug output, use ``docker -d -D``
.. _cli_attach:
``attach``
@@ -88,31 +120,65 @@ Examples:
Usage: docker build [OPTIONS] PATH | URL | -
Build a new container image from the source code at PATH
-t="": Repository name (and optionally a tag) to be applied to the resulting image in case of success.
-t="": Repository name (and optionally a tag) to be applied
to the resulting image in case of success.
-q=false: Suppress verbose build output.
-no-cache: Do not use the cache when building the image.
-rm: Remove intermediate containers after a successful build
When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context
The files at PATH or URL are called the "context" of the build. The
build process may refer to any of the files in the context, for
example when using an :ref:`ADD <dockerfile_add>` instruction. When a
single ``Dockerfile`` is given as URL, then no context is set. When a
git repository is set as URL, then the repository is used as the
context
.. _cli_build_examples:
.. seealso:: :ref:`dockerbuilder`.
Examples:
~~~~~~~~~
.. code-block:: bash
sudo docker build .
Uploading context 10240 bytes
Step 1 : FROM busybox
Pulling repository busybox
---> e9aa60c60128MB/2.284 MB (100%) endpoint: https://cdn-registry-1.docker.io/v1/
Step 2 : RUN ls -lh /
---> Running in 9c9e81692ae9
total 24
drwxr-xr-x 2 root root 4.0K Mar 12 2013 bin
drwxr-xr-x 5 root root 4.0K Oct 19 00:19 dev
drwxr-xr-x 2 root root 4.0K Oct 19 00:19 etc
drwxr-xr-x 2 root root 4.0K Nov 15 23:34 lib
lrwxrwxrwx 1 root root 3 Mar 12 2013 lib64 -> lib
dr-xr-xr-x 116 root root 0 Nov 15 23:34 proc
lrwxrwxrwx 1 root root 3 Mar 12 2013 sbin -> bin
dr-xr-xr-x 13 root root 0 Nov 15 23:34 sys
drwxr-xr-x 2 root root 4.0K Mar 12 2013 tmp
drwxr-xr-x 2 root root 4.0K Nov 15 23:34 usr
---> b35f4035db3f
Step 3 : CMD echo Hello World
---> Running in 02071fceb21b
---> f52f38b7823e
Successfully built f52f38b7823e
This will read the ``Dockerfile`` from the current directory. It will
also send any other files and directories found in the current
directory to the ``docker`` daemon.
This example specifies that the PATH is ``.``, and so all the files in
the local directory get tar'd and sent to the Docker daemon. The PATH
specifies where to find the files for the "context" of the build on
the Docker daemon. Remember that the daemon could be running on a
remote machine and that no parsing of the Dockerfile happens at the
client side (where you're running ``docker build``). That means that
*all* the files at PATH get sent, not just the ones listed to
:ref:`ADD <dockerfile_add>` in the ``Dockerfile``.
The transfer of context from the local machine to the Docker daemon is
what the ``docker`` client means when you see the "Uploading context"
message.
The contents of this directory would be used by ``ADD`` commands found
within the ``Dockerfile``. This will send a lot of data to the
``docker`` daemon if the current directory contains a lot of data. If
the absolute path is provided instead of ``.`` then only the files and
directories required by the ADD commands from the ``Dockerfile`` will be
added to the context and transferred to the ``docker`` daemon.
.. code-block:: bash
@@ -129,16 +195,15 @@ tag will be ``2.0``
This will read a ``Dockerfile`` from *stdin* without context. Due to
the lack of a context, no contents of any local directory will be sent
to the ``docker`` daemon. ``ADD`` doesn't work when running in this
mode because the absence of the context provides no source files to
copy to the container.
to the ``docker`` daemon. Since there is no context, a Dockerfile
``ADD`` only works if it refers to a remote URL.
.. code-block:: bash
sudo docker build github.com/creack/docker-firefox
This will clone the Github repository and use it as context. The
``Dockerfile`` at the root of the repository is used as
This will clone the Github repository and use the cloned repository as
context. The ``Dockerfile`` at the root of the repository is used as
``Dockerfile``. Note that you can specify an arbitrary git repository
by using the ``git://`` schema.
@@ -157,7 +222,7 @@ by using the ``git://`` schema.
-m="": Commit message
-author="": Author (eg. "John Hannibal Smith <hannibal@a-team.com>"
-run="": Configuration to be applied when the image is launched with `docker run`.
(ex: '{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
(ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
Simple commit of an existing container
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -173,7 +238,7 @@ Simple commit of an existing container
$ docker images | head
REPOSITORY TAG ID CREATED SIZE
SvenDowideit/testimage version3 f5283438590d 16 seconds ago 204.2 MB (virtual 335.7 MB)
S
Full -run example
.................
@@ -219,10 +284,15 @@ Full -run example
::
Usage: docker cp CONTAINER:RESOURCE HOSTPATH
Usage: docker cp CONTAINER:PATH HOSTPATH
Copy files/folders from the containers filesystem to the host
path. Paths are relative to the root of the filesystem.
.. code-block:: bash
$ sudo docker cp 7bb0e258aefe:/etc/debian_version .
$ sudo docker cp blue_frog:/etc/hosts .
.. _cli_diff:
@@ -331,7 +401,13 @@ Show events in the past from a specified time
Usage: docker export CONTAINER
Export the contents of a filesystem as a tar archive
Export the contents of a filesystem as a tar archive to STDOUT
for example:
.. code-block:: bash
$ sudo docker export red_panda > latest.tar
.. _cli_history:
@@ -392,18 +468,52 @@ To see how the docker:latest image was built:
List images
-a=false: show all images
-a=false: show all images (by default filter out the intermediate images used to build)
-notrunc=false: Don't truncate output
-q=false: only show numeric IDs
-tree=false: output graph in tree format
-viz=false: output graph in graphviz format
Listing the most recently created images
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: bash
$ sudo docker images | head
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 77af4d6b9913 19 hours ago 30.53 MB (virtual 1.089 GB)
committest latest b6fa739cedf5 19 hours ago 30.53 MB (virtual 1.089 GB)
<none> <none> 78a85c484f71 19 hours ago 30.53 MB (virtual 1.089 GB)
docker latest 30557a29d5ab 20 hours ago 30.53 MB (virtual 1.089 GB)
<none> <none> 0124422dd9f9 20 hours ago 30.53 MB (virtual 1.089 GB)
<none> <none> 18ad6fad3402 22 hours ago 23.68 MB (virtual 1.082 GB)
<none> <none> f9f1e26352f0 23 hours ago 30.46 MB (virtual 1.089 GB)
tryout latest 2629d1fa0b81 23 hours ago 16.4 kB (virtual 131.5 MB)
<none> <none> 5ed6274db6ce 24 hours ago 30.44 MB (virtual 1.089 GB)
Listing the full length image IDs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: bash
$ sudo docker images -notrunc | head
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 77af4d6b9913e693e8d0b4b294fa62ade6054e6b2f1ffb617ac955dd63fb0182 19 hours ago 30.53 MB (virtual 1.089 GB)
committest latest b6fa739cedf5ea12a620a439402b6004d057da800f91c7524b5086a5e4749c9f 19 hours ago 30.53 MB (virtual 1.089 GB)
<none> <none> 78a85c484f71509adeaace20e72e941f6bdd2b25b4c75da8693efd9f61a37921 19 hours ago 30.53 MB (virtual 1.089 GB)
docker latest 30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago 30.53 MB (virtual 1.089 GB)
<none> <none> 0124422dd9f9cf7ef15c0617cda3931ee68346455441d66ab8bdc5b05e9fdce5 20 hours ago 30.53 MB (virtual 1.089 GB)
<none> <none> 18ad6fad340262ac2a636efd98a6d1f0ea775ae3d45240d3418466495a19a81b 22 hours ago 23.68 MB (virtual 1.082 GB)
<none> <none> f9f1e26352f0a3ba6a0ff68167559f64f3e21ff7ada60366e2d44a04befd1d3a 23 hours ago 30.46 MB (virtual 1.089 GB)
tryout latest 2629d1fa0b81b222fca63371ca16cbf6a0772d07759ff80e8d1369b926940074 23 hours ago 16.4 kB (virtual 131.5 MB)
<none> <none> 5ed6274db6ceb2397844896966ea239290555e74ef307030ebb01ff91b1914df 24 hours ago 30.44 MB (virtual 1.089 GB)
Displaying images visually
~~~~~~~~~~~~~~~~~~~~~~~~~~
::
.. code-block:: bash
sudo docker images -viz | dot -Tpng -o docker.png
$ sudo docker images -viz | dot -Tpng -o docker.png
.. image:: docker_images.gif
:alt: Example inheritance graph of Docker images.
@@ -412,9 +522,9 @@ Displaying images visually
Displaying image hierarchy
~~~~~~~~~~~~~~~~~~~~~~~~~~
::
.. code-block:: bash
sudo docker images -tree
$ sudo docker images -tree
|─8dbd9e392a96 Size: 131.5 MB (virtual 131.5 MB) Tags: ubuntu:12.04,ubuntu:latest,ubuntu:precise
└─27cf78414709 Size: 180.1 MB (virtual 180.1 MB)
@@ -519,6 +629,12 @@ might not get preserved.
Insert a file from URL in the IMAGE at PATH
Use the specified IMAGE as the parent for a new image which adds a
:ref:`layer <layer_def>` containing the new file. ``insert`` does not modify
the original image, and the new image has the contents of the parent image,
plus the new file.
Examples
~~~~~~~~
@@ -528,6 +644,7 @@ Insert file from github
.. code-block:: bash
$ sudo docker insert 8283e18b24bc https://raw.github.com/metalivedev/django/master/postinstall /tmp/postinstall.sh
06fd35556d7b
.. _cli_inspect:
@@ -559,6 +676,18 @@ Known Issues (kill)
* :issue:`197` indicates that ``docker kill`` may leave directories
behind and make it difficult to remove the container.
.. _cli_load:
``load``
--------
::
Usage: docker load < repository.tar
Loads a tarred repository from the standard input stream.
Restores both images and tags.
.. _cli_login:
``login``
@@ -852,6 +981,17 @@ Known Issues (run -volumes-from)
could indicate a permissions problem with AppArmor. Please see the
issue for a workaround.
.. _cli_save:
``save``
::
Usage: docker save image > repository.tar
Streams a tarred repository to the standard output stream.
Contains all parent layers, and all tags + versions.
.. _cli_search:
``search``
@@ -0,0 +1,137 @@
:title: Process Management with CFEngine
:description: Managing containerized processes with CFEngine
:keywords: cfengine, process, management, usage, docker, documentation
Process Management with CFEngine
================================
Create Docker containers with managed processes.
Docker monitors one process in each running container and the container lives or dies with that process.
By introducing CFEngine inside Docker containers, we can alleviate a few of the issues that may arise:
* It is possible to easily start multiple processes within a container, all of which will be managed automatically, with the normal ``docker run`` command.
* If a managed process dies or crashes, CFEngine will start it again within 1 minute.
* The container itself will live as long as the CFEngine scheduling daemon (cf-execd) lives. With CFEngine, we are able to decouple the life of the container from the uptime of the service it provides.
How it works
------------
CFEngine, together with the cfe-docker integration policies, are installed as part of the Dockerfile. This builds CFEngine into our Docker image.
The Dockerfile's ``ENTRYPOINT`` takes an arbitrary amount of commands (with any desired arguments) as parameters.
When we run the Docker container these parameters get written to CFEngine policies and CFEngine takes over to ensure that the desired processes are running in the container.
CFEngine scans the process table for the ``basename`` of the commands given to the ``ENTRYPOINT`` and runs the command to start the process if the ``basename`` is not found.
For example, if we start the container with ``docker run "/path/to/my/application parameters"``, CFEngine will look for a process named ``application`` and run the command.
If an entry for ``application`` is not found in the process table at any point in time, CFEngine will execute ``/path/to/my/application parameters`` to start the application once again.
The check on the process table happens every minute.
Note that it is therefore important that the command to start your application leaves a process with the basename of the command.
This can be made more flexible by making some minor adjustments to the CFEngine policies, if desired.
Usage
-----
This example assumes you have Docker installed and working.
We will install and manage ``apache2`` and ``sshd`` in a single container.
There are three steps:
1. Install CFEngine into the container.
2. Copy the CFEngine Docker process management policy into the containerized CFEngine installation.
3. Start your application processes as part of the ``docker run`` command.
Building the container image
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The first two steps can be done as part of a Dockerfile, as follows.
.. code-block:: bash
FROM ubuntu
MAINTAINER Eystein Måløy Stenberg <eytein.stenberg@gmail.com>
RUN apt-get -y install wget lsb-release unzip
# install latest CFEngine
RUN wget -qO- http://cfengine.com/pub/gpg.key | apt-key add -
RUN echo "deb http://cfengine.com/pub/apt $(lsb_release -cs) main" > /etc/apt/sources.list.d/cfengine-community.list
RUN apt-get update
RUN apt-get install cfengine-community
# install cfe-docker process management policy
RUN wget --no-check-certificate https://github.com/estenberg/cfe-docker/archive/master.zip -P /tmp/ && unzip /tmp/master.zip -d /tmp/
RUN cp /tmp/cfe-docker-master/cfengine/bin/* /var/cfengine/bin/
RUN cp /tmp/cfe-docker-master/cfengine/inputs/* /var/cfengine/inputs/
RUN rm -rf /tmp/cfe-docker-master /tmp/master.zip
# apache2 and openssh are just for testing purposes, install your own apps here
RUN apt-get -y install openssh-server apache2
RUN mkdir -p /var/run/sshd
RUN echo "root:password" | chpasswd # need a password for ssh
ENTRYPOINT ["/var/cfengine/bin/docker_processes_run.sh"]
By saving this file as ``Dockerfile`` to a working directory, you can then build your container with the docker build command,
e.g. ``docker build -t managed_image``.
Testing the container
~~~~~~~~~~~~~~~~~~~~~
Start the container with ``apache2`` and ``sshd`` running and managed, forwarding a port to our SSH instance:
.. code-block:: bash
docker run -p 127.0.0.1:222:22 -d managed_image "/usr/sbin/sshd" "/etc/init.d/apache2 start"
We now clearly see one of the benefits of the cfe-docker integration: it allows to start several processes
as part of a normal ``docker run`` command.
We can now log in to our new container and see that both ``apache2`` and ``sshd`` are running. We have set the root password to
"password" in the Dockerfile above and can use that to log in with ssh:
.. code-block:: bash
ssh -p222 root@127.0.0.1
ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 07:48 ? 00:00:00 /bin/bash /var/cfengine/bin/docker_processes_run.sh /usr/sbin/sshd /etc/init.d/apache2 start
root 18 1 0 07:48 ? 00:00:00 /var/cfengine/bin/cf-execd -F
root 20 1 0 07:48 ? 00:00:00 /usr/sbin/sshd
root 32 1 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 34 32 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 35 32 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 36 32 0 07:48 ? 00:00:00 /usr/sbin/apache2 -k start
root 93 20 0 07:48 ? 00:00:00 sshd: root@pts/0
root 105 93 0 07:48 pts/0 00:00:00 -bash
root 112 105 0 07:49 pts/0 00:00:00 ps -ef
If we stop apache2, it will be started again within a minute by CFEngine.
.. code-block:: bash
service apache2 status
Apache2 is running (pid 32).
service apache2 stop
* Stopping web server apache2 ... waiting [ OK ]
service apache2 status
Apache2 is NOT running.
# ... wait up to 1 minute...
service apache2 status
Apache2 is running (pid 173).
Adapting to your applications
-----------------------------
To make sure your applications get managed in the same manner, there are just two things you need to adjust from the above example:
* In the Dockerfile used above, install your applications instead of ``apache2`` and ``sshd``.
* When you start the container with ``docker run``, specify the command line arguments to your applications rather than ``apache2`` and ``sshd``.
+2
View File
@@ -24,3 +24,5 @@ to more substantial services like those which you might find in production.
postgresql_service
mongodb
running_riak_service
using_supervisord
cfengine_process_management
+128
View File
@@ -0,0 +1,128 @@
:title: Using Supervisor with Docker
:description: How to use Supervisor process management with Docker
:keywords: docker, supervisor, process management
.. _using_supervisord:
Using Supervisor with Docker
============================
.. include:: example_header.inc
Traditionally a Docker container runs a single process when it is launched, for
example an Apache daemon or a SSH server daemon. Often though you want to run
more than one process in a container. There are a number of ways you can
achieve this ranging from using a simple Bash script as the value of your
container's ``CMD`` instruction to installing a process management tool.
In this example we're going to make use of the process management tool,
`Supervisor <http://supervisord.org/>`_, to manage multiple processes in our
container. Using Supervisor allows us to better control, manage, and restart the
processes we want to run. To demonstrate this we're going to install and manage both an
SSH daemon and an Apache daemon.
Creating a Dockerfile
---------------------
Let's start by creating a basic ``Dockerfile`` for our new image.
.. code-block:: bash
FROM ubuntu:latest
MAINTAINER examples@docker.io
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
RUN apt-get update
RUN apt-get upgrade -y
Installing Supervisor
---------------------
We can now install our SSH and Apache daemons as well as Supervisor in our container.
.. code-block:: bash
RUN apt-get install -y openssh-server apache2 supervisor
RUN mkdir -p /var/run/sshd
RUN mkdir -p /var/log/supervisor
Here we're installing the ``openssh-server``, ``apache2`` and ``supervisor``
(which provides the Supervisor daemon) packages. We're also creating two new
directories that are needed to run our SSH daemon and Supervisor.
Adding Supervisor's configuration file
--------------------------------------
Now let's add a configuration file for Supervisor. The default file is called
``supervisord.conf`` and is located in ``/etc/supervisor/conf.d/``.
.. code-block:: bash
ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf
Let's see what is inside our ``supervisord.conf`` file.
.. code-block:: bash
[supervisord]
nodaemon=true
[program:sshd]
command=/usr/sbin/sshd -D
[program:apache2]
command=/bin/bash -c "source /etc/apache2/envvars && /usr/sbin/apache2 -DFOREGROUND"
The ``supervisord.conf`` configuration file contains directives that configure
Supervisor and the processes it manages. The first block ``[supervisord]``
provides configuration for Supervisor itself. We're using one directive,
``nodaemon`` which tells Supervisor to run interactively rather than daemonize.
The next two blocks manage the services we wish to control. Each block controls
a separate process. The blocks contain a single directive, ``command``, which
specifies what command to run to start each process.
Exposing ports and running Supervisor
-------------------------------------
Now let's finish our ``Dockerfile`` by exposing some required ports and
specifying the ``CMD`` instruction to start Supervisor when our container
launches.
.. code-block:: bash
EXPOSE 22 80
CMD ["/usr/bin/supervisord"]
Here we've exposed ports 22 and 80 on the container and we're running the
``/usr/bin/supervisord`` binary when the container launches.
Building our container
----------------------
We can now build our new container.
.. code-block:: bash
sudo docker build -t <yourname>/supervisord .
Running our Supervisor container
--------------------------------
Once we've got a built image we can launch a container from it.
.. code-block:: bash
sudo docker run -p 22 -p 80 -t -i <yourname>/supervisor
2013-11-25 18:53:22,312 CRIT Supervisor running as root (no user in config file)
2013-11-25 18:53:22,312 WARN Included extra file "/etc/supervisor/conf.d/supervisord.conf" during parsing
2013-11-25 18:53:22,342 INFO supervisord started with pid 1
2013-11-25 18:53:23,346 INFO spawned: 'sshd' with pid 6
2013-11-25 18:53:23,349 INFO spawned: 'apache2' with pid 7
. . .
We've launched a new container interactively using the ``docker run`` command.
That container has run Supervisor and launched the SSH and Apache daemons with
it. We've specified the ``-p`` flag to expose ports 22 and 80. From here we can
now identify the exposed ports and connect to one or both of the SSH and Apache
daemons.
+4 -6
View File
@@ -22,12 +22,10 @@ Amazon QuickStart
1. **Choose an image:**
* Open http://cloud-images.ubuntu.com/locator/ec2/
* Enter ``amd64 precise`` in the search field (it will search as you
type)
* Pick an image by clicking on the image name. *An EBS-enabled
image will let you use a t1.micro instance.* Clicking on the image
name will take you to your AWS Console.
* Launch the `Create Instance Wizard` <https://console.aws.amazon.com/ec2/v2/home?#LaunchInstanceWizard:> menu on your AWS Console
* Select "Community AMIs" option and serch for ``amd64 precise`` (click enter to search)
* If you choose a EBS enabled AMI you will be able to launch a `t1.micro` instance (more info on `pricing` <http://aws.amazon.com/en/ec2/pricing/> )
* When you click select you'll be taken to the instance setup, and you're one click away from having your Ubuntu VM up and running.
2. **Tell CloudInit to install Docker:**
+5 -5
View File
@@ -1,5 +1,5 @@
:title: Installation on Arch Linux
:description: Docker installation on Arch Linux.
:description: Docker installation on Arch Linux.
:keywords: arch linux, virtualization, docker, documentation, installation
.. _arch_linux:
@@ -7,6 +7,10 @@
Arch Linux
==========
.. include:: install_header.inc
.. include:: install_unofficial.inc
Installing on Arch Linux is not officially supported but can be handled via
either of the following AUR packages:
@@ -32,10 +36,6 @@ either AUR package.
Installation
------------
.. include:: install_header.inc
.. include:: install_unofficial.inc
The instructions here assume **yaourt** is installed. See
`Arch User Repository <https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages>`_
for information on building and installing packages from the AUR if you have not
+4 -12
View File
@@ -12,17 +12,9 @@ Binaries
**This instruction set is meant for hackers who want to try out Docker
on a variety of environments.**
Right now, the officially supported distributions are:
- :ref:`ubuntu_precise`
- :ref:`ubuntu_raring`
But we know people have had success running it under
- Debian
- Suse
- :ref:`arch_linux`
Before following these directions, you should really check if a packaged version
of Docker is already available for your distribution. We have packages for many
distributions, and more keep showing up all the time!
Check Your Kernel
-----------------
@@ -34,7 +26,7 @@ Get the docker binary:
.. code-block:: bash
wget --output-document=docker https://get.docker.io/builds/Linux/x86_64/docker-latest
wget https://get.docker.io/builds/Linux/x86_64/docker-latest -O docker
chmod +x docker
+19
View File
@@ -0,0 +1,19 @@
:title: Requirements and Installation on Fedora
:description: Please note this project is currently under heavy development. It should not be used in production.
:keywords: Docker, Docker documentation, requirements, virtualbox, vagrant, git, ssh, putty, cygwin, linux
.. _fedora:
Fedora
======
.. include:: install_header.inc
.. include:: install_unofficial.inc
.. warning::
This is a placeholder for the Fedora installation instructions. Currently there is not an available
Docker package in the Fedora distribution. These packages are being built and should be available soon.
These instructions will be updated when the package is available.
+14 -12
View File
@@ -4,8 +4,8 @@
.. _gentoo_linux:
Gentoo Linux
============
Gentoo
======
.. include:: install_header.inc
@@ -22,17 +22,19 @@ provided at https://github.com/tianon/docker-overlay which can be added using
properly installing and using the overlay can be found in `the overlay README
<https://github.com/tianon/docker-overlay/blob/master/README.md#using-this-overlay>`_.
Note that sometimes there is a disparity between the latest version and what's
in the overlay, and between the latest version in the overlay and what's in the
portage tree. Please be patient, and the latest version should propagate
shortly.
Installation
^^^^^^^^^^^^
The package should properly pull in all the necessary dependencies and prompt
for all necessary kernel options. For the most straightforward installation
experience, use ``sys-kernel/aufs-sources`` as your kernel sources. If you
prefer not to use ``sys-kernel/aufs-sources``, the portage tree also contains
``sys-fs/aufs3``, which includes the patches necessary for adding AUFS support
to other kernel source packages such as ``sys-kernel/gentoo-sources`` (and a
``kernel-patch`` USE flag to perform the patching to ``/usr/src/linux``
automatically).
for all necessary kernel options. The ebuilds for 0.7+ include use flags to
pull in the proper dependencies of the major storage drivers, with the
"device-mapper" use flag being enabled by default, since that is the simplest
installation path.
.. code-block:: bash
@@ -47,9 +49,9 @@ the #docker IRC channel on the freenode network.
Starting Docker
^^^^^^^^^^^^^^^
Ensure that you are running a kernel that includes the necessary AUFS
patches/support and includes all the necessary modules and/or configuration for
LXC.
Ensure that you are running a kernel that includes all the necessary modules
and/or configuration for LXC (and optionally for device-mapper and/or AUFS,
depending on the storage driver you've decided to use).
OpenRC
------
+8 -7
View File
@@ -9,7 +9,7 @@ Installation
There are a number of ways to install Docker, depending on where you
want to run the daemon. The :ref:`ubuntu_linux` installation is the
officially-tested version, and the community adds more techniques for
officially-tested version. The community adds more techniques for
installing Docker all the time.
Contents:
@@ -18,13 +18,14 @@ Contents:
:maxdepth: 1
ubuntulinux
binaries
security
upgrading
kernel
fedora
archlinux
gentoolinux
vagrant
windows
amazon
rackspace
archlinux
gentoolinux
kernel
binaries
security
upgrading
+2 -13
View File
@@ -11,10 +11,10 @@ In short, Docker has the following kernel requirements:
- Linux version 3.8 or above.
- `AUFS support <http://aufs.sourceforge.net/>`_.
- Cgroups and namespaces must be enabled.
*Note: as of 0.7 docker no longer requires aufs. AUFS support is still available as an optional driver.*
The officially supported kernel is the one recommended by the
:ref:`ubuntu_linux` installation path. It is the one that most developers
will use, and the one that receives the most attention from the core
@@ -58,17 +58,6 @@ detects something older than 3.8.
See issue `#407 <https://github.com/dotcloud/docker/issues/407>`_ for details.
AUFS support
------------
Docker currently relies on AUFS, an unioning filesystem.
While AUFS is included in the kernels built by the Debian and Ubuntu
distributions, is not part of the standard kernel. This means that if
you decide to roll your own kernel, you will have to patch your
kernel tree to add AUFS. The process is documented on
`AUFS webpage <http://aufs.sourceforge.net/>`_.
Cgroups and namespaces
----------------------
+8 -9
View File
@@ -2,7 +2,6 @@
:description: Installing Docker on Ubuntu proviced by Rackspace
:keywords: Rackspace Cloud, installation, docker, linux, ubuntu
===============
Rackspace Cloud
===============
@@ -14,14 +13,14 @@ straightforward, and you should mostly be able to follow the
**However, there is one caveat:**
If you are using any linux not already shipping with the 3.8 kernel
If you are using any Linux not already shipping with the 3.8 kernel
you will need to install it. And this is a little more difficult on
Rackspace.
Rackspace boots their servers using grub's ``menu.lst`` and does not
like non 'virtual' packages (e.g. xen compatible) kernels there,
although they do work. This makes ``update-grub`` to not have the
expected result, and you need to set the kernel manually.
like non 'virtual' packages (e.g. Xen compatible) kernels there,
although they do work. This results in ``update-grub`` not having the
expected result, and you will need to set the kernel manually.
**Do not attempt this on a production machine!**
@@ -34,7 +33,7 @@ expected result, and you need to set the kernel manually.
apt-get install linux-generic-lts-raring
Great, now you have kernel installed in ``/boot/``, next is to make it
Great, now you have the kernel installed in ``/boot/``, next you need to make it
boot next time.
.. code-block:: bash
@@ -48,9 +47,9 @@ boot next time.
Now you need to manually edit ``/boot/grub/menu.lst``, you will find a
section at the bottom with the existing options. Copy the top one and
substitute the new kernel into that. Make sure the new kernel is on
top, and double check kernel and initrd point to the right files.
top, and double check the kernel and initrd lines point to the right files.
Make special care to double check the kernel and initrd entries.
Take special care to double check the kernel and initrd entries.
.. code-block:: bash
@@ -79,7 +78,7 @@ It will probably look something like this:
initrd /boot/initrd.img-3.2.0-38-virtual
Reboot server (either via command line or console)
Reboot the server (either via command line or console)
.. code-block:: bash
+52 -50
View File
@@ -4,8 +4,8 @@
.. _ubuntu_linux:
Ubuntu Linux
============
Ubuntu
======
.. warning::
@@ -14,16 +14,11 @@ Ubuntu Linux
.. include:: install_header.inc
Right now, the officially supported distribution are:
Docker is supported on the following versions of Ubuntu:
- :ref:`ubuntu_precise`
- :ref:`ubuntu_raring`
Docker has the following dependencies
* Linux kernel 3.8 (read more about :ref:`kernel`)
* AUFS file system support (we are working on BTRFS support as an alternative)
Please read :ref:`ufw`, if you plan to use `UFW (Uncomplicated
Firewall) <https://help.ubuntu.com/community/UFW>`_
@@ -70,32 +65,35 @@ Installation
Docker is available as a Debian package, which makes installation easy.
First add the Docker repository key to your local keychain. You can use the
``apt-key`` command to check the fingerprint matches: ``36A1 D786 9245 C895 0F96
6E92 D857 6A8B A88D 21E9``
.. code-block:: bash
# Add the Docker repository key to your local keychain
# using apt-key finger you can check the fingerprint matches 36A1 D786 9245 C895 0F96 6E92 D857 6A8B A88D 21E9
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
# Add the Docker repository to your apt sources list.
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
Add the Docker repository to your apt sources list, update and install the
``lxc-docker`` package.
# Update your sources
sudo apt-get update
# Install, you will see another warning that the package cannot be authenticated. Confirm install.
sudo apt-get install lxc-docker
Verify it worked
*You may receive a warning that the package isn't trusted. Answer yes to
continue installation.*
.. code-block:: bash
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
sudo apt-get update
sudo apt-get install lxc-docker
Now verify that the installation has worked by downloading the ``ubuntu`` image
and launching a container.
.. code-block:: bash
# download the base 'ubuntu' container and run bash inside it while setting up an interactive shell
sudo docker run -i -t ubuntu /bin/bash
# type 'exit' to exit
Type ``exit`` to exit
**Done!**, now continue with the :ref:`hello_world` example.
@@ -107,10 +105,13 @@ Ubuntu Raring 13.04 (64 bit)
Dependencies
------------
**AUFS filesystem support**
**Optional AUFS filesystem support**
Ubuntu Raring already comes with the 3.8 kernel, so we don't need to install it. However, not all systems
have AUFS filesystem support enabled, so we need to install it.
have AUFS filesystem support enabled. AUFS support is optional as of version 0.7, but it's still available as
a driver and we recommend using it if you can.
To make sure AUFS is installed, run the following commands:
.. code-block:: bash
@@ -123,36 +124,37 @@ Installation
Docker is available as a Debian package, which makes installation easy.
*Please note that these instructions have changed for 0.6. If you are upgrading from an earlier version, you will need
to follow them again.*
.. warning::
Please note that these instructions have changed for 0.6. If you are upgrading from an earlier version, you will need
to follow them again.
First add the Docker repository key to your local keychain. You can use the
``apt-key`` command to check the fingerprint matches: ``36A1 D786 9245 C895 0F96
6E92 D857 6A8B A88D 21E9``
.. code-block:: bash
# Add the Docker repository key to your local keychain
# using apt-key finger you can check the fingerprint matches 36A1 D786 9245 C895 0F96 6E92 D857 6A8B A88D 21E9
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
# Add the Docker repository to your apt sources list.
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
# update
sudo apt-get update
# install
sudo apt-get install lxc-docker
Verify it worked
Add the Docker repository to your apt sources list, update and install the
``lxc-docker`` package.
.. code-block:: bash
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
sudo apt-get update
sudo apt-get install lxc-docker
Now verify that the installation has worked by downloading the ``ubuntu`` image
and launching a container.
.. code-block:: bash
# download the base 'ubuntu' container
# and run bash inside it while setting up an interactive shell
sudo docker run -i -t ubuntu /bin/bash
# type exit to exit
Type ``exit`` to exit
**Done!**, now continue with the :ref:`hello_world` example.
@@ -162,8 +164,8 @@ Verify it worked
Docker and UFW
^^^^^^^^^^^^^^
Docker uses a bridge to manage container networking. By default, UFW
drops all `forwarding`, thus a first step is to enable UFW forwarding:
Docker uses a bridge to manage container networking. By default, UFW drops all
`forwarding` traffic. As a result will you need to enable UFW forwarding:
.. code-block:: bash
@@ -181,9 +183,9 @@ Then reload UFW:
sudo ufw reload
UFW's default set of rules denied all `incoming`, so if you want to be
able to reach your containers from another host, you should allow
incoming connections on the docker port (default 4243):
UFW's default set of rules denies all `incoming` traffic. If you want to be
able to reach your containers from another host then you should allow
incoming connections on the Docker port (default 4243):
.. code-block:: bash
+2 -2
View File
@@ -76,11 +76,11 @@ client commands.
# Add the docker group if it doesn't already exist.
sudo groupadd docker
# Add the user "ubuntu" to the docker group.
# Add the connected user "${USERNAME}" to the docker group.
# Change the user name to match your preferred user.
# You may have to logout and log back in again for
# this to take effect.
sudo gpasswd -a ubuntu docker
sudo gpasswd -a ${USERNAME} docker
# Restart the docker daemon.
sudo service docker restart
+42 -10
View File
@@ -15,27 +15,39 @@ commit them along the way, giving you a final image.
.. contents:: Table of Contents
.. _dockerfile_usage:
1. Usage
========
To build an image from a source repository, create a description file
called ``Dockerfile`` at the root of your repository. This file will
describe the steps to assemble the image.
To :ref:`build <cli_build>` an image from a source repository, create
a description file called ``Dockerfile`` at the root of your
repository. This file will describe the steps to assemble the image.
Then call ``docker build`` with the path of your source repository as
argument:
argument (for example, ``.``):
``sudo docker build .``
The path to the source repository defines where to find the *context*
of the build. The build is run by the Docker daemon, not by the CLI,
so the whole context must be transferred to the daemon. The Docker CLI
reports "Uploading context" when the context is sent to the daemon.
You can specify a repository and tag at which to save the new image if the
build succeeds:
``sudo docker build -t shykes/myapp .``
Docker will run your steps one-by-one, committing the result if necessary,
before finally outputting the ID of your new image.
The Docker daemon will run your steps one-by-one, committing the
result if necessary, before finally outputting the ID of your new
image. The Docker daemon will automatically clean up the context you
sent.
When you're done with your build, you're ready to look into :ref:`image_push`.
When you're done with your build, you're ready to look into
:ref:`image_push`.
.. _dockerfile_format:
2. Format
=========
@@ -63,12 +75,16 @@ allows statements like:
# Comment
RUN echo 'we are running some # of cool things'
.. _dockerfile_instructions:
3. Instructions
===============
Here is the set of instructions you can use in a ``Dockerfile`` for
building images.
.. _dockerfile_from:
3.1 FROM
--------
@@ -94,6 +110,8 @@ output by the commit before each new ``FROM`` command.
If no ``tag`` is given to the ``FROM`` instruction, ``latest`` is
assumed. If the used tag does not exist, an error will be returned.
.. _dockerfile_maintainer:
3.2 MAINTAINER
--------------
@@ -102,6 +120,8 @@ assumed. If the used tag does not exist, an error will be returned.
The ``MAINTAINER`` instruction allows you to set the *Author* field of
the generated images.
.. _dockerfile_run:
3.3 RUN
-------
@@ -124,7 +144,7 @@ Known Issues (RUN)
``rm`` a file, for example. The issue describes a workaround.
* :issue:`2424` Locale will not be set automatically.
.. _dockerfile_cmd:
3.4 CMD
-------
@@ -169,7 +189,7 @@ array:
If you would like your container to run the same executable every
time, then you should consider using ``ENTRYPOINT`` in combination
with ``CMD``. See :ref:`entrypoint_def`.
with ``CMD``. See :ref:`dockerfile_entrypoint`.
If the user specifies arguments to ``docker run`` then they will
override the default specified in CMD.
@@ -179,6 +199,8 @@ override the default specified in CMD.
command and commits the result; ``CMD`` does not execute anything at
build time, but specifies the intended command for the image.
.. _dockerfile_expose:
3.5 EXPOSE
----------
@@ -189,6 +211,8 @@ functionally equivalent to running ``docker commit -run '{"PortSpecs":
["<port>", "<port2>"]}'`` outside the builder. Refer to
:ref:`port_redirection` for detailed information.
.. _dockerfile_env:
3.6 ENV
-------
@@ -203,6 +227,8 @@ with ``<key>=<value>``
The environment variables will persist when a container is run
from the resulting image.
.. _dockerfile_add:
3.7 ADD
-------
@@ -263,7 +289,7 @@ The copy obeys the following rules:
* If ``<dest>`` doesn't exist, it is created along with all missing
directories in its path.
.. _entrypoint_def:
.. _dockerfile_entrypoint:
3.8 ENTRYPOINT
--------------
@@ -312,6 +338,7 @@ this optional but default, you could use a CMD:
CMD ["-l", "-"]
ENTRYPOINT ["/usr/bin/wc"]
.. _dockerfile_volume:
3.9 VOLUME
----------
@@ -322,6 +349,8 @@ The ``VOLUME`` instruction will create a mount point with the specified name and
as holding externally mounted volumes from native host or other containers. For more information/examples
and mounting instructions via docker client, refer to :ref:`volume_def` documentation.
.. _dockerfile_user:
3.10 USER
---------
@@ -330,6 +359,8 @@ and mounting instructions via docker client, refer to :ref:`volume_def` document
The ``USER`` instruction sets the username or UID to use when running
the image.
.. _dockerfile_workdir:
3.11 WORKDIR
------------
@@ -338,6 +369,7 @@ the image.
The ``WORKDIR`` instruction sets the working directory in which
the command given by ``CMD`` is executed.
.. _dockerfile_examples:
4. Dockerfile Examples
======================
+1 -1
View File
@@ -35,7 +35,7 @@
%}
{#
This part is hopefully complex because things like |cut '/index/' are not available in spinx jinja
This part is hopefully complex because things like |cut '/index/' are not available in Sphinx jinja
and will make it crash. (and we need index/ out.
#}
<link rel="canonical" href="http://docs.docker.io/en/latest/
+55
View File
@@ -0,0 +1,55 @@
package engine
import (
"testing"
)
func TestRegister(t *testing.T) {
if err := Register("dummy1", nil); err != nil {
t.Fatal(err)
}
if err := Register("dummy1", nil); err == nil {
t.Fatalf("Expecting error, got none")
}
eng := newTestEngine(t)
//Should fail because globan handlers are copied
//at the engine creation
if err := eng.Register("dummy1", nil); err == nil {
t.Fatalf("Expecting error, got none")
}
if err := eng.Register("dummy2", nil); err != nil {
t.Fatal(err)
}
if err := eng.Register("dummy2", nil); err == nil {
t.Fatalf("Expecting error, got none")
}
}
func TestJob(t *testing.T) {
eng := newTestEngine(t)
job1 := eng.Job("dummy1", "--level=awesome")
if job1.handler != nil {
t.Fatalf("job1.handler should be empty")
}
h := func(j *Job) string {
return j.Name
}
eng.Register("dummy2", h)
job2 := eng.Job("dummy2", "--level=awesome")
if job2.handler == nil {
t.Fatalf("job2.handler shouldn't be nil")
}
if job2.handler(job2) != job2.Name {
t.Fatalf("handler dummy2 was not found in job2")
}
}
+94
View File
@@ -23,7 +23,101 @@ func TestSetenv(t *testing.T) {
if val := job.Getenv("foo"); val != "bar" {
t.Fatalf("Getenv returns incorrect value: %s", val)
}
job.Setenv("bar", "")
if val := job.Getenv("bar"); val != "" {
t.Fatalf("Getenv returns incorrect value: %s", val)
}
if val := job.Getenv("nonexistent"); val != "" {
t.Fatalf("Getenv returns incorrect value: %s", val)
}
}
func TestSetenvBool(t *testing.T) {
job := mkJob(t, "dummy")
job.SetenvBool("foo", true)
if val := job.GetenvBool("foo"); !val {
t.Fatalf("GetenvBool returns incorrect value: %b", val)
}
job.SetenvBool("bar", false)
if val := job.GetenvBool("bar"); val {
t.Fatalf("GetenvBool returns incorrect value: %b", val)
}
if val := job.GetenvBool("nonexistent"); val {
t.Fatalf("GetenvBool returns incorrect value: %b", val)
}
}
func TestSetenvInt(t *testing.T) {
job := mkJob(t, "dummy")
job.SetenvInt("foo", -42)
if val := job.GetenvInt("foo"); val != -42 {
t.Fatalf("GetenvInt returns incorrect value: %d", val)
}
job.SetenvInt("bar", 42)
if val := job.GetenvInt("bar"); val != 42 {
t.Fatalf("GetenvInt returns incorrect value: %d", val)
}
if val := job.GetenvInt("nonexistent"); val != -1 {
t.Fatalf("GetenvInt returns incorrect value: %d", val)
}
}
func TestSetenvList(t *testing.T) {
job := mkJob(t, "dummy")
job.SetenvList("foo", []string{"bar"})
if val := job.GetenvList("foo"); len(val) != 1 || val[0] != "bar" {
t.Fatalf("GetenvList returns incorrect value: %v", val)
}
job.SetenvList("bar", nil)
if val := job.GetenvList("bar"); val != nil {
t.Fatalf("GetenvList returns incorrect value: %v", val)
}
if val := job.GetenvList("nonexistent"); val != nil {
t.Fatalf("GetenvList returns incorrect value: %v", val)
}
}
func TestImportEnv(t *testing.T) {
type dummy struct {
DummyInt int
DummyStringArray []string
}
job := mkJob(t, "dummy")
if err := job.ImportEnv(&dummy{42, []string{"foo", "bar"}}); err != nil {
t.Fatal(err)
}
dmy := dummy{}
if err := job.ExportEnv(&dmy); err != nil {
t.Fatal(err)
}
if dmy.DummyInt != 42 {
t.Fatalf("Expected 42, got %d", dmy.DummyInt)
}
if len(dmy.DummyStringArray) != 2 || dmy.DummyStringArray[0] != "foo" || dmy.DummyStringArray[1] != "bar" {
t.Fatalf("Expected {foo, bar}, got %v", dmy.DummyStringArray)
}
}
func TestEnviron(t *testing.T) {
job := mkJob(t, "dummy")
job.Setenv("foo", "bar")
val, exists := job.Environ()["foo"]
if !exists {
t.Fatalf("foo not found in the environ")
}
if val != "bar" {
t.Fatalf("bar not found in the environ")
}
}
@@ -11,10 +11,6 @@ import (
var globalTestID string
func init() {
Register("dummy", func(job *Job) string { return "" })
}
func newTestEngine(t *testing.T) *Engine {
// Use the caller function name as a prefix.
// This helps trace temp directories back to their test.
+5 -1
View File
@@ -205,8 +205,12 @@ func (job *Job) SetenvInt(key string, value int64) {
job.Setenv(key, fmt.Sprintf("%d", value))
}
// Returns nil if key not found
func (job *Job) GetenvList(key string) []string {
sval := job.Getenv(key)
if sval == "" {
return nil
}
l := make([]string, 0, 1)
if err := json.Unmarshal([]byte(sval), &l); err != nil {
l = append(l, sval)
@@ -234,7 +238,7 @@ func (job *Job) Setenv(key, value string) {
// DecodeEnv decodes `src` as a json dictionary, and adds
// each decoded key-value pair to the environment.
//
// If `text` cannot be decoded as a json dictionary, an error
// If `src` cannot be decoded as a json dictionary, an error
// is returned.
func (job *Job) DecodeEnv(src io.Reader) error {
m := make(map[string]interface{})
+32 -4
View File
@@ -52,7 +52,9 @@ func (graph *Graph) restore() error {
}
for _, v := range dir {
id := v.Name()
graph.idIndex.Add(id)
if graph.driver.Exists(id) {
graph.idIndex.Add(id)
}
}
return nil
}
@@ -110,7 +112,7 @@ func (graph *Graph) Create(layerData archive.Archive, container *Container, comm
img := &Image{
ID: GenerateID(),
Comment: comment,
Created: time.Now(),
Created: time.Now().UTC(),
DockerVersion: VERSION,
Author: author,
Config: config,
@@ -129,7 +131,15 @@ func (graph *Graph) Create(layerData archive.Archive, container *Container, comm
// Register imports a pre-existing image into the graph.
// FIXME: pass img as first argument
func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Image) error {
func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Image) (err error) {
defer func() {
// If any error occurs, remove the new dir from the driver.
// Don't check for errors since the dir might not have been created.
// FIXME: this leaves a possible race condition.
if err != nil {
graph.driver.Remove(img.ID)
}
}()
if err := ValidateID(img.ID); err != nil {
return err
}
@@ -137,6 +147,20 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Im
if graph.Exists(img.ID) {
return fmt.Errorf("Image %s already exists", img.ID)
}
// Ensure that the image root does not exist on the filesystem
// when it is not registered in the graph.
// This is common when you switch from one graph driver to another
if err := os.RemoveAll(graph.imageRoot(img.ID)); err != nil && !os.IsNotExist(err) {
return err
}
// If the driver has this ID but the graph doesn't, remove it from the driver to start fresh.
// (the graph is the source of truth).
// Ignore errors, since we don't know if the driver correctly returns ErrNotExist.
// (FIXME: make that mandatory for drivers).
graph.driver.Remove(img.ID)
tmp, err := graph.Mktemp("")
defer os.RemoveAll(tmp)
if err != nil {
@@ -177,7 +201,7 @@ func (graph *Graph) TempLayerArchive(id string, compression archive.Compression,
if err != nil {
return nil, err
}
a, err := image.TarLayer(compression)
a, err := image.TarLayer()
if err != nil {
return nil, err
}
@@ -353,3 +377,7 @@ func (graph *Graph) Heads() (map[string]*Image, error) {
func (graph *Graph) imageRoot(id string) string {
return path.Join(graph.Root, id)
}
func (graph *Graph) Driver() graphdriver.Driver {
return graph.driver
}
-297
View File
@@ -1,297 +0,0 @@
package docker
import (
"archive/tar"
"bytes"
"errors"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"os"
"testing"
"time"
)
func TestInit(t *testing.T) {
graph := tempGraph(t)
defer os.RemoveAll(graph.Root)
// Root should exist
if _, err := os.Stat(graph.Root); err != nil {
t.Fatal(err)
}
// Map() should be empty
if l, err := graph.Map(); err != nil {
t.Fatal(err)
} else if len(l) != 0 {
t.Fatalf("len(Map()) should return %d, not %d", 0, len(l))
}
}
// Test that Register can be interrupted cleanly without side effects
func TestInterruptedRegister(t *testing.T) {
graph := tempGraph(t)
defer os.RemoveAll(graph.Root)
badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data
image := &Image{
ID: GenerateID(),
Comment: "testing",
Created: time.Now(),
}
go graph.Register(nil, badArchive, image)
time.Sleep(200 * time.Millisecond)
w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling)
if _, err := graph.Get(image.ID); err == nil {
t.Fatal("Image should not exist after Register is interrupted")
}
// Registering the same image again should succeed if the first register was interrupted
goodArchive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
if err := graph.Register(nil, goodArchive, image); err != nil {
t.Fatal(err)
}
}
// FIXME: Do more extensive tests (ex: create multiple, delete, recreate;
// create multiple, check the amount of images and paths, etc..)
func TestGraphCreate(t *testing.T) {
graph := tempGraph(t)
defer os.RemoveAll(graph.Root)
archive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
image, err := graph.Create(archive, nil, "Testing", "", nil)
if err != nil {
t.Fatal(err)
}
if err := ValidateID(image.ID); err != nil {
t.Fatal(err)
}
if image.Comment != "Testing" {
t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", image.Comment)
}
if image.DockerVersion != VERSION {
t.Fatalf("Wrong docker_version: should be '%s', not '%s'", VERSION, image.DockerVersion)
}
images, err := graph.Map()
if err != nil {
t.Fatal(err)
} else if l := len(images); l != 1 {
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
}
if images[image.ID] == nil {
t.Fatalf("Could not find image with id %s", image.ID)
}
}
func TestRegister(t *testing.T) {
graph := tempGraph(t)
defer os.RemoveAll(graph.Root)
archive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
image := &Image{
ID: GenerateID(),
Comment: "testing",
Created: time.Now(),
}
err = graph.Register(nil, archive, image)
if err != nil {
t.Fatal(err)
}
if images, err := graph.Map(); err != nil {
t.Fatal(err)
} else if l := len(images); l != 1 {
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
}
if resultImg, err := graph.Get(image.ID); err != nil {
t.Fatal(err)
} else {
if resultImg.ID != image.ID {
t.Fatalf("Wrong image ID. Should be '%s', not '%s'", image.ID, resultImg.ID)
}
if resultImg.Comment != image.Comment {
t.Fatalf("Wrong image comment. Should be '%s', not '%s'", image.Comment, resultImg.Comment)
}
}
}
// Test that an image can be deleted by its shorthand prefix
func TestDeletePrefix(t *testing.T) {
graph := tempGraph(t)
defer os.RemoveAll(graph.Root)
img := createTestImage(graph, t)
if err := graph.Delete(utils.TruncateID(img.ID)); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 0)
}
func createTestImage(graph *Graph, t *testing.T) *Image {
archive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
img, err := graph.Create(archive, nil, "Test image", "", nil)
if err != nil {
t.Fatal(err)
}
return img
}
func TestDelete(t *testing.T) {
graph := tempGraph(t)
defer os.RemoveAll(graph.Root)
archive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 0)
img, err := graph.Create(archive, nil, "Bla bla", "", nil)
if err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 1)
if err := graph.Delete(img.ID); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 0)
archive, err = fakeTar()
if err != nil {
t.Fatal(err)
}
// Test 2 create (same name) / 1 delete
img1, err := graph.Create(archive, nil, "Testing", "", nil)
if err != nil {
t.Fatal(err)
}
archive, err = fakeTar()
if err != nil {
t.Fatal(err)
}
if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 2)
if err := graph.Delete(img1.ID); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 1)
// Test delete wrong name
if err := graph.Delete("Not_foo"); err == nil {
t.Fatalf("Deleting wrong ID should return an error")
}
assertNImages(graph, t, 1)
archive, err = fakeTar()
if err != nil {
t.Fatal(err)
}
// Test delete twice (pull -> rm -> pull -> rm)
if err := graph.Register(nil, archive, img1); err != nil {
t.Fatal(err)
}
if err := graph.Delete(img1.ID); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 1)
}
func TestByParent(t *testing.T) {
archive1, _ := fakeTar()
archive2, _ := fakeTar()
archive3, _ := fakeTar()
graph := tempGraph(t)
defer os.RemoveAll(graph.Root)
parentImage := &Image{
ID: GenerateID(),
Comment: "parent",
Created: time.Now(),
Parent: "",
}
childImage1 := &Image{
ID: GenerateID(),
Comment: "child1",
Created: time.Now(),
Parent: parentImage.ID,
}
childImage2 := &Image{
ID: GenerateID(),
Comment: "child2",
Created: time.Now(),
Parent: parentImage.ID,
}
_ = graph.Register(nil, archive1, parentImage)
_ = graph.Register(nil, archive2, childImage1)
_ = graph.Register(nil, archive3, childImage2)
byParent, err := graph.ByParent()
if err != nil {
t.Fatal(err)
}
numChildren := len(byParent[parentImage.ID])
if numChildren != 2 {
t.Fatalf("Expected 2 children, found %d", numChildren)
}
}
func assertNImages(graph *Graph, t *testing.T, n int) {
if images, err := graph.Map(); err != nil {
t.Fatal(err)
} else if actualN := len(images); actualN != n {
t.Fatalf("Expected %d images, found %d", n, actualN)
}
}
/*
* HELPER FUNCTIONS
*/
func tempGraph(t *testing.T) *Graph {
tmp, err := ioutil.TempDir("", "docker-graph-")
if err != nil {
t.Fatal(err)
}
backend, err := graphdriver.New(tmp)
if err != nil {
t.Fatal(err)
}
graph, err := NewGraph(tmp, backend)
if err != nil {
t.Fatal(err)
}
return graph
}
func testArchive(t *testing.T) archive.Archive {
archive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
return archive
}
func fakeTar() (io.Reader, error) {
content := []byte("Hello world!\n")
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
hdr := new(tar.Header)
hdr.Size = int64(len(content))
hdr.Name = name
if err := tw.WriteHeader(hdr); err != nil {
return nil, err
}
tw.Write([]byte(content))
}
tw.Close()
return buf, nil
}
+6 -2
View File
@@ -103,8 +103,12 @@ func (Driver) String() string {
return "aufs"
}
func (Driver) Status() [][2]string {
return nil
func (a Driver) Status() [][2]string {
ids, _ := loadIds(path.Join(a.rootPath(), "layers"))
return [][2]string{
{"Root Dir", a.rootPath()},
{"Dirs", fmt.Sprintf("%d", len(ids))},
}
}
// Exists returns true if the given id is registered with
+108 -2
View File
@@ -446,7 +446,9 @@ func TestDiffSize(t *testing.T) {
if err != nil {
t.Fatal(err)
}
f.Truncate(size)
if err := f.Truncate(size); err != nil {
t.Fatal(err)
}
s, err := f.Stat()
if err != nil {
t.Fatal(err)
@@ -465,6 +467,108 @@ func TestDiffSize(t *testing.T) {
}
}
func TestChildDiffSize(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
defer d.Cleanup()
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
diffPath, err := d.Get("1")
if err != nil {
t.Fatal(err)
}
// Add a file to the diff path with a fixed size
size := int64(1024)
f, err := os.Create(path.Join(diffPath, "test_file"))
if err != nil {
t.Fatal(err)
}
if err := f.Truncate(size); err != nil {
t.Fatal(err)
}
s, err := f.Stat()
if err != nil {
t.Fatal(err)
}
size = s.Size()
if err := f.Close(); err != nil {
t.Fatal(err)
}
diffSize, err := d.DiffSize("1")
if err != nil {
t.Fatal(err)
}
if diffSize != size {
t.Fatalf("Expected size to be %d got %d", size, diffSize)
}
if err := d.Create("2", "1"); err != nil {
t.Fatal(err)
}
diffSize, err = d.DiffSize("2")
if err != nil {
t.Fatal(err)
}
// The diff size for the child should be zero
if diffSize != 0 {
t.Fatalf("Expected size to be %d got %d", 0, diffSize)
}
}
func TestExists(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
defer d.Cleanup()
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
if d.Exists("none") {
t.Fatal("id name should not exist in the driver")
}
if !d.Exists("1") {
t.Fatal("id 1 should exist in the driver")
}
}
func TestStatus(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
defer d.Cleanup()
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
status := d.Status()
if status == nil || len(status) == 0 {
t.Fatal("Status should not be nil or empty")
}
rootDir := status[0]
dirs := status[1]
if rootDir[0] != "Root Dir" {
t.Fatalf("Expected Root Dir got %s", rootDir[0])
}
if rootDir[1] != d.rootPath() {
t.Fatalf("Expected %s got %s", d.rootPath(), rootDir[1])
}
if dirs[0] != "Dirs" {
t.Fatalf("Expected Dirs got %s", dirs[0])
}
if dirs[1] != "1" {
t.Fatalf("Expected 1 got %s", dirs[1])
}
}
func TestApplyDiff(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
@@ -486,7 +590,9 @@ func TestApplyDiff(t *testing.T) {
if err != nil {
t.Fatal(err)
}
f.Truncate(size)
if err := f.Truncate(size); err != nil {
t.Fatal(err)
}
f.Close()
diff, err := d.Diff("1")
+11
View File
@@ -38,6 +38,9 @@ func pathExists(pth string) bool {
// symlink.
func (a *Driver) Migrate(pth string, setupInit func(p string) error) error {
if pathExists(path.Join(pth, "graph")) {
if err := a.migrateRepositories(pth); err != nil {
return err
}
if err := a.migrateImages(path.Join(pth, "graph")); err != nil {
return err
}
@@ -46,6 +49,14 @@ func (a *Driver) Migrate(pth string, setupInit func(p string) error) error {
return nil
}
func (a *Driver) migrateRepositories(pth string) error {
name := path.Join(pth, "repositories")
if err := os.Rename(name, name+"-aufs"); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}
func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) error {
fis, err := ioutil.ReadDir(pth)
if err != nil {
+36 -34
View File
@@ -6,13 +6,10 @@ import (
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"sync"
"syscall"
"time"
)
@@ -105,7 +102,7 @@ func (devices *DeviceSet) hasImage(name string) bool {
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)
_, err := os.Stat(filename)
_, err := osStat(filename)
return err == nil
}
@@ -117,16 +114,16 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)
if err := os.MkdirAll(dirname, 0700); err != nil && !os.IsExist(err) {
if err := osMkdirAll(dirname, 0700); err != nil && !osIsExist(err) {
return "", err
}
if _, err := os.Stat(filename); err != nil {
if !os.IsNotExist(err) {
if _, err := osStat(filename); err != nil {
if !osIsNotExist(err) {
return "", err
}
utils.Debugf("Creating loopback file %s for device-manage use", filename)
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600)
file, err := osOpenFile(filename, osORdWr|osOCreate, 0600)
if err != nil {
return "", err
}
@@ -174,7 +171,7 @@ func (devices *DeviceSet) saveMetadata() error {
if err := tmpFile.Close(); err != nil {
return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err)
}
if err := os.Rename(tmpFile.Name(), devices.jsonFile()); err != nil {
if err := osRename(tmpFile.Name(), devices.jsonFile()); err != nil {
return fmt.Errorf("Error committing metadata file", err)
}
@@ -225,9 +222,9 @@ func (devices *DeviceSet) activateDeviceIfNeeded(hash string) error {
func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
devname := info.DevName()
err := exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run()
err := execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname)
if err != nil {
err = exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname).Run()
err = execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname)
}
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
@@ -252,7 +249,7 @@ func (devices *DeviceSet) loadMetaData() error {
devices.NewTransactionId = devices.TransactionId
jsonData, err := ioutil.ReadFile(devices.jsonFile())
if err != nil && !os.IsNotExist(err) {
if err != nil && !osIsNotExist(err) {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
@@ -337,14 +334,13 @@ func (devices *DeviceSet) setupBaseImage() error {
}
func setCloseOnExec(name string) {
fileInfos, _ := ioutil.ReadDir("/proc/self/fd")
if fileInfos != nil {
if fileInfos, _ := ioutil.ReadDir("/proc/self/fd"); fileInfos != nil {
for _, i := range fileInfos {
link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name()))
link, _ := osReadlink(filepath.Join("/proc/self/fd", i.Name()))
if link == name {
fd, err := strconv.Atoi(i.Name())
if err == nil {
syscall.CloseOnExec(fd)
sysCloseOnExec(fd)
}
}
}
@@ -372,7 +368,7 @@ func (devices *DeviceSet) ResizePool(size int64) error {
datafilename := path.Join(dirname, "data")
metadatafilename := path.Join(dirname, "metadata")
datafile, err := os.OpenFile(datafilename, os.O_RDWR, 0)
datafile, err := osOpenFile(datafilename, osORdWr, 0)
if datafile == nil {
return err
}
@@ -387,19 +383,19 @@ func (devices *DeviceSet) ResizePool(size int64) error {
return fmt.Errorf("Can't shrink file")
}
dataloopback := FindLoopDeviceFor(datafile)
dataloopback := FindLoopDeviceFor(&osFile{File: datafile})
if dataloopback == nil {
return fmt.Errorf("Unable to find loopback mount for: %s", datafilename)
}
defer dataloopback.Close()
metadatafile, err := os.OpenFile(metadatafilename, os.O_RDWR, 0)
metadatafile, err := osOpenFile(metadatafilename, osORdWr, 0)
if metadatafile == nil {
return err
}
defer metadatafile.Close()
metadataloopback := FindLoopDeviceFor(metadatafile)
metadataloopback := FindLoopDeviceFor(&osFile{File: metadatafile})
if metadataloopback == nil {
return fmt.Errorf("Unable to find loopback mount for: %s", metadatafilename)
}
@@ -464,11 +460,11 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
// Set the device prefix from the device id and inode of the docker root dir
st, err := os.Stat(devices.root)
st, err := osStat(devices.root)
if err != nil {
return fmt.Errorf("Error looking up dir %s: %s", devices.root, err)
}
sysSt := st.Sys().(*syscall.Stat_t)
sysSt := toSysStatT(st.Sys())
// "reg-" stands for "regular file".
// In the future we might use "dev-" for "device file", etc.
// docker-maj,min[-inode] stands for:
@@ -654,10 +650,13 @@ func (devices *DeviceSet) waitRemove(hash string) error {
// The error might actually be something else, but we can't differentiate.
return nil
}
utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists)
if i%100 == 0 {
utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists)
}
if devinfo.Exists == 0 {
break
}
time.Sleep(1 * time.Millisecond)
}
if i == 1000 {
@@ -680,7 +679,9 @@ func (devices *DeviceSet) waitClose(hash string) error {
if err != nil {
return err
}
utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount)
if i%100 == 0 {
utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount)
}
if devinfo.OpenCount == 0 {
break
}
@@ -708,15 +709,16 @@ func (devices *DeviceSet) byHash(hash string) (devname string, err error) {
}
func (devices *DeviceSet) Shutdown() error {
utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
devices.Lock()
utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
defer devices.Unlock()
utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
for path, count := range devices.activeMounts {
for i := count; i > 0; i-- {
if err := syscall.Unmount(path, 0); err != nil {
if err := sysUnmount(path, 0); err != nil {
utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err)
}
}
@@ -752,15 +754,15 @@ func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error {
info := devices.Devices[hash]
var flags uintptr = syscall.MS_MGC_VAL
var flags uintptr = sysMsMgcVal
if readOnly {
flags = flags | syscall.MS_RDONLY
flags = flags | sysMsRdOnly
}
err := syscall.Mount(info.DevName(), path, "ext4", flags, "discard")
if err != nil && err == syscall.EINVAL {
err = syscall.Mount(info.DevName(), path, "ext4", flags, "")
err := sysMount(info.DevName(), path, "ext4", flags, "discard")
if err != nil && err == sysEInval {
err = sysMount(info.DevName(), path, "ext4", flags, "")
}
if err != nil {
return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
@@ -779,7 +781,7 @@ func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) erro
defer devices.Unlock()
utils.Debugf("[devmapper] Unmount(%s)", path)
if err := syscall.Unmount(path, 0); err != nil {
if err := sysUnmount(path, 0); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
+19 -23
View File
@@ -4,9 +4,7 @@ import (
"errors"
"fmt"
"github.com/dotcloud/docker/utils"
"os"
"runtime"
"syscall"
)
type DevmapperLogger interface {
@@ -49,7 +47,6 @@ var (
ErrTaskAddTarget = errors.New("dm_task_add_target failed")
ErrTaskSetSector = errors.New("dm_task_set_sector failed")
ErrTaskGetInfo = errors.New("dm_task_get_info failed")
ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed")
ErrTaskSetCookie = errors.New("dm_task_set_cookie failed")
ErrNilCookie = errors.New("cookie ptr can't be nil")
ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
@@ -86,7 +83,7 @@ type (
func (t *Task) destroy() {
if t != nil {
DmTaskDestory(t.unmanaged)
DmTaskDestroy(t.unmanaged)
runtime.SetFinalizer(t, nil)
}
}
@@ -180,45 +177,44 @@ func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64,
start, length, targetType, params
}
func AttachLoopDevice(filename string) (*os.File, error) {
func AttachLoopDevice(filename string) (*osFile, error) {
var fd int
res := DmAttachLoopDevice(filename, &fd)
if res == "" {
return nil, ErrAttachLoopbackDevice
}
return os.NewFile(uintptr(fd), res), nil
return &osFile{File: osNewFile(uintptr(fd), res)}, nil
}
func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) {
dev, inode, err := dmGetLoopbackBackingFile(file.Fd())
func getLoopbackBackingFile(file *osFile) (uint64, uint64, error) {
dev, inode, err := DmGetLoopbackBackingFile(file.Fd())
if err != 0 {
return 0, 0, ErrGetLoopbackBackingFile
}
return dev, inode, nil
}
func LoopbackSetCapacity(file *os.File) error {
err := dmLoopbackSetCapacity(file.Fd())
if err != 0 {
func LoopbackSetCapacity(file *osFile) error {
if err := DmLoopbackSetCapacity(file.Fd()); err != 0 {
return ErrLoopbackSetCapacity
}
return nil
}
func FindLoopDeviceFor(file *os.File) *os.File {
func FindLoopDeviceFor(file *osFile) *osFile {
stat, err := file.Stat()
if err != nil {
return nil
}
targetInode := stat.Sys().(*syscall.Stat_t).Ino
targetDevice := stat.Sys().(*syscall.Stat_t).Dev
targetInode := stat.Sys().(*sysStatT).Ino
targetDevice := stat.Sys().(*sysStatT).Dev
for i := 0; true; i++ {
path := fmt.Sprintf("/dev/loop%d", i)
file, err := os.OpenFile(path, os.O_RDWR, 0)
file, err := osOpenFile(path, osORdWr, 0)
if err != nil {
if os.IsNotExist(err) {
if osIsNotExist(err) {
return nil
}
@@ -227,9 +223,9 @@ func FindLoopDeviceFor(file *os.File) *os.File {
continue
}
dev, inode, err := getLoopbackBackingFile(file)
dev, inode, err := getLoopbackBackingFile(&osFile{File: file})
if err == nil && dev == targetDevice && inode == targetInode {
return file
return &osFile{File: file}
}
file.Close()
@@ -289,7 +285,7 @@ func RemoveDevice(name string) error {
return nil
}
func GetBlockDeviceSize(file *os.File) (uint64, error) {
func GetBlockDeviceSize(file *osFile) (uint64, error) {
size, errno := DmGetBlockSize(file.Fd())
if size == -1 || errno != 0 {
return 0, ErrGetBlockSize
@@ -298,7 +294,7 @@ func GetBlockDeviceSize(file *os.File) (uint64, error) {
}
// This is the programmatic example of "dmsetup create"
func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error {
func createPool(poolName string, dataFile, metadataFile *osFile) error {
task, err := createTask(DeviceCreate, poolName)
if task == nil {
return err
@@ -328,7 +324,7 @@ func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error
return nil
}
func reloadPool(poolName string, dataFile *os.File, metadataFile *os.File) error {
func reloadPool(poolName string, dataFile, metadataFile *osFile) error {
task, err := createTask(DeviceReload, poolName)
if task == nil {
return err
@@ -394,8 +390,8 @@ func getStatus(name string) (uint64, uint64, string, string, error) {
return 0, 0, "", "", fmt.Errorf("Non existing device %s", name)
}
_, start, length, target_type, params := task.GetNextTarget(0)
return start, length, target_type, params, nil
_, start, length, targetType, params := task.GetNextTarget(0)
return start, length, targetType, params, nil
}
func setTransactionId(poolName string, oldId uint64, newId uint64) error {
+106
View File
@@ -0,0 +1,106 @@
package devmapper
// Definition of struct dm_task and sub structures (from lvm2)
//
// struct dm_ioctl {
// /*
// * The version number is made up of three parts:
// * major - no backward or forward compatibility,
// * minor - only backwards compatible,
// * patch - both backwards and forwards compatible.
// *
// * All clients of the ioctl interface should fill in the
// * version number of the interface that they were
// * compiled with.
// *
// * All recognised ioctl commands (ie. those that don't
// * return -ENOTTY) fill out this field, even if the
// * command failed.
// */
// uint32_t version[3]; /* in/out */
// uint32_t data_size; /* total size of data passed in
// * including this struct */
// uint32_t data_start; /* offset to start of data
// * relative to start of this struct */
// uint32_t target_count; /* in/out */
// int32_t open_count; /* out */
// uint32_t flags; /* in/out */
// /*
// * event_nr holds either the event number (input and output) or the
// * udev cookie value (input only).
// * The DM_DEV_WAIT ioctl takes an event number as input.
// * The DM_SUSPEND, DM_DEV_REMOVE and DM_DEV_RENAME ioctls
// * use the field as a cookie to return in the DM_COOKIE
// * variable with the uevents they issue.
// * For output, the ioctls return the event number, not the cookie.
// */
// uint32_t event_nr; /* in/out */
// uint32_t padding;
// uint64_t dev; /* in/out */
// char name[DM_NAME_LEN]; /* device name */
// char uuid[DM_UUID_LEN]; /* unique identifier for
// * the block device */
// char data[7]; /* padding or data */
// };
// struct target {
// uint64_t start;
// uint64_t length;
// char *type;
// char *params;
// struct target *next;
// };
// typedef enum {
// DM_ADD_NODE_ON_RESUME, /* add /dev/mapper node with dmsetup resume */
// DM_ADD_NODE_ON_CREATE /* add /dev/mapper node with dmsetup create */
// } dm_add_node_t;
// struct dm_task {
// int type;
// char *dev_name;
// char *mangled_dev_name;
// struct target *head, *tail;
// int read_only;
// uint32_t event_nr;
// int major;
// int minor;
// int allow_default_major_fallback;
// uid_t uid;
// gid_t gid;
// mode_t mode;
// uint32_t read_ahead;
// uint32_t read_ahead_flags;
// union {
// struct dm_ioctl *v4;
// } dmi;
// char *newname;
// char *message;
// char *geometry;
// uint64_t sector;
// int no_flush;
// int no_open_count;
// int skip_lockfs;
// int query_inactive_table;
// int suppress_identical_reload;
// dm_add_node_t add_node;
// uint64_t existing_table_size;
// int cookie_set;
// int new_uuid;
// int secure_data;
// int retry_remove;
// int enable_checks;
// int expected_errno;
// char *uuid;
// char *mangled_uuid;
// };
//
+10 -10
View File
@@ -1,11 +1,11 @@
package devmapper
import (
"syscall"
"testing"
)
func TestTaskCreate(t *testing.T) {
t.Skip("FIXME: not a unit test")
// Test success
taskCreate(t, DeviceInfo)
@@ -18,6 +18,7 @@ func TestTaskCreate(t *testing.T) {
}
func TestTaskRun(t *testing.T) {
t.Skip("FIXME: not a unit test")
task := taskCreate(t, DeviceInfo)
// Test success
@@ -46,6 +47,7 @@ func TestTaskRun(t *testing.T) {
}
func TestTaskSetName(t *testing.T) {
t.Skip("FIXME: not a unit test")
task := taskCreate(t, DeviceInfo)
// Test success
@@ -63,6 +65,7 @@ func TestTaskSetName(t *testing.T) {
}
func TestTaskSetMessage(t *testing.T) {
t.Skip("FIXME: not a unit test")
task := taskCreate(t, DeviceInfo)
// Test success
@@ -80,6 +83,7 @@ func TestTaskSetMessage(t *testing.T) {
}
func TestTaskSetSector(t *testing.T) {
t.Skip("FIXME: not a unit test")
task := taskCreate(t, DeviceInfo)
// Test success
@@ -97,6 +101,7 @@ func TestTaskSetSector(t *testing.T) {
}
func TestTaskSetCookie(t *testing.T) {
t.Skip("FIXME: not a unit test")
var (
cookie uint = 0
task = taskCreate(t, DeviceInfo)
@@ -121,6 +126,7 @@ func TestTaskSetCookie(t *testing.T) {
}
func TestTaskSetAddNode(t *testing.T) {
t.Skip("FIXME: not a unit test")
task := taskCreate(t, DeviceInfo)
// Test success
@@ -142,6 +148,7 @@ func TestTaskSetAddNode(t *testing.T) {
}
func TestTaskSetRo(t *testing.T) {
t.Skip("FIXME: not a unit test")
task := taskCreate(t, DeviceInfo)
// Test success
@@ -159,6 +166,7 @@ func TestTaskSetRo(t *testing.T) {
}
func TestTaskAddTarget(t *testing.T) {
t.Skip("FIXME: not a unit test")
task := taskCreate(t, DeviceInfo)
// Test success
@@ -247,10 +255,6 @@ func dmTaskAddTargetFail(task *CDmTask,
return -1
}
func dmTaskGetDriverVersionFail(task *CDmTask, version *string) int {
return -1
}
func dmTaskGetInfoFail(task *CDmTask, info *Info) int {
return -1
}
@@ -264,14 +268,10 @@ func dmAttachLoopDeviceFail(filename string, fd *int) string {
return ""
}
func sysGetBlockSizeFail(fd uintptr, size *uint64) syscall.Errno {
func sysGetBlockSizeFail(fd uintptr, size *uint64) sysErrno {
return 1
}
func dmGetBlockSizeFail(fd uintptr) int64 {
return -1
}
func dmUdevWaitFail(cookie uint) int {
return -1
}
+36 -38
View File
@@ -140,7 +140,6 @@ static void log_with_errno_init()
import "C"
import (
"syscall"
"unsafe"
)
@@ -149,26 +148,28 @@ type (
)
var (
DmTaskDestory = dmTaskDestroyFct
DmTaskCreate = dmTaskCreateFct
DmTaskRun = dmTaskRunFct
DmTaskSetName = dmTaskSetNameFct
DmTaskSetMessage = dmTaskSetMessageFct
DmTaskSetSector = dmTaskSetSectorFct
DmTaskSetCookie = dmTaskSetCookieFct
DmTaskSetAddNode = dmTaskSetAddNodeFct
DmTaskSetRo = dmTaskSetRoFct
DmTaskAddTarget = dmTaskAddTargetFct
DmTaskGetInfo = dmTaskGetInfoFct
DmGetNextTarget = dmGetNextTargetFct
DmGetBlockSize = dmGetBlockSizeFct
DmAttachLoopDevice = dmAttachLoopDeviceFct
DmUdevWait = dmUdevWaitFct
DmLogInitVerbose = dmLogInitVerboseFct
DmSetDevDir = dmSetDevDirFct
DmGetLibraryVersion = dmGetLibraryVersionFct
LogWithErrnoInit = logWithErrnoInitFct
GetBlockSize = getBlockSizeFct
DmAttachLoopDevice = dmAttachLoopDeviceFct
DmGetBlockSize = dmGetBlockSizeFct
DmGetLibraryVersion = dmGetLibraryVersionFct
DmGetNextTarget = dmGetNextTargetFct
DmLogInitVerbose = dmLogInitVerboseFct
DmSetDevDir = dmSetDevDirFct
DmTaskAddTarget = dmTaskAddTargetFct
DmTaskCreate = dmTaskCreateFct
DmTaskDestroy = dmTaskDestroyFct
DmTaskGetInfo = dmTaskGetInfoFct
DmTaskRun = dmTaskRunFct
DmTaskSetAddNode = dmTaskSetAddNodeFct
DmTaskSetCookie = dmTaskSetCookieFct
DmTaskSetMessage = dmTaskSetMessageFct
DmTaskSetName = dmTaskSetNameFct
DmTaskSetRo = dmTaskSetRoFct
DmTaskSetSector = dmTaskSetSectorFct
DmUdevWait = dmUdevWaitFct
GetBlockSize = getBlockSizeFct
LogWithErrnoInit = logWithErrnoInitFct
DmGetLoopbackBackingFile = dmGetLoopbackBackingFileFct
DmLoopbackSetCapacity = dmLoopbackSetCapacityFct
)
func free(p *C.char) {
@@ -239,23 +240,22 @@ func dmTaskAddTargetFct(task *CDmTask,
C.uint64_t(start), C.uint64_t(size), Cttype, Cparams))
}
func dmGetLoopbackBackingFile(fd uintptr) (uint64, uint64, syscall.Errno) {
func dmGetLoopbackBackingFileFct(fd uintptr) (uint64, uint64, sysErrno) {
var lo64 C.struct_loop_info64
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_GET_STATUS64,
_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_GET_STATUS64,
uintptr(unsafe.Pointer(&lo64)))
return uint64(lo64.lo_device), uint64(lo64.lo_inode), err
return uint64(lo64.lo_device), uint64(lo64.lo_inode), sysErrno(err)
}
func dmLoopbackSetCapacity(fd uintptr) syscall.Errno {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_SET_CAPACITY, 0)
return err
func dmLoopbackSetCapacityFct(fd uintptr) sysErrno {
_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_SET_CAPACITY, 0)
return sysErrno(err)
}
func dmGetBlockSizeFct(fd uintptr) (int64, syscall.Errno) {
func dmGetBlockSizeFct(fd uintptr) (int64, sysErrno) {
var size int64
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64,
uintptr(unsafe.Pointer(&size)))
return size, err
_, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size)))
return size, sysErrno(err)
}
func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
@@ -275,9 +275,7 @@ func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
}
func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64,
target, params *string) uintptr {
func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
var (
Cstart, Clength C.uint64_t
CtargetType, Cparams *C.char
@@ -288,6 +286,7 @@ func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64,
*target = C.GoString(CtargetType)
*params = C.GoString(Cparams)
}()
nextp := C.dm_get_next_target((*C.struct_dm_task)(task),
unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams)
return uintptr(nextp)
@@ -307,10 +306,9 @@ func dmAttachLoopDeviceFct(filename string, fd *int) string {
return C.GoString(ret)
}
func getBlockSizeFct(fd uintptr, size *uint64) syscall.Errno {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64,
uintptr(unsafe.Pointer(&size)))
return err
func getBlockSizeFct(fd uintptr, size *uint64) sysErrno {
_, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size)))
return sysErrno(err)
}
func dmUdevWaitFct(cookie uint) int {
+8 -5
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"github.com/dotcloud/docker/graphdriver"
"io/ioutil"
"os"
"path"
)
@@ -22,7 +21,7 @@ type Driver struct {
home string
}
func Init(home string) (graphdriver.Driver, error) {
var Init = func(home string) (graphdriver.Driver, error) {
deviceSet, err := NewDeviceSet(home, true)
if err != nil {
return nil, err
@@ -57,7 +56,7 @@ func (d *Driver) Cleanup() error {
return d.DeviceSet.Shutdown()
}
func (d *Driver) Create(id string, parent string) error {
func (d *Driver) Create(id, parent string) error {
if err := d.DeviceSet.AddDevice(id, parent); err != nil {
return err
}
@@ -67,7 +66,7 @@ func (d *Driver) Create(id string, parent string) error {
return err
}
if err := os.MkdirAll(path.Join(mp, "rootfs"), 0755); err != nil && !os.IsExist(err) {
if err := osMkdirAll(path.Join(mp, "rootfs"), 0755); err != nil && !osIsExist(err) {
return err
}
@@ -98,7 +97,7 @@ func (d *Driver) Get(id string) (string, error) {
func (d *Driver) mount(id, mountPoint string) error {
// Create the target directories if they don't exist
if err := os.MkdirAll(mountPoint, 0755); err != nil && !os.IsExist(err) {
if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) {
return err
}
// If mountpoint is already mounted, do nothing
@@ -121,3 +120,7 @@ func (d *Driver) unmount(id, mountPoint string) error {
// Unmount the device
return d.DeviceSet.UnmountDevice(id, mountPoint, true)
}
func (d *Driver) Exists(id string) bool {
return d.Devices[id] != nil
}
+604 -35
View File
@@ -1,9 +1,13 @@
package devmapper
import (
"fmt"
"github.com/dotcloud/docker/graphdriver"
"io/ioutil"
"os"
"path"
"runtime"
"strings"
"syscall"
"testing"
)
@@ -12,7 +16,105 @@ func init() {
DefaultDataLoopbackSize = 300 * 1024 * 1024
DefaultMetaDataLoopbackSize = 200 * 1024 * 1024
DefaultBaseFsSize = 300 * 1024 * 1024
}
// denyAllDevmapper mocks all calls to libdevmapper in the unit tests, and denies them by default
func denyAllDevmapper() {
// Hijack all calls to libdevmapper with default panics.
// Authorized calls are selectively hijacked in each tests.
DmTaskCreate = func(t int) *CDmTask {
panic("DmTaskCreate: this method should not be called here")
}
DmTaskRun = func(task *CDmTask) int {
panic("DmTaskRun: this method should not be called here")
}
DmTaskSetName = func(task *CDmTask, name string) int {
panic("DmTaskSetName: this method should not be called here")
}
DmTaskSetMessage = func(task *CDmTask, message string) int {
panic("DmTaskSetMessage: this method should not be called here")
}
DmTaskSetSector = func(task *CDmTask, sector uint64) int {
panic("DmTaskSetSector: this method should not be called here")
}
DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
panic("DmTaskSetCookie: this method should not be called here")
}
DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
panic("DmTaskSetAddNode: this method should not be called here")
}
DmTaskSetRo = func(task *CDmTask) int {
panic("DmTaskSetRo: this method should not be called here")
}
DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
panic("DmTaskAddTarget: this method should not be called here")
}
DmTaskGetInfo = func(task *CDmTask, info *Info) int {
panic("DmTaskGetInfo: this method should not be called here")
}
DmGetNextTarget = func(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
panic("DmGetNextTarget: this method should not be called here")
}
DmAttachLoopDevice = func(filename string, fd *int) string {
panic("DmAttachLoopDevice: this method should not be called here")
}
DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
panic("DmGetBlockSize: this method should not be called here")
}
DmUdevWait = func(cookie uint) int {
panic("DmUdevWait: this method should not be called here")
}
DmSetDevDir = func(dir string) int {
panic("DmSetDevDir: this method should not be called here")
}
DmGetLibraryVersion = func(version *string) int {
panic("DmGetLibraryVersion: this method should not be called here")
}
DmLogInitVerbose = func(level int) {
panic("DmLogInitVerbose: this method should not be called here")
}
DmTaskDestroy = func(task *CDmTask) {
panic("DmTaskDestroy: this method should not be called here")
}
GetBlockSize = func(fd uintptr, size *uint64) sysErrno {
panic("GetBlockSize: this method should not be called here")
}
LogWithErrnoInit = func() {
panic("LogWithErrnoInit: this method should not be called here")
}
}
func denyAllSyscall() {
sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
panic("sysMount: this method should not be called here")
}
sysUnmount = func(target string, flags int) (err error) {
panic("sysUnmount: this method should not be called here")
}
sysCloseOnExec = func(fd int) {
panic("sysCloseOnExec: this method should not be called here")
}
sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
panic("sysSyscall: this method should not be called here")
}
// Not a syscall, but forbidding it here anyway
Mounted = func(mnt string) (bool, error) {
panic("devmapper.Mounted: this method should not be called here")
}
// osOpenFile = os.OpenFile
// osNewFile = os.NewFile
// osCreate = os.Create
// osStat = os.Stat
// osIsNotExist = os.IsNotExist
// osIsExist = os.IsExist
// osMkdirAll = os.MkdirAll
// osRemoveAll = os.RemoveAll
// osRename = os.Rename
// osReadlink = os.Readlink
// execRun = func(name string, args ...string) error {
// return exec.Command(name, args...).Run()
// }
}
func mkTestDirectory(t *testing.T) string {
@@ -34,72 +136,521 @@ func newDriver(t *testing.T) *Driver {
func cleanup(d *Driver) {
d.Cleanup()
os.RemoveAll(d.home)
osRemoveAll(d.home)
}
type Set map[string]bool
func (r Set) Assert(t *testing.T, names ...string) {
for _, key := range names {
if _, exists := r[key]; !exists {
t.Fatalf("Key not set: %s", key)
}
delete(r, key)
}
if len(r) != 0 {
t.Fatalf("Unexpected keys: %v", r)
}
}
func TestInit(t *testing.T) {
home := mkTestDirectory(t)
defer os.RemoveAll(home)
driver, err := Init(home)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := driver.Cleanup(); err != nil {
var (
calls = make(Set)
devicesAttached = make(Set)
taskMessages = make(Set)
taskTypes = make(Set)
home = mkTestDirectory(t)
)
defer osRemoveAll(home)
func() {
denyAllDevmapper()
DmSetDevDir = func(dir string) int {
calls["DmSetDevDir"] = true
expectedDir := "/dev"
if dir != expectedDir {
t.Fatalf("Wrong libdevmapper call\nExpected: DmSetDevDir(%v)\nReceived: DmSetDevDir(%v)\n", expectedDir, dir)
}
return 0
}
LogWithErrnoInit = func() {
calls["DmLogWithErrnoInit"] = true
}
var task1 CDmTask
DmTaskCreate = func(taskType int) *CDmTask {
calls["DmTaskCreate"] = true
taskTypes[fmt.Sprintf("%d", taskType)] = true
return &task1
}
DmTaskSetName = func(task *CDmTask, name string) int {
calls["DmTaskSetName"] = true
expectedTask := &task1
if task != expectedTask {
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", expectedTask, task)
}
// FIXME: use Set.AssertRegexp()
if !strings.HasPrefix(name, "docker-") && !strings.HasPrefix(name, "/dev/mapper/docker-") ||
!strings.HasSuffix(name, "-pool") && !strings.HasSuffix(name, "-base") {
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", "docker-...-pool", name)
}
return 1
}
DmTaskRun = func(task *CDmTask) int {
calls["DmTaskRun"] = true
expectedTask := &task1
if task != expectedTask {
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskRun(%v)\nReceived: DmTaskRun(%v)\n", expectedTask, task)
}
return 1
}
DmTaskGetInfo = func(task *CDmTask, info *Info) int {
calls["DmTaskGetInfo"] = true
expectedTask := &task1
if task != expectedTask {
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskGetInfo(%v)\nReceived: DmTaskGetInfo(%v)\n", expectedTask, task)
}
// This will crash if info is not dereferenceable
info.Exists = 0
return 1
}
DmTaskSetSector = func(task *CDmTask, sector uint64) int {
calls["DmTaskSetSector"] = true
expectedTask := &task1
if task != expectedTask {
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task)
}
if expectedSector := uint64(0); sector != expectedSector {
t.Fatalf("Wrong libdevmapper call to DmTaskSetSector\nExpected: %v\nReceived: %v\n", expectedSector, sector)
}
return 1
}
DmTaskSetMessage = func(task *CDmTask, message string) int {
calls["DmTaskSetMessage"] = true
expectedTask := &task1
if task != expectedTask {
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task)
}
taskMessages[message] = true
return 1
}
var (
fakeDataLoop = "/dev/loop42"
fakeMetadataLoop = "/dev/loop43"
fakeDataLoopFd = 42
fakeMetadataLoopFd = 43
)
var attachCount int
DmAttachLoopDevice = func(filename string, fd *int) string {
calls["DmAttachLoopDevice"] = true
if _, exists := devicesAttached[filename]; exists {
t.Fatalf("Already attached %s", filename)
}
devicesAttached[filename] = true
// This will crash if fd is not dereferenceable
if attachCount == 0 {
attachCount++
*fd = fakeDataLoopFd
return fakeDataLoop
} else {
*fd = fakeMetadataLoopFd
return fakeMetadataLoop
}
}
DmTaskDestroy = func(task *CDmTask) {
calls["DmTaskDestroy"] = true
expectedTask := &task1
if task != expectedTask {
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
}
}
fakeBlockSize := int64(4242 * 512)
DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
calls["DmGetBlockSize"] = true
if expectedFd := uintptr(42); fd != expectedFd {
t.Fatalf("Wrong libdevmapper call\nExpected: DmGetBlockSize(%v)\nReceived: DmGetBlockSize(%v)\n", expectedFd, fd)
}
return fakeBlockSize, 0
}
DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
calls["DmTaskSetTarget"] = true
expectedTask := &task1
if task != expectedTask {
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
}
if start != 0 {
t.Fatalf("Wrong start: %d != %d", start, 0)
}
if ttype != "thin" && ttype != "thin-pool" {
t.Fatalf("Wrong ttype: %s", ttype)
}
// Quick smoke test
if params == "" {
t.Fatalf("Params should not be empty")
}
return 1
}
fakeCookie := uint(4321)
DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
calls["DmTaskSetCookie"] = true
expectedTask := &task1
if task != expectedTask {
t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
}
if flags != 0 {
t.Fatalf("Cookie flags should be 0 (not %x)", flags)
}
*cookie = fakeCookie
return 1
}
DmUdevWait = func(cookie uint) int {
calls["DmUdevWait"] = true
if cookie != fakeCookie {
t.Fatalf("Wrong cookie: %d != %d", cookie, fakeCookie)
}
return 1
}
DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
if addNode != AddNodeOnCreate {
t.Fatalf("Wrong AddNoteType: %v (expected %v)", addNode, AddNodeOnCreate)
}
calls["DmTaskSetAddNode"] = true
return 1
}
execRun = func(name string, args ...string) error {
calls["execRun"] = true
if name != "mkfs.ext4" {
t.Fatalf("Expected %s to be executed, not %s", "mkfs.ext4", name)
}
return nil
}
driver, err := Init(home)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := driver.Cleanup(); err != nil {
t.Fatal(err)
}
}()
}()
// Put all tests in a funciton to make sure the garbage collection will
// occur.
id := "foo"
if err := driver.Create(id, ""); err != nil {
t.Fatal(err)
// Call GC to cleanup runtime.Finalizers
runtime.GC()
calls.Assert(t,
"DmSetDevDir",
"DmLogWithErrnoInit",
"DmTaskSetName",
"DmTaskRun",
"DmTaskGetInfo",
"DmAttachLoopDevice",
"DmTaskDestroy",
"execRun",
"DmTaskCreate",
"DmGetBlockSize",
"DmTaskSetTarget",
"DmTaskSetCookie",
"DmUdevWait",
"DmTaskSetSector",
"DmTaskSetMessage",
"DmTaskSetAddNode",
)
devicesAttached.Assert(t, path.Join(home, "devicemapper", "data"), path.Join(home, "devicemapper", "metadata"))
taskTypes.Assert(t, "0", "6", "17")
taskMessages.Assert(t, "create_thin 0", "set_transaction_id 0 1")
}
func fakeInit() func(home string) (graphdriver.Driver, error) {
oldInit := Init
Init = func(home string) (graphdriver.Driver, error) {
return &Driver{
home: home,
}, nil
}
dir, err := driver.Get(id)
if err != nil {
t.Fatal(err)
return oldInit
}
func restoreInit(init func(home string) (graphdriver.Driver, error)) {
Init = init
}
func mockAllDevmapper(calls Set) {
DmSetDevDir = func(dir string) int {
calls["DmSetDevDir"] = true
return 0
}
if st, err := os.Stat(dir); err != nil {
t.Fatal(err)
} else if !st.IsDir() {
t.Fatalf("Get(%V) did not return a directory", id)
LogWithErrnoInit = func() {
calls["DmLogWithErrnoInit"] = true
}
DmTaskCreate = func(taskType int) *CDmTask {
calls["DmTaskCreate"] = true
return &CDmTask{}
}
DmTaskSetName = func(task *CDmTask, name string) int {
calls["DmTaskSetName"] = true
return 1
}
DmTaskRun = func(task *CDmTask) int {
calls["DmTaskRun"] = true
return 1
}
DmTaskGetInfo = func(task *CDmTask, info *Info) int {
calls["DmTaskGetInfo"] = true
return 1
}
DmTaskSetSector = func(task *CDmTask, sector uint64) int {
calls["DmTaskSetSector"] = true
return 1
}
DmTaskSetMessage = func(task *CDmTask, message string) int {
calls["DmTaskSetMessage"] = true
return 1
}
DmAttachLoopDevice = func(filename string, fd *int) string {
calls["DmAttachLoopDevice"] = true
return "/dev/loop42"
}
DmTaskDestroy = func(task *CDmTask) {
calls["DmTaskDestroy"] = true
}
DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
calls["DmGetBlockSize"] = true
return int64(4242 * 512), 0
}
DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
calls["DmTaskSetTarget"] = true
return 1
}
DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
calls["DmTaskSetCookie"] = true
return 1
}
DmUdevWait = func(cookie uint) int {
calls["DmUdevWait"] = true
return 1
}
DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
calls["DmTaskSetAddNode"] = true
return 1
}
execRun = func(name string, args ...string) error {
calls["execRun"] = true
return nil
}
}
func TestDriverName(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
denyAllDevmapper()
defer denyAllDevmapper()
oldInit := fakeInit()
defer restoreInit(oldInit)
d := newDriver(t)
if d.String() != "devicemapper" {
t.Fatalf("Expected driver name to be devicemapper got %s", d.String())
}
}
func TestDriverCreate(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
denyAllDevmapper()
denyAllSyscall()
defer denyAllSyscall()
defer denyAllDevmapper()
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
calls := make(Set)
mockAllDevmapper(calls)
sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
calls["sysMount"] = true
// FIXME: compare the exact source and target strings (inodes + devname)
if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) {
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source)
}
if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
}
if expectedFstype := "ext4"; fstype != expectedFstype {
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype)
}
if expectedFlags := uintptr(3236757504); flags != expectedFlags {
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
}
return nil
}
Mounted = func(mnt string) (bool, error) {
calls["Mounted"] = true
if !strings.HasPrefix(mnt, "/tmp/docker-test-devmapper-") || !strings.HasSuffix(mnt, "/mnt/1") {
t.Fatalf("Wrong mounted call\nExpected: Mounted(%v)\nReceived: Mounted(%v)\n", "/tmp/docker-test-devmapper-.../mnt/1", mnt)
}
return false, nil
}
func() {
d := newDriver(t)
calls.Assert(t,
"DmSetDevDir",
"DmLogWithErrnoInit",
"DmTaskSetName",
"DmTaskRun",
"DmTaskGetInfo",
"DmAttachLoopDevice",
"execRun",
"DmTaskCreate",
"DmGetBlockSize",
"DmTaskSetTarget",
"DmTaskSetCookie",
"DmUdevWait",
"DmTaskSetSector",
"DmTaskSetMessage",
"DmTaskSetAddNode",
)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
calls.Assert(t,
"DmTaskCreate",
"DmTaskGetInfo",
"sysMount",
"Mounted",
"DmTaskRun",
"DmTaskSetTarget",
"DmTaskSetSector",
"DmTaskSetCookie",
"DmUdevWait",
"DmTaskSetName",
"DmTaskSetMessage",
"DmTaskSetAddNode",
)
}()
runtime.GC()
calls.Assert(t,
"DmTaskDestroy",
)
}
func TestDriverRemove(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
denyAllDevmapper()
denyAllSyscall()
defer denyAllSyscall()
defer denyAllDevmapper()
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
calls := make(Set)
mockAllDevmapper(calls)
sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
calls["sysMount"] = true
// FIXME: compare the exact source and target strings (inodes + devname)
if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) {
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source)
}
if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
}
if expectedFstype := "ext4"; fstype != expectedFstype {
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype)
}
if expectedFlags := uintptr(3236757504); flags != expectedFlags {
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
}
return nil
}
sysUnmount = func(target string, flags int) (err error) {
calls["sysUnmount"] = true
// FIXME: compare the exact source and target strings (inodes + devname)
if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
}
if expectedFlags := 0; flags != expectedFlags {
t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
}
return nil
}
Mounted = func(mnt string) (bool, error) {
calls["Mounted"] = true
return false, nil
}
if err := d.Remove("1"); err != nil {
t.Fatal(err)
}
func() {
d := newDriver(t)
calls.Assert(t,
"DmSetDevDir",
"DmLogWithErrnoInit",
"DmTaskSetName",
"DmTaskRun",
"DmTaskGetInfo",
"DmAttachLoopDevice",
"execRun",
"DmTaskCreate",
"DmGetBlockSize",
"DmTaskSetTarget",
"DmTaskSetCookie",
"DmUdevWait",
"DmTaskSetSector",
"DmTaskSetMessage",
"DmTaskSetAddNode",
)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
calls.Assert(t,
"DmTaskCreate",
"DmTaskGetInfo",
"sysMount",
"Mounted",
"DmTaskRun",
"DmTaskSetTarget",
"DmTaskSetSector",
"DmTaskSetCookie",
"DmUdevWait",
"DmTaskSetName",
"DmTaskSetMessage",
"DmTaskSetAddNode",
)
Mounted = func(mnt string) (bool, error) {
calls["Mounted"] = true
return true, nil
}
if err := d.Remove("1"); err != nil {
t.Fatal(err)
}
calls.Assert(t,
"DmTaskRun",
"DmTaskSetSector",
"DmTaskSetName",
"DmTaskSetMessage",
"DmTaskCreate",
"DmTaskGetInfo",
"Mounted",
"sysUnmount",
)
}()
runtime.GC()
calls.Assert(t,
"DmTaskDestroy",
)
}
func TestCleanup(t *testing.T) {
t.Skip("FIXME: not a unit test")
t.Skip("Unimplemented")
d := newDriver(t)
defer os.RemoveAll(d.home)
defer osRemoveAll(d.home)
mountPoints := make([]string, 2)
@@ -161,6 +712,7 @@ func TestCleanup(t *testing.T) {
}
func TestNotMounted(t *testing.T) {
t.Skip("FIXME: not a unit test")
t.Skip("Not implemented")
d := newDriver(t)
defer cleanup(d)
@@ -179,6 +731,7 @@ func TestNotMounted(t *testing.T) {
}
func TestMounted(t *testing.T) {
t.Skip("FIXME: not a unit test")
d := newDriver(t)
defer cleanup(d)
@@ -199,6 +752,7 @@ func TestMounted(t *testing.T) {
}
func TestInitCleanedDriver(t *testing.T) {
t.Skip("FIXME: not a unit test")
d := newDriver(t)
if err := d.Create("1", ""); err != nil {
@@ -225,6 +779,7 @@ func TestInitCleanedDriver(t *testing.T) {
}
func TestMountMountedDriver(t *testing.T) {
t.Skip("FIXME: not a unit test")
d := newDriver(t)
defer cleanup(d)
@@ -243,6 +798,7 @@ func TestMountMountedDriver(t *testing.T) {
}
func TestGetReturnsValidDevice(t *testing.T) {
t.Skip("FIXME: not a unit test")
d := newDriver(t)
defer cleanup(d)
@@ -268,6 +824,7 @@ func TestGetReturnsValidDevice(t *testing.T) {
}
func TestDriverGetSize(t *testing.T) {
t.Skip("FIXME: not a unit test")
t.Skipf("Size is currently not implemented")
d := newDriver(t)
@@ -284,7 +841,7 @@ func TestDriverGetSize(t *testing.T) {
size := int64(1024)
f, err := os.Create(path.Join(mountPoint, "test_file"))
f, err := osCreate(path.Join(mountPoint, "test_file"))
if err != nil {
t.Fatal(err)
}
@@ -301,3 +858,15 @@ func TestDriverGetSize(t *testing.T) {
// t.Fatalf("Expected size %d got %d", size, diffSize)
// }
}
func assertMap(t *testing.T, m map[string]bool, keys ...string) {
for _, key := range keys {
if _, exists := m[key]; !exists {
t.Fatalf("Key not set: %s", key)
}
delete(m, key)
}
if len(m) != 0 {
t.Fatalf("Unexpected keys: %v", m)
}
}
+6 -8
View File
@@ -1,27 +1,25 @@
package devmapper
import (
"os"
"path/filepath"
"syscall"
)
// FIXME: this is copy-pasted from the aufs driver.
// It should be moved into the core.
func Mounted(mountpoint string) (bool, error) {
mntpoint, err := os.Stat(mountpoint)
var Mounted = func(mountpoint string) (bool, error) {
mntpoint, err := osStat(mountpoint)
if err != nil {
if os.IsNotExist(err) {
if osIsNotExist(err) {
return false, nil
}
return false, err
}
parent, err := os.Stat(filepath.Join(mountpoint, ".."))
parent, err := osStat(filepath.Join(mountpoint, ".."))
if err != nil {
return false, err
}
mntpointSt := mntpoint.Sys().(*syscall.Stat_t)
parentSt := parent.Sys().(*syscall.Stat_t)
mntpointSt := toSysStatT(mntpoint.Sys())
parentSt := toSysStatT(parent.Sys())
return mntpointSt.Dev != parentSt.Dev, nil
}
+50
View File
@@ -0,0 +1,50 @@
package devmapper
import (
"os"
"os/exec"
"syscall"
)
type (
sysStatT syscall.Stat_t
sysErrno syscall.Errno
osFile struct{ *os.File }
)
var (
sysMount = syscall.Mount
sysUnmount = syscall.Unmount
sysCloseOnExec = syscall.CloseOnExec
sysSyscall = syscall.Syscall
osOpenFile = os.OpenFile
osNewFile = os.NewFile
osCreate = os.Create
osStat = os.Stat
osIsNotExist = os.IsNotExist
osIsExist = os.IsExist
osMkdirAll = os.MkdirAll
osRemoveAll = os.RemoveAll
osRename = os.Rename
osReadlink = os.Readlink
execRun = func(name string, args ...string) error {
return exec.Command(name, args...).Run()
}
)
const (
sysMsMgcVal = syscall.MS_MGC_VAL
sysMsRdOnly = syscall.MS_RDONLY
sysEInval = syscall.EINVAL
sysSysIoctl = syscall.SYS_IOCTL
osORdWr = os.O_RDWR
osOCreate = os.O_CREATE
)
func toSysStatT(i interface{}) *sysStatT {
return (*sysStatT)(i.(*syscall.Stat_t))
}
+9 -17
View File
@@ -8,8 +8,6 @@ import (
"path"
)
var DefaultDriver string
type InitFunc func(root string) (Driver, error)
type Driver interface {
@@ -19,6 +17,7 @@ type Driver interface {
Remove(id string) error
Get(id string) (dir string, err error)
Exists(id string) bool
Status() [][2]string
@@ -33,13 +32,14 @@ type Differ interface {
}
var (
DefaultDriver string
// All registred drivers
drivers map[string]InitFunc
// Slice of drivers that should be used in an order
priority = []string{
"aufs",
"devicemapper",
"dummy",
"vfs",
}
)
@@ -63,14 +63,8 @@ func GetDriver(name, home string) (Driver, error) {
return nil, fmt.Errorf("No such driver: %s", name)
}
func New(root string) (Driver, error) {
var driver Driver
var lastError error
for _, name := range []string{
os.Getenv("DOCKER_DRIVER"),
DefaultDriver,
} {
func New(root string) (driver Driver, err error) {
for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} {
if name != "" {
return GetDriver(name, root)
}
@@ -78,9 +72,8 @@ func New(root string) (Driver, error) {
// Check for priority drivers first
for _, name := range priority {
driver, lastError = GetDriver(name, root)
if lastError != nil {
utils.Debugf("Error loading driver %s: %s", name, lastError)
if driver, err = GetDriver(name, root); err != nil {
utils.Debugf("Error loading driver %s: %s", name, err)
continue
}
return driver, nil
@@ -88,11 +81,10 @@ func New(root string) (Driver, error) {
// Check all registered drivers if no priority driver is found
for _, initFunc := range drivers {
driver, lastError = initFunc(root)
if lastError != nil {
if driver, err = initFunc(root); err != nil {
continue
}
return driver, nil
}
return nil, lastError
return nil, err
}
@@ -1,4 +1,4 @@
package dummy
package vfs
import (
"fmt"
@@ -9,7 +9,7 @@ import (
)
func init() {
graphdriver.Register("dummy", Init)
graphdriver.Register("vfs", Init)
}
func Init(home string) (graphdriver.Driver, error) {
@@ -24,7 +24,7 @@ type Driver struct {
}
func (d *Driver) String() string {
return "dummy"
return "vfs"
}
func (d *Driver) Status() [][2]string {
@@ -84,3 +84,8 @@ func (d *Driver) Get(id string) (string, error) {
}
return dir, nil
}
func (d *Driver) Exists(id string) bool {
_, err := os.Stat(d.dir(id))
return err == nil
}
+14 -3
View File
@@ -37,13 +37,24 @@ DEFAULT_BUNDLES=(
test
dynbinary
dyntest
tgz
ubuntu
)
VERSION=$(cat ./VERSION)
GITCOMMIT=$(git rev-parse --short HEAD)
if [ -n "$(git status --porcelain)" ]; then
GITCOMMIT="$GITCOMMIT-dirty"
if [ -d .git ] && command -v git &> /dev/null; then
GITCOMMIT=$(git rev-parse --short HEAD)
if [ -n "$(git status --porcelain)" ]; then
GITCOMMIT="$GITCOMMIT-dirty"
fi
elif [ "$DOCKER_GITCOMMIT" ]; then
GITCOMMIT="$DOCKER_GITCOMMIT"
else
echo >&2 'error: .git directory missing and DOCKER_GITCOMMIT not specified'
echo >&2 ' Please either build with the .git directory accessible, or specify the'
echo >&2 ' exact (--short) commit hash you are building using DOCKER_GITCOMMIT for'
echo >&2 ' future accountability in diagnosing build issues. Thanks!'
exit 1
fi
# Use these flags when compiling the tests and final binary
+23
View File
@@ -0,0 +1,23 @@
#!/bin/sh
DEST="$1"
BINARY="$DEST/../binary/docker-$VERSION"
TGZ="$DEST/docker-$VERSION.tgz"
set -e
if [ ! -x "$BINARY" ]; then
echo >&2 'error: binary must be run before tgz'
false
fi
mkdir -p "$DEST/build"
mkdir -p "$DEST/build/usr/local/bin"
cp -L "$BINARY" "$DEST/build/usr/local/bin/docker"
tar --numeric-owner --owner 0 -C "$DEST/build" -czf "$TGZ" usr
rm -rf "$DEST/build"
echo "Created tgz: $TGZ"
+18
View File
@@ -47,6 +47,7 @@ cd /go/src/github.com/dotcloud/docker
RELEASE_BUNDLES=(
binary
tgz
ubuntu
)
@@ -188,6 +189,22 @@ EOF
echo "APT repository uploaded. Instructions available at $(s3_url)/ubuntu"
}
# Upload a tgz to S3
release_tgz() {
[ -e bundles/$VERSION/tgz/docker-$VERSION.tgz ] || {
echo >&2 './hack/make.sh must be run before release_binary'
exit 1
}
S3DIR=s3://$BUCKET/builds/Linux/x86_64
s3cmd --acl-public put bundles/$VERSION/tgz/docker-$VERSION.tgz $S3DIR/docker-$VERSION.tgz
if [ -z "$NOLATEST" ]; then
echo "Copying docker-$VERSION.tgz to docker-latest.tgz"
s3cmd --acl-public cp $S3DIR/docker-$VERSION.tgz $S3DIR/docker-latest.tgz
fi
}
# Upload a static binary to S3
release_binary() {
[ -e bundles/$VERSION/binary/docker-$VERSION ] || {
@@ -230,6 +247,7 @@ release_test() {
main() {
setup_s3
release_binary
release_tgz
release_ubuntu
release_index
release_test
+53 -15
View File
@@ -62,26 +62,52 @@ func LoadImage(root string) (*Image, error) {
return img, nil
}
func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, rootfs string) error {
func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, layer string) error {
// Store the layer
layer := rootfs
var (
size int64
err error
driver = img.graph.driver
)
if err := os.MkdirAll(layer, 0755); err != nil {
return err
}
// If layerData is not nil, unpack it into the new layer
if layerData != nil {
if differ, ok := img.graph.driver.(graphdriver.Differ); ok {
if differ, ok := driver.(graphdriver.Differ); ok {
if err := differ.ApplyDiff(img.ID, layerData); err != nil {
return err
}
if size, err = differ.DiffSize(img.ID); err != nil {
return err
}
} else {
start := time.Now()
start := time.Now().UTC()
utils.Debugf("Start untar layer")
if err := archive.ApplyLayer(layer, layerData); err != nil {
return err
}
utils.Debugf("Untar time: %vs", time.Now().Sub(start).Seconds())
utils.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
if img.Parent == "" {
if size, err = utils.TreeSize(layer); err != nil {
return err
}
} else {
parent, err := driver.Get(img.Parent)
if err != nil {
return err
}
changes, err := archive.ChangesDirs(layer, parent)
if err != nil {
return err
}
if size = archive.ChangesSize(layer, changes); err != nil {
return err
}
}
}
}
@@ -90,18 +116,13 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, ro
return ioutil.WriteFile(jsonPath(root), jsonData, 0600)
}
// Otherwise, unmarshal the image
jsonData, err := json.Marshal(img)
if err != nil {
if jsonData, err = json.Marshal(img); err != nil {
return err
}
if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
return err
}
// Compute and save the size of the rootfs
size, err := utils.TreeSize(rootfs)
if err != nil {
return fmt.Errorf("Error computing size of rootfs %s: %s", img.ID, err)
}
img.Size = size
if err := img.SaveSize(root); err != nil {
return err
@@ -123,15 +144,32 @@ func jsonPath(root string) string {
}
// TarLayer returns a tar archive of the image's filesystem layer.
func (img *Image) TarLayer(compression archive.Compression) (archive.Archive, error) {
func (img *Image) TarLayer() (archive.Archive, error) {
if img.graph == nil {
return nil, fmt.Errorf("Can't load storage driver for unregistered image %s", img.ID)
}
layerPath, err := img.graph.driver.Get(img.ID)
driver := img.graph.driver
if differ, ok := driver.(graphdriver.Differ); ok {
return differ.Diff(img.ID)
}
imgFs, err := driver.Get(img.ID)
if err != nil {
return nil, err
}
return archive.Tar(layerPath, compression)
if img.Parent == "" {
return archive.Tar(imgFs, archive.Uncompressed)
} else {
parentFs, err := driver.Get(img.Parent)
if err != nil {
return nil, err
}
changes, err := archive.ChangesDirs(imgFs, parentFs)
if err != nil {
return nil, err
}
return archive.ExportChanges(imgFs, changes)
}
}
func ValidateID(id string) error {
+9 -9
View File
@@ -289,7 +289,7 @@ func TestRunDisconnect(t *testing.T) {
setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
container := globalRuntime.List()[0]
container.Wait()
if container.State.Running {
if container.State.IsRunning() {
t.Fatalf("/bin/cat is still running after closing stdin")
}
})
@@ -319,7 +319,7 @@ func TestRunDisconnectTty(t *testing.T) {
for {
// Client disconnect after run -i should keep stdin out in TTY mode
l := globalRuntime.List()
if len(l) == 1 && l[0].State.Running {
if len(l) == 1 && l[0].State.IsRunning() {
break
}
time.Sleep(10 * time.Millisecond)
@@ -329,7 +329,7 @@ func TestRunDisconnectTty(t *testing.T) {
// Client disconnect after run -i should keep stdin out in TTY mode
container := globalRuntime.List()[0]
setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() {
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
t.Fatal(err)
}
@@ -345,7 +345,7 @@ func TestRunDisconnectTty(t *testing.T) {
// Give some time to monitor to do his thing
container.WaitTimeout(500 * time.Millisecond)
if !container.State.Running {
if !container.State.IsRunning() {
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
}
}
@@ -454,7 +454,7 @@ func TestRunDetach(t *testing.T) {
})
time.Sleep(500 * time.Millisecond)
if !container.State.Running {
if !container.State.IsRunning() {
t.Fatal("The detached container should be still running")
}
@@ -534,7 +534,7 @@ func TestAttachDetach(t *testing.T) {
})
time.Sleep(500 * time.Millisecond)
if !container.State.Running {
if !container.State.IsRunning() {
t.Fatal("The detached container should be still running")
}
@@ -596,7 +596,7 @@ func TestAttachDetachTruncatedID(t *testing.T) {
})
time.Sleep(500 * time.Millisecond)
if !container.State.Running {
if !container.State.IsRunning() {
t.Fatal("The detached container should be still running")
}
@@ -629,7 +629,7 @@ func TestAttachDisconnect(t *testing.T) {
setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
for {
l := globalRuntime.List()
if len(l) == 1 && l[0].State.Running {
if len(l) == 1 && l[0].State.IsRunning() {
break
}
time.Sleep(10 * time.Millisecond)
@@ -665,7 +665,7 @@ func TestAttachDisconnect(t *testing.T) {
// We closed stdin, expect /bin/cat to still be running
// Wait a little bit to make sure container.monitor() did his thing
err := container.WaitTimeout(500 * time.Millisecond)
if err == nil || !container.State.Running {
if err == nil || !container.State.IsRunning() {
t.Fatalf("/bin/cat is not running after closing stdin")
}
+45 -40
View File
@@ -224,13 +224,13 @@ func TestCommitAutoRun(t *testing.T) {
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
defer runtime.Destroy(container1)
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container1.Run(); err != nil {
t.Fatal(err)
}
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
@@ -280,13 +280,13 @@ func TestCommitRun(t *testing.T) {
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
defer runtime.Destroy(container1)
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container1.Run(); err != nil {
t.Fatal(err)
}
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
@@ -330,6 +330,11 @@ func TestCommitRun(t *testing.T) {
}
func TestStart(t *testing.T) {
_, err1 := os.Stat("/sys/fs/cgroup/cpuacct,cpu")
_, err2 := os.Stat("/sys/fs/cgroup/cpu,cpuacct")
if err1 == nil || err2 == nil {
t.Skip("Fixme. Setting cpu cgroup shares doesn't work in dind on a Fedora host. The lxc utils are confused by the cpu,cpuacct mount.")
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, _, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
@@ -347,7 +352,7 @@ func TestStart(t *testing.T) {
// Give some time to the process to start
container.WaitTimeout(500 * time.Millisecond)
if !container.State.Running {
if !container.State.IsRunning() {
t.Errorf("Container should be running")
}
if err := container.Start(); err == nil {
@@ -365,13 +370,13 @@ func TestRun(t *testing.T) {
container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
defer runtime.Destroy(container)
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container.Run(); err != nil {
t.Fatal(err)
}
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
}
@@ -395,7 +400,7 @@ func TestOutput(t *testing.T) {
t.Fatal(err)
}
if string(output) != "foobar" {
t.Error(string(output))
t.Fatalf("%s != %s", string(output), "foobar")
}
}
@@ -416,8 +421,8 @@ func TestContainerNetwork(t *testing.T) {
if err := container.Run(); err != nil {
t.Fatal(err)
}
if container.State.ExitCode != 0 {
t.Errorf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", container.State.ExitCode)
if code := container.State.GetExitCode(); code != 0 {
t.Fatalf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", code)
}
}
@@ -441,7 +446,7 @@ func TestKillDifferentUser(t *testing.T) {
// there is a side effect I'm not seeing.
// defer container.stdin.Close()
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container.Start(); err != nil {
@@ -449,7 +454,7 @@ func TestKillDifferentUser(t *testing.T) {
}
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
for !container.State.Running {
for !container.State.IsRunning() {
time.Sleep(10 * time.Millisecond)
}
})
@@ -466,11 +471,11 @@ func TestKillDifferentUser(t *testing.T) {
t.Fatal(err)
}
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
container.Wait()
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
// Try stopping twice
@@ -528,7 +533,7 @@ func TestKill(t *testing.T) {
}
defer runtime.Destroy(container)
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container.Start(); err != nil {
@@ -538,17 +543,17 @@ func TestKill(t *testing.T) {
// Give some time to lxc to spawn the process
container.WaitTimeout(500 * time.Millisecond)
if !container.State.Running {
if !container.State.IsRunning() {
t.Errorf("Container should be running")
}
if err := container.Kill(); err != nil {
t.Fatal(err)
}
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
container.Wait()
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
// Try stopping twice
@@ -563,7 +568,7 @@ func TestExitCode(t *testing.T) {
trueContainer, _, err := runtime.Create(&docker.Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true", ""},
Cmd: []string{"/bin/true"},
}, "")
if err != nil {
t.Fatal(err)
@@ -572,13 +577,13 @@ 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)
if code := trueContainer.State.GetExitCode(); code != 0 {
t.Fatalf("Unexpected exit code %d (expected 0)", code)
}
falseContainer, _, err := runtime.Create(&docker.Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/false", ""},
Cmd: []string{"/bin/false"},
}, "")
if err != nil {
t.Fatal(err)
@@ -587,8 +592,8 @@ func TestExitCode(t *testing.T) {
if err := falseContainer.Run(); err != nil {
t.Fatal(err)
}
if falseContainer.State.ExitCode != 1 {
t.Errorf("Unexpected exit code %d (expected 1)", falseContainer.State.ExitCode)
if code := falseContainer.State.GetExitCode(); code != 1 {
t.Fatalf("Unexpected exit code %d (expected 1)", code)
}
}
@@ -736,7 +741,7 @@ func TestUser(t *testing.T) {
}
defer runtime.Destroy(container)
output, err = container.Output()
if err != nil || container.State.ExitCode != 0 {
if code := container.State.GetExitCode(); err != nil || code != 0 {
t.Fatal(err)
}
if !strings.Contains(string(output), "uid=0(root) gid=0(root)") {
@@ -752,12 +757,12 @@ func TestUser(t *testing.T) {
},
"",
)
if err != nil || container.State.ExitCode != 0 {
if code := container.State.GetExitCode(); err != nil || code != 0 {
t.Fatal(err)
}
defer runtime.Destroy(container)
output, err = container.Output()
if err != nil || container.State.ExitCode != 0 {
if code := container.State.GetExitCode(); err != nil || code != 0 {
t.Fatal(err)
}
if !strings.Contains(string(output), "uid=0(root) gid=0(root)") {
@@ -780,8 +785,8 @@ func TestUser(t *testing.T) {
output, err = container.Output()
if err != nil {
t.Fatal(err)
} else if container.State.ExitCode != 0 {
t.Fatalf("Container exit code is invalid: %d\nOutput:\n%s\n", container.State.ExitCode, output)
} else if code := container.State.GetExitCode(); code != 0 {
t.Fatalf("Container exit code is invalid: %d\nOutput:\n%s\n", code, output)
}
if !strings.Contains(string(output), "uid=1(daemon) gid=1(daemon)") {
t.Error(string(output))
@@ -801,7 +806,7 @@ func TestUser(t *testing.T) {
}
defer runtime.Destroy(container)
output, err = container.Output()
if err != nil || container.State.ExitCode != 0 {
if code := container.State.GetExitCode(); err != nil || code != 0 {
t.Fatal(err)
}
if !strings.Contains(string(output), "uid=1(daemon) gid=1(daemon)") {
@@ -822,7 +827,7 @@ func TestUser(t *testing.T) {
}
defer runtime.Destroy(container)
output, err = container.Output()
if container.State.ExitCode == 0 {
if container.State.GetExitCode() == 0 {
t.Fatal("Starting container with wrong uid should fail but it passed.")
}
}
@@ -866,10 +871,10 @@ func TestMultipleContainers(t *testing.T) {
container2.WaitTimeout(250 * time.Millisecond)
// If we are here, both containers should be running
if !container1.State.Running {
if !container1.State.IsRunning() {
t.Fatal("Container not running")
}
if !container2.State.Running {
if !container2.State.IsRunning() {
t.Fatal("Container not running")
}
@@ -1171,13 +1176,13 @@ func TestCopyVolumeUidGid(t *testing.T) {
container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && touch /hello/test.txt && chown daemon.daemon /hello"}, t)
defer r.Destroy(container1)
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container1.Run(); err != nil {
t.Fatal(err)
}
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
@@ -1205,13 +1210,13 @@ func TestCopyVolumeContent(t *testing.T) {
container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello/local && echo hello > /hello/local/world"}, t)
defer r.Destroy(container1)
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container1.Run(); err != nil {
t.Fatal(err)
}
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
@@ -1661,17 +1666,17 @@ func TestRestartGhost(t *testing.T) {
},
"",
)
if err != nil {
t.Fatal(err)
}
if err := container.Kill(); err != nil {
t.Fatal(err)
}
container.State.Ghost = true
_, err = container.Output()
container.State.SetGhost(true)
_, err = container.Output()
if err != nil {
t.Fatal(err)
}
+268 -11
View File
@@ -1,12 +1,17 @@
package docker
import (
"errors"
"github.com/dotcloud/docker"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"os"
"path"
"testing"
"time"
)
func TestMount(t *testing.T) {
@@ -41,19 +46,271 @@ func TestMount(t *testing.T) {
}
}
//FIXME: duplicate
func tempGraph(t *testing.T) (*docker.Graph, graphdriver.Driver) {
tmp, err := ioutil.TempDir("", "docker-graph-")
if err != nil {
func TestInit(t *testing.T) {
graph, _ := tempGraph(t)
defer nukeGraph(graph)
// Root should exist
if _, err := os.Stat(graph.Root); err != nil {
t.Fatal(err)
}
driver, err := graphdriver.New(tmp)
if err != nil {
// Map() should be empty
if l, err := graph.Map(); err != nil {
t.Fatal(err)
} else if len(l) != 0 {
t.Fatalf("len(Map()) should return %d, not %d", 0, len(l))
}
graph, err := docker.NewGraph(tmp, driver)
if err != nil {
t.Fatal(err)
}
return graph, driver
}
// Test that Register can be interrupted cleanly without side effects
func TestInterruptedRegister(t *testing.T) {
graph, _ := tempGraph(t)
defer nukeGraph(graph)
badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data
image := &docker.Image{
ID: docker.GenerateID(),
Comment: "testing",
Created: time.Now(),
}
w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling)
graph.Register(nil, badArchive, image)
if _, err := graph.Get(image.ID); err == nil {
t.Fatal("Image should not exist after Register is interrupted")
}
// Registering the same image again should succeed if the first register was interrupted
goodArchive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
if err := graph.Register(nil, goodArchive, image); err != nil {
t.Fatal(err)
}
}
// FIXME: Do more extensive tests (ex: create multiple, delete, recreate;
// create multiple, check the amount of images and paths, etc..)
func TestGraphCreate(t *testing.T) {
graph, _ := tempGraph(t)
defer nukeGraph(graph)
archive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
image, err := graph.Create(archive, nil, "Testing", "", nil)
if err != nil {
t.Fatal(err)
}
if err := docker.ValidateID(image.ID); err != nil {
t.Fatal(err)
}
if image.Comment != "Testing" {
t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", image.Comment)
}
if image.DockerVersion != docker.VERSION {
t.Fatalf("Wrong docker_version: should be '%s', not '%s'", docker.VERSION, image.DockerVersion)
}
images, err := graph.Map()
if err != nil {
t.Fatal(err)
} else if l := len(images); l != 1 {
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
}
if images[image.ID] == nil {
t.Fatalf("Could not find image with id %s", image.ID)
}
}
func TestRegister(t *testing.T) {
graph, _ := tempGraph(t)
defer nukeGraph(graph)
archive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
image := &docker.Image{
ID: docker.GenerateID(),
Comment: "testing",
Created: time.Now(),
}
err = graph.Register(nil, archive, image)
if err != nil {
t.Fatal(err)
}
if images, err := graph.Map(); err != nil {
t.Fatal(err)
} else if l := len(images); l != 1 {
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
}
if resultImg, err := graph.Get(image.ID); err != nil {
t.Fatal(err)
} else {
if resultImg.ID != image.ID {
t.Fatalf("Wrong image ID. Should be '%s', not '%s'", image.ID, resultImg.ID)
}
if resultImg.Comment != image.Comment {
t.Fatalf("Wrong image comment. Should be '%s', not '%s'", image.Comment, resultImg.Comment)
}
}
}
// Test that an image can be deleted by its shorthand prefix
func TestDeletePrefix(t *testing.T) {
graph, _ := tempGraph(t)
defer nukeGraph(graph)
img := createTestImage(graph, t)
if err := graph.Delete(utils.TruncateID(img.ID)); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 0)
}
func createTestImage(graph *docker.Graph, t *testing.T) *docker.Image {
archive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
img, err := graph.Create(archive, nil, "Test image", "", nil)
if err != nil {
t.Fatal(err)
}
return img
}
func TestDelete(t *testing.T) {
graph, _ := tempGraph(t)
defer nukeGraph(graph)
archive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 0)
img, err := graph.Create(archive, nil, "Bla bla", "", nil)
if err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 1)
if err := graph.Delete(img.ID); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 0)
archive, err = fakeTar()
if err != nil {
t.Fatal(err)
}
// Test 2 create (same name) / 1 delete
img1, err := graph.Create(archive, nil, "Testing", "", nil)
if err != nil {
t.Fatal(err)
}
archive, err = fakeTar()
if err != nil {
t.Fatal(err)
}
if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 2)
if err := graph.Delete(img1.ID); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 1)
// Test delete wrong name
if err := graph.Delete("Not_foo"); err == nil {
t.Fatalf("Deleting wrong ID should return an error")
}
assertNImages(graph, t, 1)
archive, err = fakeTar()
if err != nil {
t.Fatal(err)
}
// Test delete twice (pull -> rm -> pull -> rm)
if err := graph.Register(nil, archive, img1); err != nil {
t.Fatal(err)
}
if err := graph.Delete(img1.ID); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 1)
}
func TestByParent(t *testing.T) {
archive1, _ := fakeTar()
archive2, _ := fakeTar()
archive3, _ := fakeTar()
graph, _ := tempGraph(t)
defer nukeGraph(graph)
parentImage := &docker.Image{
ID: docker.GenerateID(),
Comment: "parent",
Created: time.Now(),
Parent: "",
}
childImage1 := &docker.Image{
ID: docker.GenerateID(),
Comment: "child1",
Created: time.Now(),
Parent: parentImage.ID,
}
childImage2 := &docker.Image{
ID: docker.GenerateID(),
Comment: "child2",
Created: time.Now(),
Parent: parentImage.ID,
}
_ = graph.Register(nil, archive1, parentImage)
_ = graph.Register(nil, archive2, childImage1)
_ = graph.Register(nil, archive3, childImage2)
byParent, err := graph.ByParent()
if err != nil {
t.Fatal(err)
}
numChildren := len(byParent[parentImage.ID])
if numChildren != 2 {
t.Fatalf("Expected 2 children, found %d", numChildren)
}
}
/*
* HELPER FUNCTIONS
*/
func assertNImages(graph *docker.Graph, t *testing.T, n int) {
if images, err := graph.Map(); err != nil {
t.Fatal(err)
} else if actualN := len(images); actualN != n {
t.Fatalf("Expected %d images, found %d", n, actualN)
}
}
func tempGraph(t *testing.T) (*docker.Graph, graphdriver.Driver) {
tmp, err := ioutil.TempDir("", "docker-graph-")
if err != nil {
t.Fatal(err)
}
driver, err := graphdriver.New(tmp)
if err != nil {
t.Fatal(err)
}
graph, err := docker.NewGraph(tmp, driver)
if err != nil {
t.Fatal(err)
}
return graph, driver
}
func nukeGraph(graph *docker.Graph) {
graph.Driver().Cleanup()
os.RemoveAll(graph.Root)
}
func testArchive(t *testing.T) archive.Archive {
archive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
return archive
}
+12 -9
View File
@@ -74,6 +74,9 @@ func layerArchive(tarfile string) (io.Reader, error) {
}
func init() {
// Always use the same driver (vfs) for all integration tests.
// To test other drivers, we need a dedicated driver validation suite.
os.Setenv("DOCKER_DRIVER", "vfs")
os.Setenv("TEST", "1")
// Hack to run sys init during unit testing
@@ -419,7 +422,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *doc
}
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
for !container.State.Running {
for !container.State.IsRunning() {
time.Sleep(10 * time.Millisecond)
}
})
@@ -533,7 +536,7 @@ func TestRestore(t *testing.T) {
t.Fatal(err)
}
if !container2.State.Running {
if !container2.State.IsRunning() {
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
@@ -543,7 +546,7 @@ func TestRestore(t *testing.T) {
if err := container2.WaitTimeout(2 * time.Second); err != nil {
t.Fatal(err)
}
container2.State.Running = true
container2.State.SetRunning(42)
container2.ToDisk()
if len(runtime1.List()) != 2 {
@@ -553,7 +556,7 @@ func TestRestore(t *testing.T) {
t.Fatal(err)
}
if !container2.State.Running {
if !container2.State.IsRunning() {
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
@@ -577,7 +580,7 @@ func TestRestore(t *testing.T) {
}
runningCount := 0
for _, c := range runtime2.List() {
if c.State.Running {
if c.State.IsRunning() {
t.Errorf("Running container found: %v (%v)", c.ID, c.Path)
runningCount++
}
@@ -592,7 +595,7 @@ func TestRestore(t *testing.T) {
if err := container3.Run(); err != nil {
t.Fatal(err)
}
container2.State.Running = false
container2.State.SetStopped(0)
}
func TestReloadContainerLinks(t *testing.T) {
@@ -638,11 +641,11 @@ func TestReloadContainerLinks(t *testing.T) {
t.Fatal(err)
}
if !container2.State.Running {
if !container2.State.IsRunning() {
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
if !container1.State.Running {
if !container1.State.IsRunning() {
t.Fatalf("Container %s should appear as running but isn't", container1.ID)
}
@@ -669,7 +672,7 @@ func TestReloadContainerLinks(t *testing.T) {
}
runningCount := 0
for _, c := range runtime2.List() {
if c.State.Running {
if c.State.IsRunning() {
runningCount++
}
}
+11 -3
View File
@@ -109,7 +109,7 @@ func TestCreateRmVolumes(t *testing.T) {
srv := mkServerFromEngine(eng, t)
defer mkRuntimeFromEngine(eng, t).Nuke()
config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo test"}, nil)
config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo", "test"}, nil)
if err != nil {
t.Fatal(err)
}
@@ -164,7 +164,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
srv := mkServerFromEngine(eng, t)
defer mkRuntimeFromEngine(eng, t).Nuke()
config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "/bin/cat"}, nil)
config, hostConfig, _, err := docker.ParseRun([]string{"-i", unitTestImageID, "/bin/cat"}, nil)
if err != nil {
t.Fatal(err)
}
@@ -240,7 +240,7 @@ func TestRmi(t *testing.T) {
t.Fatal(err)
}
config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo", "test"}, nil)
if err != nil {
t.Fatal(err)
}
@@ -256,6 +256,10 @@ func TestRmi(t *testing.T) {
t.Fatal(err)
}
if _, err := srv.ContainerWait(containerID); err != nil {
t.Fatal(err)
}
imageID, err := srv.ContainerCommit(containerID, "test", "", "", "", nil)
if err != nil {
t.Fatal(err)
@@ -277,6 +281,10 @@ func TestRmi(t *testing.T) {
t.Fatal(err)
}
if _, err := srv.ContainerWait(containerID); err != nil {
t.Fatal(err)
}
_, err = srv.ContainerCommit(containerID, "test", "", "", "", nil)
if err != nil {
t.Fatal(err)
+1 -1
View File
@@ -111,7 +111,7 @@ func containerKill(eng *engine.Engine, id string, t utils.Fataler) {
}
func containerRunning(eng *engine.Engine, id string, t utils.Fataler) bool {
return getContainer(eng, id, t).State.Running
return getContainer(eng, id, t).State.IsRunning()
}
func containerAssertExists(eng *engine.Engine, id string, t utils.Fataler) {
+1 -1
View File
@@ -21,7 +21,7 @@ func NewLink(parent, child *Container, name, bridgeInterface string) (*Link, err
if parent.ID == child.ID {
return nil, fmt.Errorf("Cannot link to self: %s == %s", parent.ID, child.ID)
}
if !child.State.Running {
if !child.State.IsRunning() {
return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, name)
}
+40 -3
View File
@@ -11,14 +11,51 @@ type NameChecker interface {
}
var (
colors = [...]string{"white", "silver", "gray", "black", "blue", "green", "cyan", "yellow", "gold", "orange", "brown", "red", "violet", "pink", "magenta", "purple", "maroon", "crimson", "plum", "fuchsia", "lavender", "slate", "navy", "azure", "aqua", "olive", "teal", "lime", "beige", "tan", "sienna"}
animals = [...]string{"ant", "bear", "bird", "cat", "chicken", "cow", "deer", "dog", "donkey", "duck", "fish", "fox", "frog", "horse", "kangaroo", "koala", "lemur", "lion", "lizard", "monkey", "octopus", "pig", "shark", "sheep", "sloth", "spider", "squirrel", "tiger", "toad", "weasel", "whale", "wolf"}
left = [...]string{"happy", "jolly", "dreamy", "sad", "angry", "pensive", "focused", "sleepy", "grave", "distracted", "determined", "stoic", "stupefied", "sharp", "agitated", "cocky", "tender", "goofy", "furious", "desperate", "hopeful", "compassionate", "silly", "lonely", "condescending", "naughty", "kickass", "drunk", "boring", "nostalgic", "ecstatic", "insane", "cranky", "mad", "jovial", "sick", "hungry", "thirsty", "elegant", "backstabbing", "clever", "trusting", "loving", "suspicious", "berserk", "high", "romantic", "prickly", "evil"}
// Docker 0.7.x generates names from notable scientists and hackers.
//
// Ada Lovelace invented the first algorithm. http://en.wikipedia.org/wiki/Ada_Lovelace (thanks James Turnbull)
// Alan Turing was a founding father of computer science. http://en.wikipedia.org/wiki/Alan_Turing.
// Albert Einstein invented the general theory of relativity. http://en.wikipedia.org/wiki/Albert_Einstein
// Ambroise Pare invented modern surgery. http://en.wikipedia.org/wiki/Ambroise_Par%C3%A9
// Archimedes was a physicist, engineer and mathematician who invented too many things to list them here. http://en.wikipedia.org/wiki/Archimedes
// Benjamin Franklin is famous for his experiments in electricity and the invention of the lightning rod.
// Charles Babbage invented the concept of a programmable computer. http://en.wikipedia.org/wiki/Charles_Babbage.
// Charles Darwin established the principles of natural evolution. http://en.wikipedia.org/wiki/Charles_Darwin.
// Dennis Ritchie and Ken Thompson created UNIX and the C programming language. http://en.wikipedia.org/wiki/Dennis_Ritchie http://en.wikipedia.org/wiki/Ken_Thompson
// Douglas Engelbart gave the mother of all demos: http://en.wikipedia.org/wiki/Douglas_Engelbart
// Emmett Brown invented time travel. http://en.wikipedia.org/wiki/Emmett_Brown (thanks Brian Goff)
// Enrico Fermi invented the first nuclear reactor. http://en.wikipedia.org/wiki/Enrico_Fermi.
// Euclid invented geometry. http://en.wikipedia.org/wiki/Euclid
// Galileo was a founding father of modern astronomy, and faced politics and obscurantism to establish scientific truth. http://en.wikipedia.org/wiki/Galileo_Galilei
// Henry Poincare made fundamental contributions in several fields of mathematics. http://en.wikipedia.org/wiki/Henri_Poincar%C3%A9
// Isaac Newton invented classic mechanics and modern optics. http://en.wikipedia.org/wiki/Isaac_Newton
// John McCarthy invented LISP: http://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)
// Leonardo Da Vinci invented too many things to list here. http://en.wikipedia.org/wiki/Leonardo_da_Vinci.
// Linus Torvalds invented Linux and Git. http://en.wikipedia.org/wiki/Linus_Torvalds
// Louis Pasteur discovered vaccination, fermentation and pasteurization. http://en.wikipedia.org/wiki/Louis_Pasteur.
// Malcolm McLean invented the modern shipping container: http://en.wikipedia.org/wiki/Malcom_McLean
// Marie Curie discovered radioactivity. http://en.wikipedia.org/wiki/Marie_Curie.
// Muhammad ibn Jābir al-Ḥarrānī al-Battānī was a founding father of astronomy. http://en.wikipedia.org/wiki/Mu%E1%B8%A5ammad_ibn_J%C4%81bir_al-%E1%B8%A4arr%C4%81n%C4%AB_al-Batt%C4%81n%C4%AB
// Niels Bohr is the father of quantum theory. http://en.wikipedia.org/wiki/Niels_Bohr.
// Nikola Tesla invented the AC electric system and every gaget ever used by a James Bond villain. http://en.wikipedia.org/wiki/Nikola_Tesla
// Pierre de Fermat pioneered several aspects of modern mathematics. http://en.wikipedia.org/wiki/Pierre_de_Fermat
// Richard Feynmann was a key contributor to quantum mechanics and particle physics. http://en.wikipedia.org/wiki/Richard_Feynman
// Rob Pike was a key contributor to Unix, Plan 9, the X graphic system, utf-8, and the Go programming language. http://en.wikipedia.org/wiki/Rob_Pike
// Stephen Hawking pioneered the field of cosmology by combining general relativity and quantum mechanics. http://en.wikipedia.org/wiki/Stephen_Hawking
// Steve Wozniak invented the Apple I and Apple II. http://en.wikipedia.org/wiki/Steve_Wozniak
// Werner Heisenberg was a founding father of quantum mechanics. http://en.wikipedia.org/wiki/Werner_Heisenberg
// William Shockley, Walter Houser Brattain and John Bardeen co-invented the transistor (thanks Brian Goff).
// http://en.wikipedia.org/wiki/John_Bardeen
// http://en.wikipedia.org/wiki/Walter_Houser_Brattain
// http://en.wikipedia.org/wiki/William_Shockley
right = [...]string{"lovelace", "franklin", "tesla", "einstein", "bohr", "davinci", "pasteur", "nobel", "curie", "darwin", "turing", "ritchie", "torvalds", "pike", "thompson", "wozniak", "galileo", "euclide", "newton", "fermat", "archimede", "poincare", "heisenberg", "feynmann", "hawkings", "fermi", "pare", "mccarthy", "engelbart", "babbage", "albattani", "ptolemy", "bell", "wright", "lumiere", "morse", "mclean", "brown", "bardeen", "brattain", "shockley"}
)
func GenerateRandomName(checker NameChecker) (string, error) {
retry := 5
rand.Seed(time.Now().UnixNano())
name := fmt.Sprintf("%s_%s", colors[rand.Intn(len(colors))], animals[rand.Intn(len(animals))])
name := fmt.Sprintf("%s_%s", left[rand.Intn(len(left))], right[rand.Intn(len(right))])
for checker != nil && checker.Exists(name) && retry > 0 {
name = fmt.Sprintf("%s%d", name, rand.Intn(10))
retry = retry - 1
+21
View File
@@ -26,3 +26,24 @@ func TestGenerateRandomName(t *testing.T) {
}
}
// Make sure the generated names are awesome
func TestGenerateAwesomeNames(t *testing.T) {
name, err := GenerateRandomName(&FalseChecker{})
if err != nil {
t.Error(err)
}
if !isAwesome(name) {
t.Fatalf("Generated name '%s' is not awesome.", name)
}
}
// To be awesome, a container name must involve cool inventors, be easy to remember,
// be at least mildly funny, and always be politically correct for enterprise adoption.
func isAwesome(name string) bool {
coolInventorNames := true
easyToRemember := true
mildlyFunnyOnOccasion := true
politicallyCorrect := true
return coolInventorNames && easyToRemember && mildlyFunnyOnOccasion && politicallyCorrect
}
+3
View File
@@ -661,6 +661,9 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
}
func (manager *NetworkManager) Close() error {
if manager.disabled {
return nil
}
err1 := manager.tcpPortAllocator.Close()
err2 := manager.udpPortAllocator.Close()
err3 := manager.ipAllocator.Close()
+33 -16
View File
@@ -10,7 +10,7 @@ import (
"github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/graphdriver/aufs"
_ "github.com/dotcloud/docker/graphdriver/devmapper"
_ "github.com/dotcloud/docker/graphdriver/dummy"
_ "github.com/dotcloud/docker/graphdriver/vfs"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
@@ -38,6 +38,7 @@ type Capabilities struct {
type Runtime struct {
repository string
sysInitPath string
containers *list.List
networkManager *NetworkManager
graph *Graph
@@ -109,8 +110,8 @@ func (runtime *Runtime) load(id string) (*Container, error) {
if container.ID != id {
return container, fmt.Errorf("Container %s is stored at %s", container.ID, id)
}
if container.State.Running {
container.State.Ghost = true
if container.State.IsRunning() {
container.State.SetGhost(true)
}
return container, nil
}
@@ -152,7 +153,7 @@ func (runtime *Runtime) Register(container *Container) error {
// FIXME: if the container is supposed to be running but is not, auto restart it?
// if so, then we need to restart monitor and init a new lock
// If the container is supposed to be running, make sure of it
if container.State.Running {
if container.State.IsRunning() {
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput()
if err != nil {
return err
@@ -161,14 +162,14 @@ func (runtime *Runtime) Register(container *Container) error {
utils.Debugf("Container %s was supposed to be running be is not.", container.ID)
if runtime.config.AutoRestart {
utils.Debugf("Restarting")
container.State.Ghost = false
container.State.setStopped(0)
container.State.SetGhost(false)
container.State.SetStopped(0)
if err := container.Start(); err != nil {
return err
}
} else {
utils.Debugf("Marking as stopped")
container.State.setStopped(-127)
container.State.SetStopped(-127)
if err := container.ToDisk(); err != nil {
return err
}
@@ -404,11 +405,6 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
return nil, nil, fmt.Errorf("No command specified")
}
sysInitPath := utils.DockerInitPath()
if sysInitPath == "" {
return nil, nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See http://docs.docker.io/en/latest/contributing/devenvironment for official build instructions.")
}
// Generate id
id := GenerateID()
@@ -451,7 +447,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
container := &Container{
// FIXME: we should generate the ID here instead of receiving it as an argument
ID: id,
Created: time.Now(),
Created: time.Now().UTC(),
Path: entrypoint,
Args: args, //FIXME: de-duplicate from config
Config: config,
@@ -459,7 +455,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
Image: img.ID, // Always use the resolved image id
NetworkSettings: &NetworkSettings{},
// FIXME: do we need to store this in the container?
SysInitPath: sysInitPath,
SysInitPath: runtime.sysInitPath,
Name: name,
Driver: runtime.driver.String(),
}
@@ -663,7 +659,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
// We don't want to use a complex driver like aufs or devmapper
// for volumes, just a plain filesystem
volumesDriver, err := graphdriver.GetDriver("dummy", config.Root)
volumesDriver, err := graphdriver.GetDriver("vfs", config.Root)
if err != nil {
return nil, err
}
@@ -671,7 +667,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
if err != nil {
return nil, err
}
repositories, err := NewTagStore(path.Join(config.Root, "repositories"), g)
repositories, err := NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g)
if err != nil {
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
}
@@ -701,6 +697,26 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
return nil, err
}
localCopy := path.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", VERSION))
sysInitPath := utils.DockerInitPath(localCopy)
if sysInitPath == "" {
return nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See http://docs.docker.io/en/latest/contributing/devenvironment for official build instructions.")
}
if !utils.IAMSTATIC {
if err := os.Mkdir(path.Join(config.Root, fmt.Sprintf("init")), 0700); err != nil && !os.IsExist(err) {
return nil, err
}
if _, err := utils.CopyFile(sysInitPath, localCopy); err != nil {
return nil, err
}
sysInitPath = localCopy
if err := os.Chmod(sysInitPath, 0700); err != nil {
return nil, err
}
}
runtime := &Runtime{
repository: runtimeRepo,
containers: list.New(),
@@ -713,6 +729,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
config: config,
containerGraph: graph,
driver: driver,
sysInitPath: sysInitPath,
}
if err := runtime.restore(); err != nil {
+303 -55
View File
@@ -63,7 +63,10 @@ func jobInitApi(job *engine.Job) string {
}()
job.Eng.Hack_SetGlobalVar("httpapi.server", srv)
job.Eng.Hack_SetGlobalVar("httpapi.runtime", srv.runtime)
job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", srv.runtime.networkManager.bridgeNetwork.IP)
// https://github.com/dotcloud/docker/issues/2768
if srv.runtime.networkManager.bridgeNetwork != nil {
job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", srv.runtime.networkManager.bridgeNetwork.IP)
}
if err := job.Eng.Register("create", srv.ContainerCreate); err != nil {
return err.Error()
}
@@ -197,6 +200,222 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
return fmt.Errorf("No such container: %s", name)
}
// ImageExport exports all images with the given tag. All versions
// containing the same tag are exported. The resulting output is an
// uncompressed tar ball.
// name is the set of tags to export.
// out is the writer where the images are written to.
func (srv *Server) ImageExport(name string, out io.Writer) error {
// get image json
tempdir, err := ioutil.TempDir("", "docker-export-")
if err != nil {
return err
}
defer os.RemoveAll(tempdir)
utils.Debugf("Serializing %s", name)
rootRepo, err := srv.runtime.repositories.Get(name)
if err != nil {
return err
}
if rootRepo != nil {
for _, id := range rootRepo {
image, err := srv.ImageInspect(id)
if err != nil {
return err
}
if err := srv.exportImage(image, tempdir); err != nil {
return err
}
}
// write repositories
rootRepoMap := map[string]Repository{}
rootRepoMap[name] = rootRepo
rootRepoJson, _ := json.Marshal(rootRepoMap)
if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend); err != nil {
return err
}
} else {
image, err := srv.ImageInspect(name)
if err != nil {
return err
}
if err := srv.exportImage(image, tempdir); err != nil {
return err
}
}
fs, err := archive.Tar(tempdir, archive.Uncompressed)
if err != nil {
return err
}
if _, err := io.Copy(out, fs); err != nil {
return err
}
return nil
}
func (srv *Server) exportImage(image *Image, tempdir string) error {
for i := image; i != nil; {
// temporary directory
tmpImageDir := path.Join(tempdir, i.ID)
if err := os.Mkdir(tmpImageDir, os.ModeDir); err != nil {
return err
}
var version = "1.0"
var versionBuf = []byte(version)
if err := ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.ModeAppend); err != nil {
return err
}
// serialize json
b, err := json.Marshal(i)
if err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(tmpImageDir, "json"), b, os.ModeAppend); err != nil {
return err
}
// serialize filesystem
fs, err := i.TarLayer()
if err != nil {
return err
}
fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar"))
if err != nil {
return err
}
if _, err = io.Copy(fsTar, fs); err != nil {
return err
}
fsTar.Close()
// find parent
if i.Parent != "" {
i, err = srv.ImageInspect(i.Parent)
if err != nil {
return err
}
} else {
i = nil
}
}
return nil
}
// Loads a set of images into the repository. This is the complementary of ImageExport.
// The input stream is an uncompressed tar ball containing images and metadata.
func (srv *Server) ImageLoad(in io.Reader) error {
tmpImageDir, err := ioutil.TempDir("", "docker-import-")
if err != nil {
return err
}
defer os.RemoveAll(tmpImageDir)
var (
repoTarFile = path.Join(tmpImageDir, "repo.tar")
repoDir = path.Join(tmpImageDir, "repo")
)
tarFile, err := os.Create(repoTarFile)
if err != nil {
return err
}
if _, err := io.Copy(tarFile, in); err != nil {
return err
}
tarFile.Close()
repoFile, err := os.Open(repoTarFile)
if err != nil {
return err
}
if err := os.Mkdir(repoDir, os.ModeDir); err != nil {
return err
}
if err := archive.Untar(repoFile, repoDir, nil); err != nil {
return err
}
dirs, err := ioutil.ReadDir(repoDir)
if err != nil {
return err
}
for _, d := range dirs {
if d.IsDir() {
if err := srv.recursiveLoad(d.Name(), tmpImageDir); err != nil {
return err
}
}
}
repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories"))
if err == nil {
repositories := map[string]Repository{}
if err := json.Unmarshal(repositoriesJson, &repositories); err != nil {
return err
}
for imageName, tagMap := range repositories {
for tag, address := range tagMap {
if err := srv.runtime.repositories.Set(imageName, tag, address, true); err != nil {
return err
}
}
}
} else if !os.IsNotExist(err) {
return err
}
return nil
}
func (srv *Server) recursiveLoad(address, tmpImageDir string) error {
if _, err := srv.ImageInspect(address); err != nil {
utils.Debugf("Loading %s", address)
imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json"))
if err != nil {
return err
utils.Debugf("Error reading json", err)
}
layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar"))
if err != nil {
utils.Debugf("Error reading embedded tar", err)
return err
}
img, err := NewImgJSON(imageJson)
if err != nil {
utils.Debugf("Error unmarshalling json", err)
return err
}
if img.Parent != "" {
if !srv.runtime.graph.Exists(img.Parent) {
if err := srv.recursiveLoad(img.Parent, tmpImageDir); err != nil {
return err
}
}
}
if err := srv.runtime.graph.Register(imageJson, layer, img); err != nil {
return err
}
}
utils.Debugf("Completed processing %s", address)
return nil
}
func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) {
r, err := registry.NewRegistry(srv.runtime.config.Root, nil, srv.HTTPRequestFactory(nil))
if err != nil {
@@ -473,18 +692,21 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
var displayed int
out := []APIContainers{}
names := map[string][]string{}
srv.runtime.containerGraph.Walk("/", func(p string, e *graphdb.Entity) error {
names[e.ID()] = append(names[e.ID()], p)
return nil
}, -1)
for _, container := range srv.runtime.List() {
if !container.State.Running && !all && n == -1 && since == "" && before == "" {
if !container.State.IsRunning() && !all && n == -1 && since == "" && before == "" {
continue
}
if before != "" {
if before != "" && !foundBefore {
if container.ID == before || utils.TruncateID(container.ID) == before {
foundBefore = true
continue
}
if !foundBefore {
continue
}
continue
}
if displayed == n {
break
@@ -493,25 +715,17 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
break
}
displayed++
c := createAPIContainer(container, size, srv.runtime)
c := createAPIContainer(names[container.ID], container, size, srv.runtime)
out = append(out, c)
}
return out
}
func createAPIContainer(container *Container, size bool, runtime *Runtime) APIContainers {
func createAPIContainer(names []string, container *Container, size bool, runtime *Runtime) APIContainers {
c := APIContainers{
ID: container.ID,
}
names := []string{}
runtime.containerGraph.Walk("/", func(p string, e *graphdb.Entity) error {
if e.ID() == container.ID {
names = append(names, p)
}
return nil
}, -1)
c.Names = names
c.Image = runtime.repositories.ImageName(container.Image)
c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
c.Created = container.Created.Unix()
@@ -547,7 +761,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
if err != nil {
return err
}
out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling", "dependend layers"))
out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling", "dependent layers"))
// FIXME: Try to stream the images?
// FIXME: Launch the getRemoteImage() in goroutines
@@ -555,9 +769,9 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
id := history[i]
// ensure no two downloads of the same layer happen at the same time
if err := srv.poolAdd("pull", "layer:"+id); err != nil {
if c, err := srv.poolAdd("pull", "layer:"+id); err != nil {
utils.Errorf("Image (id: %s) pull is already running, skipping: %v", id, err)
return nil
<-c
}
defer srv.poolRemove("pull", "layer:"+id)
@@ -565,13 +779,13 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling", "metadata"))
imgJSON, imgSize, err := r.GetRemoteImageJSON(id, endpoint, token)
if err != nil {
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependend layers"))
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
// FIXME: Keep going in case of error?
return err
}
img, err := NewImgJSON(imgJSON)
if err != nil {
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependend layers"))
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
return fmt.Errorf("Failed to parse json: %s", err)
}
@@ -579,12 +793,12 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling", "fs layer"))
layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token)
if err != nil {
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependend layers"))
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
return err
}
defer layer.Close()
if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress(utils.TruncateID(id), "Downloading", "%8v/%v (%v)"), sf, false), img); err != nil {
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "downloading dependend layers"))
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "downloading dependent layers"))
return err
}
}
@@ -652,7 +866,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName
}
// ensure no two downloads of the same image happen at the same time
if err := srv.poolAdd("pull", "img:"+img.ID); err != nil {
if _, err := srv.poolAdd("pull", "img:"+img.ID); err != nil {
utils.Errorf("Image (id: %s) pull is already running, skipping: %v", img.ID, err)
if parallel {
errors <- nil
@@ -723,38 +937,43 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName
return nil
}
func (srv *Server) poolAdd(kind, key string) error {
func (srv *Server) poolAdd(kind, key string) (chan struct{}, error) {
srv.Lock()
defer srv.Unlock()
if _, exists := srv.pullingPool[key]; exists {
return fmt.Errorf("pull %s is already in progress", key)
if c, exists := srv.pullingPool[key]; exists {
return c, fmt.Errorf("pull %s is already in progress", key)
}
if _, exists := srv.pushingPool[key]; exists {
return fmt.Errorf("push %s is already in progress", key)
if c, exists := srv.pushingPool[key]; exists {
return c, fmt.Errorf("push %s is already in progress", key)
}
c := make(chan struct{})
switch kind {
case "pull":
srv.pullingPool[key] = struct{}{}
break
srv.pullingPool[key] = c
case "push":
srv.pushingPool[key] = struct{}{}
break
srv.pushingPool[key] = c
default:
return fmt.Errorf("Unknown pool type")
return nil, fmt.Errorf("Unknown pool type")
}
return nil
return c, nil
}
func (srv *Server) poolRemove(kind, key string) error {
srv.Lock()
defer srv.Unlock()
switch kind {
case "pull":
delete(srv.pullingPool, key)
break
if c, exists := srv.pullingPool[key]; exists {
close(c)
delete(srv.pullingPool, key)
}
case "push":
delete(srv.pushingPool, key)
break
if c, exists := srv.pushingPool[key]; exists {
close(c)
delete(srv.pushingPool, key)
}
default:
return fmt.Errorf("Unknown pool type")
}
@@ -766,7 +985,7 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut
if err != nil {
return err
}
if err := srv.poolAdd("pull", localName+":"+tag); err != nil {
if _, err := srv.poolAdd("pull", localName+":"+tag); err != nil {
return err
}
defer srv.poolRemove("pull", localName+":"+tag)
@@ -961,7 +1180,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID,
// FIXME: Allow to interrupt current push when new push of same image is done.
func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, metaHeaders map[string][]string) error {
if err := srv.poolAdd("push", localName); err != nil {
if _, err := srv.poolAdd("push", localName); err != nil {
return err
}
defer srv.poolRemove("push", localName)
@@ -1131,13 +1350,28 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool)
}
if container != nil {
if container.State.Running {
if container.State.IsRunning() {
return fmt.Errorf("Impossible to remove a running container, please stop it first")
}
volumes := make(map[string]struct{})
binds := make(map[string]struct{})
for _, bind := range container.hostConfig.Binds {
splitBind := strings.Split(bind, ":")
source := splitBind[0]
binds[source] = struct{}{}
}
// Store all the deleted containers volumes
for _, volumeId := range container.Volumes {
volumeId = strings.TrimRight(volumeId, "/layer")
// Skip the volumes mounted from external
if _, exists := binds[volumeId]; exists {
continue
}
volumeId = strings.TrimSuffix(volumeId, "/layer")
volumeId = filepath.Base(volumeId)
volumes[volumeId] = struct{}{}
}
@@ -1210,8 +1444,8 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
if err != nil {
return err
}
*imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)})
srv.LogEvent("delete", utils.TruncateID(id), "")
*imgs = append(*imgs, APIRmi{Deleted: id})
srv.LogEvent("delete", id, "")
return nil
}
return nil
@@ -1293,7 +1527,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) {
// Prevent deletion if image is used by a running container
for _, container := range srv.runtime.List() {
if container.State.Running {
if container.State.IsRunning() {
parent, err := srv.runtime.repositories.LookupImage(container.Image)
if err != nil {
return nil, err
@@ -1515,7 +1749,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
//stream
if stream {
if container.State.Ghost {
if container.State.IsGhost() {
return fmt.Errorf("Impossible to attach to a ghost container")
}
@@ -1592,8 +1826,8 @@ func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) {
srv := &Server{
Eng: eng,
runtime: runtime,
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
pullingPool: make(map[string]chan struct{}),
pushingPool: make(map[string]chan struct{}),
events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events
listeners: make(map[string]chan utils.JSONMessage),
reqFactory: nil,
@@ -1603,6 +1837,8 @@ func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) {
}
func (srv *Server) HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory {
srv.Lock()
defer srv.Unlock()
if srv.reqFactory == nil {
ud := utils.NewHTTPUserAgentDecorator(srv.versionInfos()...)
md := &utils.HTTPMetaHeadersDecorator{
@@ -1615,9 +1851,9 @@ func (srv *Server) HTTPRequestFactory(metaHeaders map[string][]string) *utils.HT
}
func (srv *Server) LogEvent(action, id, from string) *utils.JSONMessage {
now := time.Now().Unix()
now := time.Now().UTC().Unix()
jm := utils.JSONMessage{Status: action, ID: id, From: from, Time: now}
srv.events = append(srv.events, jm)
srv.AddEvent(jm)
for _, c := range srv.listeners {
select { // non blocking channel
case c <- jm:
@@ -1627,11 +1863,23 @@ func (srv *Server) LogEvent(action, id, from string) *utils.JSONMessage {
return &jm
}
func (srv *Server) AddEvent(jm utils.JSONMessage) {
srv.Lock()
defer srv.Unlock()
srv.events = append(srv.events, jm)
}
func (srv *Server) GetEvents() []utils.JSONMessage {
srv.RLock()
defer srv.RUnlock()
return srv.events
}
type Server struct {
sync.Mutex
sync.RWMutex
runtime *Runtime
pullingPool map[string]struct{}
pushingPool map[string]struct{}
pullingPool map[string]chan struct{}
pushingPool map[string]chan struct{}
events []utils.JSONMessage
listeners map[string]chan utils.JSONMessage
reqFactory *utils.HTTPRequestFactory
+16 -26
View File
@@ -8,49 +8,38 @@ import (
func TestPools(t *testing.T) {
srv := &Server{
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
pullingPool: make(map[string]chan struct{}),
pushingPool: make(map[string]chan struct{}),
}
err := srv.poolAdd("pull", "test1")
if err != nil {
if _, err := srv.poolAdd("pull", "test1"); err != nil {
t.Fatal(err)
}
err = srv.poolAdd("pull", "test2")
if err != nil {
if _, err := srv.poolAdd("pull", "test2"); err != nil {
t.Fatal(err)
}
err = srv.poolAdd("push", "test1")
if err == nil || err.Error() != "pull test1 is already in progress" {
if _, err := srv.poolAdd("push", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
t.Fatalf("Expected `pull test1 is already in progress`")
}
err = srv.poolAdd("pull", "test1")
if err == nil || err.Error() != "pull test1 is already in progress" {
if _, err := srv.poolAdd("pull", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
t.Fatalf("Expected `pull test1 is already in progress`")
}
err = srv.poolAdd("wait", "test3")
if err == nil || err.Error() != "Unknown pool type" {
if _, err := srv.poolAdd("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
t.Fatalf("Expected `Unknown pool type`")
}
err = srv.poolRemove("pull", "test2")
if err != nil {
if err := srv.poolRemove("pull", "test2"); err != nil {
t.Fatal(err)
}
err = srv.poolRemove("pull", "test2")
if err != nil {
if err := srv.poolRemove("pull", "test2"); err != nil {
t.Fatal(err)
}
err = srv.poolRemove("pull", "test1")
if err != nil {
if err := srv.poolRemove("pull", "test1"); err != nil {
t.Fatal(err)
}
err = srv.poolRemove("push", "test1")
if err != nil {
if err := srv.poolRemove("push", "test1"); err != nil {
t.Fatal(err)
}
err = srv.poolRemove("wait", "test3")
if err == nil || err.Error() != "Unknown pool type" {
if err := srv.poolRemove("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
t.Fatalf("Expected `Unknown pool type`")
}
}
@@ -70,8 +59,9 @@ func TestLogEvent(t *testing.T) {
srv.LogEvent("fakeaction2", "fakeid", "fakeimage")
if len(srv.events) != 2 {
t.Fatalf("Expected 2 events, found %d", len(srv.events))
numEvents := len(srv.GetEvents())
if numEvents != 2 {
t.Fatalf("Expected 2 events, found %d", numEvents)
}
go func() {
time.Sleep(200 * time.Millisecond)
@@ -83,7 +73,7 @@ func TestLogEvent(t *testing.T) {
setTimeout(t, "Listening for events timed out", 2*time.Second, func() {
for i := 2; i < 4; i++ {
event := <-listener
if event != srv.events[i] {
if event != srv.GetEvents()[i] {
t.Fatalf("Event received it different than expected")
}
}
+43 -6
View File
@@ -8,7 +8,7 @@ import (
)
type State struct {
sync.Mutex
sync.RWMutex
Running bool
Pid int
ExitCode int
@@ -19,26 +19,63 @@ type State struct {
// String returns a human-readable description of the state
func (s *State) String() string {
s.RLock()
defer s.RUnlock()
if s.Running {
if s.Ghost {
return fmt.Sprintf("Ghost")
}
return fmt.Sprintf("Up %s", utils.HumanDuration(time.Now().Sub(s.StartedAt)))
return fmt.Sprintf("Up %s", utils.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
}
return fmt.Sprintf("Exit %d", s.ExitCode)
}
func (s *State) setRunning(pid int) {
func (s *State) IsRunning() bool {
s.RLock()
defer s.RUnlock()
return s.Running
}
func (s *State) IsGhost() bool {
s.RLock()
defer s.RUnlock()
return s.Ghost
}
func (s *State) GetExitCode() int {
s.RLock()
defer s.RUnlock()
return s.ExitCode
}
func (s *State) SetGhost(val bool) {
s.Lock()
defer s.Unlock()
s.Ghost = val
}
func (s *State) SetRunning(pid int) {
s.Lock()
defer s.Unlock()
s.Running = true
s.Ghost = false
s.ExitCode = 0
s.Pid = pid
s.StartedAt = time.Now()
s.StartedAt = time.Now().UTC()
}
func (s *State) setStopped(exitCode int) {
func (s *State) SetStopped(exitCode int) {
s.Lock()
defer s.Unlock()
s.Running = false
s.Pid = 0
s.FinishedAt = time.Now()
s.FinishedAt = time.Now().UTC()
s.ExitCode = exitCode
}
+1
View File
@@ -47,6 +47,7 @@ func TestLookupImage(t *testing.T) {
}
defer os.RemoveAll(tmp)
store := mkTestTagStore(tmp, t)
defer store.graph.driver.Cleanup()
if img, err := store.LookupImage(testImageName); err != nil {
t.Fatal(err)
+18 -6
View File
@@ -235,14 +235,23 @@ func parseLxcOpt(opt string) (string, string, error) {
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
}
// FIXME: network related stuff (including parsing) should be grouped in network file
const (
PortSpecTemplate = "ip:hostPort:containerPort"
PortSpecTemplateFormat = "ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort"
)
// We will receive port specs in the format of ip:public:private/proto and these need to be
// parsed in the internal types
func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) {
exposedPorts := make(map[Port]struct{}, len(ports))
bindings := make(map[Port][]PortBinding)
var (
exposedPorts = make(map[Port]struct{}, len(ports))
bindings = make(map[Port][]PortBinding)
)
for _, rawPort := range ports {
proto := "tcp"
if i := strings.LastIndex(rawPort, "/"); i != -1 {
proto = rawPort[i+1:]
rawPort = rawPort[:i]
@@ -253,13 +262,16 @@ func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding,
rawPort = fmt.Sprintf(":%s", rawPort)
}
parts, err := utils.PartParser("ip:hostPort:containerPort", rawPort)
parts, err := utils.PartParser(PortSpecTemplate, rawPort)
if err != nil {
return nil, nil, err
}
containerPort := parts["containerPort"]
rawIp := parts["ip"]
hostPort := parts["hostPort"]
var (
containerPort = parts["containerPort"]
rawIp = parts["ip"]
hostPort = parts["hostPort"]
)
if containerPort == "" {
return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
+18 -2
View File
@@ -3,16 +3,32 @@ package utils
import (
"os"
"path/filepath"
"syscall"
)
// TreeSize walks a directory tree and returns its total size in bytes.
func TreeSize(dir string) (size int64, err error) {
data := make(map[uint64]bool)
err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error {
// Ignore directory sizes
if fileInfo.IsDir() {
if fileInfo == nil {
return nil
}
size += fileInfo.Size()
s := fileInfo.Size()
if fileInfo.IsDir() || s == 0 {
return nil
}
// Check inode to handle hard links correctly
inode := fileInfo.Sys().(*syscall.Stat_t).Ino
if _, exists := data[inode]; exists {
return nil
}
data[inode] = false
size += s
return nil
})
return
+47 -13
View File
@@ -270,13 +270,14 @@ func isValidDockerInitPath(target string, selfPath string) bool { // target and
}
// Figure out the path of our dockerinit (which may be SelfPath())
func DockerInitPath() string {
func DockerInitPath(localCopy string) string {
selfPath := SelfPath()
if isValidDockerInitPath(selfPath, selfPath) {
// if we're valid, don't bother checking anything else
return selfPath
}
var possibleInits = []string{
localCopy,
filepath.Join(filepath.Dir(selfPath), "dockerinit"),
// "/usr/libexec includes internal binaries that are not intended to be executed directly by users or shell scripts. Applications may use a single subdirectory under /usr/libexec."
"/usr/libexec/docker/dockerinit",
@@ -411,7 +412,7 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
w.buf.Write([]byte(line))
break
}
b, err := json.Marshal(&JSONLog{Log: line, Stream: sw.stream, Created: time.Now()})
b, err := json.Marshal(&JSONLog{Log: line, Stream: sw.stream, Created: time.Now().UTC()})
if err != nil {
// On error, evict the writer
delete(w.writers, sw)
@@ -779,14 +780,19 @@ func NewHTTPRequestError(msg string, res *http.Response) error {
}
}
func (jm *JSONMessage) Display(out io.Writer) error {
func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
if jm.Error != nil {
if jm.Error.Code == 401 {
return fmt.Errorf("Authentication is required.")
}
return jm.Error
}
fmt.Fprintf(out, "%c[2K\r", 27)
endl := ""
if isTerminal {
// <ESC>[2K = erase entire current line
fmt.Fprintf(out, "%c[2K\r", 27)
endl = "\r"
}
if jm.Time != 0 {
fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0))
}
@@ -797,14 +803,14 @@ func (jm *JSONMessage) Display(out io.Writer) error {
fmt.Fprintf(out, "(from %s) ", jm.From)
}
if jm.Progress != "" {
fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress)
fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress, endl)
} else {
fmt.Fprintf(out, "%s\r\n", jm.Status)
fmt.Fprintf(out, "%s%s\n", jm.Status, endl)
}
return nil
}
func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error {
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, isTerminal bool) error {
dec := json.NewDecoder(in)
ids := make(map[string]int)
diff := 0
@@ -825,11 +831,17 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error {
} else {
diff = len(ids) - line
}
fmt.Fprintf(out, "%c[%dA", 27, diff)
if isTerminal {
// <ESC>[{diff}A = move cursor up diff rows
fmt.Fprintf(out, "%c[%dA", 27, diff)
}
}
err := jm.Display(out)
err := jm.Display(out, isTerminal)
if jm.ID != "" {
fmt.Fprintf(out, "%c[%dB", 27, diff)
if isTerminal {
// <ESC>[{diff}B = move cursor down diff rows
fmt.Fprintf(out, "%c[%dB", 27, diff)
}
}
if err != nil {
return err
@@ -1226,12 +1238,14 @@ func IsClosedError(err error) bool {
func PartParser(template, data string) (map[string]string, error) {
// ip:public:private
templateParts := strings.Split(template, ":")
parts := strings.Split(data, ":")
var (
templateParts = strings.Split(template, ":")
parts = strings.Split(data, ":")
out = make(map[string]string, len(templateParts))
)
if len(parts) != len(templateParts) {
return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template)
}
out := make(map[string]string, len(templateParts))
for i, t := range templateParts {
value := ""
@@ -1279,3 +1293,23 @@ func GetCallerName(depth int) string {
callerShortName := parts[len(parts)-1]
return callerShortName
}
func CopyFile(src, dst string) (int64, error) {
if src == dst {
return 0, nil
}
sf, err := os.Open(src)
if err != nil {
return 0, err
}
defer sf.Close()
if err := os.Remove(dst); err != nil && !os.IsNotExist(err) {
return 0, err
}
df, err := os.Create(dst)
if err != nil {
return 0, err
}
defer df.Close()
return io.Copy(df, sf)
}
+24
View File
@@ -0,0 +1,24 @@
package docker
import (
"io"
"archive/tar"
"bytes"
)
func fakeTar() (io.Reader, error) {
content := []byte("Hello world!\n")
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
hdr := new(tar.Header)
hdr.Size = int64(len(content))
hdr.Name = name
if err := tw.WriteHeader(hdr); err != nil {
return nil, err
}
tw.Write([]byte(content))
}
tw.Close()
return buf, nil
}