Compare commits

..

305 Commits

Author SHA1 Message Date
Victor Vieux 360a694c52 Bump version to v0.6.6 2013-11-06 11:43:02 -08:00
Victor Vieux 0dd6c6645b Merge pull request #2568 from mrallen1/fix/1965
See if a path exists before injecting Reader there
2013-11-06 10:57:06 -08:00
Guillaume J. Charmes 45aba3dc95 Merge pull request #2334 from SvenDowideit/2149-small-cli-usability-change
change ID to CONTAINER ID/IMAGE ID
2013-11-06 09:45:23 -08:00
Guillaume J. Charmes c22ff0296e Merge pull request #1577 from mrallen1/fix/1502
Prevent DNS server conflicts in CreateBridgeIface
2013-11-06 07:52:50 -08:00
Mark Allen 74f853a2ae See if a path exists before injecting Reader there
Closes #1965
2013-11-05 23:44:52 -06:00
Mark Allen 3560c922b1 Prevent DNS server conflicts in CreateBridgeIface
Retrieve /etc/resolv.conf data (if available)

Add checkNameserverOverlaps and call it to
make sure there are no conflicts

Add utils.GetNameserversAsCIDR and tests

Read /etc/resolv.conf and pull out nameservers,
formatting them as a CIDR block ("1.2.3.4/32")
2013-11-05 21:24:37 -06:00
Guillaume J. Charmes 6113e1d62d Merge pull request #2566 from unclejack/fix_commands_test
fix build for commands_test
2013-11-05 15:45:22 -08:00
unclejack cdf0cf495d fix build for commands_test 2013-11-06 01:41:54 +02:00
Andy Rothfusz 980eda4c25 Merge pull request #2562 from tchaudhri/patch-1
(doc) Update ubuntulinux.rst
2013-11-05 14:52:07 -08:00
Andy Rothfusz bf05eb8ac0 Merge pull request #2423 from rogaha/volumes_doc
Added volumes documentation
2013-11-05 14:38:28 -08:00
Tianon Gravi f3f9f652e1 Merge pull request #2563 from mzdaniel/desktop-integration
contrib: Desktop integration. Firefox usecase.
2013-11-05 14:02:13 -08:00
Victor Vieux 2205bb43ea Merge pull request #2298 from unclejack/2070-validate_src_for_bind_mounts
validate source for bind mounts
2013-11-05 13:31:10 -08:00
Daniel Mizyrycki 5af87d1475 contrib: Desktop integration. Firefox usecase. 2013-11-05 13:21:52 -08:00
unclejack 35430e8920 test: error out when bind mount source doesn't exist
This adds a test to verify that the server is checking whether the path
to be bind mounted actually exists on the server.
2013-11-05 22:54:24 +02:00
unclejack 4d2ba779e1 validate bind mounts on the server side
This changes the server side code to make sure that:
1) the source of a bind mount isn't /
   The bind mount "/:/foo" isn't allowed.
2) Check that the source exists
   The source to be bind mounted must exist.

This fixes issue #2070.
2013-11-05 22:54:24 +02:00
unclejack f1f39616eb add test to ensure / can't be bind mounted
This adds a test which checks that we're erroring out when we attempt
to bind mount root in a container.
2013-11-05 22:54:24 +02:00
unclejack 4b8c41c4a2 disallow / as source for bind mount in the cli
This makes the docker cli reject docker run commands which include bind
mounts like "/:/some/path/in/the/container".
Bind mounting the root directory is a bad idea and the cli should throw
an error right away.

The same check will also be made by the remote API via another commit.
2013-11-05 22:52:54 +02:00
Tehmasp Chaudhri 4d5ecdbeb4 (doc) Update ubuntulinux.rst
small grammar changes - hopefully not worse than original :)
2013-11-05 12:08:56 -07:00
Victor Vieux 114e01cdc1 Merge pull request #2554 from dotcloud/copy-config-on-build
Use parent image config in buildfile
2013-11-05 10:54:49 -08:00
Victor Vieux 7f7926525a Merge pull request #2517 from gurjeet/patch-2
Add gurjeet  as an author
2013-11-05 10:52:54 -08:00
Andy Rothfusz 714acf5ade Merge pull request #2559 from pattichen/master
Fix typo
2013-11-05 10:30:15 -08:00
Andy Rothfusz 962a66cd36 Merge pull request #2460 from SvenDowideit/2294-use-repo-colon-tag-everywhere
covert docker (commit|import|tag) to use REPOSITORY[:TAG]
2013-11-05 10:29:54 -08:00
Victor Vieux dcaaecc815 Merge pull request #2558 from tianon/go-bump
Bump Dockerfile to go1.2rc3
2013-11-04 23:39:10 -08:00
pattichen 2cac813937 Fix typo 2013-11-04 22:36:57 -08:00
Tianon Gravi fcc0ac6109 Bump Dockerfile to go1.2rc3 2013-11-04 21:12:18 -07:00
Victor Vieux c5d4459a02 Merge pull request #2502 from hamo/unlink_err_v4
rewrite protocol check with switch-case in Server.Daemon
2013-11-04 17:46:46 -08:00
Victor Vieux a482bfd715 Merge pull request #2382 from dotcloud/reflect_future_changes_search_api
Update docker search to reflect future changes of the api
2013-11-04 16:14:14 -08:00
Tianon Gravi 8ad46ef401 Merge pull request #2365 from tianon/sysvinit-consistency
Update sysvinit script to fix a few inconsistencies and be more useful/correct downstream
2013-11-04 15:34:51 -08:00
Victor Vieux 854cc5f559 remove column size hack 2013-11-04 15:34:51 -08:00
Michael Crosby 57cd17f656 Merge pull request #2499 from dotcloud/2414-logs_tty-fix
Fix logs with tty
2013-11-04 14:22:08 -08:00
Michael Crosby 99141ea3ca Use parent image config in buildfile 2013-11-04 13:20:14 -08:00
Roberto Gandolfo Hashioka 3061e9f374 - Removed some unconfirmed info
- Added reference to the index.rst
2013-11-04 13:16:51 -08:00
Andy Rothfusz 19ad299600 Merge pull request #2534 from jamtur01/examples_fixes
A variety of syntax and style fixes for the Docker examples
2013-11-04 12:35:57 -08:00
Victor Vieux 177a2f5946 add CmdLogs test 2013-11-04 12:23:35 -08:00
Victor Vieux 1f44fd8624 Fix logs with tty 2013-11-04 12:19:20 -08:00
Guillaume J. Charmes c065e564fb Merge pull request #2549 from dotcloud/ensure-container-name
Ensure container name on register
2013-11-04 12:12:42 -08:00
Guillaume J. Charmes 342bd43b76 Merge pull request #2525 from dotcloud/fix_hostnames
Fix regression in /etc/hosts
2013-11-04 11:26:27 -08:00
Guillaume J. Charmes b380866f44 Merge pull request #2551 from dotcloud/concurrent-db-access
Add lock around write operations in graph
2013-11-04 11:24:59 -08:00
Andy Rothfusz dc333878d2 Merge pull request #2533 from jamtur01/add_sudo
Some format and syntax changes.
2013-11-04 11:24:51 -08:00
Guillaume J. Charmes 261c2e23d3 Merge pull request #2501 from dotcloud/2480-check_port-fix
check port is valid
2013-11-04 11:23:55 -08:00
Michael Crosby 957db15ef4 Add lock around write operations in graph
The graph uses a persistent database connection so
a lock is required because our current sqlite3 driver
does not implement retry logic when the ErrBusy is received.
2013-11-04 10:28:38 -08:00
Michael Crosby a9ed238bb7 Ensure container name on register 2013-11-04 09:28:40 -08:00
Michael Crosby 35690e76b4 Merge pull request #2409 from pnasrat/2089-fix-restart-runtime-error
Fix restart runtime error with ghost container networking
2013-11-04 08:55:11 -08:00
Michael Crosby 2a0efb2324 Merge pull request #2427 from thequux/send-headers-immediately
Make /events API send headers immediately
2013-11-04 08:49:27 -08:00
Michael Crosby a4ccbc6e95 Merge pull request #2542 from oguzbilgic/changelog-0.6.5
Remove duplicate titles in changelog entries of v0.6.5
2013-11-04 08:25:22 -08:00
Michael Crosby 2824b05235 Merge pull request #2537 from jamtur01/more_names
Added some more colors and animals to increase the pool
2013-11-04 08:23:23 -08:00
Oguz Bilgic fbfb7595b1 Remove duplicate titles in changelog entries of v0.6.5 2013-11-04 08:20:05 -05:00
Sven Dowideit b840b73b08 tiny table heading tweak for #2149 - change ID to CONTAINER ID/IMAGE ID or IMAGE (the later if it could be an ID or a repo:tag 2013-11-04 22:10:51 +10:00
Sven Dowideit 1f3cdc11ee conert docker (commit|import|tag) to use REPOSITORY[:TAG] (and repove support for REPOSITORY TAG) 2013-11-04 22:07:55 +10:00
Paul Nasrat 95708dd35c Create new network on dead ghosts on restart.
Fixes #2089
2013-11-03 14:13:18 -05:00
James Turnbull a51fb4dd35 Added some more colors and animals to increase the pool 2013-11-03 11:49:29 -06:00
Paul Nasrat 4cbb6ce13b Introduce failing test case for #2089 2013-11-03 11:14:59 -05:00
James Turnbull 1cb4557cc8 A variety of syntax and style fixes for the Docker examples
* Made Docker a proper noun when needed.
* Fixed code syntax
* Fixed some whitespace issues
* Fixed some typos and grammar
* Tidied up some examples
2013-11-02 18:26:52 -07:00
James Turnbull c33d26a8da Some format and syntax changes.
* Added sudo as per convention to docker commands
* Break the Dockerfile block up
* Redis is a proper noun
* Minor whitespace fixes
2013-11-02 17:53:44 -07:00
Victor Vieux f3685333c0 fix regression in /etc/hosts 2013-11-01 15:46:26 -07:00
Victor Vieux fc45535197 Merge pull request #2503 from dotcloud/2500-inspect-fix
Client: Inspect fix
2013-11-01 15:24:05 -07:00
Victor Vieux 8f27e23b4b Merge pull request #2457 from dotcloud/2454_do_not_split_last_column
RemoteAPI: Do not split last column docker top
2013-11-01 15:21:47 -07:00
Victor Vieux d8bcd1c231 Merge pull request #2497 from dotcloud/jpetazzo-escape-apparmor-confinement
Runtime: escape apparmor confinement
2013-11-01 15:07:54 -07:00
Andy Rothfusz f6b4a1ec95 Merge pull request #2521 from jamtur01/porttypo
Fixed 127.0.0.0 typo in cli.rst
2013-11-01 14:08:27 -07:00
Victor Vieux 7ddd4fe66a rebase 2013-11-01 14:01:32 -07:00
James Turnbull dcf4633e95 Fixed 127.0.0.0 typo in cli.rst 2013-11-01 14:00:02 -07:00
Andy Rothfusz b30dc8c3ea Merge pull request #2510 from GrilloPress/patch-1
Update windows.rst
2013-11-01 13:56:23 -07:00
Andy Rothfusz feef16bd88 Merge pull request #2512 from dudebout/correct-basics-doc
Corrects the TCP section of the basics use
2013-11-01 13:55:28 -07:00
Victor Vieux f9901ead06 use a symlink instead of a copy 2013-11-01 13:55:19 -07:00
Jérôme Petazzoni 31638ab2ad Refactor HostConfig and escape apparmor confinement 2013-11-01 13:55:19 -07:00
Andy Rothfusz 33d8c736fc Merge pull request #2479 from dudebout/port-redirection-doc
Port redirection doc
2013-11-01 13:43:53 -07:00
Gurjeet Singh 9ebac49be0 Add myself as an author 7e09c633
One of my patches was incorporated into Docker, and at that time I didn't know that I was supposed to add myself into this list of contributors.

Commit ID: 7e09c6332d

And a few other patches are already in the pipeline.
2013-11-01 12:31:50 -05:00
Michael Crosby 65ba2868d7 Merge pull request #2492 from dotcloud/publish-all-flag
Add -P flag to publish all exposed ports
2013-11-01 10:21:02 -07:00
Dan Hirsch a963ff5d8d Added explicit Flush method to utils.WriteFlusher 2013-11-01 13:11:21 -04:00
Nicolas Dudebout c0e51a8f27 Rework the port direction documentation
+ remove PUBLIC and PRIVATE keywords
+ add the <host_interface>
+ add the -expose flag through a simple link example
+ update the meaning of the EXPOSE command
2013-11-01 12:18:04 -04:00
Nicolas Dudebout f2ebccb5aa Corrects the TCP section of the basics use 2013-11-01 12:14:43 -04:00
Andrew Duckworth db2e2e831b Update windows.rst
Added further information regarding how to access BIOS menus on Windows machines (laptops). My laptop required restarting to find the options and this information will undoubtedly help other users.
2013-11-01 13:59:25 +00:00
Solomon Hykes 4dc156252b Merge pull request #2498 from dotcloud/move-archive
Move archive.go to sub package
2013-10-31 20:33:53 -07:00
Victor Vieux 747967b4a4 rebase and add some filters 2013-10-31 19:25:55 -07:00
Victor Vieux 02b5202432 update docker search to reflect future changes of the api 2013-10-31 19:14:11 -07:00
Victor Vieux 7f07ae9e42 check port is valid 2013-10-31 19:00:14 -07:00
Victor Vieux a89a98e594 fix inspect error message 2013-10-31 18:50:58 -07:00
Yang Bai 7848007c3a rewrite protocol check with switch-case in Server.Daemon 2013-11-01 09:42:44 +08:00
Victor Vieux 40ed10cc32 fix inspect when it returns nothing valid 2013-10-31 18:39:44 -07:00
Michael Crosby 96d1e9bb5a Move archive.go to sub package 2013-10-31 16:57:45 -07:00
Victor Vieux e3c49843d7 Merge pull request #2494 from shykes/engine-links
Engine: Minimal, unintrusive implementation of a cleaner Job API.
2013-10-31 16:32:49 -07:00
Tianon Gravi 3e7b9763aa Merge pull request #2496 from philips/patch-1
Update systemd service file with a better description
2013-10-31 14:55:30 -07:00
Brandon Philips 393562340c fix(systemd): provide a better description
Fix-up the docker service file description to declare what the service is not what it does.

When a systemd machine starts up the Description of each unit scrolls by instead of the service's filename. Because the current description doesn't say what it is it isn't very friendly:

```
Oct 31 20:40:49 localhost systemd[1]: Started Update Engine.
Oct 31 20:40:49 localhost systemd[1]: Starting Multi-User System.
Oct 31 20:40:49 localhost systemd[1]: Reached target Multi-User System.
Oct 31 20:40:49 localhost systemd[1]: Starting Easily create lightweight, portable, self-sufficient containers from any application!...
Oct 31 20:40:49 localhost systemd[1]: Started Easily create lightweight, portable, self-sufficient containers from any application!.
```
2013-10-31 14:41:49 -07:00
Andy Rothfusz d5891f9fb0 Merge pull request #2482 from elee/patch-1
correct assignment of $PORT from docker port output
2013-10-31 12:18:24 -07:00
Andy Rothfusz 09607bb8f6 Merge pull request #2475 from dudebout/patch-2
Fixes the password value in the redis/links example
2013-10-31 12:17:43 -07:00
Andy Rothfusz 35044efce5 Merge pull request #2477 from dudebout/patch-3
Fixes typo in the CLI documentation
2013-10-31 12:17:06 -07:00
Michael Crosby 652f07bb59 Add -P flag to publish all exposed ports 2013-10-31 11:28:30 -07:00
Andy Rothfusz 54024ddd8a Merge pull request #2462 from tlunter/fix_remote_api_doc
Fix formatting on build-an-image-from-dockerfile-via-stdin
2013-10-31 10:56:38 -07:00
Michael Crosby e37a7d4981 Merge pull request #2305 from mzdaniel/1800-testing-dind
Testing infrastructure, issue #1800: Refactor docker testing using Docker in Docker
2013-10-31 09:42:19 -07:00
Nicolas Dudebout 7e7c931d2b Fixes another instant of the password 2013-10-31 08:55:14 -04:00
Todd Lunter 3a14d4c923 Fixed the quoted text 2013-10-31 08:46:16 -04:00
Victor Vieux 15457febbb Merge pull request #2481 from tmc/patch-1
Fix typo in redis doc
2013-10-31 01:24:04 -07:00
Eric Lee df1621914b correct assignment of $PORT from docker port output
in the example, PORT is assigned to "0.0.0.0:<port>" vs "<port>" as in previous versions of docker (I encountered this documentation discrepancy tonight after installing docker v0.6.5)
2013-10-31 01:08:16 -04:00
Travis Cline f77bbfca56 Fix typo 2013-10-31 00:07:58 -05:00
Nicolas Dudebout 87a250b711 Fixes typo in the CLI documentation 2013-10-30 23:09:02 -04:00
Nicolas Dudebout ccc1a5dcfb Fixes the password value in the redis/links example 2013-10-30 22:52:47 -04:00
Victor Vieux ca2bbe716c add 0.6.5 2013-10-30 19:30:32 -07:00
Oguz Bilgic 13ab83a8a5 Reformat CHANGELOG.md
* Add line after headers
* Remove space before bullet points
* Fix ' character
* Create sub-headers
* Group add fix etc. Upper-case
* Use head to trac current progress
2013-10-30 19:30:32 -07:00
Victor Vieux 429c3108da Merge pull request #2473 from dotcloud/remove-panic-from-name
Assign id as name when all else fails
2013-10-30 19:22:57 -07:00
Victor Vieux bdbb0371a7 Merge pull request #1536 from griff/envfile
Runtime: Keep env variables out of the command line
2013-10-30 19:20:29 -07:00
Victor Vieux c1c65f972a Merge pull request #2470 from metalivedev/2369-docuncompress
Documenting decompression behavior.
2013-10-30 19:14:51 -07:00
Victor Vieux 4b8a176ee0 Merge pull request #2432 from SvenDowideit/doc-stop-kill-details
IRC: add SIGTERM and SIGKILL details to docker stop and kill docco
2013-10-30 18:57:07 -07:00
Victor Vieux 4dfa28fb95 Merge pull request #2299 from tmc/master
Client: Add -notrunc and -q flags to docker history
RemoteAPI: Add size to history
2013-10-30 18:52:18 -07:00
Michael Crosby 8227ea35cf Assign id as name when all else fails 2013-10-30 18:31:18 -07:00
Travis Cline 1099d172a2 Add flags to history, add size flag 2013-10-30 19:41:20 -05:00
Brian Olsen 1c3a674444 Fixed problem with variables containing \n.
The /.dockerenv file is now a marshalled json array so that environment
variables with \n and other weird values are supported.
2013-10-31 01:19:32 +01:00
Brian Olsen 25c4c87c86 Support copying value from env with -e option. 2013-10-31 01:19:09 +01:00
Andy Rothfusz 26f4d6bf59 Fix #2369 by documenting decompression behavior. 2013-10-30 16:50:58 -07:00
Brian Olsen be7eb4bfcb Set environment variables using a file. 2013-10-31 00:48:12 +01:00
Michael Crosby 5eb472025a Merge pull request #2456 from dotcloud/update_release_checklist.md
Update RELEASE-CHECKLIST.md to reflect new changes
2013-10-30 16:38:09 -07:00
Andy Rothfusz bdd12bb703 Merge pull request #2274 from peterbraden/2137-docs
Re #2137 Note about vagrant dns in the test documentation
2013-10-30 16:32:35 -07:00
Michael Crosby ef00317a70 Merge branch 'release' back to master 2013-10-30 16:09:02 -07:00
Victor Vieux 3ff845904d set containers comm to true by default 2013-10-30 15:06:28 -07:00
Victor Vieux ec488fa123 fix warning in build 2013-10-30 15:06:01 -07:00
Michael Crosby d7fac67e0a Merge pull request #2469 from dotcloud/hotfix_release2
Hotfix changes for default values in 0.6.5
2013-10-30 15:04:38 -07:00
Victor Vieux f7c40a87f3 set containers comm to true by default 2013-10-30 14:48:19 -07:00
Victor Vieux ee81405567 fix warning in build 2013-10-30 14:36:38 -07:00
Michael Crosby 23b3747945 Merge pull request #2467 from dotcloud/release_hotfix
0.6.5 hotfixes
2013-10-30 13:21:20 -07:00
Victor Vieux 1c7ca51f32 fix typos 2013-10-30 13:18:48 -07:00
Michael Crosby 1edde0e8a3 Update links and host integration documentation 2013-10-30 13:11:02 -07:00
Victor Vieux 596facd124 fix typos 2013-10-30 13:06:47 -07:00
Victor Vieux 4fd389eee5 fix remove root name and fix error messages 2013-10-30 13:06:43 -07:00
Victor Vieux 6a9fb81c57 improve error message 2013-10-30 13:06:39 -07:00
Victor Vieux 5fb6d6e47c continue build on expose with :, but displays a warning 2013-10-30 13:06:34 -07:00
Michael Crosby a4d97a4e80 Update links and host integration documentation 2013-10-30 11:59:05 -07:00
Victor Vieux 3362aaa4df fix remove root name and fix error messages 2013-10-30 11:45:11 -07:00
Victor Vieux 8fa6e2b103 improve error message 2013-10-30 11:24:50 -07:00
Victor Vieux b2503a72f1 continue build on expose with :, but displays a warning 2013-10-30 11:13:10 -07:00
Michael Crosby bb662bc12c Merge pull request #2466 from dotcloud/hot-fix-restart-public-mappings
Hotfix: Migrate public mappings after upgrade
2013-10-30 09:35:56 -07:00
Michael Crosby 79bf2c2b0e Hotfix: Migrate public mappings after upgrade 2013-10-30 09:02:23 -07:00
Michael Crosby 746ba127bf Hotfix: Migrate public mappings after upgrade 2013-10-30 08:15:12 -07:00
Todd Lunter e7402ed9cc Fix formatted on build 2013-10-30 08:50:21 -04:00
Victor Vieux fb4672c52c do not split last column docker top 2013-10-29 19:03:41 -07:00
Victor Vieux 9420e8def7 update RELEASE-CHECKLIST.md to reflect new changes 2013-10-29 15:34:33 -07:00
Michael Crosby 4f6ce1ba9e Merge pull request #2428 from dotcloud/bump_0.6.5
Bump to version 0.6.5
2013-10-29 14:56:54 -07:00
Victor Vieux 2a9c18af52 Merge pull request #2453 from dotcloud/remove_logs_from_tests
Testing: remove some logs from tests
2013-10-29 14:43:37 -07:00
Victor Vieux 86970d00bb remove some logs from tests 2013-10-29 14:42:34 -07:00
Michael Crosby da054148dc Bump to version 0.6.5 2013-10-29 14:42:20 -07:00
Michael Crosby 0174951195 Merge branch 'master' into bump_0.6.5 2013-10-29 14:41:47 -07:00
Michael Crosby caa7cf14c0 Merge pull request #2426 from dotcloud/enable_sigproxy_default
Enable sig-proxy by default in run and attach
2013-10-29 14:39:17 -07:00
Victor Vieux 700a71e6b6 stop proxy at one point 2013-10-29 14:29:01 -07:00
Andy Rothfusz 02e0fdee57 Merge pull request #2429 from SvenDowideit/add-more-doc-pointers
tell potential doc updaters and contributors more details
2013-10-29 14:07:12 -07:00
Andy Rothfusz baf23d3c3e Merge pull request #2443 from tlunter/fix_remote_api_doc
Container commit doesn't use a query parameter, uses POST body
2013-10-29 14:04:22 -07:00
Michael Crosby 5b75aea704 Merge pull request #2449 from tianon/remove-stackbrew-remnants
Remove old remnants of stackbrew now that it's a separate project
2013-10-29 14:00:33 -07:00
Michael Crosby d91e145332 Merge pull request #2448 from keeb/fix-dsfg-readme
Fix export clause in readme
2013-10-29 13:44:03 -07:00
Michael Crosby 1384db5e7f Merge pull request #2451 from tianon/clarify-go-requirement
Clarify Go 1.2rc2 / Go 1.1.2 requirements in hack/PACKAGERS.md
2013-10-29 13:41:01 -07:00
Tianon Gravi ff147707b2 Clarify Go 1.2rc2 / Go 1.1.2 requirements in hack/PACKAGERS.md 2013-10-29 13:41:26 -06:00
Tianon Gravi 05430c7170 Merge pull request #2433 from tianon/sqlite-dep
Add SQLite dep to hack/PACKAGERS.md
2013-10-29 12:27:26 -07:00
Tianon Gravi eb79e8c5dd Remove old remnants of stackbrew now that it's a separate project 2013-10-29 13:14:15 -06:00
Nick Stinemates b48fc96579 Fix export clause
This makes our clause DSFG acceptable.
2013-10-29 12:06:48 -07:00
Victor Vieux e0b59ab52b Enable sig-proxy by default in run and attach 2013-10-29 11:18:34 -07:00
Michael Crosby a6a274f312 Merge pull request #2431 from relateiq/2430-links-ip-env
Expose addr, port, proto env variables when linking, Closes #2430
2013-10-29 11:02:46 -07:00
Todd Lunter 4f5790d104 Update all older docs too 2013-10-29 13:57:42 -04:00
Todd Lunter 70c976de03 Doesn't use a query parameter, uses POST body 2013-10-29 10:17:12 -04:00
Julien Barbier 4947f61c20 Updated dotCloud,Inc. to Docker, Inc. 2013-10-29 06:11:36 -07:00
Scott Bessler 1de23f1b81 Expose IP, port, proto as sep. env vars when linking, Closes #2430 2013-10-28 23:02:28 -07:00
Tianon Gravi b283900764 Add SQLite dep to hack/PACKAGERS.md and clean up a few other tiny inconsistencies 2013-10-28 21:57:20 -06:00
Sven Dowideit 82f1514895 docker stop sends SIGTERM and SIGKILL, docker kill sends SIGKILL 2013-10-29 13:37:43 +10:00
Sven Dowideit e47f9984f8 add links to the Dockerfiles as canonical external requirements, add example of building and exposing the docs and simplify the Dockerfile for cacheability 2013-10-29 12:58:58 +10:00
Michael Crosby b038b0cd44 Merge pull request #2162 from dotcloud/links
Links and Container Naming
2013-10-28 19:24:10 -07:00
Michael Crosby 7e8b2c3836 Make sure only links are removed and not names 2013-10-28 19:19:31 -07:00
Dan Hirsch dd9f4524d1 Make /events API send headers immediately
Go's net/http API does not send headers until the first call to
ResponseWriter.Write().  Ordinarily, this is fine, because in most
cases, responses are returned immediately.  However, for the events
API, nothing is written until some event is sent, which causes
timeouts and/or hangs in some HTTP client APIs, which wait for headers
before returning from the "make request" call.
2013-10-28 21:39:21 -04:00
Michael Crosby a52e23c5e4 Merge pull request #2425 from dotcloud/links-allow-name
Add -name for docker run
2013-10-28 18:16:06 -07:00
Michael Crosby 0d2924408b Add -name for docker run
Remove docker link
Do not add container id as default name
Create an auto generated container name if not
specified at runtime.
2013-10-28 18:09:08 -07:00
rogaha 91d721aaf4 - Added volumes documentation 2013-10-28 17:24:03 -07:00
Andy Rothfusz 76afb6e73d Merge pull request #2421 from dudebout/patch-1
Fixes the flags for nc in example
2013-10-28 17:01:53 -07:00
Victor Vieux c0662488c7 Merge pull request #2422 from dotcloud/links_names_generator
Add name generator for container without -name
2013-10-28 15:34:16 -07:00
Victor Vieux 971cf56d89 add name generator 2013-10-28 15:19:52 -07:00
Tianon Gravi 865649d7ac Update sysvinit script to fix a few inconsistencies and be more useful/correct downstream 2013-10-28 14:08:21 -06:00
Nicolas Dudebout 50bb4a1636 Fixes the flags for nc
The `-p` flag for `nc` should not be used with `-l`.
2013-10-28 15:48:18 -04:00
Andy Rothfusz 3c0c75bbf9 Merge pull request #2413 from rutsky/patch-1
Fix typo
2013-10-28 12:08:04 -07:00
Andy Rothfusz 7f95c7aeec Merge pull request #2417 from rutsky/patch-2
fix typo
2013-10-28 12:07:46 -07:00
Vladimir Rutsky 395a24ffd5 fix typo 2013-10-28 09:35:09 +03:00
Vladimir Rutsky 45d94944f3 Fix typo 2013-10-28 02:45:12 +03:00
Solomon Hykes a13241d370 Engine: Engine.Job() never fails, to mimic the os/exec API (and make usage less verbose) 2013-10-26 17:49:16 -07:00
Solomon Hykes e8491ae54c Engine: cleanly log the start and end of each engine job 2013-10-26 17:17:45 -07:00
Solomon Hykes 464ded79fc Close sqlite persistent connection on SIGINT, SITERM or SIGKILL 2013-10-26 14:28:53 -07:00
Solomon Hykes c1ae1a0e1c Setup daemon pidfile/cleanup in Server.Daemon() instead of main() 2013-10-26 14:26:07 -07:00
Solomon Hykes c542b2f873 Rename ConfigGetenv to ConfigFromJob 2013-10-26 14:24:25 -07:00
Solomon Hykes 7e691e11b0 Rename 'GraphPath' to the more logical 'Root'. This does not affect users except for a slight text change in the usage messge 2013-10-26 14:24:13 -07:00
Solomon Hykes 1b8eef4efb Engine: centralize checks for supported architectures and kernel versions 2013-10-26 14:21:04 -07:00
Solomon Hykes 2a29bf6245 Engine: basic testing harness 2013-10-26 14:18:28 -07:00
Solomon Hykes 0d1a825137 Minimal, unintrusive implementation of a cleaner Job API.
* Implement a new package: engine. It exposes a useful but minimalist job API.
* Refactor main() to instanciate an Engine instead of a Server directly.
* Refactor server.go to register an engine job.

This is the smallest possible refactor which can include the new Engine design
into master. More gradual refactoring will follow.
2013-10-26 14:18:13 -07:00
Solomon Hykes 60b97576cf Fix build on darwin 2013-10-26 14:14:50 -07:00
Victor Vieux 54d3b4dd28 Merge pull request #2400 from dotcloud/remove_newline_debugf
Removes \n from debugf calls
2013-10-25 18:01:19 -07:00
Victor Vieux 8f39f0b57d Removes \\n from debugf calls 2013-10-25 17:50:40 -07:00
Victor Vieux d5c7e92a9a fix docker ps display 2013-10-25 17:36:23 -07:00
Michael Crosby 110b944f45 Merge pull request #2399 from dotcloud/fix_race_stdin_cli
Only pass stdin to hijack when needed
2013-10-25 17:32:36 -07:00
Victor Vieux 13c521526d only pass stdin to hijack when needed 2013-10-25 17:17:33 -07:00
Victor Vieux 516cf54843 prevent default name to be deleted and return an error on invalid link 2013-10-25 16:49:49 -07:00
Michael Crosby 356af1540f Ensure child containers are started before parents 2013-10-25 15:13:26 -07:00
Michael Crosby 2e6b241dc7 Add default entity and edge to graph on runtime load 2013-10-25 15:13:26 -07:00
Michael Crosby 94e5081bac Move iptable rules outside of create bridge
This allows the user to toggle enabling and
disabling intercontainer communication when
they run the daemon.
2013-10-25 15:13:26 -07:00
Michael Crosby e179a24ad5 Update TestLinkEnv for default port 2013-10-25 15:13:25 -07:00
Victor Vieux da864a845b Remove proto frpm default port 2013-10-25 15:13:25 -07:00
Tianon Gravi 640682d56c Fix "go test -i" to include the proper LDFLAGS and BUILDFLAGS 2013-10-25 15:13:25 -07:00
Victor Vieux 66a159342b update docker port command 2013-10-25 15:13:25 -07:00
Michael Crosby 7c882a8003 Move name parsing logic to GetByName method 2013-10-25 15:13:25 -07:00
Michael Crosby 7d440f70fd Add missing else when checking for linkgraph.db 2013-10-25 15:13:25 -07:00
Solomon Hykes 0c9b319dd0 Hack: fix test and dyntest to correctly use $TESTFLAGS 2013-10-25 15:13:25 -07:00
Michael Crosby 7c08aeeba4 Use persistent connection for links database
Add close method to Runtime and Server to make sure
that any underlying connections are cleaned up
2013-10-25 15:13:25 -07:00
Michael Crosby 7072d2aaca Close connection in Get method 2013-10-25 15:13:25 -07:00
Tianon Gravi 21161dbd51 Add dynbinary and dyntest scripts for building/testing a separate static dockerinit binary
After a nice long brainstorming session with @shykes on IRC, we decided on using a SHA1 hash of dockerinit compiled into the dynamic docker binary to ensure that we always use the two in a perfect pair, and never mix and match.
2013-10-25 15:13:25 -07:00
Tianon Gravi cf86e2bb22 Rename all cases of "docker-init" to "dockerinit" for consistency 2013-10-25 15:13:25 -07:00
Tianon Gravi d789750851 Refactor the handling of static-related build flags
This is as discussed with @shykes on IRC
2013-10-25 15:13:25 -07:00
Tianon Gravi fb8d488896 Add a few small consistency tweaks in hack 2013-10-25 15:13:25 -07:00
Victor Vieux dbe7d67c18 fix tests 2013-10-25 15:13:25 -07:00
Victor Vieux 6c863b49ef remove docker ls and add docker link in usage and switch to go1.2rc2 2013-10-25 15:13:25 -07:00
Victor Vieux 89fb51f606 fix merge issue and gofmt 2013-10-25 15:13:25 -07:00
Victor Vieux 256b7537e3 no more name encoding 2013-10-25 15:13:25 -07:00
Victor Vieux c4923757f1 replace == by HasPrefix for names and improve error message 2013-10-25 15:13:25 -07:00
Victor Vieux 387aeb78fb refactor notrunc in commands.go to add names support 2013-10-25 15:13:24 -07:00
Victor Vieux 99a9783abc fix typo in docs 2013-10-25 15:13:24 -07:00
Michael Crosby 0e24db3a68 Updated fixes post rebase from master
Removed test cases that are no longer
applicable with links and the port changes.
Remove test case where a test was hitting an
external ip.
2013-10-25 15:13:24 -07:00
Alexander Larsson db99966724 Move SysInit to a submodule
This allows docker-init to not import the main docker
module, which means it won't e.g. pick up any sqlite
dependencies.
2013-10-25 15:13:24 -07:00
Alexander Larsson d063c8d941 Move ListOps to utils submodule
This will be needed for later use in docker-init without a docker
dependency
2013-10-25 15:13:24 -07:00
Alexander Larsson 249f5a65a5 Add a network test
This just tries pinging first 127.0.0.1 and then 8.8.8.8.

We can't ping via a dns name, because libresolv.so is missing
from the test image, so dns resolving doesn't work.
2013-10-25 15:13:24 -07:00
Alexander Larsson 494cd07f72 network: Stop shelling out to /sbin/ip
We have our own netlink version of all the required calls now, so
we can just skip the /sbin/ip dependency.
2013-10-25 15:13:24 -07:00
Alexander Larsson 607c1a520e Use netlink directly instead of /bin/ip in Sysinit
The sysinit code only uses /bin/ip to set a default gateway. This
is pretty easy to do via netlink directly, so we can avoid
the ip dependency.
2013-10-25 15:13:24 -07:00
Alexander Larsson bf61d41d6c Add netlink submodule
This contains various operations that can replace the use of
shelling out to "ip".
2013-10-25 15:13:24 -07:00
Michael Crosby ce965b8c43 Add flag for inter-container communication 2013-10-25 15:13:24 -07:00
Solomon Hykes f7a2f0b937 Typos and FIXMEs 2013-10-25 15:13:24 -07:00
Solomon Hykes 4576d0f802 gograph: fix a bug which caused the unicity of (parent, name) to not be enforced 2013-10-25 15:13:24 -07:00
Michael Crosby 515070d513 Remove docker ls from docs and cli usage 2013-10-25 15:13:24 -07:00
Michael Crosby 1cbdaebaa1 Add links for container relationships and introspection 2013-10-25 15:13:24 -07:00
Michael Crosby ff567f8729 Merge pull request #2385 from dotcloud/suppress_even_more_warnings_test
Improve tests again, remove warnings and prevent some mount issues
2013-10-25 15:04:56 -07:00
Jérôme Petazzoni 279791bc74 Merge pull request #2378 from tianon/linkify-github-ids
Linkify @github references in the infrastructure README.md
2013-10-25 11:30:25 -07:00
Andy Rothfusz 1e704a4ddc Merge pull request #2355 from xdissent/patch-1
Typo in host integration docs
2013-10-24 19:05:22 -07:00
Victor Vieux 31111f61e0 Merge pull request #1963 from rogpeppe/master
Client: Use less reflection in command-line method invocation
2013-10-24 18:03:20 -07:00
Michael Crosby 217b6070f0 Merge pull request #2383 from jpetazzo/cleanup-errclosedpipe-for-stdin
Ignore ErrClosedPipe for stdin in Container.Attach.
2013-10-24 17:18:28 -07:00
Michael Crosby 36c26a0f0f Merge pull request #2384 from dotcloud/suppress_warning_tty_tests
Change logic for tty resize to avoid warning in tests
2013-10-24 16:59:13 -07:00
Victor Vieux a675e249b1 fix workdir, hostname tests and a bunch on mount issue 2013-10-24 16:57:35 -07:00
Victor Vieux acc2aec988 remove log 2013-10-24 15:13:47 -07:00
Victor Vieux 0e021ed470 swap cat by top to prevent 2 warnings 2013-10-24 15:06:10 -07:00
Jérôme Petazzoni b9eb5e04ae Ignore ErrClosedPipe for stdin in Container.Attach.
But pass `err` in the error channel anyway; don't ignore it anymore.
2013-10-24 15:04:58 -07:00
Victor Vieux 0e44864b66 change logic for tty resize to avoid warning in tests 2013-10-24 14:54:04 -07:00
Michael Crosby e661473bcd Merge pull request #2379 from dotcloud/2377-remove_resize_race-fix
Monitor the tty after starting the container
2013-10-24 12:31:31 -07:00
Tianon Gravi 98c94dd4db Merge pull request #2364 from tianon/sudo-modprobe
Fix install.sh with $sh_c to get sudo/su for modprobe
2013-10-24 11:24:23 -07:00
Victor Vieux 656b66e51b monitor the tty after starting the container 2013-10-24 11:19:06 -07:00
Tianon Gravi c9375d14fc Linkify @github references in the infrastructure README.md 2013-10-24 11:54:06 -06:00
Michael Crosby 489c370fcc Merge pull request #2367 from dotcloud/remove_useless_exit
Remove useless os.Exit() after log.Fatalf
2013-10-24 09:18:45 -07:00
Tianon Gravi aa6c99aff6 Merge pull request #2363 from tianon/mkimage-numeric-owner
Update all the mkimage scripts to use --numeric-owner as a tar argument
2013-10-24 06:42:52 -07:00
Victor Vieux 92384649cf remove useless os.Exit() after log.Fatalf 2013-10-24 00:25:55 -07:00
Victor Vieux c2413889bb Merge pull request #2354 from hamo/refac_parsehost
refactor utils.ParseHost with switch-case, so we can add other proto support easily
2013-10-24 00:22:50 -07:00
Yang Bai e81da876df let utils.ParseHost return err when errors happen 2013-10-24 13:23:02 +08:00
Tianon Gravi f0dbdba5c0 Merge pull request #2263 from tianon/hack-release
Update hack/release.sh process to automatically invoke hack/make.sh and bail on building/testing issues
2013-10-23 15:31:16 -07:00
Tianon Gravi 0324e567ad Fix install.sh with $sh_c to get sudo/su for modprobe 2013-10-23 15:48:30 -06:00
Tianon Gravi ef5cf6c1ec Merge pull request #2226 from tianon/init
Add initial init scripts library and better/safer Ubuntu packaging that works for Debian, too
2013-10-23 14:29:41 -07:00
Tianon Gravi afaddd04f7 Merge pull request #2306 from tianon/fix-ubuntu-install-aufs
Update hack/install.sh with some more Ubuntu-specific installation stuff...
2013-10-23 14:22:55 -07:00
Tianon Gravi 8e43db95f2 Update all the mkimage scripts to use --numeric-owner as a tar argument to properly preserve ownership 2013-10-23 14:08:16 -06:00
Victor Vieux 7a116220e7 Merge pull request #2327 from dotcloud/2325_fix_untag_rmi
fix wrong untag while docker rmi
2013-10-23 10:24:20 -07:00
Victor Vieux 894a68d2ee Merge pull request #2349 from shykes/runtime-cleanup
Runtime: remove unused field kernelVersion
2013-10-23 09:54:00 -07:00
Tianon Gravi dc57d4ca26 Merge pull request #2353 from wrouesnel/mkimage-debootstrap-improvements
mkimage-debootstrap.sh: Add -p option to invoke debootstrap with http_proxy
2013-10-23 09:26:45 -07:00
Will Rouesnel bc8a1081b0 mkimage-debootstrap.sh: Add -p option to invoke debootstrap with http_proxy.
This patch allows debootstrap to be run with the http_proxy environment
variable. This is principally useful when you're using apt-cacher and
apt-cacher-ng locally.
2013-10-24 03:21:10 +11:00
Greg Thornton 33ccd29ae7 Typo in host integration docs 2013-10-23 10:19:12 -05:00
Tianon Gravi 2aaaf90320 Update hack/make/ubuntu to make less ubuntu-centric packages, to fix the issue of deleting the docker group on every upgrade, to use the new init library scripts for upstart and sysvinit, and to include the dependency on iptables 2013-10-23 07:38:12 -06:00
Yang Bai 99f1675566 refactor utils.ParseHost with switch-case, so we can add other proto support easily 2013-10-23 18:29:35 +08:00
Solomon Hykes 7e4d008403 Runtime: remove unused field kernelVersion 2013-10-23 00:44:57 -06:00
Victor Vieux b0515a7016 Merge pull request #2279 from dotcloud/fix_docker_inspect
Client: improve docker inspect
2013-10-22 17:17:35 -07:00
Victor Vieux 8ff7b70c91 Merge pull request #2341 from dotcloud/1327-race_build_verbose-fix
fix race condition in docker build with verbose + cleanup hostIntegration debug
2013-10-22 16:46:36 -07:00
Victor Vieux 01284cbf73 fix status code 2013-10-22 15:39:51 -07:00
Victor Vieux 10e10c9573 fix race condition in docker build with verbose + cleanup hostIntegration debug 2013-10-22 15:01:06 -07:00
Michael Crosby b73065ab33 Merge pull request #2338 from proppy/patch-1
Registry: Fix content-type for PushImageJSONIndex
2013-10-22 14:48:12 -07:00
Johan Euphrosine d93023daa9 registry: fix content-type for PushImageJSONIndex 2013-10-22 11:56:48 -07:00
Andy Rothfusz 81866087fe Merge pull request #2261 from edmund-wagner/master
make copy paste user friendly
2013-10-22 11:31:49 -07:00
Michael Crosby e70030a0c3 Merge pull request #2150 from pnasrat/fix-mnt-subdir-volumes
Fixes issues with mounting subdirectories of /mnt in container.
2013-10-22 11:26:50 -07:00
Andy Rothfusz 3bd45824da Merge pull request #2318 from Tranquility/master
Fix github link in docs
2013-10-22 09:55:33 -07:00
Victor Vieux 13c913d883 Merge pull request #2198 from hamo/hamo/chdir_error
check return value of syscall.Chdir when changing working directory
2013-10-22 09:54:25 -07:00
Tianon Gravi 92beaed066 Merge pull request #2326 from justinforce/commentstring
Set commentstring in Vim syntax plugin
2013-10-22 00:41:34 -07:00
Victor Vieux 6b04913e73 Merge pull request #2307 from gurjeet/sorted_commands_in_help
Place 'top' subcommand's help in proper order.
2013-10-21 18:07:39 -07:00
Victor Vieux 93810b289c fix wrong untag while docker rmi 2013-10-21 17:03:32 -07:00
Justin Force ea59ce60c5 Set commentstring in Vim syntax plugin
By setting the commentstring, you support commenting with plugins like tComment.
Now Vim won't default to incorrect `/* */` comments.
2013-10-21 16:15:10 -07:00
Andy Rothfusz a1fc8e17a7 Clean up level 3 headers. 2013-10-21 14:03:09 -07:00
Ole Reifschneider f903feee03 Fix github link in docs 2013-10-21 22:58:56 +02:00
Andy Rothfusz 26adda6c31 Merge pull request #2303 from metalivedev/2302-fixintro
Fix #2302
2013-10-21 09:39:34 -07:00
Gurjeet Singh 7e09c6332d Place 'top' subcommand's help in proper order. 2013-10-20 02:15:36 -04:00
Tianon Gravi 3b0cbc59ea Update hack/install.sh with some more Ubuntu-specific installation stuff while we're still on AUFS (installing linux-image-extra-$(uname -r) when we can't find AUFS to try to find AUFS) 2013-10-19 23:56:14 -06:00
Daniel Mizyrycki 95f3f77fc4 testing infrastructure, issue #1800: Refactor docker testing using Docker in Docker 2013-10-18 22:33:16 -07:00
Andy Rothfusz bba5fd4555 Fix #2302 2013-10-18 18:00:44 -07:00
Guillaume J. Charmes 9bceae2440 Add a chan to hijack() to block until the hijack is completed 2013-10-18 16:26:29 -07:00
Guillaume J. Charmes e7269b9841 Fix issue with autoremove 2013-10-18 16:26:29 -07:00
Andy Rothfusz 8cf0f2dbe5 Update documentation 2013-10-18 16:26:29 -07:00
Guillaume J. Charmes 3e014aa662 Fix attach race condition, improve unit tests, make sure the container is started before unblocking Start 2013-10-18 16:26:16 -07:00
Guillaume J. Charmes 333bc23f21 Fix issue killing container while sending signal
Fix rebase issue
Update docs
2013-10-18 16:19:27 -07:00
Tianon Gravi 7cf1877098 Add POSIX shell version of host_integration/manager.go in the style of hack/make.sh
Rename host_integration to host-integration for consistency
2013-10-18 16:16:29 -07:00
Guillaume J. Charmes 4918769b1a Add -nostdin and -proxy to docker attach, allow arbirary signal to be sent via docker kill api endpoint
Allow attach from `docker start`
Add host integration script generation
Update doc with host integration
2013-10-18 16:16:29 -07:00
Tianon Gravi 4b7c071e9c Merge pull request #2222 from tianon/debootstrap
Renamed and refactored mkimage-debian to mkimage-debootstrap to more clearly reflect its true function
2013-10-18 14:46:14 -07:00
Tianon Gravi 4870265a9f Add "vendor/pkg/" to .gitignore now that we "go test -i" 2013-10-18 12:18:13 -06:00
Roger Peppe 43fd85eb8f Merge remote-tracking branch 'upstream/master'
Conflicts:
	commands.go
2013-10-18 09:22:20 +01:00
Tianon Gravi ac5bc86771 Merge pull request #1 from jpetazzo/cherry-on-top-of-hack-release
Cherry on top of hack release
2013-10-17 23:21:41 -07:00
Jérôme Petazzoni 85956c70b5 Change bundle order, and fix shell array expansion.
I believe that it is helpful to build the binary first. That way,
if you interrupt the tests, you still get a binary to play with.
If you run the tests first and interrupt them, no binary for you!

Also, the second part of this commit is an undeniable proof that
Bash array syntax is nothing else than an elaborate troll by Bash
authors.
2013-10-17 23:20:18 -07:00
Jérôme Petazzoni 8c38c30985 Remove spurious messages, and use proper args expansion.
Also, display some (hopefully helpful) message when no command was given.
2013-10-17 23:19:36 -07:00
Jérôme Petazzoni 255b6aadfa Work around an exotic bug affecting Docker-in-Docker + systemd. 2013-10-17 20:42:11 -07:00
Victor Vieux 8bce284496 clean split between errors and output. JSON output always valid 2013-10-17 23:40:43 +00:00
Tianon Gravi 83a2e92d47 Update hack/release.sh process to automatically invoke hack/make.sh and bail on building/testing issues 2013-10-17 16:49:35 -06:00
Peter Braden 3db381bf08 Re #2137 Note about vagrant dns in the test documentation 2013-10-17 13:38:39 -07:00
Victor Vieux f248a8e13c Update CHANGELOG.md 2013-10-17 11:27:44 -07:00
Tianon Gravi c405d53b6e Add initial init scripts library, including systemd, sysvinit, upstart, and openrc 2013-10-17 12:03:49 -06:00
Edmund Wagner bc39ac71e6 c&p improvement for 13.04
first line killed sources list
2013-10-17 10:07:12 +02:00
Edmund Wagner 2110b3cca8 make copy paste user friendly
copying only the first line killed my sources.list file
2013-10-17 09:31:35 +02:00
Tianon Gravi 095aab9d97 Refactor mkimage-debootstrap.sh to be much more full-featured
This includes the following:
- options to control certain parameters
- an option specifically for creating tarballs directly, esp. for dockerbrew
- the addition of the updates and security repositories for Debian images
- the addition of the universe, updates, and security repositories for Ubuntu images
- more correct tagging of Debian images
- tagging of Ubuntu image versions (12.04, 12.10, etc) and latest tag for LTS
2013-10-15 03:44:03 -06:00
Tianon Gravi 4967f9f19c Rename mkimage-debian.sh to mkimage-debootstrap.sh to more clearly reflect its true function 2013-10-15 02:26:10 -06:00
Yang Bai 1043055b10 check return value of syscall.Chdir when changing working directory 2013-10-14 15:27:38 +08:00
Paul Nasrat aa3697520a Fixes issues with mounting subdirectories of /mnt in container.
Tested with
mkdir /mnt/data
docker run -v /mnt/data:/mnt/data  -t ubuntu:12.10 touch /mnt/data/bar

Expected /mnt/data/bar on host.
2013-10-09 16:40:46 -04:00
Roger Peppe 181b9baddd use less reflection in command line method invocation 2013-09-23 01:06:31 +01:00
153 changed files with 5305 additions and 5495 deletions
+1
View File
@@ -17,3 +17,4 @@ docs/_templates
bundles/
.hg/
.git/
vendor/pkg/
+5
View File
@@ -69,12 +69,14 @@ Gabriel Monroy <gabriel@opdemand.com>
Gareth Rushgrove <gareth@morethanseven.net>
Greg Thornton <xdissent@me.com>
Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
Gurjeet Singh <gurjeet@singh.im>
Guruprasad <lgp171188@gmail.com>
Harley Laue <losinggeneration@gmail.com>
Hector Castro <hectcastro@gmail.com>
Hunter Blanks <hunter@twilio.com>
Isao Jonas <isao.jonas@gmail.com>
James Carr <james.r.carr@gmail.com>
James Turnbull <james@lovedthanlost.net>
Jason McVetta <jason.mcvetta@gmail.com>
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
Jeff Lindsay <progrium@gmail.com>
@@ -140,6 +142,7 @@ odk- <github@odkurzacz.org>
Pascal Borreli <pascal@borreli.com>
Paul Bowsher <pbowsher@globalpersonals.co.uk>
Paul Hammond <paul@paulhammond.org>
Paul Nasrat <pnasrat@gmail.com>
Phil Spitler <pspitler@gmail.com>
Piotr Bogdan <ppbogdan@gmail.com>
pysqz <randomq@126.com>
@@ -151,6 +154,7 @@ Roberto Hashioka <roberto_hashioka@hotmail.com>
Ryan Fowler <rwfowler@gmail.com>
Sam Alba <sam.alba@gmail.com>
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
Scott Bessler <scottbessler@gmail.com>
Sean P. Kane <skane@newrelic.com>
Shawn Siefkas <shawn.siefkas@meredith.com>
Shih-Yuan Lee <fourdollars@gmail.com>
@@ -185,4 +189,5 @@ Vladimir Kirillov <proger@wilab.org.ua>
Walter Stanish <walter@pratyeka.org>
Wes Morgan <cap10morgan@gmail.com>
Will Dietz <w@wdtz.org>
Yang Bai <hamo.by@gmail.com>
Zaiste! <oh@zaiste.net>
+701 -403
View File
File diff suppressed because it is too large Load Diff
+32 -46
View File
@@ -4,78 +4,64 @@
#
# # Assemble the full dev environment. This is slow the first time.
# docker build -t docker .
# # Apparmor messes with privileged mode: disable it
# /etc/init.d/apparmor stop ; /etc/init.d/apparmor teardown
#
# # Mount your source in an interactive container for quick testing:
# docker run -v `pwd`:/go/src/github.com/dotcloud/docker -privileged -lxc-conf=lxc.aa_profile=unconfined -i -t docker bash
#
# docker run -v `pwd`:/go/src/github.com/dotcloud/docker -privileged -i -t docker bash
#
# # Run the test suite:
# docker run -privileged -lxc-conf=lxc.aa_profile=unconfined docker hack/make.sh test
# docker run -privileged docker hack/make.sh test
#
# # Publish a release:
# docker run -privileged -lxc-conf=lxc.aa_profile=unconfined \
# docker run -privileged \
# -e AWS_S3_BUCKET=baz \
# -e AWS_ACCESS_KEY=foo \
# -e AWS_SECRET_KEY=bar \
# -e GPG_PASSPHRASE=gloubiboulga \
# docker hack/release.sh
#
# Note: Apparmor used to mess with privileged mode, but this is no longer
# the case. Therefore, you don't have to disable it anymore.
#
docker-version 0.6.1
from ubuntu:12.10
maintainer Solomon Hykes <solomon@dotcloud.com>
from ubuntu:12.04
maintainer Solomon Hykes <solomon@dotcloud.com>
# Build dependencies
run apt-get update
run apt-get install -y -q curl
run apt-get install -y -q git
run apt-get install -y -q mercurial
run apt-get install -y -q build-essential
run apt-get install -y -q libsqlite3-dev
run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
run apt-get update
run apt-get install -y -q curl
run apt-get install -y -q git
run apt-get install -y -q mercurial
run apt-get install -y -q build-essential libsqlite3-dev
# Install Go from source (for eventual cross-compiling)
run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C / -xz && mv /go /goroot
run cd /goroot/src && ./make.bash
env GOROOT /goroot
env PATH $PATH:/goroot/bin
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
# Create Go cache with tag netgo (for static compilation of Go while preserving CGO support)
run go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
# Get lvm2 source for compiling statically
run git clone git://git.fedorahosted.org/git/lvm2.git /lvm2
run cd /lvm2 && git checkout v2_02_102
# can't use git clone -b because it's not supported by git versions before 1.7.10
run cd /lvm2 && ./configure --enable-static_link && make && make install_device-mapper
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
# Install Go
run curl -s https://go.googlecode.com/files/go1.2rc3.src.tar.gz | tar -v -C /usr/local -xz
env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
# Ubuntu stuff
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
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 /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
run apt-get install -y -q python-pip
run pip install s3cmd
run pip install python-magic
run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
# Runtime dependencies
run apt-get install -y -q iptables
run dpkg-divert --local --rename --add /sbin/initctl && \
ln -s /bin/true /sbin/initctl && \
apt-get install -y -q lxc
run apt-get install -y -q iptables
run apt-get install -y -q lxc
run apt-get install -y -q aufs-tools
volume /var/lib/docker
workdir /go/src/github.com/dotcloud/docker
volume /var/lib/docker
workdir /go/src/github.com/dotcloud/docker
# Wrap all commands in the "docker-in-docker" script to allow nested containers
entrypoint ["hack/dind"]
# Upload docker source
add . /go/src/github.com/dotcloud/docker
add . /go/src/github.com/dotcloud/docker
+2 -3
View File
@@ -1,8 +1,7 @@
Docker
Copyright 2012-2013 dotCloud, inc.
Copyright 2012-2013 Docker, Inc.
This product includes software developed at dotCloud,
inc. (http://www.dotcloud.com).
This product includes software developed at Docker, Inc. (http://www.docker.com).
This product contains software (https://github.com/kr/pty) developed
by Keith Rarick, licensed under the MIT License.
+6 -6
View File
@@ -193,10 +193,10 @@ wrong or incomplete.
*Brought to you courtesy of our legal counsel. For more context,
please see the Notice document.*
Transfers of Docker shall be in accordance with applicable export
controls of any country and all other applicable legal requirements.
Docker shall not be distributed or downloaded to or in Cuba, Iran,
North Korea, Sudan or Syria and shall not be distributed or downloaded
to any person on the Denied Persons List administered by the U.S.
Department of Commerce.
Transfers of Docker shall be in accordance with applicable export controls
of any country and all other applicable legal requirements. Without limiting the
foregoing, Docker shall not be distributed or downloaded to any individual or
location if such distribution or download would violate the applicable US
government export regulations.
For more information, please see http://www.bis.doc.gov
+1 -1
View File
@@ -1 +1 @@
0.6.4-dev
0.6.6
+31 -74
View File
@@ -5,8 +5,8 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/utils"
"github.com/gorilla/mux"
"io"
@@ -43,6 +43,9 @@ func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
//If we don't do this, POST method without Content-type (even with empty body) will fail
func parseForm(r *http.Request) error {
if r == nil {
return nil
}
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
return err
}
@@ -136,8 +139,23 @@ func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *
if vars == nil {
return fmt.Errorf("Missing parameter")
}
if err := parseForm(r); err != nil {
return err
}
name := vars["name"]
if err := srv.ContainerKill(name); err != nil {
signal := 0
if r != nil {
s := r.Form.Get("signal")
if s != "" {
if s, err := strconv.Atoi(s); err != nil {
return err
} else {
signal = s
}
}
}
if err := srv.ContainerKill(name, signal); err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)
@@ -218,6 +236,7 @@ func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
}
w.Header().Set("Content-Type", "application/json")
wf := utils.NewWriteFlusher(w)
wf.Flush()
if since != 0 {
// If since, send previous events that happened after the timestamp
for _, event := range srv.events {
@@ -504,8 +523,12 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
}
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
}
config := &Config{}
out := &APIRun{}
name := r.Form.Get("name")
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
return err
@@ -521,7 +544,7 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
config.Dns = defaultDns
}
id, warnings, err := srv.ContainerCreate(config)
id, warnings, err := srv.ContainerCreate(config, name)
if err != nil {
return err
}
@@ -631,8 +654,11 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
// Register any links from the host config before starting the container
if err := srv.RegisterLinks(name, hostConfig); err != nil {
return err
}
if err := srv.ContainerStart(name, hostConfig); err != nil {
utils.Debugf("error ContainerStart: %s", err)
return err
}
w.WriteHeader(http.StatusNoContent)
@@ -881,7 +907,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
}
c, err := Tar(root, Bzip2)
c, err := archive.Tar(root, archive.Bzip2)
if err != nil {
return err
}
@@ -1002,73 +1028,6 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s
}
}
func getContainersLinks(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
runtime := srv.runtime
all, err := getBoolParam(r.Form.Get("all"))
if err != nil {
return err
}
out := []APILink{}
err = runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error {
if container := runtime.Get(e.ID()); container != nil {
if !all && strings.Contains(p, container.ID) {
return nil
}
out = append(out, APILink{
Path: p,
ContainerID: container.ID,
Image: runtime.repositories.ImageName(container.Image),
})
}
return nil
}, -1)
if err != nil {
return err
}
return writeJSON(w, http.StatusOK, out)
}
func postContainerLink(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
}
values := make(map[string]string)
if matchesContentType(r.Header.Get("Content-Type"), "application/json") && r.Body != nil {
defer r.Body.Close()
dec := json.NewDecoder(r.Body)
if err := dec.Decode(&values); err != nil {
return err
}
} else {
return fmt.Errorf("Invalid json body")
}
currentName := values["currentName"]
newName := values["newName"]
if currentName == "" {
return fmt.Errorf("currentName cannot be empty")
}
if newName == "" {
return fmt.Errorf("newName cannot be empty")
}
if err := srv.runtime.RenameLink(currentName, newName); err != nil {
if strings.HasSuffix(err.Error(), "name are not unique") {
return fmt.Errorf("Conflict, %s already exists", newName)
}
return err
}
return nil
}
func createRouter(srv *Server, logging bool) (*mux.Router, error) {
r := mux.NewRouter()
@@ -1089,7 +1048,6 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/containers/{name:.*}/json": getContainersByName,
"/containers/{name:.*}/top": getContainersTop,
"/containers/{name:.*}/attach/ws": wsContainersAttach,
"/containers/links": getContainersLinks,
},
"POST": {
"/auth": postAuth,
@@ -1108,7 +1066,6 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/containers/{name:.*}/resize": postContainersResize,
"/containers/{name:.*}/attach": postContainersAttach,
"/containers/{name:.*}/copy": postContainersCopy,
"/containers/link": postContainerLink,
},
"DELETE": {
"/containers/{name:.*}": deleteContainers,
+13 -28
View File
@@ -5,6 +5,7 @@ type APIHistory struct {
Tags []string `json:",omitempty"`
Created int64
CreatedBy string `json:",omitempty"`
Size int64
}
type APIImages struct {
@@ -17,23 +18,18 @@ type APIImages struct {
}
type APIInfo struct {
Debug bool
Containers int
Images int
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
MemoryLimit bool `json:",omitempty"`
SwapLimit bool `json:",omitempty"`
IPv4Forwarding bool `json:",omitempty"`
LXCVersion string `json:",omitempty"`
NEventsListener int `json:",omitempty"`
KernelVersion string `json:",omitempty"`
IndexServerAddress string `json:",omitempty"`
DevmapperPool string `json:",omitempty"`
DevmapperDataUsed uint64 `json:",omitempty"`
DevmapperDataTotal uint64 `json:",omitempty"`
DevmapperMetadataUsed uint64 `json:",omitempty"`
DevmapperMetadataTotal uint64 `json:",omitempty"`
Debug bool
Containers int
Images int
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
MemoryLimit bool `json:",omitempty"`
SwapLimit bool `json:",omitempty"`
IPv4Forwarding bool `json:",omitempty"`
LXCVersion string `json:",omitempty"`
NEventsListener int `json:",omitempty"`
KernelVersion string `json:",omitempty"`
IndexServerAddress string `json:",omitempty"`
}
type APITop struct {
@@ -82,11 +78,6 @@ type APIContainersOld struct {
SizeRootFs int64
}
type APISearch struct {
Name string
Description string
}
type APIID struct {
ID string `json:"Id"`
}
@@ -126,9 +117,3 @@ type APICopy struct {
Resource string
HostPath string
}
type APILink struct {
Path string
ContainerID string
Image string
}
+33 -35
View File
@@ -347,10 +347,12 @@ func TestGetContainersJSON(t *testing.T) {
srv := &Server{runtime: runtime}
beginLen := runtime.containers.Len()
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "test"},
})
}, "")
if err != nil {
t.Fatal(err)
}
@@ -362,17 +364,15 @@ func TestGetContainersJSON(t *testing.T) {
}
r := httptest.NewRecorder()
setTimeout(t, "getContainerJSON timed out", 5*time.Second, func() {
if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
})
if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
containers := []APIContainers{}
if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil {
t.Fatal(err)
}
if len(containers) != 1 {
t.Fatalf("Expected %d container, %d found", 1, len(containers))
if len(containers) != beginLen+1 {
t.Fatalf("Expected %d container, %d found (started with: %d)", beginLen+1, len(containers), beginLen)
}
if containers[0].ID != container.ID {
t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ID, containers[0].ID)
@@ -391,6 +391,7 @@ func TestGetContainersExport(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -402,7 +403,7 @@ func TestGetContainersExport(t *testing.T) {
}
r := httptest.NewRecorder()
if err := getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
if err = getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
@@ -441,6 +442,7 @@ func TestGetContainersChanges(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/rm", "/etc/passwd"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -485,6 +487,7 @@ func TestGetContainersTop(t *testing.T) {
Cmd: []string{"/bin/sh", "-c", "cat"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -496,8 +499,7 @@ func TestGetContainersTop(t *testing.T) {
container.WaitTimeout(2 * time.Second)
}()
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
@@ -566,6 +568,7 @@ func TestGetContainersByName(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "test"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -597,6 +600,7 @@ func TestPostCommit(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -671,21 +675,13 @@ func TestPostContainersCreate(t *testing.T) {
t.Fatal(err)
}
if err := container.EnsureMounted(); err != nil {
t.Fatalf("Unable to mount container: %s", err)
}
if _, err := os.Stat(path.Join(container.RootfsPath(), "test")); err != nil {
if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil {
if os.IsNotExist(err) {
utils.Debugf("Err: %s", err)
t.Fatalf("The test file has not been created")
}
t.Fatal(err)
}
if err := container.Unmount(); err != nil {
t.Fatalf("Unable to unmount container: %s", err)
}
}
func TestPostContainersKill(t *testing.T) {
@@ -700,14 +696,14 @@ func TestPostContainersKill(t *testing.T) {
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
@@ -739,17 +735,17 @@ func TestPostContainersRestart(t *testing.T) {
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
Cmd: []string{"/bin/top"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
@@ -796,6 +792,7 @@ func TestPostContainersStart(t *testing.T) {
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -845,17 +842,17 @@ func TestPostContainersStop(t *testing.T) {
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
Cmd: []string{"/bin/top"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
@@ -895,14 +892,14 @@ func TestPostContainersWait(t *testing.T) {
Cmd: []string{"/bin/sleep", "1"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
@@ -937,6 +934,7 @@ func TestPostContainersAttach(t *testing.T) {
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -944,8 +942,7 @@ func TestPostContainersAttach(t *testing.T) {
defer runtime.Destroy(container)
// Start the process
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
@@ -1026,6 +1023,7 @@ func TestPostContainersAttachStderr(t *testing.T) {
Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1033,8 +1031,7 @@ func TestPostContainersAttachStderr(t *testing.T) {
defer runtime.Destroy(container)
// Start the process
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
@@ -1115,7 +1112,7 @@ func TestDeleteContainers(t *testing.T) {
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
})
}, "")
if err != nil {
t.Fatal(err)
}
@@ -1307,6 +1304,7 @@ func TestPostContainersCopy(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test.txt"},
},
"",
)
if err != nil {
t.Fatal(err)
+1
View File
@@ -0,0 +1 @@
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
+8 -85
View File
@@ -1,4 +1,4 @@
package docker
package archive
import (
"archive/tar"
@@ -80,74 +80,20 @@ func (compression *Compression) Extension() string {
// Tar creates an archive from the directory at `path`, and returns it as a
// stream of bytes.
func Tar(path string, compression Compression) (io.Reader, error) {
return TarFilter(path, compression, nil, true, nil)
}
func escapeName(name string) string {
escaped := make([]byte, 0)
for i, c := range []byte(name) {
if i == 0 && c == '/' {
continue
}
// all printable chars except "-" which is 0x2d
if (0x20 <= c && c <= 0x7E) && c != 0x2d {
escaped = append(escaped, c)
} else {
escaped = append(escaped, fmt.Sprintf("\\%03o", c)...)
}
}
return string(escaped)
return TarFilter(path, compression, nil)
}
// Tar creates an archive from the directory at `path`, only including files whose relative
// paths are included in `filter`. If `filter` is nil, then all files are included.
func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) {
args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-"}
func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) {
args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path}
if filter == nil {
filter = []string{"."}
}
args = append(args, "-c"+compression.Flag())
if !recursive {
args = append(args, "--no-recursion")
}
files := ""
for _, f := range filter {
files = files + escapeName(f) + "\n"
args = append(args, "-c"+compression.Flag(), f)
}
tmpDir := ""
if createFiles != nil {
var err error // Can't use := here or we override the outer tmpDir
tmpDir, err = ioutil.TempDir("", "docker-tar")
if err != nil {
return nil, err
}
files = files + "-C" + tmpDir + "\n"
for _, f := range createFiles {
path := filepath.Join(tmpDir, f)
err := os.MkdirAll(filepath.Dir(path), 0600)
if err != nil {
return nil, err
}
if file, err := os.OpenFile(path, os.O_CREATE, 0600); err != nil {
return nil, err
} else {
file.Close()
}
files = files + escapeName(f) + "\n"
}
}
return CmdStream(exec.Command(args[0], args[1:]...), &files, func() {
if tmpDir != "" {
_ = os.RemoveAll(tmpDir)
}
})
return CmdStream(exec.Command(args[0], args[1:]...))
}
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
@@ -194,7 +140,7 @@ func Untar(archive io.Reader, path string) error {
// TarUntar aborts and returns the error.
func TarUntar(src string, filter []string, dst string) error {
utils.Debugf("TarUntar(%s %s %s)", src, filter, dst)
archive, err := TarFilter(src, Uncompressed, filter, true, nil)
archive, err := TarFilter(src, Uncompressed, filter)
if err != nil {
return err
}
@@ -281,33 +227,13 @@ func CopyFileWithTar(src, dst string) error {
// CmdStream executes a command, and returns its stdout as a stream.
// If the command fails to run or doesn't complete successfully, an error
// will be returned, including anything written on stderr.
func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) {
if input != nil {
stdin, err := cmd.StdinPipe()
if err != nil {
if atEnd != nil {
atEnd()
}
return nil, err
}
// Write stdin if any
go func() {
_, _ = stdin.Write([]byte(*input))
stdin.Close()
}()
}
func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
stdout, err := cmd.StdoutPipe()
if err != nil {
if atEnd != nil {
atEnd()
}
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
if atEnd != nil {
atEnd()
}
return nil, err
}
pipeR, pipeW := io.Pipe()
@@ -332,9 +258,6 @@ func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) {
} else {
pipeW.Close()
}
if atEnd != nil {
atEnd()
}
}()
// Run the command and return the pipe
if err := cmd.Start(); err != nil {
+4 -4
View File
@@ -1,4 +1,4 @@
package docker
package archive
import (
"bytes"
@@ -14,7 +14,7 @@ import (
func TestCmdStreamLargeStderr(t *testing.T) {
cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
out, err := CmdStream(cmd, nil, nil)
out, err := CmdStream(cmd)
if err != nil {
t.Fatalf("Failed to start command: %s", err)
}
@@ -35,7 +35,7 @@ func TestCmdStreamLargeStderr(t *testing.T) {
func TestCmdStreamBad(t *testing.T) {
badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
out, err := CmdStream(badCmd, nil, nil)
out, err := CmdStream(badCmd)
if err != nil {
t.Fatalf("Failed to start command: %s", err)
}
@@ -50,7 +50,7 @@ func TestCmdStreamBad(t *testing.T) {
func TestCmdStreamGood(t *testing.T) {
cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
out, err := CmdStream(cmd, nil, nil)
out, err := CmdStream(cmd)
if err != nil {
t.Fatal(err)
}
+26 -18
View File
@@ -3,6 +3,7 @@ package docker
import (
"encoding/json"
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
@@ -64,6 +65,9 @@ func (b *buildFile) CmdFrom(name string) error {
}
b.image = image.ID
b.config = &Config{}
if image.Config != nil {
b.config = image.Config
}
if b.config.Env == nil || len(b.config.Env) == 0 {
b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
}
@@ -176,8 +180,6 @@ func (b *buildFile) CmdEnv(args string) error {
func (b *buildFile) CmdCmd(args string) error {
var cmd []string
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
// If the unmarshal fails, it is not an error, we just use the
// args as a string.
utils.Debugf("Error unmarshalling: %s, setting cmd to /bin/sh -c", err)
cmd = []string{"/bin/sh", "-c", args}
}
@@ -189,9 +191,6 @@ func (b *buildFile) CmdCmd(args string) error {
}
func (b *buildFile) CmdExpose(args string) error {
if strings.Contains(args, ":") {
return fmt.Errorf("EXPOSE cannot be used to bind to a host ip or port")
}
ports := strings.Split(args, " ")
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
@@ -296,17 +295,17 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
return fmt.Errorf("%s: no such file or directory", orig)
}
if fi.IsDir() {
if err := CopyWithTar(origPath, destPath); err != nil {
if err := archive.CopyWithTar(origPath, destPath); err != nil {
return err
}
// First try to unpack the source as an archive
} else if err := UntarPath(origPath, destPath); err != nil {
utils.Debugf("[tar] Not a directory nor a tar archive. Copying as a file. Untar error from %s to %s: %s", origPath, destPath, err)
} else if err := archive.UntarPath(origPath, destPath); err != nil {
utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
// If that fails, just copy it as a regular file
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
return err
}
if err := CopyWithTar(origPath, destPath); err != nil {
if err := archive.CopyWithTar(origPath, destPath); err != nil {
return err
}
}
@@ -337,7 +336,7 @@ func (b *buildFile) CmdAdd(args string) error {
b.config.Image = b.image
// Create the container and start it
container, _, err := b.runtime.Create(b.config)
container, _, err := b.runtime.Create(b.config, "")
if err != nil {
return err
}
@@ -372,7 +371,7 @@ func (b *buildFile) run() (string, error) {
b.config.Image = b.image
// Create the container and start it
c, _, err := b.runtime.Create(b.config)
c, _, err := b.runtime.Create(b.config, "")
if err != nil {
return "", err
}
@@ -383,15 +382,21 @@ func (b *buildFile) run() (string, error) {
c.Path = b.config.Cmd[0]
c.Args = b.config.Cmd[1:]
var errCh chan error
if b.verbose {
errCh = utils.Go(func() error {
return <-c.Attach(nil, nil, b.out, b.out)
})
}
//start the container
hostConfig := &HostConfig{}
if err := c.Start(hostConfig); err != nil {
if err := c.Start(); err != nil {
return "", err
}
if b.verbose {
err = <-c.Attach(nil, nil, b.out, b.out)
if err != nil {
if errCh != nil {
if err := <-errCh; err != nil {
return "", err
}
}
@@ -428,10 +433,13 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
}
}
container, _, err := b.runtime.Create(b.config)
container, warnings, err := b.runtime.Create(b.config, "")
if err != nil {
return err
}
for _, warning := range warnings {
fmt.Fprintf(b.out, " ---> [Warning] %s\n", warning)
}
b.tmpContainers[container.ID] = struct{}{}
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
id = container.ID
@@ -468,7 +476,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
if err != nil {
return "", err
}
if err := Untar(context, name); err != nil {
if err := archive.Untar(context, name); err != nil {
return "", err
}
defer os.RemoveAll(name)
+38 -1
View File
@@ -2,6 +2,7 @@ package docker
import (
"fmt"
"github.com/dotcloud/docker/archive"
"io/ioutil"
"net"
"net/http"
@@ -12,7 +13,7 @@ import (
// mkTestContext generates a build context from the contents of the provided dockerfile.
// This context is suitable for use as an argument to BuildFile.Build()
func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
func mkTestContext(dockerfile string, files [][2]string, t *testing.T) archive.Archive {
context, err := mkBuildContext(dockerfile, files)
if err != nil {
t.Fatal(err)
@@ -541,3 +542,39 @@ func TestBuildADDFileNotFound(t *testing.T) {
t.Fail()
}
}
func TestBuildInheritance(t *testing.T) {
runtime, err := newTestRuntime("")
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{
runtime: runtime,
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
}
img := buildImage(testContextTemplate{`
from {IMAGE}
expose 4243
`,
nil, nil}, t, srv, true)
img2 := buildImage(testContextTemplate{fmt.Sprintf(`
from %s
entrypoint ["/bin/echo"]
`, img.ID),
nil, nil}, t, srv, true)
// from child
if img2.Config.Entrypoint[0] != "/bin/echo" {
t.Fail()
}
// from parent
if img.Config.PortSpecs[0] != "4243" {
t.Fail()
}
}
+49 -259
View File
@@ -5,7 +5,6 @@ import (
"os"
"path/filepath"
"strings"
"syscall"
)
type ChangeType int
@@ -34,283 +33,74 @@ func (change *Change) String() string {
return fmt.Sprintf("%s %s", kind, change.Path)
}
type FileInfo struct {
parent *FileInfo
name string
stat syscall.Stat_t
children map[string]*FileInfo
}
func (root *FileInfo) LookUp(path string) *FileInfo {
parent := root
if path == "/" {
return root
}
pathElements := strings.Split(path, "/")
for _, elem := range pathElements {
if elem != "" {
child := parent.children[elem]
if child == nil {
return nil
}
parent = child
}
}
return parent
}
func (info *FileInfo) path() string {
if info.parent == nil {
return "/"
}
return filepath.Join(info.parent.path(), info.name)
}
func (info *FileInfo) unlink() {
if info.parent != nil {
delete(info.parent.children, info.name)
}
}
func (info *FileInfo) Remove(path string) bool {
child := info.LookUp(path)
if child != nil {
child.unlink()
return true
}
return false
}
func (info *FileInfo) isDir() bool {
return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR
}
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
if oldInfo == nil {
// add
change := Change{
Path: info.path(),
Kind: ChangeAdd,
}
*changes = append(*changes, change)
}
// We make a copy so we can modify it to detect additions
// also, we only recurse on the old dir if the new info is a directory
// otherwise any previous delete/change is considered recursive
oldChildren := make(map[string]*FileInfo)
if oldInfo != nil && info.isDir() {
for k, v := range oldInfo.children {
oldChildren[k] = v
}
}
for name, newChild := range info.children {
oldChild, _ := oldChildren[name]
if oldChild != nil {
// change?
oldStat := &oldChild.stat
newStat := &newChild.stat
// Note: We can't compare inode or ctime or blocksize here, because these change
// when copying a file into a container. However, that is not generally a problem
// because any content change will change mtime, and any status change should
// be visible when actually comparing the stat fields. The only time this
// breaks down is if some code intentionally hides a change by setting
// back mtime
oldMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano())
newMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano())
if oldStat.Mode != newStat.Mode ||
oldStat.Uid != newStat.Uid ||
oldStat.Gid != newStat.Gid ||
oldStat.Rdev != newStat.Rdev ||
// Don't look at size for dirs, its not a good measure of change
(oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) ||
oldMtime.Sec != newMtime.Sec ||
oldMtime.Usec != newMtime.Usec {
change := Change{
Path: newChild.path(),
Kind: ChangeModify,
}
*changes = append(*changes, change)
}
// Remove from copy so we can detect deletions
delete(oldChildren, name)
}
newChild.addChanges(oldChild, changes)
}
for _, oldChild := range oldChildren {
// delete
change := Change{
Path: oldChild.path(),
Kind: ChangeDelete,
}
*changes = append(*changes, change)
}
}
func (info *FileInfo) Changes(oldInfo *FileInfo) []Change {
func Changes(layers []string, rw string) ([]Change, error) {
var changes []Change
info.addChanges(oldInfo, &changes)
return changes
}
func newRootFileInfo() *FileInfo {
root := &FileInfo{
name: "/",
children: make(map[string]*FileInfo),
}
return root
}
func applyLayer(root *FileInfo, layer string) error {
err := filepath.Walk(layer, func(layerPath string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip root
if layerPath == layer {
return nil
}
// rebase path
relPath, err := filepath.Rel(layer, layerPath)
if err != nil {
return err
}
relPath = filepath.Join("/", relPath)
// Skip AUFS metadata
if matched, err := filepath.Match("/.wh..wh.*", relPath); err != nil || matched {
if err != nil || !f.IsDir() {
return err
}
return filepath.SkipDir
}
var layerStat syscall.Stat_t
err = syscall.Lstat(layerPath, &layerStat)
if err != nil {
return err
}
file := filepath.Base(relPath)
// If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") {
originalFile := file[len(".wh."):]
deletePath := filepath.Join(filepath.Dir(relPath), originalFile)
root.Remove(deletePath)
} else {
// Added or changed file
existing := root.LookUp(relPath)
if existing != nil {
// Changed file
existing.stat = layerStat
if !existing.isDir() {
// Changed from dir to non-dir, delete all previous files
existing.children = make(map[string]*FileInfo)
}
} else {
// Added file
parent := root.LookUp(filepath.Dir(relPath))
if parent == nil {
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
}
info := &FileInfo{
name: filepath.Base(relPath),
children: make(map[string]*FileInfo),
parent: parent,
stat: layerStat,
}
parent.children[info.name] = info
}
}
return nil
})
return err
}
func collectFileInfo(sourceDir string) (*FileInfo, error) {
root := newRootFileInfo()
err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Rebase path
relPath, err := filepath.Rel(sourceDir, path)
path, err = filepath.Rel(rw, path)
if err != nil {
return err
}
relPath = filepath.Join("/", relPath)
path = filepath.Join("/", path)
if relPath == "/" {
// Skip root
if path == "/" {
return nil
}
parent := root.LookUp(filepath.Dir(relPath))
if parent == nil {
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
}
info := &FileInfo{
name: filepath.Base(relPath),
children: make(map[string]*FileInfo),
parent: parent,
}
if err := syscall.Lstat(path, &info.stat); err != nil {
// Skip AUFS metadata
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
return err
}
parent.children[info.name] = info
change := Change{
Path: path,
}
// Find out what kind of modification happened
file := filepath.Base(path)
// If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") {
originalFile := file[len(".wh."):]
change.Path = filepath.Join(filepath.Dir(path), originalFile)
change.Kind = ChangeDelete
} else {
// Otherwise, the file was added
change.Kind = ChangeAdd
// ...Unless it already existed in a top layer, in which case, it's a modification
for _, layer := range layers {
stat, err := os.Stat(filepath.Join(layer, path))
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
// The file existed in the top layer, so that's a modification
// However, if it's a directory, maybe it wasn't actually modified.
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
if stat.IsDir() && f.IsDir() {
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
// Both directories are the same, don't record the change
return nil
}
}
change.Kind = ChangeModify
break
}
}
}
// Record change
changes = append(changes, change)
return nil
})
if err != nil {
if err != nil && !os.IsNotExist(err) {
return nil, err
}
return root, nil
}
// Compare a directory with an array of layer directories it was based on and
// generate an array of Change objects describing the changes
func ChangesLayers(newDir string, layers []string) ([]Change, error) {
newRoot, err := collectFileInfo(newDir)
if err != nil {
return nil, err
}
oldRoot := newRootFileInfo()
for i := len(layers) - 1; i >= 0; i-- {
layer := layers[i]
if err = applyLayer(oldRoot, layer); err != nil {
return nil, err
}
}
return newRoot.Changes(oldRoot), nil
}
// Compare two directories and generate an array of Change objects describing the changes
func ChangesDirs(newDir, oldDir string) ([]Change, error) {
oldRoot, err := collectFileInfo(oldDir)
if err != nil {
return nil, err
}
newRoot, err := collectFileInfo(newDir)
if err != nil {
return nil, err
}
return newRoot.Changes(oldRoot), nil
return changes, nil
}
+305 -184
View File
@@ -9,6 +9,7 @@ import (
"errors"
"flag"
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/registry"
"github.com/dotcloud/docker/term"
@@ -42,9 +43,13 @@ var (
ErrConnectionRefused = errors.New("Can't connect to docker daemon. Is 'docker -d' running on this host?")
)
func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) {
methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
return reflect.TypeOf(cli).MethodByName(methodName)
method := reflect.ValueOf(cli).MethodByName(methodName)
if !method.IsValid() {
return nil, false
}
return method.Interface().(func(...string) error), true
}
func ParseCommands(proto, addr string, args ...string) error {
@@ -56,14 +61,7 @@ func ParseCommands(proto, addr string, args ...string) error {
fmt.Println("Error: Command not found:", args[0])
return cli.CmdHelp(args[1:]...)
}
ret := method.Func.CallSlice([]reflect.Value{
reflect.ValueOf(cli),
reflect.ValueOf(args[1:]),
})[0].Interface()
if ret == nil {
return nil
}
return ret.(error)
return method(args[1:]...)
}
return cli.CmdHelp(args...)
}
@@ -74,10 +72,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
if !exists {
fmt.Fprintf(cli.err, "Error: Command not found: %s\n", args[0])
} else {
method.Func.CallSlice([]reflect.Value{
reflect.ValueOf(cli),
reflect.ValueOf([]string{"--help"}),
})[0].Interface()
method("--help")
return nil
}
}
@@ -100,7 +95,6 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"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"},
{"top", "Lookup the running processes of a container"},
{"ps", "List containers"},
{"pull", "Pull an image or a repository from the docker registry server"},
{"push", "Push an image or a repository to the docker registry server"},
@@ -112,6 +106,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"start", "Start a stopped container"},
{"stop", "Stop a running container"},
{"tag", "Tag an image into a repository"},
{"top", "Lookup the running processes of a container"},
{"version", "Show the docker version information"},
{"wait", "Block until a container stops, then print its exit code"},
} {
@@ -143,7 +138,7 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
// mkBuildContext returns an archive of an empty context with the contents
// of `dockerfile` at the path ./Dockerfile
func mkBuildContext(dockerfile string, files [][2]string) (Archive, error) {
func mkBuildContext(dockerfile string, files [][2]string) (archive.Archive, error) {
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
files = append(files, [2]string{"Dockerfile", dockerfile})
@@ -181,7 +176,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
}
var (
context Archive
context archive.Archive
isRemote bool
err error
)
@@ -200,7 +195,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
if _, err := os.Stat(cmd.Arg(0)); err != nil {
return err
}
context, err = Tar(cmd.Arg(0), Uncompressed)
context, err = archive.Tar(cmd.Arg(0), archive.Uncompressed)
}
var body io.Reader
// Setup an upload progress bar
@@ -466,11 +461,6 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
fmt.Fprintf(cli.out, "Containers: %d\n", out.Containers)
fmt.Fprintf(cli.out, "Images: %d\n", out.Images)
if out.DevmapperDataTotal != 0 {
fmt.Fprintf(cli.out, "Devmapper disk use: Data: %.1f/%.1f Metadata: %.1f/%.1f\n",
float64(out.DevmapperDataUsed)/(1024*1024), float64(out.DevmapperDataTotal)/(1024*1024),
float64(out.DevmapperMetadataUsed)/(1024*1024), float64(out.DevmapperMetadataTotal)/(1024*1024))
}
if out.Debug || os.Getenv("DEBUG") != "" {
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", out.Debug)
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
@@ -479,7 +469,6 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
fmt.Fprintf(cli.out, "LXC Version: %s\n", out.LXCVersion)
fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener)
fmt.Fprintf(cli.out, "Kernel Version: %s\n", out.KernelVersion)
fmt.Fprintf(cli.out, "Devmapper pool: %s\n", out.DevmapperPool)
}
if len(out.IndexServerAddress) != 0 {
@@ -503,7 +492,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")
cmd := 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
@@ -552,8 +541,23 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
return nil
}
func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
sigc := make(chan os.Signal, 1)
utils.CatchAll(sigc)
go func() {
for s := range sigc {
if _, _, err := cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cid, s), nil); err != nil {
utils.Debugf("Error sending signal: %s", err)
}
}
}()
return sigc
}
func (cli *DockerCli) CmdStart(args ...string) error {
cmd := 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 {
return nil
}
@@ -562,17 +566,75 @@ func (cli *DockerCli) CmdStart(args ...string) error {
return nil
}
var cErr chan error
if *attach || *openStdin {
if cmd.NArg() > 1 {
return fmt.Errorf("Impossible to start and attach multiple containers at once.")
}
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
if err != nil {
return err
}
container := &Container{}
err = json.Unmarshal(body, container)
if err != nil {
return err
}
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{}
v.Set("stream", "1")
if *openStdin && container.Config.OpenStdin {
v.Set("stdin", "1")
in = cli.in
}
v.Set("stdout", "1")
v.Set("stderr", "1")
cErr = utils.Go(func() error {
return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil)
})
}
var encounteredError error
for _, name := range args {
for _, name := range cmd.Args() {
_, _, err := cli.call("POST", "/containers/"+name+"/start", nil)
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
encounteredError = fmt.Errorf("Error: failed to start one or more containers")
if !*attach || !*openStdin {
fmt.Fprintf(cli.err, "%s\n", err)
encounteredError = fmt.Errorf("Error: failed to start one or more containers")
}
} else {
fmt.Fprintf(cli.out, "%s\n", name)
if !*attach || !*openStdin {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
}
return encounteredError
if encounteredError != nil {
if *openStdin || *attach {
cli.in.Close()
<-cErr
}
return encounteredError
}
if *openStdin || *attach {
return <-cErr
}
return nil
}
func (cli *DockerCli) CmdInspect(args ...string) error {
@@ -584,35 +646,50 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
cmd.Usage()
return nil
}
fmt.Fprintf(cli.out, "[")
for i, name := range args {
if i > 0 {
fmt.Fprintf(cli.out, ",")
}
indented := new(bytes.Buffer)
status := 0
for _, name := range args {
obj, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
if err != nil {
obj, _, err = cli.call("GET", "/images/"+name+"/json", nil)
if err != nil {
fmt.Fprintf(cli.err, "No such image or container: %s\n", name)
if strings.Contains(err.Error(), "No such") {
fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
} else {
fmt.Fprintf(cli.err, "%s", err)
}
status = 1
continue
}
}
indented := new(bytes.Buffer)
if err = json.Indent(indented, obj, "", " "); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
if _, err := io.Copy(cli.out, indented); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
}
indented.WriteString(",")
}
if indented.Len() > 0 {
// Remove trailing ','
indented.Truncate(indented.Len() - 1)
}
fmt.Fprintf(cli.out, "[")
if _, err := io.Copy(cli.out, indented); err != nil {
return err
}
fmt.Fprintf(cli.out, "]")
if status != 0 {
return &utils.StatusError{Status: status}
}
return nil
}
func (cli *DockerCli) CmdTop(args ...string) error {
cmd := Subcmd("top", "CONTAINER", "Lookup the running processes of a container")
cmd := Subcmd("top", "CONTAINER [ps OPTIONS]", "Lookup the running processes of a container")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -654,11 +731,11 @@ func (cli *DockerCli) CmdPort(args ...string) error {
}
port := cmd.Arg(1)
proto := "Tcp"
proto := "tcp"
parts := strings.SplitN(port, "/", 2)
if len(parts) == 2 && len(parts[1]) != 0 {
port = parts[0]
proto = strings.ToUpper(parts[1][:1]) + strings.ToLower(parts[1][1:])
proto = parts[1]
}
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
if err != nil {
@@ -670,8 +747,14 @@ func (cli *DockerCli) CmdPort(args ...string) error {
return err
}
if frontend, exists := out.NetworkSettings.PortMapping[proto][port]; exists {
fmt.Fprintf(cli.out, "%s\n", frontend)
if frontends, exists := out.NetworkSettings.Ports[Port(port+"/"+proto)]; exists {
if frontends == nil {
fmt.Fprintf(cli.out, "%s\n", port)
} else {
for _, frontend := range frontends {
fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort)
}
}
} else {
return fmt.Errorf("Error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0))
}
@@ -712,7 +795,10 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
}
func (cli *DockerCli) CmdHistory(args ...string) error {
cmd := Subcmd("history", "IMAGE", "Show the history of an image")
cmd := 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")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -731,14 +817,35 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintln(w, "ID\tCREATED\tCREATED BY")
if !*quiet {
fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE")
}
for _, out := range outs {
if out.Tags != nil {
out.ID = out.Tags[0]
if !*quiet {
if *noTrunc {
fmt.Fprintf(w, "%s\t", out.ID)
} else {
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))))
if *noTrunc {
fmt.Fprintf(w, "%s\t", out.CreatedBy)
} else {
fmt.Fprintf(w, "%s\t", utils.Trunc(out.CreatedBy, 45))
}
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
} else {
if *noTrunc {
fmt.Fprintln(w, out.ID)
} else {
fmt.Fprintln(w, utils.TruncateID(out.ID))
}
}
fmt.Fprintf(w, "%s \t%s ago\t%s\n", out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy)
}
w.Flush()
return nil
@@ -776,7 +883,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")
cmd := Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL)")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -797,7 +904,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 := 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
@@ -806,7 +913,8 @@ func (cli *DockerCli) CmdImport(args ...string) error {
cmd.Usage()
return nil
}
src, repository, tag := cmd.Arg(0), cmd.Arg(1), cmd.Arg(2)
src := cmd.Arg(0)
repository, tag := utils.ParseRepositoryTag(cmd.Arg(1))
v := url.Values{}
v.Set("repo", repository)
v.Set("tag", tag)
@@ -986,7 +1094,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED\tSIZE")
fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tSIZE")
}
for _, out := range outs {
@@ -1079,7 +1187,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
if *size {
fmt.Fprintln(w, "\tSIZE")
} else {
@@ -1091,14 +1199,17 @@ func (cli *DockerCli) CmdPs(args ...string) error {
if !*noTrunc {
out.ID = utils.TruncateID(out.ID)
}
// Remove the leading / from the names
for i := 0; i < len(out.Names); i++ {
out.Names[i] = out.Names[i][1:]
}
if !*quiet {
if !*noTrunc {
out.Command = utils.Trunc(out.Command, 20)
for i := 0; i < len(out.Names); i++ {
out.Names[i] = utils.Trunc(out.Names[i], 10)
}
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\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().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))
@@ -1119,73 +1230,17 @@ func (cli *DockerCli) CmdPs(args ...string) error {
return nil
}
func (cli *DockerCli) CmdLs(args ...string) error {
cmd := Subcmd("ls", "", "List links for containers")
flAll := cmd.Bool("a", false, "Show all links")
if err := cmd.Parse(args); err != nil {
return nil
}
v := url.Values{}
if *flAll {
v.Set("all", "1")
}
body, _, err := cli.call("GET", "/containers/links?"+v.Encode(), nil)
if err != nil {
return err
}
var links []APILink
if err := json.Unmarshal(body, &links); err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tID\tIMAGE")
fmt.Fprintf(w, "\n")
sortLinks(links, func(i, j APILink) bool {
return len(i.Path) < len(j.Path)
})
for _, link := range links {
fmt.Fprintf(w, "%s\t%s\t%s", link.Path, utils.TruncateID(link.ContainerID), link.Image)
fmt.Fprintf(w, "\n")
}
w.Flush()
return nil
}
func (cli *DockerCli) CmdLink(args ...string) error {
cmd := Subcmd("link", "CURRENT_NAME NEW_NAME", "Link the container with a new name")
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() != 2 {
cmd.Usage()
return nil
}
body := map[string]string{
"currentName": cmd.Arg(0),
"newName": cmd.Arg(1),
}
_, _, err := cli.call("POST", "/containers/link", body)
if err != nil {
return err
}
return nil
}
func (cli *DockerCli) CmdCommit(args ...string) error {
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes")
cmd := 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"]}')`)
if err := cmd.Parse(args); err != nil {
return nil
}
name, repository, tag := cmd.Arg(0), cmd.Arg(1), cmd.Arg(2)
name := cmd.Arg(0)
repository, tag := utils.ParseRepositoryTag(cmd.Arg(1))
if name == "" {
cmd.Usage()
return nil
@@ -1295,15 +1350,27 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
return nil
}
name := cmd.Arg(0)
body, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
if err != nil {
return err
}
if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err); err != nil {
container := &Container{}
err = json.Unmarshal(body, container)
if err != nil {
return err
}
if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", container.Config.Tty, nil, cli.out, cli.err, nil); err != nil {
return err
}
return nil
}
func (cli *DockerCli) CmdAttach(args ...string) error {
cmd := Subcmd("attach", "CONTAINER", "Attach to a running container")
cmd := 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 {
return nil
}
@@ -1327,27 +1394,39 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
}
if container.Config.Tty {
if container.Config.Tty && cli.isTerminal {
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
utils.Debugf("Error monitoring tty size: %s", err)
utils.Debugf("Error monitoring TTY size: %s", err)
}
}
var in io.ReadCloser
v := url.Values{}
v.Set("stream", "1")
v.Set("stdin", "1")
if !*noStdin && container.Config.OpenStdin {
v.Set("stdin", "1")
in = cli.in
}
v.Set("stdout", "1")
v.Set("stderr", "1")
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, cli.in, cli.out, cli.err); err != nil {
if *proxy && !container.Config.Tty {
sigc := cli.forwardAllSignals(cmd.Arg(0))
defer utils.StopCatch(sigc)
}
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil); err != nil {
return err
}
return nil
}
func (cli *DockerCli) CmdSearch(args ...string) error {
cmd := Subcmd("search", "NAME", "Search the docker index for images")
cmd := 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")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1363,27 +1442,32 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
return err
}
outs := []APISearch{}
outs := []registry.SearchResult{}
err = json.Unmarshal(body, &outs)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "Found %d results matching your query (\"%s\")\n", len(outs), cmd.Arg(0))
w := tabwriter.NewWriter(cli.out, 33, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\n")
_, width := cli.getTtySize()
if width == 0 {
width = 45
} else {
width = width - 33 //remote the first column
}
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tTRUSTED\n")
for _, out := range outs {
if (*trusted && !out.IsTrusted) || (*stars > out.StarCount) {
continue
}
desc := strings.Replace(out.Description, "\n", " ", -1)
desc = strings.Replace(desc, "\r", " ", -1)
if !*noTrunc && len(desc) > width {
desc = utils.Trunc(desc, width-3) + "..."
if !*noTrunc && len(desc) > 45 {
desc = utils.Trunc(desc, 42) + "..."
}
fmt.Fprintf(w, "%s\t%s\n", out.Name, desc)
fmt.Fprintf(w, "%s\t%s\t%d\t", out.Name, desc, out.StarCount)
if out.IsOfficial {
fmt.Fprint(w, "[OK]")
}
fmt.Fprint(w, "\t")
if out.IsTrusted {
fmt.Fprint(w, "[OK]")
}
fmt.Fprint(w, "\n")
}
w.Flush()
return nil
@@ -1451,7 +1535,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 := 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
@@ -1462,10 +1546,10 @@ func (cli *DockerCli) CmdTag(args ...string) error {
}
v := url.Values{}
v.Set("repo", cmd.Arg(1))
if cmd.NArg() == 3 {
v.Set("tag", cmd.Arg(2))
}
repository, tag := utils.ParseRepositoryTag(cmd.Arg(1))
v.Set("repo", repository)
v.Set("tag", tag)
if *force {
v.Set("force", "1")
@@ -1490,6 +1574,13 @@ func (cli *DockerCli) CmdRun(args ...string) error {
flRm := cmd.Lookup("rm")
autoRemove, _ := strconv.ParseBool(flRm.Value.String())
flSigProxy := cmd.Lookup("sig-proxy")
sigProxy, _ := strconv.ParseBool(flSigProxy.Value.String())
flName := cmd.Lookup("name")
if config.Tty {
sigProxy = false
}
var containerIDFile *os.File
if len(hostConfig.ContainerIDFile) > 0 {
if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
@@ -1501,9 +1592,14 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}
defer containerIDFile.Close()
}
containerValues := url.Values{}
name := flName.Value.String()
if name != "" {
containerValues.Set("name", name)
}
//create the container
body, statusCode, err := cli.call("POST", "/containers/create", config)
body, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config)
//if image not found try to pull it
if statusCode == 404 {
_, tag := utils.ParseRepositoryTag(config.Image)
@@ -1544,7 +1640,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if err != nil {
return err
}
body, _, err = cli.call("POST", "/containers/create", config)
body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config)
if err != nil {
return err
}
@@ -1568,12 +1664,15 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}
}
//start the container
if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
return err
if sigProxy {
sigc := cli.forwardAllSignals(runResult.ID)
defer utils.StopCatch(sigc)
}
var wait chan struct{}
var (
wait 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
@@ -1584,24 +1683,18 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}()
}
hijacked := make(chan bool)
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
if config.Tty {
if err := cli.monitorTtySize(runResult.ID); err != nil {
// When running the test suite, there is no terminal, just pipes.
// Discard the error then.
if os.Getenv("TEST") != "1" {
utils.Errorf("Error monitoring TTY size: %s\n", err)
}
}
}
v := url.Values{}
v.Set("logs", "1")
v.Set("stream", "1")
var out, stderr io.Writer
var in io.ReadCloser
if config.AttachStdin {
v.Set("stdin", "1")
in = cli.in
}
if config.AttachStdout {
v.Set("stdout", "1")
@@ -1616,18 +1709,36 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}
}
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
go func() {
for sig := range signals {
fmt.Printf("\nReceived signal: %s; cleaning up\n", sig)
if err := cli.CmdStop("-t", "4", runResult.ID); err != nil {
fmt.Printf("failed to stop container: %v", err)
}
}
}()
errCh = utils.Go(func() error {
return cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked)
})
} else {
close(hijacked)
}
if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, out, stderr); err != nil {
// Acknowledge the hijack before starting
select {
case <-hijacked:
case err := <-errCh:
if err != nil {
utils.Debugf("Error hijack: %s", err)
return err
}
}
//start the container
if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
return err
}
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminal {
if err := cli.monitorTtySize(runResult.ID); err != nil {
utils.Errorf("Error monitoring TTY size: %s\n", err)
}
}
if errCh != nil {
if err := <-errCh; err != nil {
utils.Debugf("Error hijack: %s", err)
return err
}
@@ -1637,13 +1748,19 @@ func (cli *DockerCli) CmdRun(args ...string) error {
// Detached mode
<-wait
} else {
status, err := getExitCode(cli, runResult.ID)
running, status, err := getExitCode(cli, runResult.ID)
if err != nil {
return err
}
if autoRemove {
_, _, err = cli.call("DELETE", "/containers/"+runResult.ID, nil)
if err != nil {
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
}
}
@@ -1683,7 +1800,7 @@ func (cli *DockerCli) CmdCp(args ...string) error {
if statusCode == 200 {
r := bytes.NewReader(data)
if err := Untar(r, copyData.HostPath); err != nil {
if err := archive.Untar(r, copyData.HostPath); err != nil {
return err
}
}
@@ -1732,6 +1849,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
return nil, -1, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, -1, err
@@ -1809,7 +1927,7 @@ 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) error {
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan bool) error {
// fixme: refactor client to support redirect
re := regexp.MustCompile("/+")
path = re.ReplaceAllString(path, "/")
@@ -1838,6 +1956,10 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
rwc, br := clientconn.Hijack()
defer rwc.Close()
if started != nil {
started <- true
}
var receiveStdout chan error
if stdout != nil {
@@ -1924,9 +2046,6 @@ func (cli *DockerCli) resizeTty(id string) {
}
func (cli *DockerCli) monitorTtySize(id string) error {
if !cli.isTerminal {
return fmt.Errorf("Impossible to monitor size on non-tty")
}
cli.resizeTty(id)
sigchan := make(chan os.Signal, 1)
@@ -1974,20 +2093,22 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) {
return out.StatusCode, nil
}
func getExitCode(cli *DockerCli, containerId string) (int, error) {
// getExitCode perform an inspect on the container. It returns
// the running state and the exit code.
func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
body, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != ErrConnectionRefused {
return -1, err
return false, -1, err
}
return -1, nil
return false, -1, nil
}
c := &Container{}
if err := json.Unmarshal(body, c); err != nil {
return -1, err
return false, -1, err
}
return c.State.ExitCode, nil
return c.State.Running, c.State.ExitCode, nil
}
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
+135 -16
View File
@@ -84,10 +84,24 @@ func TestRunHostname(t *testing.T) {
}
})
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
container := globalRuntime.List()[0]
setTimeout(t, "CmdRun timed out", 10*time.Second, func() {
<-c
go func() {
cli.CmdWait(container.ID)
}()
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
t.Fatal(err)
}
})
// Cleanup pipes
if err := closeWrap(stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
}
// TestRunWorkdir checks that 'docker run -w' correctly sets a custom working directory
@@ -115,10 +129,24 @@ func TestRunWorkdir(t *testing.T) {
}
})
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
container := globalRuntime.List()[0]
setTimeout(t, "CmdRun timed out", 10*time.Second, func() {
<-c
go func() {
cli.CmdWait(container.ID)
}()
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
t.Fatal(err)
}
})
// Cleanup pipes
if err := closeWrap(stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
}
// TestRunWorkdirExists checks that 'docker run -w' correctly sets a custom working directory, even if it exists
@@ -146,10 +174,24 @@ func TestRunWorkdirExists(t *testing.T) {
}
})
container := globalRuntime.List()[0]
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c
go func() {
cli.CmdWait(container.ID)
}()
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
t.Fatal(err)
}
})
// Cleanup pipes
if err := closeWrap(stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
}
func TestRunExit(t *testing.T) {
@@ -262,7 +304,7 @@ func TestRunDisconnectTty(t *testing.T) {
// We're simulating a disconnect so the return value doesn't matter. What matters is the
// fact that CmdRun returns.
if err := cli.CmdRun("-i", "-t", unitTestImageID, "/bin/cat"); err != nil {
utils.Debugf("Error CmdRun: %s\n", err)
utils.Debugf("Error CmdRun: %s", err)
}
close(c1)
@@ -393,12 +435,14 @@ func TestRunDetach(t *testing.T) {
container := globalRuntime.List()[0]
setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
stdinPipe.Write([]byte{'', ''})
stdinPipe.Write([]byte{16, 17})
if err := stdinPipe.Close(); err != nil {
t.Fatal(err)
}
})
closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
// wait for CmdRun to return
setTimeout(t, "Waiting for CmdRun timed out", 15*time.Second, func() {
<-ch
@@ -411,7 +455,6 @@ func TestRunDetach(t *testing.T) {
setTimeout(t, "Waiting for container to die timed out", 20*time.Second, func() {
container.Kill()
container.Wait()
})
}
@@ -423,39 +466,62 @@ func TestAttachDetach(t *testing.T) {
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
go stdout.Read(make([]byte, 1024))
setTimeout(t, "Starting container timed out", 2*time.Second, func() {
ch := make(chan struct{})
go func() {
defer close(ch)
if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil {
t.Fatal(err)
}
})
}()
container := globalRuntime.List()[0]
var container *Container
setTimeout(t, "Reading container's id timed out", 10*time.Second, func() {
buf := make([]byte, 1024)
n, err := stdout.Read(buf)
if err != nil {
t.Fatal(err)
}
container = globalRuntime.List()[0]
if strings.Trim(string(buf[:n]), " \r\n") != container.ShortID() {
t.Fatalf("Wrong ID received. Expect %s, received %s", container.ShortID(), buf[:n])
}
})
setTimeout(t, "Starting container timed out", 10*time.Second, func() {
<-ch
})
stdin, stdinPipe = io.Pipe()
stdout, stdoutPipe = io.Pipe()
cli = NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
ch := make(chan struct{})
ch = make(chan struct{})
go func() {
defer close(ch)
if err := cli.CmdAttach(container.ShortID()); err != nil {
t.Fatal(err)
if err != io.ErrClosedPipe {
t.Fatal(err)
}
}
}()
setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
t.Fatal(err)
if err != io.ErrClosedPipe {
t.Fatal(err)
}
}
})
setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
stdinPipe.Write([]byte{'', ''})
stdinPipe.Write([]byte{16, 17})
if err := stdinPipe.Close(); err != nil {
t.Fatal(err)
}
})
closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
// wait for CmdRun to return
setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() {
@@ -469,7 +535,6 @@ func TestAttachDetach(t *testing.T) {
setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() {
container.Kill()
container.Wait()
})
}
@@ -484,7 +549,7 @@ func TestAttachDisconnect(t *testing.T) {
go func() {
// Start a process in daemon mode
if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil {
utils.Debugf("Error CmdRun: %s\n", err)
utils.Debugf("Error CmdRun: %s", err)
}
}()
@@ -570,7 +635,7 @@ func TestRunAutoRemove(t *testing.T) {
}
})
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
setTimeout(t, "CmdRun timed out", 10*time.Second, func() {
<-c
})
@@ -580,3 +645,57 @@ func TestRunAutoRemove(t *testing.T) {
t.Fatalf("failed to remove container automatically: container %s still exists", temporaryContainerID)
}
}
func TestCmdLogs(t *testing.T) {
cli := NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
if err := cli.CmdRun(unitTestImageID, "sh", "-c", "ls -l"); err != nil {
t.Fatal(err)
}
if err := cli.CmdRun("-t", unitTestImageID, "sh", "-c", "ls -l"); err != nil {
t.Fatal(err)
}
if err := cli.CmdLogs(globalRuntime.List()[0].ID); err != nil {
t.Fatal(err)
}
}
// Expected behaviour: using / as a bind mount source should throw an error
func TestRunErrorBindMountRootSource(t *testing.T) {
cli := NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
c := make(chan struct{})
go func() {
defer close(c)
if err := cli.CmdRun("-v", "/:/tmp", unitTestImageID, "echo 'should fail'"); err == nil {
t.Fatal("should have failed to run when using / as a source for the bind mount")
}
}()
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c
})
}
// Expected behaviour: error out when attempting to bind mount non-existing source paths
func TestRunErrorBindNonExistingSource(t *testing.T) {
cli := NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
c := make(chan struct{})
go func() {
defer close(c)
if err := cli.CmdRun("-v", "/i/dont/exist:/tmp", unitTestImageID, "echo 'should fail'"); err == nil {
t.Fatal("should have failed to run when using /i/dont/exist as a source for the bind mount")
}
}()
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c
})
}
+26 -1
View File
@@ -2,11 +2,13 @@ package docker
import (
"net"
"github.com/dotcloud/docker/engine"
)
// FIXME: separate runtime configuration from http api configuration
type DaemonConfig struct {
Pidfile string
GraphPath string
Root string
ProtoAddresses []string
AutoRestart bool
EnableCors bool
@@ -16,3 +18,26 @@ type DaemonConfig struct {
DefaultIp net.IP
InterContainerCommunication bool
}
// ConfigFromJob creates and returns a new DaemonConfig object
// by parsing the contents of a job's environment.
func ConfigFromJob(job *engine.Job) *DaemonConfig {
var config DaemonConfig
config.Pidfile = job.Getenv("Pidfile")
config.Root = job.Getenv("Root")
config.AutoRestart = job.GetenvBool("AutoRestart")
config.EnableCors = job.GetenvBool("EnableCors")
if dns := job.Getenv("Dns"); dns != "" {
config.Dns = []string{dns}
}
config.EnableIptables = job.GetenvBool("EnableIptables")
if br := job.Getenv("BridgeIface"); br != "" {
config.BridgeIface = br
} else {
config.BridgeIface = DefaultNetworkBridge
}
config.ProtoAddresses = job.GetenvList("ProtoAddresses")
config.DefaultIp = net.ParseIP(job.Getenv("DefaultIp"))
config.InterContainerCommunication = job.GetenvBool("InterContainerCommunication")
return &config
}
+226 -169
View File
@@ -1,10 +1,12 @@
package docker
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/term"
"github.com/dotcloud/docker/utils"
"github.com/kr/pty"
@@ -16,6 +18,7 @@ import (
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
@@ -42,7 +45,7 @@ type Container struct {
ResolvConfPath string
HostnamePath string
HostsPath string
FilesystemType string
Name string
cmd *exec.Cmd
stdout *utils.WriteBroadcaster
@@ -57,11 +60,15 @@ type Container struct {
Volumes map[string]string
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
// Easier than migrating older container configs :)
VolumesRW map[string]bool
VolumesRW map[string]bool
hostConfig *HostConfig
activeLinks map[string]*Link
}
// Note: the Config structure should hold only portable information about the container.
// Here, "portable" means "independent from the host we are running on".
// Non-portable information *should* appear in HostConfig.
type Config struct {
Hostname string
Domainname string
@@ -86,15 +93,16 @@ type Config struct {
WorkingDir string
Entrypoint []string
NetworkDisabled bool
Privileged bool
}
type HostConfig struct {
Binds []string
ContainerIDFile string
LxcConf []KeyValuePair
Privileged bool
PortBindings map[Port][]PortBinding
Links []string
PublishAllPorts bool
}
type BindMap struct {
@@ -104,7 +112,11 @@ type BindMap struct {
}
var (
ErrInvaidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
ErrContainerStart = errors.New("The container failed to start. Unkown error")
ErrContainerStartTimeout = errors.New("The container failed to start due to timed out.")
ErrInvalidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
ErrConflictAttachDetach = errors.New("Conflicting options: -a and -d")
ErrConflictDetachAutoRemove = errors.New("Conflicting options: -rm and -d")
)
type KeyValuePair struct {
@@ -160,6 +172,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
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 && *flMemory > 0 && !capabilities.MemoryLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
@@ -192,17 +207,21 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
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 (containerid:alias)")
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, fmt.Errorf("Conflicting options: -a and -d")
return nil, nil, cmd, ErrConflictAttachDetach
}
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
return nil, nil, cmd, ErrInvaidWorikingDirectory
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 {
@@ -214,8 +233,16 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
}
}
if *flDetach && *flAutoRemove {
return nil, nil, cmd, fmt.Errorf("Conflicting options: -rm and -d")
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 binds []string
@@ -224,6 +251,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
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)
@@ -277,7 +307,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
}
config := &Config{
Hostname: *flHostname,
Hostname: hostname,
Domainname: domainname,
PortSpecs: nil, // Deprecated
ExposedPorts: ports,
@@ -290,14 +320,13 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
AttachStdin: flAttach.Get("stdin"),
AttachStdout: flAttach.Get("stdout"),
AttachStderr: flAttach.Get("stderr"),
Env: flEnv,
Env: envs,
Cmd: runCmd,
Dns: flDns,
Image: image,
Volumes: flVolumes,
VolumesFrom: strings.Join(flVolumesFrom, ","),
Entrypoint: entrypoint,
Privileged: *flPrivileged,
WorkingDir: *flWorkingDir,
}
@@ -305,8 +334,10 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
Binds: binds,
ContainerIDFile: *flContainerIDFile,
LxcConf: lxcConf,
Privileged: *flPrivileged,
PortBindings: portBindings,
Links: flLinks,
PublishAllPorts: *flPublishAll,
}
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
@@ -359,17 +390,23 @@ func (settings *NetworkSettings) PortMappingAPI() []APIPort {
// Inject the io.Reader at the given path. Note: do not close the reader
func (container *Container) Inject(file io.Reader, pth string) error {
if err := container.EnsureMounted(); err != nil {
// Return error if path exists
if _, err := os.Stat(path.Join(container.rwPath(), pth)); err == nil {
// Since err is nil, the path could be stat'd and it exists
return fmt.Errorf("%s exists", pth)
} else if ! os.IsNotExist(err) {
// Expect err might be that the file doesn't exist, so
// if it's some other error, return that.
return err
}
// Make sure the directory exists
if err := os.MkdirAll(path.Join(container.RootfsPath(), path.Dir(pth)), 0755); err != nil {
if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil {
return err
}
// FIXME: Handle permissions/already existing dest
dest, err := os.Create(path.Join(container.RootfsPath(), pth))
dest, err := os.Create(path.Join(container.rwPath(), pth))
if err != nil {
return err
}
@@ -397,7 +434,7 @@ func (container *Container) FromDisk() error {
if err := json.Unmarshal(data, container); err != nil && !strings.Contains(err.Error(), "docker.PortMapping") {
return err
}
return nil
return container.readHostConfig()
}
func (container *Container) ToDisk() (err error) {
@@ -405,44 +442,53 @@ func (container *Container) ToDisk() (err error) {
if err != nil {
return
}
return ioutil.WriteFile(container.jsonPath(), data, 0666)
err = ioutil.WriteFile(container.jsonPath(), data, 0666)
if err != nil {
return
}
return container.writeHostConfig()
}
func (container *Container) ReadHostConfig() (*HostConfig, error) {
func (container *Container) readHostConfig() error {
container.hostConfig = &HostConfig{}
// If the hostconfig file does not exist, do not read it.
// (We still have to initialize container.hostConfig,
// but that's OK, since we just did that above.)
_, err := os.Stat(container.hostConfigPath())
if os.IsNotExist(err) {
return nil
}
data, err := ioutil.ReadFile(container.hostConfigPath())
if err != nil {
return &HostConfig{}, err
return err
}
hostConfig := &HostConfig{}
if err := json.Unmarshal(data, hostConfig); err != nil {
return &HostConfig{}, err
}
return hostConfig, nil
return json.Unmarshal(data, container.hostConfig)
}
func (container *Container) SaveHostConfig(hostConfig *HostConfig) (err error) {
data, err := json.Marshal(hostConfig)
func (container *Container) writeHostConfig() (err error) {
data, err := json.Marshal(container.hostConfig)
if err != nil {
return
}
return ioutil.WriteFile(container.hostConfigPath(), data, 0666)
}
func (container *Container) generateLXCConfig(hostConfig *HostConfig) error {
func (container *Container) generateEnvConfig(env []string) error {
data, err := json.Marshal(env)
if err != nil {
return err
}
ioutil.WriteFile(container.EnvConfigPath(), data, 0600)
return nil
}
func (container *Container) generateLXCConfig() error {
fo, err := os.Create(container.lxcConfigPath())
if err != nil {
return err
}
defer fo.Close()
if err := LxcTemplateCompiled.Execute(fo, container); err != nil {
return err
}
if hostConfig != nil {
if err := LxcHostConfigTemplateCompiled.Execute(fo, hostConfig); err != nil {
return err
}
}
return nil
return LxcTemplateCompiled.Execute(fo, container)
}
func (container *Container) startPty() error {
@@ -527,11 +573,13 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
} else {
_, err = io.Copy(cStdin, stdin)
}
if err == io.ErrClosedPipe {
err = nil
}
if err != nil {
utils.Errorf("attach: stdin: %s", err)
}
// Discard error, expecting pipe error
errors <- nil
errors <- err
}()
}
}
@@ -635,7 +683,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
})
}
func (container *Container) Start(hostConfig *HostConfig) (err error) {
func (container *Container) Start() (err error) {
container.State.Lock()
defer container.State.Unlock()
defer func() {
@@ -644,10 +692,6 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
}
}()
if hostConfig == nil { // in docker start of docker restart we want to reuse previous HostConfigFile
hostConfig, _ = container.ReadHostConfig()
}
if container.State.Running {
return fmt.Errorf("The container %s is already running.", container.ID)
}
@@ -657,7 +701,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
if container.runtime.networkManager.disabled {
container.Config.NetworkDisabled = true
} else {
if err := container.allocateNetwork(hostConfig); err != nil {
if err := container.allocateNetwork(); err != nil {
return err
}
}
@@ -681,7 +725,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
// Define illegal container destinations
illegalDsts := []string{"/", "."}
for _, bind := range hostConfig.Binds {
for _, bind := range container.hostConfig.Binds {
// FIXME: factorize bind parsing in parseBind
var src, dst, mode string
arr := strings.Split(bind, ":")
@@ -791,7 +835,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
}
if len(srcList) == 0 {
// If the source volume is empty copy files from the root into the volume
if err := CopyWithTar(rootVolPath, srcPath); err != nil {
if err := archive.CopyWithTar(rootVolPath, srcPath); err != nil {
return err
}
@@ -815,12 +859,11 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
}
}
if err := container.generateLXCConfig(hostConfig); err != nil {
if err := container.generateLXCConfig(); err != nil {
return err
}
params := []string{
"lxc-start",
"-n", container.ID,
"-f", container.lxcConfigPath(),
"--",
@@ -837,22 +880,22 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
params = append(params, "-u", container.Config.User)
}
if container.Config.Tty {
params = append(params, "-e", "TERM=xterm")
// Setup environment
env := []string{
"HOME=/",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"container=lxc",
"HOSTNAME=" + container.Config.Hostname,
}
// Setup environment
params = append(params,
"-e", "HOME=/",
"-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"-e", "container=lxc",
"-e", "HOSTNAME="+container.Config.Hostname,
)
if container.Config.Tty {
env = append(env, "TERM=xterm")
}
// Init any links between the parent and children
runtime := container.runtime
children, err := runtime.Children(fmt.Sprintf("/%s", container.ID))
children, err := runtime.Children(container.Name)
if err != nil {
return err
}
@@ -883,11 +926,19 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
}
for _, envVar := range link.ToEnv() {
params = append(params, "-e", envVar)
env = append(env, envVar)
}
}
}
for _, elem := range container.Config.Env {
env = append(env, elem)
}
if err := container.generateEnvConfig(env); err != nil {
return err
}
if container.Config.WorkingDir != "" {
workingDir := path.Clean(container.Config.WorkingDir)
utils.Debugf("[working dir] working dir is %s", workingDir)
@@ -901,30 +952,15 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
)
}
for _, elem := range container.Config.Env {
params = append(params, "-e", elem)
}
// Program
params = append(params, "--", container.Path)
params = append(params, container.Args...)
if RootIsShared() {
// lxc-start really needs / to be private, or all kinds of stuff break
// What we really want is to clone into a new namespace and then
// mount / MS_REC|MS_PRIVATE, but since we can't really clone or fork
// without exec in go we have to do this horrible shell hack...
shellString :=
"mount --make-rprivate /; exec " +
utils.ShellQuoteArguments(params)
params = []string{
"unshare", "-m", "--", "/bin/sh", "-c", shellString,
}
var lxcStart string = "lxc-start"
if container.hostConfig.Privileged && container.runtime.capabilities.AppArmor {
lxcStart = path.Join(container.runtime.config.Root, "lxc-start-unconfined")
}
container.cmd = exec.Command(params[0], params[1:]...)
container.cmd = exec.Command(lxcStart, params...)
// Setup logging of stdout and stderr to disk
if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
return err
@@ -951,14 +987,44 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
container.waitLock = make(chan struct{})
container.ToDisk()
container.SaveHostConfig(hostConfig)
go container.monitor(hostConfig)
return nil
go container.monitor()
defer utils.Debugf("Container running: %v", container.State.Running)
// 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 {
return nil
}
output, err := exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
if err != nil {
utils.Debugf("Error with lxc-info: %s (%s)", err, output)
output, err = exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
if err != nil {
utils.Debugf("Second Error with lxc-info: %s (%s)", err, output)
return err
}
}
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))
time.Sleep(50 * time.Millisecond)
}
if container.State.Running {
return ErrContainerStartTimeout
}
return ErrContainerStart
}
func (container *Container) Run() error {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
return err
}
container.Wait()
@@ -971,8 +1037,7 @@ func (container *Container) Output() (output []byte, err error) {
return nil, err
}
defer pipe.Close()
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
return nil, err
}
output, err = ioutil.ReadAll(pipe)
@@ -1004,19 +1069,14 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
return utils.NewBufReader(reader), nil
}
func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
func (container *Container) allocateNetwork() error {
if container.Config.NetworkDisabled {
return nil
}
var iface *NetworkInterface
var err error
if !container.State.Ghost {
iface, err = container.runtime.networkManager.Allocate()
if err != nil {
return err
}
} else {
if container.State.Ghost {
manager := container.runtime.networkManager
if manager.disabled {
iface = &NetworkInterface{disabled: true}
@@ -1026,17 +1086,32 @@ func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
Gateway: manager.bridgeNetwork.IP,
manager: manager,
}
ipNum := ipToInt(iface.IPNet.IP)
manager.ipAllocator.inUse[ipNum] = struct{}{}
if iface !=nil && iface.IPNet.IP != nil {
ipNum := ipToInt(iface.IPNet.IP)
manager.ipAllocator.inUse[ipNum] = struct{}{}
} else {
iface, err = container.runtime.networkManager.Allocate()
if err != nil {
return err
}
}
}
} else {
iface, err = container.runtime.networkManager.Allocate()
if err != nil {
return err
}
}
if container.Config.PortSpecs != nil {
utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", "))
if err := migratePortMappings(container.Config); err != nil {
if err := migratePortMappings(container.Config, container.hostConfig); err != nil {
return err
}
container.Config.PortSpecs = nil
if err := container.writeHostConfig(); err != nil {
return err
}
}
portSpecs := make(map[Port]struct{})
@@ -1046,8 +1121,8 @@ func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
if container.Config.ExposedPorts != nil {
portSpecs = container.Config.ExposedPorts
}
if hostConfig.PortBindings != nil {
bindings = hostConfig.PortBindings
if container.hostConfig.PortBindings != nil {
bindings = container.hostConfig.PortBindings
}
} else {
if container.NetworkSettings.Ports != nil {
@@ -1062,6 +1137,9 @@ func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
for port := range portSpecs {
binding := bindings[port]
if container.hostConfig.PublishAllPorts && len(binding) == 0 {
binding = append(binding, PortBinding{})
}
for i := 0; i < len(binding); i++ {
b := binding[i]
nat, err := iface.AllocatePort(port, b)
@@ -1074,7 +1152,7 @@ func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
}
bindings[port] = binding
}
container.SaveHostConfig(hostConfig)
container.writeHostConfig()
container.NetworkSettings.Ports = bindings
container.network = iface
@@ -1110,7 +1188,7 @@ func (container *Container) waitLxc() error {
}
}
func (container *Container) monitor(hostConfig *HostConfig) {
func (container *Container) monitor() {
// Wait for the program to exit
// If the command does not exist, try to wait via lxc
@@ -1118,8 +1196,7 @@ func (container *Container) monitor(hostConfig *HostConfig) {
if container.cmd == nil {
utils.Debugf("monitor: waiting for container %s using waitLxc", container.ID)
if err := container.waitLxc(); err != nil {
// Discard the error as any signals or non 0 returns will generate an error
utils.Debugf("monitor: while waiting for container %s, waitLxc had a problem: %s", container.ShortID(), err)
utils.Errorf("monitor: while waiting for container %s, waitLxc had a problem: %s", container.ID, err)
}
} else {
utils.Debugf("monitor: waiting for container %s using cmd.Wait", container.ID)
@@ -1198,54 +1275,57 @@ func (container *Container) cleanup() {
}
}
func (container *Container) kill() error {
func (container *Container) kill(sig int) error {
container.State.Lock()
defer container.State.Unlock()
if !container.State.Running {
return nil
}
// Sending SIGKILL to the process via lxc
output, err := exec.Command("lxc-kill", "-n", container.ID, "9").CombinedOutput()
if err != nil {
log.Printf("error killing container %s (%s, %s)", container.ID, output, err)
if output, err := exec.Command("lxc-kill", "-n", container.ID, strconv.Itoa(sig)).CombinedOutput(); err != nil {
log.Printf("error killing container %s (%s, %s)", container.ShortID(), output, err)
return err
}
return nil
}
func (container *Container) Kill() error {
if !container.State.Running {
return nil
}
// 1. Send SIGKILL
if err := container.kill(9); err != nil {
return err
}
// 2. Wait for the process to die, in last resort, try to kill the process directly
if err := container.WaitTimeout(10 * time.Second); err != nil {
if container.cmd == nil {
return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ID)
return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ShortID())
}
log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.ID)
log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", container.ShortID())
if err := container.cmd.Process.Kill(); err != nil {
return err
}
}
// Wait for the container to be actually stopped
container.Wait()
return nil
}
func (container *Container) Kill() error {
container.State.Lock()
defer container.State.Unlock()
if !container.State.Running {
return nil
}
return container.kill()
}
func (container *Container) Stop(seconds int) error {
container.State.Lock()
defer container.State.Unlock()
if !container.State.Running {
return nil
}
// 1. Send a SIGTERM
if output, err := exec.Command("lxc-kill", "-n", container.ID, "15").CombinedOutput(); err != nil {
log.Print(string(output))
if err := container.kill(15); err != nil {
utils.Debugf("Error sending kill SIGTERM: %s", err)
log.Print("Failed to send SIGTERM to the process, force killing")
if err := container.kill(); err != nil {
if err := container.kill(9); err != nil {
return err
}
}
@@ -1253,7 +1333,8 @@ func (container *Container) Stop(seconds int) error {
// 2. Wait for the process to exit on its own
if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil {
log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds)
if err := container.kill(); err != nil {
// 3. If it doesn't, then send SIGKILL
if err := container.Kill(); err != nil {
return err
}
}
@@ -1264,11 +1345,7 @@ func (container *Container) Restart(seconds int) error {
if err := container.Stop(seconds); err != nil {
return err
}
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return err
}
return nil
return container.Start()
}
// Wait blocks until the container stops running, then returns its exit code.
@@ -1285,31 +1362,23 @@ func (container *Container) Resize(h, w int) error {
return term.SetWinsize(pty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
}
func (container *Container) ExportRw() (Archive, error) {
if err := container.EnsureMounted(); err != nil {
return nil, err
}
image, err := container.GetImage()
if err != nil {
return nil, err
}
return image.ExportChanges(container.runtime, container.RootfsPath(), container.rwPath(), container.ID)
func (container *Container) ExportRw() (archive.Archive, error) {
return archive.Tar(container.rwPath(), archive.Uncompressed)
}
func (container *Container) RwChecksum() (string, error) {
rwData, err := Tar(container.rwPath(), Xz)
rwData, err := archive.Tar(container.rwPath(), archive.Xz)
if err != nil {
return "", err
}
return utils.HashData(rwData)
}
func (container *Container) Export() (Archive, error) {
func (container *Container) Export() (archive.Archive, error) {
if err := container.EnsureMounted(); err != nil {
return nil, err
}
return Tar(container.RootfsPath(), Uncompressed)
return archive.Tar(container.RootfsPath(), archive.Uncompressed)
}
func (container *Container) WaitTimeout(timeout time.Duration) error {
@@ -1336,33 +1405,20 @@ func (container *Container) EnsureMounted() error {
return container.Mount()
}
func (container *Container) EnsureUnmounted() error {
if mounted, err := container.Mounted(); err != nil {
return err
} else if !mounted {
return nil
}
return container.Unmount()
}
func (container *Container) Mount() error {
image, err := container.GetImage()
if err != nil {
return err
}
return image.Mount(container.runtime, container.RootfsPath(), container.rwPath(), container.ID)
return image.Mount(container.RootfsPath(), container.rwPath())
}
func (container *Container) Changes() ([]Change, error) {
if err := container.EnsureMounted(); err != nil {
return nil, err
}
image, err := container.GetImage()
if err != nil {
return nil, err
}
return image.Changes(container.runtime, container.RootfsPath(), container.rwPath(), container.ID)
return image.Changes(container.rwPath())
}
func (container *Container) GetImage() (*Image, error) {
@@ -1373,20 +1429,17 @@ func (container *Container) GetImage() (*Image, error) {
}
func (container *Container) Mounted() (bool, error) {
image, err := container.GetImage()
if err != nil {
return false, err
}
return image.Mounted(container.runtime, container.RootfsPath(), container.rwPath())
return Mounted(container.RootfsPath())
}
func (container *Container) Unmount() error {
image, err := container.GetImage()
if err != nil {
if _, err := os.Stat(container.RootfsPath()); err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
err = image.Unmount(container.runtime, container.RootfsPath(), container.ID)
return err
return Unmount(container.RootfsPath())
}
// ShortID returns a shorthand version of the container's id for convenience.
@@ -1413,6 +1466,10 @@ func (container *Container) jsonPath() string {
return path.Join(container.root, "config.json")
}
func (container *Container) EnvConfigPath() string {
return path.Join(container.root, "config.env")
}
func (container *Container) lxcConfigPath() string {
return path.Join(container.root, "config.lxc")
}
@@ -1456,7 +1513,7 @@ func (container *Container) GetSize() (int64, int64) {
return sizeRw, sizeRootfs
}
func (container *Container) Copy(resource string) (Archive, error) {
func (container *Container) Copy(resource string) (archive.Archive, error) {
if err := container.EnsureMounted(); err != nil {
return nil, err
}
@@ -1474,7 +1531,7 @@ func (container *Container) Copy(resource string) (Archive, error) {
filter = []string{path.Base(basePath)}
basePath = path.Dir(basePath)
}
return TarFilter(basePath, Uncompressed, filter, true, nil)
return archive.TarFilter(basePath, archive.Uncompressed, filter)
}
// Returns true if the container exposes a certain port
+111 -52
View File
@@ -23,6 +23,7 @@ func TestIDFormat(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "echo hello world"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -39,7 +40,7 @@ func TestIDFormat(t *testing.T) {
func TestMultipleAttachRestart(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, hostConfig, _ := mkContainer(
container, _ := mkContainer(
runtime,
[]string{"_", "/bin/sh", "-c", "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"},
t,
@@ -60,7 +61,7 @@ func TestMultipleAttachRestart(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
l1, err := bufio.NewReader(stdout1).ReadString('\n')
@@ -101,7 +102,7 @@ func TestMultipleAttachRestart(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
@@ -135,7 +136,7 @@ func TestDiff(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
// Create a container and remove a file
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/rm", "/etc/passwd"}, t)
container1, _ := mkContainer(runtime, []string{"_", "/bin/rm", "/etc/passwd"}, t)
defer runtime.Destroy(container1)
// The changelog should be empty and not fail before run. See #1705
@@ -177,7 +178,7 @@ func TestDiff(t *testing.T) {
}
// Create a new container from the commited image
container2, _, _ := mkContainer(runtime, []string{img.ID, "cat", "/etc/passwd"}, t)
container2, _ := mkContainer(runtime, []string{img.ID, "cat", "/etc/passwd"}, t)
defer runtime.Destroy(container2)
if err := container2.Run(); err != nil {
@@ -196,7 +197,7 @@ func TestDiff(t *testing.T) {
}
// Create a new container
container3, _, _ := mkContainer(runtime, []string{"_", "rm", "/bin/httpd"}, t)
container3, _ := mkContainer(runtime, []string{"_", "rm", "/bin/httpd"}, t)
defer runtime.Destroy(container3)
if err := container3.Run(); err != nil {
@@ -222,7 +223,7 @@ func TestDiff(t *testing.T) {
func TestCommitAutoRun(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
container1, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
defer runtime.Destroy(container1)
if container1.State.Running {
@@ -245,7 +246,7 @@ func TestCommitAutoRun(t *testing.T) {
}
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
container2, hostConfig, _ := mkContainer(runtime, []string{img.ID}, t)
container2, _ := mkContainer(runtime, []string{img.ID}, t)
defer runtime.Destroy(container2)
stdout, err := container2.StdoutPipe()
if err != nil {
@@ -255,7 +256,7 @@ func TestCommitAutoRun(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container2.Start(hostConfig); err != nil {
if err := container2.Start(); err != nil {
t.Fatal(err)
}
container2.Wait()
@@ -282,7 +283,7 @@ func TestCommitRun(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container1, hostConfig, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
container1, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
defer runtime.Destroy(container1)
if container1.State.Running {
@@ -305,7 +306,7 @@ func TestCommitRun(t *testing.T) {
}
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
container2, hostConfig, _ := mkContainer(runtime, []string{img.ID, "cat", "/world"}, t)
container2, _ := mkContainer(runtime, []string{img.ID, "cat", "/world"}, t)
defer runtime.Destroy(container2)
stdout, err := container2.StdoutPipe()
if err != nil {
@@ -315,7 +316,7 @@ func TestCommitRun(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container2.Start(hostConfig); err != nil {
if err := container2.Start(); err != nil {
t.Fatal(err)
}
container2.Wait()
@@ -341,7 +342,7 @@ func TestCommitRun(t *testing.T) {
func TestStart(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, hostConfig, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
container, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
defer runtime.Destroy(container)
cStdin, err := container.StdinPipe()
@@ -349,7 +350,7 @@ func TestStart(t *testing.T) {
t.Fatal(err)
}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
@@ -359,7 +360,7 @@ func TestStart(t *testing.T) {
if !container.State.Running {
t.Errorf("Container should be running")
}
if err := container.Start(hostConfig); err == nil {
if err := container.Start(); err == nil {
t.Fatalf("A running container should be able to be started")
}
@@ -371,7 +372,7 @@ func TestStart(t *testing.T) {
func TestRun(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
container, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
defer runtime.Destroy(container)
if container.State.Running {
@@ -393,6 +394,7 @@ func TestOutput(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -415,6 +417,7 @@ func TestContainerNetwork(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"ping", "-c", "1", "127.0.0.1"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -438,6 +441,7 @@ func TestKillDifferentUser(t *testing.T) {
OpenStdin: true,
User: "daemon",
},
"",
)
if err != nil {
t.Fatal(err)
@@ -448,7 +452,7 @@ func TestKillDifferentUser(t *testing.T) {
if container.State.Running {
t.Errorf("Container shouldn't be running")
}
if err := container.Start(&HostConfig{}); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
@@ -492,12 +496,13 @@ func TestCreateVolume(t *testing.T) {
if err != nil {
t.Fatal(err)
}
c, _, err := runtime.Create(config)
c, _, err := runtime.Create(config, "")
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(c)
if err := c.Start(hc); err != nil {
c.hostConfig = hc
if err := c.Start(); err != nil {
t.Fatal(err)
}
c.WaitTimeout(500 * time.Millisecond)
@@ -511,6 +516,7 @@ func TestKill(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"sleep", "2"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -520,8 +526,7 @@ func TestKill(t *testing.T) {
if container.State.Running {
t.Errorf("Container shouldn't be running")
}
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
@@ -554,7 +559,7 @@ func TestExitCode(t *testing.T) {
trueContainer, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true", ""},
})
}, "")
if err != nil {
t.Fatal(err)
}
@@ -569,7 +574,7 @@ func TestExitCode(t *testing.T) {
falseContainer, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/false", ""},
})
}, "")
if err != nil {
t.Fatal(err)
}
@@ -589,6 +594,7 @@ func TestRestart(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -621,6 +627,7 @@ func TestRestartStdin(t *testing.T) {
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -635,8 +642,7 @@ func TestRestartStdin(t *testing.T) {
if err != nil {
t.Fatal(err)
}
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
if _, err := io.WriteString(stdin, "hello world"); err != nil {
@@ -666,7 +672,7 @@ func TestRestartStdin(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
if _, err := io.WriteString(stdin, "hello world #2"); err != nil {
@@ -697,6 +703,7 @@ func TestUser(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -717,6 +724,7 @@ func TestUser(t *testing.T) {
User: "root",
},
"",
)
if err != nil {
t.Fatal(err)
@@ -737,6 +745,7 @@ func TestUser(t *testing.T) {
User: "0",
},
"",
)
if err != nil || container.State.ExitCode != 0 {
t.Fatal(err)
@@ -757,6 +766,7 @@ func TestUser(t *testing.T) {
User: "1",
},
"",
)
if err != nil {
t.Fatal(err)
@@ -779,6 +789,7 @@ func TestUser(t *testing.T) {
User: "daemon",
},
"",
)
if err != nil {
t.Fatal(err)
@@ -799,6 +810,7 @@ func TestUser(t *testing.T) {
User: "unknownuser",
},
"",
)
if err != nil {
t.Fatal(err)
@@ -818,6 +830,7 @@ func TestMultipleContainers(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"sleep", "2"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -828,6 +841,7 @@ func TestMultipleContainers(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"sleep", "2"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -835,11 +849,10 @@ func TestMultipleContainers(t *testing.T) {
defer runtime.Destroy(container2)
// Start both containers
hostConfig := &HostConfig{}
if err := container1.Start(hostConfig); err != nil {
if err := container1.Start(); err != nil {
t.Fatal(err)
}
if err := container2.Start(hostConfig); err != nil {
if err := container2.Start(); err != nil {
t.Fatal(err)
}
@@ -874,6 +887,7 @@ func TestStdin(t *testing.T) {
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -888,8 +902,7 @@ func TestStdin(t *testing.T) {
if err != nil {
t.Fatal(err)
}
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
defer stdin.Close()
@@ -919,6 +932,7 @@ func TestTty(t *testing.T) {
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -933,8 +947,7 @@ func TestTty(t *testing.T) {
if err != nil {
t.Fatal(err)
}
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
defer stdin.Close()
@@ -956,13 +969,15 @@ func TestTty(t *testing.T) {
}
func TestEnv(t *testing.T) {
os.Setenv("TRUE", "false")
os.Setenv("TRICKY", "tri\ncky\n")
runtime := mkRuntime(t)
defer nuke(runtime)
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"env"},
},
)
config, _, _, err := ParseRun([]string{"-e=FALSE=true", "-e=TRUE", "-e=TRICKY", GetTestImage(runtime).ID, "env"}, nil)
if err != nil {
t.Fatal(err)
}
container, _, err := runtime.Create(config, "")
if err != nil {
t.Fatal(err)
}
@@ -973,8 +988,7 @@ func TestEnv(t *testing.T) {
t.Fatal(err)
}
defer stdout.Close()
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
t.Fatal(err)
}
container.Wait()
@@ -992,6 +1006,11 @@ func TestEnv(t *testing.T) {
"HOME=/",
"container=lxc",
"HOSTNAME=" + container.ShortID(),
"FALSE=true",
"TRUE=false",
"TRICKY=tri",
"cky",
"",
}
sort.Strings(goodEnv)
if len(goodEnv) != len(actualEnv) {
@@ -1013,6 +1032,7 @@ func TestEntrypoint(t *testing.T) {
Entrypoint: []string{"/bin/echo"},
Cmd: []string{"-n", "foobar"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1035,6 +1055,7 @@ func TestEntrypointNoCmd(t *testing.T) {
Image: GetTestImage(runtime).ID,
Entrypoint: []string{"/bin/echo", "foobar"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1089,11 +1110,13 @@ func TestLXCConfig(t *testing.T) {
Memory: int64(mem),
CpuShares: int64(cpu),
},
"",
)
if err != nil {
t.Fatal(err)
}
container.generateLXCConfig(nil)
defer runtime.Destroy(container)
container.generateLXCConfig()
grepFile(t, container.lxcConfigPath(), "lxc.utsname = foobar")
grepFile(t, container.lxcConfigPath(),
fmt.Sprintf("lxc.cgroup.memory.limit_in_bytes = %d", mem))
@@ -1110,12 +1133,13 @@ func TestCustomLxcConfig(t *testing.T) {
Hostname: "foobar",
},
"",
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
hostConfig := &HostConfig{LxcConf: []KeyValuePair{
container.hostConfig = &HostConfig{LxcConf: []KeyValuePair{
{
Key: "lxc.utsname",
Value: "docker",
@@ -1126,7 +1150,7 @@ func TestCustomLxcConfig(t *testing.T) {
},
}}
container.generateLXCConfig(hostConfig)
container.generateLXCConfig()
grepFile(t, container.lxcConfigPath(), "lxc.utsname = docker")
grepFile(t, container.lxcConfigPath(), "lxc.cgroup.cpuset.cpus = 0,1")
}
@@ -1139,6 +1163,7 @@ func BenchmarkRunSequencial(b *testing.B) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foo"},
},
"",
)
if err != nil {
b.Fatal(err)
@@ -1171,14 +1196,14 @@ func BenchmarkRunParallel(b *testing.B) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foo"},
},
"",
)
if err != nil {
complete <- err
return
}
defer runtime.Destroy(container)
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(); err != nil {
complete <- err
return
}
@@ -1222,7 +1247,7 @@ func TestCopyVolumeUidGid(t *testing.T) {
defer nuke(r)
// Add directory not owned by root
container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && touch /hello/test.txt && chown daemon.daemon /hello"}, 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 {
@@ -1259,7 +1284,7 @@ func TestCopyVolumeContent(t *testing.T) {
defer nuke(r)
// Put some content in a directory of a container and commit it
container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello/local && echo hello > /hello/local/world"}, 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 {
@@ -1323,6 +1348,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) {
Cmd: []string{"/bin/echo", "-n", "foobar"},
Volumes: map[string]struct{}{"/test": {}},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1342,6 +1368,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) {
Cmd: []string{"/bin/echo", "-n", "foobar"},
VolumesFrom: container.ID,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1377,6 +1404,7 @@ func TestRestartWithVolumes(t *testing.T) {
Cmd: []string{"echo", "-n", "foobar"},
Volumes: map[string]struct{}{"/test": {}},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1420,6 +1448,7 @@ func TestVolumesFromWithVolumes(t *testing.T) {
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
Volumes: map[string]struct{}{"/test": {}},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1449,6 +1478,7 @@ func TestVolumesFromWithVolumes(t *testing.T) {
VolumesFrom: container.ID,
Volumes: map[string]struct{}{"/test": {}},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1483,7 +1513,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
if err != nil {
t.Fatal(err)
}
c, _, err := runtime.Create(config)
c, _, err := runtime.Create(config, "")
if err != nil {
t.Fatal(err)
}
@@ -1491,9 +1521,9 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(c)
if err := c.Start(hc); err != nil {
c.hostConfig = hc
if err := c.Start(); err != nil {
t.Fatal(err)
}
c.WaitTimeout(500 * time.Millisecond)
@@ -1554,6 +1584,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
Volumes: map[string]struct{}{"/test": {}},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1582,6 +1613,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
Cmd: []string{"sh", "-c", "echo -n bar > /other/foo"},
Volumes: map[string]struct{}{"/other": {}},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1602,7 +1634,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/echo", "-n", "foobar"},
VolumesFrom: strings.Join([]string{container.ID, container2.ID}, ","),
})
}, "")
if err != nil {
t.Fatal(err)
@@ -1613,7 +1645,6 @@ func TestMultipleVolumesFrom(t *testing.T) {
t.Fatal(err)
}
t.Log(container3.Volumes)
if container3.Volumes["/test"] != container.Volumes["/test"] {
t.Fail()
}
@@ -1621,3 +1652,31 @@ func TestMultipleVolumesFrom(t *testing.T) {
t.Fail()
}
}
func TestRestartGhost(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
Volumes: map[string]struct{}{"/test": {}},
},
"",
)
if err != nil {
t.Fatal(err)
}
if err := container.Kill(); err != nil {
t.Fatal(err)
}
container.State.Ghost = true
_, err = container.Output()
if err != nil {
t.Fatal(err)
}
}
-1
View File
@@ -1 +0,0 @@
*.pyc
-78
View File
@@ -1,78 +0,0 @@
# docker-brew
docker-brew is a command-line tool used to build the docker standard library.
## Install instructions
1. Install python if it isn't already available on your OS of choice
1. Install the easy_install tool (`sudo apt-get install python-setuptools`
for Debian)
1. Install the python package manager, `pip` (`easy_install pip`)
1. Run the following command: `sudo pip install -r requirements.txt`
1. You should now be able to use the `docker-brew` script as such.
## Basics
./docker-brew -h
Display usage and help.
./docker-brew
Default build from the default repo/branch. Images will be created under the
`library/` namespace. Does not perform a remote push.
./docker-brew -n mycorp.com -b stable --push git://github.com/mycorp/docker
Will fetch the library definition files in the `stable` branch of the
`git://github.com/mycorp/docker` repository and create images under the
`mycorp.com` namespace (e.g. `mycorp.com/ubuntu`). Created images will then
be pushed to the official docker repository (pending: support for private
repositories)
## Library definition files
The library definition files are plain text files found in the `library/`
subfolder of the docker repository.
### File names
The name of a definition file will determine the name of the image(s) it
creates. For example, the `library/ubuntu` file will create images in the
`<namespace>/ubuntu` repository. If multiple instructions are present in
a single file, all images are expected to be created under a different tag.
### Instruction format
Each line represents a build instruction.
There are different formats that `docker-brew` is able to parse.
<git-url>
git://github.com/dotcloud/hipache
https://github.com/dotcloud/docker.git
The simplest format. `docker-brew` will fetch data from the provided git
repository from the `HEAD`of its `master` branch. Generated image will be
tagged as `latest`. Use of this format is discouraged because there is no
way to ensure stability.
<docker-tag> <git-url>
bleeding-edge git://github.com/dotcloud/docker
unstable https://github.com/dotcloud/docker-redis.git
A more advanced format. `docker-brew` will fetch data from the provided git
repository from the `HEAD`of its `master` branch. Generated image will be
tagged as `<docker-tag>`. Recommended if we always want to provide a snapshot
of the latest development. Again, no way to ensure stability.
<docker-tag> <git-url> T:<git-tag>
2.4.0 git://github.com/dotcloud/docker-redis T:2.4.0
<docker-tag> <git-url> B:<git-branch>
zfs git://github.com/dotcloud/docker B:zfs-support
<docker-tag> <git-url> C:<git-commit-id>
2.2.0 https://github.com/dotcloud/docker-redis.git C:a4bf8923ee4ec566d3ddc212
The most complete format. `docker-brew` will fetch data from the provided git
repository from the provided reference (if it's a branch, brew will fetch its
`HEAD`). Generated image will be tagged as `<docker-tag>`. Recommended whenever
possible.
-1
View File
@@ -1 +0,0 @@
from brew import build_library, DEFAULT_REPOSITORY, DEFAULT_BRANCH
-185
View File
@@ -1,185 +0,0 @@
import os
import logging
from shutil import rmtree
import docker
import git
DEFAULT_REPOSITORY = 'git://github.com/dotcloud/docker'
DEFAULT_BRANCH = 'master'
logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
level='INFO')
client = docker.Client()
processed = {}
processed_folders = []
def build_library(repository=None, branch=None, namespace=None, push=False,
debug=False, prefill=True, registry=None):
dst_folder = None
summary = Summary()
if repository is None:
repository = DEFAULT_REPOSITORY
if branch is None:
branch = DEFAULT_BRANCH
if debug:
logger.setLevel('DEBUG')
if not (repository.startswith('https://') or repository.startswith('git://')):
logger.info('Repository provided assumed to be a local path')
dst_folder = repository
try:
client.version()
except Exception as e:
logger.error('Could not reach the docker daemon. Please make sure it '
'is running.')
logger.warning('Also make sure you have access to the docker UNIX '
'socket (use sudo)')
return
#FIXME: set destination folder and only pull latest changes instead of
# cloning the whole repo everytime
if not dst_folder:
logger.info('Cloning docker repo from {0}, branch: {1}'.format(
repository, branch))
try:
rep, dst_folder = git.clone_branch(repository, branch)
except Exception as e:
logger.exception(e)
logger.error('Source repository could not be fetched. Check '
'that the address is correct and the branch exists.')
return
try:
dirlist = os.listdir(os.path.join(dst_folder, 'library'))
except OSError as e:
logger.error('The path provided ({0}) could not be found or didn\'t'
'contain a library/ folder.'.format(dst_folder))
return
for buildfile in dirlist:
if buildfile == 'MAINTAINERS':
continue
f = open(os.path.join(dst_folder, 'library', buildfile))
linecnt = 0
for line in f:
linecnt = linecnt + 1
logger.debug('{0} ---> {1}'.format(buildfile, line))
args = line.split()
try:
if len(args) > 3:
raise RuntimeError('Incorrect line format, '
'please refer to the docs')
url = None
ref = 'refs/heads/master'
tag = None
if len(args) == 1: # Just a URL, simple mode
url = args[0]
elif len(args) == 2 or len(args) == 3: # docker-tag url
url = args[1]
tag = args[0]
if len(args) == 3: # docker-tag url B:branch or T:tag
ref = None
if args[2].startswith('B:'):
ref = 'refs/heads/' + args[2][2:]
elif args[2].startswith('T:'):
ref = 'refs/tags/' + args[2][2:]
elif args[2].startswith('C:'):
ref = args[2][2:]
else:
raise RuntimeError('Incorrect line format, '
'please refer to the docs')
if prefill:
logger.debug('Pulling {0} from official repository (cache '
'fill)'.format(buildfile))
client.pull(buildfile)
img = build_repo(url, ref, buildfile, tag, namespace, push,
registry)
summary.add_success(buildfile, (linecnt, line), img)
processed['{0}@{1}'.format(url, ref)] = img
except Exception as e:
logger.exception(e)
summary.add_exception(buildfile, (linecnt, line), e)
f.close()
if dst_folder != repository:
rmtree(dst_folder, True)
for d in processed_folders:
rmtree(d, True)
summary.print_summary(logger)
def build_repo(repository, ref, docker_repo, docker_tag, namespace, push, registry):
docker_repo = '{0}/{1}'.format(namespace or 'library', docker_repo)
img_id = None
dst_folder = None
if '{0}@{1}'.format(repository, ref) not in processed.keys():
logger.info('Cloning {0} (ref: {1})'.format(repository, ref))
if repository not in processed:
rep, dst_folder = git.clone(repository, ref)
processed[repository] = rep
processed_folders.append(dst_folder)
else:
dst_folder = git.checkout(processed[repository], ref)
if not 'Dockerfile' in os.listdir(dst_folder):
raise RuntimeError('Dockerfile not found in cloned repository')
logger.info('Building using dockerfile...')
img_id, logs = client.build(path=dst_folder, quiet=True)
else:
img_id = processed['{0}@{1}'.format(repository, ref)]
logger.info('Committing to {0}:{1}'.format(docker_repo,
docker_tag or 'latest'))
client.tag(img_id, docker_repo, docker_tag)
if push:
logger.info('Pushing result to registry {0}'.format(
registry or "default"))
if registry is not None:
docker_repo = '{0}/{1}'.format(registry, docker_repo)
logger.info('Also tagging {0}'.format(docker_repo))
client.tag(img_id, docker_repo, docker_tag)
client.push(docker_repo)
return img_id
class Summary(object):
def __init__(self):
self._summary = {}
self._has_exc = False
def _add_data(self, image, linestr, data):
if image not in self._summary:
self._summary[image] = { linestr: data }
else:
self._summary[image][linestr] = data
def add_exception(self, image, line, exc):
lineno, linestr = line
self._add_data(image, linestr, { 'line': lineno, 'exc': str(exc) })
self._has_exc = True
def add_success(self, image, line, img_id):
lineno, linestr = line
self._add_data(image, linestr, { 'line': lineno, 'id': img_id })
def print_summary(self, logger=None):
linesep = ''.center(61, '-') + '\n'
s = 'BREW BUILD SUMMARY\n' + linesep
success = 'OVERALL SUCCESS: {}\n'.format(not self._has_exc)
details = linesep
for image, lines in self._summary.iteritems():
details = details + '{}\n{}'.format(image, linesep)
for linestr, data in lines.iteritems():
details = details + '{0:2} | {1} | {2:50}\n'.format(
data['line'],
'KO' if 'exc' in data else 'OK',
data['exc'] if 'exc' in data else data['id']
)
details = details + linesep
if logger:
logger.info(s + success + details)
else:
print s, success, details
-63
View File
@@ -1,63 +0,0 @@
import tempfile
import logging
from dulwich import index
from dulwich.client import get_transport_and_path
from dulwich.repo import Repo
logger = logging.getLogger(__name__)
def clone_branch(repo_url, branch="master", folder=None):
return clone(repo_url, 'refs/heads/' + branch, folder)
def clone_tag(repo_url, tag, folder=None):
return clone(repo_url, 'refs/tags/' + tag, folder)
def checkout(rep, ref=None):
is_commit = False
if ref is None:
ref = 'refs/heads/master'
elif not ref.startswith('refs/'):
is_commit = True
if is_commit:
rep['HEAD'] = rep.commit(ref)
else:
rep['HEAD'] = rep.refs[ref]
indexfile = rep.index_path()
tree = rep["HEAD"].tree
index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree)
return rep.path
def clone(repo_url, ref=None, folder=None):
is_commit = False
if ref is None:
ref = 'refs/heads/master'
elif not ref.startswith('refs/'):
is_commit = True
logger.debug("clone repo_url={0}, ref={1}".format(repo_url, ref))
if folder is None:
folder = tempfile.mkdtemp()
logger.debug("folder = {0}".format(folder))
rep = Repo.init(folder)
client, relative_path = get_transport_and_path(repo_url)
logger.debug("client={0}".format(client))
remote_refs = client.fetch(relative_path, rep)
for k, v in remote_refs.iteritems():
try:
rep.refs.add_if_new(k, v)
except:
pass
if is_commit:
rep['HEAD'] = rep.commit(ref)
else:
rep['HEAD'] = remote_refs[ref]
indexfile = rep.index_path()
tree = rep["HEAD"].tree
index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree)
logger.debug("done")
return rep, folder
-35
View File
@@ -1,35 +0,0 @@
#!/usr/bin/env python
import argparse
import sys
try:
import brew
except ImportError as e:
print str(e)
print 'Please install the required dependencies first'
print 'sudo pip install -r requirements.txt'
sys.exit(1)
if __name__ == '__main__':
parser = argparse.ArgumentParser('Build the docker standard library')
parser.add_argument('--push', action='store_true', default=False,
help='Push generated repositories')
parser.add_argument('--debug', default=False, action='store_true',
help='Enable debugging output')
parser.add_argument('--noprefill', default=True, action='store_false',
dest='prefill', help='Disable cache prefill')
parser.add_argument('-n', metavar='NAMESPACE', default='library',
help='Namespace used for generated repositories.'
' Default is library')
parser.add_argument('-b', metavar='BRANCH', default=brew.DEFAULT_BRANCH,
help='Branch in the repository where the library definition'
' files will be fetched. Default is ' + brew.DEFAULT_BRANCH)
parser.add_argument('repository', default=brew.DEFAULT_REPOSITORY,
nargs='?', help='git repository containing the library definition'
' files. Default is ' + brew.DEFAULT_REPOSITORY)
parser.add_argument('--reg', default=None, help='Registry address to'
' push build results to. Also sets push to true.')
args = parser.parse_args()
brew.build_library(args.repository, args.b, args.n,
args.push or args.reg is not None, args.debug, args.prefill, args.reg)
-2
View File
@@ -1,2 +0,0 @@
dulwich==0.9.0
-e git://github.com/dotcloud/docker-py.git#egg=docker-py
-22
View File
@@ -1,22 +0,0 @@
#!/usr/bin/env python
import os
from setuptools import setup
ROOT_DIR = os.path.dirname(__file__)
SOURCE_DIR = os.path.join(ROOT_DIR)
test_requirements = []
setup(
name="docker-brew",
version='0.0.1',
description="-",
packages=['dockerbrew'],
install_requires=['dulwich', 'docker'] + test_requirements,
zip_safe=False,
classifiers=['Development Status :: 3 - Alpha',
'Environment :: Other Environment',
'Intended Audience :: Developers',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Utilities'],
)
+1 -1
View File
@@ -426,7 +426,7 @@ _docker_run()
_docker_search()
{
COMPREPLY=( $( compgen -W "-notrunc" -- "$cur" ) )
COMPREPLY=( $( compgen -W "-notrunc" "-stars" "-trusted" -- "$cur" ) )
}
_docker_start()
+11
View File
@@ -0,0 +1,11 @@
Desktop Integration
===================
The ./contrib/desktop-integration contains examples of typical dockerized
desktop applications.
Examples
========
* Data container: ./data/Dockerfile creates a data image sharing /data volume
* Firefox: ./firefox/Dockerfile shows a way to dockerize a common multimedia application
@@ -0,0 +1,38 @@
# VERSION: 0.1
# DESCRIPTION: Create data image sharing /data volume
# AUTHOR: Daniel Mizyrycki <daniel@dotcloud.com>
# COMMENTS:
# This image is used as base for all data containers.
# /data volume is owned by sysadmin.
# USAGE:
# # Download data Dockerfile
# wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/data/Dockerfile
#
# # Build data image
# docker build -t data -rm .
#
# # Create a data container. (eg: firefox-data)
# docker run -name firefox-data data true
#
# # List data from it
# docker run -volumes-from firefox-data busybox ls -al /data
docker-version 0.6.5
# Smallest base image, just to launch a container
from busybox
maintainer Daniel Mizyrycki <daniel@docker.com>
# Create a regular user
run echo 'sysadmin:x:1000:1000::/data:/bin/sh' >> /etc/passwd
run echo 'sysadmin:x:1000:' >> /etc/group
# Create directory for that user
run mkdir /data
run chown sysadmin.sysadmin /data
# Add content to /data. This will keep sysadmin ownership
run touch /data/init_volume
# Create /data volume
VOLUME /data
@@ -0,0 +1,49 @@
# VERSION: 0.7
# DESCRIPTION: Create firefox container with its dependencies
# AUTHOR: Daniel Mizyrycki <daniel@dotcloud.com>
# COMMENTS:
# This file describes how to build a Firefox container with all
# dependencies installed. It uses native X11 unix socket and alsa
# sound devices. Tested on Debian 7.2
# USAGE:
# # Download Firefox Dockerfile
# wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/firefox/Dockerfile
#
# # Build firefox image
# docker build -t firefox -rm .
#
# # Run stateful data-on-host firefox. For ephemeral, remove -v /data/firefox:/data
# docker run -v /data/firefox:/data -v /tmp/.X11-unix:/tmp/.X11-unix \
# -v /dev/snd:/dev/snd -lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
# -e DISPLAY=unix$DISPLAY firefox
#
# # To run stateful dockerized data containers
# docker run -volumes-from firefox-data -v /tmp/.X11-unix:/tmp/.X11-unix \
# -v /dev/snd:/dev/snd -lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
# -e DISPLAY=unix$DISPLAY firefox
docker-version 0.6.5
# Base docker image
from tianon/debian:wheezy
maintainer Daniel Mizyrycki <daniel@docker.com>
# Install firefox dependencies
run echo "deb http://ftp.debian.org/debian/ wheezy main contrib" > /etc/apt/sources.list
run apt-get update
run DEBIAN_FRONTEND=noninteractive apt-get install -y libXrender1 libasound2 \
libdbus-glib-1-2 libgtk2.0-0 libpango1.0-0 libxt6 wget bzip2 sudo
# Install Firefox
run mkdir /application
run cd /application; wget -O - \
http://ftp.mozilla.org/pub/mozilla.org/firefox/releases/25.0/linux-x86_64/en-US/firefox-25.0.tar.bz2 | tar jx
# create sysadmin account
run useradd -m -d /data -p saIVpsc0EVTwA sysadmin
run sed -Ei 's/sudo:x:27:/sudo:x:27:sysadmin/' /etc/group
run sed -Ei 's/(\%sudo\s+ALL=\(ALL\:ALL\) )ALL/\1 NOPASSWD:ALL/' /etc/sudoers
# Autorun firefox. -no-remote is necessary to create a new container, as firefox
# appears to communicate with itself through X11.
cmd ["/bin/sh", "-c", "/usr/bin/sudo -u sysadmin -H -E /application/firefox/firefox -no-remote"]
+27
View File
@@ -0,0 +1,27 @@
#
# This Dockerfile will create an image that allows to generate upstart and
# systemd scripts (more to come)
#
# docker-version 0.6.2
#
FROM ubuntu:12.10
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
RUN apt-get update && apt-get install -y wget git mercurial
# Install Go
RUN wget --no-check-certificate https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz -O go-1.1.2.tar.gz
RUN tar -xzvf go-1.1.2.tar.gz && mv /go /goroot
RUN mkdir /go
ENV GOROOT /goroot
ENV GOPATH /go
ENV PATH $GOROOT/bin:$PATH
RUN go get github.com/dotcloud/docker && cd /go/src/github.com/dotcloud/docker && git checkout v0.6.3
ADD manager.go /manager/
RUN cd /manager && go build -o /usr/bin/manager
ENTRYPOINT ["/usr/bin/manager"]
+4
View File
@@ -0,0 +1,4 @@
FROM busybox
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
ADD manager /usr/bin/
ENTRYPOINT ["/usr/bin/manager"]
+130
View File
@@ -0,0 +1,130 @@
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"github.com/dotcloud/docker"
"os"
"strings"
"text/template"
)
var templates = map[string]string{
"upstart": `description "{{.description}}"
author "{{.author}}"
start on filesystem and started lxc-net and started docker
stop on runlevel [!2345]
respawn
exec /home/vagrant/goroot/bin/docker start -a {{.container_id}}
`,
"systemd": `[Unit]
Description={{.description}}
Author={{.author}}
After=docker.service
[Service]
Restart=always
ExecStart=/usr/bin/docker start -a {{.container_id}}
ExecStop=/usr/bin/docker stop -t 2 {{.container_id}}
[Install]
WantedBy=local.target
`,
}
func main() {
// Parse command line for custom options
kind := flag.String("t", "upstart", "Type of manager requested")
author := flag.String("a", "<none>", "Author of the image")
description := flag.String("d", "<none>", "Description of the image")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "\nUsage: manager <container id>\n\n")
flag.PrintDefaults()
}
flag.Parse()
// We require at least the container ID
if flag.NArg() != 1 {
println(flag.NArg())
flag.Usage()
return
}
// Check that the requested process manager is supported
if _, exists := templates[*kind]; !exists {
panic("Unkown script template")
}
// Load the requested template
tpl, err := template.New("processManager").Parse(templates[*kind])
if err != nil {
panic(err)
}
// Create stdout/stderr buffers
bufOut := bytes.NewBuffer(nil)
bufErr := bytes.NewBuffer(nil)
// Instanciate the Docker CLI
cli := docker.NewDockerCli(nil, bufOut, bufErr, "unix", "/var/run/docker.sock")
// Retrieve the container info
if err := cli.CmdInspect(flag.Arg(0)); err != nil {
// As of docker v0.6.3, CmdInspect always returns nil
panic(err)
}
// If there is nothing in the error buffer, then the Docker daemon is there and the container has been found
if bufErr.Len() == 0 {
// Unmarshall the resulting container data
c := []*docker.Container{{}}
if err := json.Unmarshal(bufOut.Bytes(), &c); err != nil {
panic(err)
}
// Reset the buffers
bufOut.Reset()
bufErr.Reset()
// Retrieve the info of the linked image
if err := cli.CmdInspect(c[0].Image); err != nil {
panic(err)
}
// If there is nothing in the error buffer, then the image has been found.
if bufErr.Len() == 0 {
// Unmarshall the resulting image data
img := []*docker.Image{{}}
if err := json.Unmarshal(bufOut.Bytes(), &img); err != nil {
panic(err)
}
// If no author has been set, use the one from the image
if *author == "<none>" && img[0].Author != "" {
*author = strings.Replace(img[0].Author, "\"", "", -1)
}
// If no description has been set, use the comment from the image
if *description == "<none>" && img[0].Comment != "" {
*description = strings.Replace(img[0].Comment, "\"", "", -1)
}
}
}
/// Old version: Wrtie the resulting script to file
// f, err := os.OpenFile(kind, os.O_CREATE|os.O_WRONLY, 0755)
// if err != nil {
// panic(err)
// }
// defer f.Close()
// Create a map with needed data
data := map[string]string{
"author": *author,
"description": *description,
"container_id": flag.Arg(0),
}
// Process the template and output it on Stdout
if err := tpl.Execute(os.Stdout, data); err != nil {
panic(err)
}
}
+53
View File
@@ -0,0 +1,53 @@
#!/bin/sh
set -e
usage() {
echo >&2 "usage: $0 [-a author] [-d description] container [manager]"
echo >&2 " ie: $0 -a 'John Smith' 4ec9612a37cd systemd"
echo >&2 " ie: $0 -d 'Super Cool System' 4ec9612a37cd # defaults to upstart"
exit 1
}
auth='<none>'
desc='<none>'
have_auth=
have_desc=
while getopts a:d: opt; do
case "$opt" in
a)
auth="$OPTARG"
have_auth=1
;;
d)
desc="$OPTARG"
have_desc=1
;;
esac
done
shift $(($OPTIND - 1))
[ $# -ge 1 -a $# -le 2 ] || usage
cid="$1"
script="${2:-upstart}"
if [ ! -e "manager/$script" ]; then
echo >&2 "Error: manager type '$script' is unknown (PRs always welcome!)."
echo >&2 'The currently supported types are:'
echo >&2 " $(cd manager && echo *)"
exit 1
fi
# TODO https://github.com/dotcloud/docker/issues/734 (docker inspect formatting)
#if command -v docker > /dev/null 2>&1; then
# image="$(docker inspect -f '{{.Image}}' "$cid")"
# if [ "$image" ]; then
# if [ -z "$have_auth" ]; then
# auth="$(docker inspect -f '{{.Author}}' "$image")"
# fi
# if [ -z "$have_desc" ]; then
# desc="$(docker inspect -f '{{.Comment}}' "$image")"
# fi
# fi
#fi
exec "manager/$script" "$cid" "$auth" "$desc"
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
set -e
cid="$1"
auth="$2"
desc="$3"
cat <<-EOF
[Unit]
Description=$desc
Author=$auth
After=docker.service
[Service]
ExecStart=/usr/bin/docker start -a $cid
ExecStop=/usr/bin/docker stop -t 2 $cid
[Install]
WantedBy=local.target
EOF
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
set -e
cid="$1"
auth="$2"
desc="$3"
cat <<-EOF
description "$(echo "$desc" | sed 's/"/\\"/g')"
author "$(echo "$auth" | sed 's/"/\\"/g')"
start on filesystem and started lxc-net and started docker
stop on runlevel [!2345]
respawn
exec /usr/bin/docker start -a "$cid"
EOF
+13
View File
@@ -0,0 +1,13 @@
# /etc/conf.d/docker: config file for /etc/init.d/docker
# where the docker daemon output gets piped
#DOCKER_LOGFILE="/var/log/docker.log"
# where docker's pid get stored
#DOCKER_PIDFILE="/run/docker.pid"
# where the docker daemon itself is run from
#DOCKER_BINARY="/usr/bin/docker"
# any other random options you want to pass to docker
DOCKER_OPTS=""
+31
View File
@@ -0,0 +1,31 @@
#!/sbin/runscript
# Copyright 1999-2013 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: $
DOCKER_LOGFILE=${DOCKER_LOGFILE:-/var/log/${SVCNAME}.log}
DOCKER_PIDFILE=${DOCKER_PIDFILE:-/run/${SVCNAME}.pid}
DOCKER_BINARY=${DOCKER_BINARY:-/usr/bin/docker}
DOCKER_OPTS=${DOCKER_OPTS:-}
start() {
checkpath -f -m 0644 -o root:docker "$DOCKER_LOGFILE"
ebegin "Starting docker daemon"
start-stop-daemon --start --background \
--exec "$DOCKER_BINARY" \
--pidfile "$DOCKER_PIDFILE" \
--stdout "$DOCKER_LOGFILE" \
--stderr "$DOCKER_LOGFILE" \
-- -d -p "$DOCKER_PIDFILE" \
$DOCKER_OPTS
eend $?
}
stop() {
ebegin "Stopping docker daemon"
start-stop-daemon --stop \
--exec "$DOCKER_BINARY" \
--pidfile "$DOCKER_PIDFILE"
eend $?
}
+13
View File
@@ -0,0 +1,13 @@
[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.io
Requires=network.target
After=multi-user.target
[Service]
Type=simple
ExecStartPre=/bin/mount --make-rprivate /
ExecStart=/usr/bin/docker -d
[Install]
WantedBy=multi-user.target
+94
View File
@@ -0,0 +1,94 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: docker
# Required-Start: $syslog $remote_fs
# Required-Stop: $syslog $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Create lightweight, portable, self-sufficient containers.
# Description:
# Docker is an open-source project to easily create lightweight, portable,
# self-sufficient containers from any application. The same container that a
# developer builds and tests on a laptop can run at scale, in production, on
# VMs, bare metal, OpenStack clusters, public clouds and more.
### END INIT INFO
BASE=$(basename $0)
DOCKER=/usr/bin/$BASE
DOCKER_PIDFILE=/var/run/$BASE.pid
DOCKER_OPTS=
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
# Get lsb functions
. /lib/lsb/init-functions
if [ -f /etc/default/$BASE ]; then
. /etc/default/$BASE
fi
if [ "$1" = start ] && which initctl >/dev/null && initctl version | grep -q upstart; then
exit 1
fi
# Check docker is present
if [ ! -x $DOCKER ]; then
log_failure_msg "$DOCKER not present or not executable"
exit 1
fi
fail_unless_root() {
if [ "$(id -u)" != '0' ]; then
log_failure_msg "Docker must be run as root"
exit 1
fi
}
case "$1" in
start)
fail_unless_root
log_begin_msg "Starting Docker: $BASE"
mount | grep cgroup >/dev/null || mount -t cgroup none /sys/fs/cgroup 2>/dev/null
start-stop-daemon --start --background \
--exec "$DOCKER" \
--pidfile "$DOCKER_PIDFILE" \
-- -d -p "$DOCKER_PIDFILE" \
$DOCKER_OPTS
log_end_msg $?
;;
stop)
fail_unless_root
log_begin_msg "Stopping Docker: $BASE"
start-stop-daemon --stop \
--pidfile "$DOCKER_PIDFILE"
log_end_msg $?
;;
restart)
fail_unless_root
docker_pid=`cat "$DOCKER_PIDFILE" 2>/dev/null`
[ -n "$docker_pid" ] \
&& ps -p $docker_pid > /dev/null 2>&1 \
&& $0 stop
$0 start
;;
force-reload)
fail_unless_root
$0 restart
;;
status)
status_of_proc -p "$DOCKER_PIDFILE" "$DOCKER" docker
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
exit 0
+10
View File
@@ -0,0 +1,10 @@
description "Docker daemon"
start on filesystem and started lxc-net
stop on runlevel [!2345]
respawn
script
/usr/bin/docker -d
end script
+1 -1
View File
@@ -62,6 +62,6 @@ mknod -m 666 ${DEV}/full c 1 7
mknod -m 600 ${DEV}/initctl p
mknod -m 666 ${DEV}/ptmx c 5 2
tar -C $ROOTFS -c . | docker import - archlinux
tar --numeric-owner -C $ROOTFS -c . | docker import - archlinux
docker run -i -t archlinux echo Success.
rm -rf $ROOTFS
+1 -1
View File
@@ -35,5 +35,5 @@ do
cp -a /dev/$X dev
done
tar -cf- . | docker import - busybox
tar --numeric-owner -cf- . | docker import - busybox
docker run -i -u root busybox /bin/echo Success.
-83
View File
@@ -1,83 +0,0 @@
#!/bin/bash
set -e
# these should match the names found at http://www.debian.org/releases/
stableSuite='wheezy'
testingSuite='jessie'
unstableSuite='sid'
variant='minbase'
include='iproute,iputils-ping'
repo="$1"
suite="$2"
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
if [ ! "$repo" ] || [ ! "$suite" ]; then
echo >&2 "usage: $0 repo suite [mirror]"
echo >&2
echo >&2 " ie: $0 tianon/debian squeeze"
echo >&2 " $0 tianon/debian squeeze http://ftp.uk.debian.org/debian/"
echo >&2
echo >&2 " ie: $0 tianon/ubuntu precise"
echo >&2 " $0 tianon/ubuntu precise http://mirrors.melbourne.co.uk/ubuntu/"
echo >&2
exit 1
fi
target="/tmp/docker-rootfs-debian-$suite-$$-$RANDOM"
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
returnTo="$(pwd -P)"
set -x
# bootstrap
mkdir -p "$target"
sudo debootstrap --verbose --variant="$variant" --include="$include" "$suite" "$target" "$mirror"
cd "$target"
# prevent init scripts from running during install/update
# policy-rc.d (for most scripts)
echo $'#!/bin/sh\nexit 101' | sudo tee usr/sbin/policy-rc.d > /dev/null
sudo chmod +x usr/sbin/policy-rc.d
# initctl (for some pesky upstart scripts)
sudo chroot . dpkg-divert --local --rename --add /sbin/initctl
sudo ln -sf /bin/true sbin/initctl
# see https://github.com/dotcloud/docker/issues/446#issuecomment-16953173
# shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB)
sudo chroot . apt-get clean
# while we're at it, apt is unnecessarily slow inside containers
# this forces dpkg not to call sync() after package extraction and speeds up install
# the benefit is huge on spinning disks, and the penalty is nonexistent on SSD or decent server virtualization
echo 'force-unsafe-io' | sudo tee etc/dpkg/dpkg.cfg.d/02apt-speedup > /dev/null
# we want to effectively run "apt-get clean" after every install to keep images small
echo 'DPkg::Post-Invoke {"/bin/rm -f /var/cache/apt/archives/*.deb || true";};' | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null
# helpful undo lines for each the above tweaks (for lack of a better home to keep track of them):
# rm /usr/sbin/policy-rc.d
# rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl
# rm /etc/dpkg/dpkg.cfg.d/02apt-speedup
# rm /etc/apt/apt.conf.d/no-cache
# create the image (and tag $repo:$suite)
sudo tar -c . | docker import - $repo $suite
# test the image
docker run -i -t $repo:$suite echo success
if [ "$suite" = "$stableSuite" -o "$suite" = 'stable' ]; then
# tag latest
docker tag $repo:$suite $repo latest
# tag the specific debian release version
ver=$(docker run $repo:$suite cat /etc/debian_version)
docker tag $repo:$suite $repo $ver
fi
# cleanup
cd "$returnTo"
sudo rm -rf "$target"
+233
View File
@@ -0,0 +1,233 @@
#!/bin/bash
set -e
variant='minbase'
include='iproute,iputils-ping'
arch='amd64' # intentionally undocumented for now
skipDetection=
strictDebootstrap=
justTar=
usage() {
echo >&2
echo >&2 "usage: $0 [options] repo suite [mirror]"
echo >&2
echo >&2 'options: (not recommended)'
echo >&2 " -p set an http_proxy for debootstrap"
echo >&2 " -v $variant # change default debootstrap variant"
echo >&2 " -i $include # change default package includes"
echo >&2 " -d # strict debootstrap (do not apply any docker-specific tweaks)"
echo >&2 " -s # skip version detection and tagging (ie, precise also tagged as 12.04)"
echo >&2 " # note that this will also skip adding universe and/or security/updates to sources.list"
echo >&2 " -t # just create a tarball, especially for dockerbrew (uses repo as tarball name)"
echo >&2
echo >&2 " ie: $0 username/debian squeeze"
echo >&2 " $0 username/debian squeeze http://ftp.uk.debian.org/debian/"
echo >&2
echo >&2 " ie: $0 username/ubuntu precise"
echo >&2 " $0 username/ubuntu precise http://mirrors.melbourne.co.uk/ubuntu/"
echo >&2
echo >&2 " ie: $0 -t precise.tar.bz2 precise"
echo >&2 " $0 -t wheezy.tgz wheezy"
echo >&2 " $0 -t wheezy-uk.tar.xz wheezy http://ftp.uk.debian.org/debian/"
echo >&2
}
# these should match the names found at http://www.debian.org/releases/
debianStable=wheezy
debianUnstable=sid
# this should match the name found at http://releases.ubuntu.com/
ubuntuLatestLTS=precise
while getopts v:i:a:p:dst name; do
case "$name" in
p)
http_proxy="$OPTARG"
;;
v)
variant="$OPTARG"
;;
i)
include="$OPTARG"
;;
a)
arch="$OPTARG"
;;
d)
strictDebootstrap=1
;;
s)
skipDetection=1
;;
t)
justTar=1
;;
?)
usage
exit 0
;;
esac
done
shift $(($OPTIND - 1))
repo="$1"
suite="$2"
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
if [ ! "$repo" ] || [ ! "$suite" ]; then
usage
exit 1
fi
# some rudimentary detection for whether we need to "sudo" our docker calls
docker=''
if docker version > /dev/null 2>&1; then
docker='docker'
elif sudo docker version > /dev/null 2>&1; then
docker='sudo docker'
elif command -v docker > /dev/null 2>&1; then
docker='docker'
else
echo >&2 "warning: either docker isn't installed, or your current user cannot run it;"
echo >&2 " this script is not likely to work as expected"
sleep 3
docker='docker' # give us a command-not-found later
fi
# make sure we have an absolute path to our final tarball so we can still reference it properly after we change directory
if [ "$justTar" ]; then
if [ ! -d "$(dirname "$repo")" ]; then
echo >&2 "error: $(dirname "$repo") does not exist"
exit 1
fi
repo="$(cd "$(dirname "$repo")" && pwd -P)/$(basename "$repo")"
fi
# will be filled in later, if [ -z "$skipDetection" ]
lsbDist=''
target="/tmp/docker-rootfs-debootstrap-$suite-$$-$RANDOM"
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
returnTo="$(pwd -P)"
set -x
# bootstrap
mkdir -p "$target"
sudo http_proxy=$http_proxy debootstrap --verbose --variant="$variant" --include="$include" --arch="$arch" "$suite" "$target" "$mirror"
cd "$target"
if [ -z "$strictDebootstrap" ]; then
# prevent init scripts from running during install/update
# policy-rc.d (for most scripts)
echo $'#!/bin/sh\nexit 101' | sudo tee usr/sbin/policy-rc.d > /dev/null
sudo chmod +x usr/sbin/policy-rc.d
# initctl (for some pesky upstart scripts)
sudo chroot . dpkg-divert --local --rename --add /sbin/initctl
sudo ln -sf /bin/true sbin/initctl
# see https://github.com/dotcloud/docker/issues/446#issuecomment-16953173
# shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB)
sudo chroot . apt-get clean
# while we're at it, apt is unnecessarily slow inside containers
# this forces dpkg not to call sync() after package extraction and speeds up install
# the benefit is huge on spinning disks, and the penalty is nonexistent on SSD or decent server virtualization
echo 'force-unsafe-io' | sudo tee etc/dpkg/dpkg.cfg.d/02apt-speedup > /dev/null
# we want to effectively run "apt-get clean" after every install to keep images small
echo 'DPkg::Post-Invoke {"/bin/rm -f /var/cache/apt/archives/*.deb || true";};' | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null
# helpful undo lines for each the above tweaks (for lack of a better home to keep track of them):
# rm /usr/sbin/policy-rc.d
# rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl
# rm /etc/dpkg/dpkg.cfg.d/02apt-speedup
# rm /etc/apt/apt.conf.d/no-cache
if [ -z "$skipDetection" ]; then
# see also rudimentary platform detection in hack/install.sh
lsbDist=''
if [ -r etc/lsb-release ]; then
lsbDist="$(. etc/lsb-release && echo "$DISTRIB_ID")"
fi
if [ -z "$lsbDist" ] && [ -r etc/debian_version ]; then
lsbDist='Debian'
fi
case "$lsbDist" in
Debian)
# add the updates and security repositories
if [ "$suite" != "$debianUnstable" -a "$suite" != 'unstable' ]; then
# ${suite}-updates only applies to non-unstable
sudo sed -i "p; s/ $suite main$/ ${suite}-updates main/" etc/apt/sources.list
# same for security updates
echo "deb http://security.debian.org/ $suite/updates main" | sudo tee -a etc/apt/sources.list > /dev/null
fi
;;
Ubuntu)
# add the universe, updates, and security repositories
sudo sed -i "
s/ $suite main$/ $suite main universe/; p;
s/ $suite main/ ${suite}-updates main/; p;
s/ $suite-updates main/ ${suite}-security main/
" etc/apt/sources.list
;;
esac
fi
fi
if [ "$justTar" ]; then
# create the tarball file so it has the right permissions (ie, not root)
touch "$repo"
# fill the tarball
sudo tar --numeric-owner -caf "$repo" .
else
# create the image (and tag $repo:$suite)
sudo tar --numeric-owner -c . | $docker import - $repo $suite
# test the image
$docker run -i -t $repo:$suite echo success
if [ -z "$skipDetection" ]; then
case "$lsbDist" in
Debian)
if [ "$suite" = "$debianStable" -o "$suite" = 'stable' ] && [ -r etc/debian_version ]; then
# tag latest
$docker tag $repo:$suite $repo latest
if [ -r etc/debian_version ]; then
# tag the specific debian release version (which is only reasonable to tag on debian stable)
ver=$(cat etc/debian_version)
$docker tag $repo:$suite $repo $ver
fi
fi
;;
Ubuntu)
if [ "$suite" = "$ubuntuLatestLTS" ]; then
# tag latest
$docker tag $repo:$suite $repo latest
fi
if [ -r etc/lsb-release ]; then
lsbRelease="$(. etc/lsb-release && echo "$DISTRIB_RELEASE")"
if [ "$lsbRelease" ]; then
# tag specific Ubuntu version number, if available (12.04, etc.)
$docker tag $repo:$suite $repo $lsbRelease
fi
fi
;;
esac
fi
fi
# cleanup
cd "$returnTo"
sudo rm -rf "$target"
+1 -1
View File
@@ -44,6 +44,6 @@ do
done
chmod 0755 $ROOTFS # See #486
tar -cf- . | docker import - docker-ut
tar --numeric-owner -cf- . | docker import - docker-ut
docker run -i -u root docker-ut /bin/echo Success.
rm -rf $ROOTFS
+2
View File
@@ -20,3 +20,5 @@ highlight link dockerfileString String
syntax match dockerfileComment "\v^\s*#.*$"
highlight link dockerfileComment Comment
set commentstring=#\ %s
-850
View File
@@ -1,850 +0,0 @@
package devmapper
import (
"encoding/json"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"sync"
"syscall"
"time"
)
var (
DefaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024
DefaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024
DefaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024
)
type DevInfo struct {
Hash string `json:"-"`
DeviceId int `json:"device_id"`
Size uint64 `json:"size"`
TransactionId uint64 `json:"transaction_id"`
Initialized bool `json:"initialized"`
devices *DeviceSet `json:"-"`
}
type MetaData struct {
Devices map[string]*DevInfo `json:devices`
}
type DeviceSet struct {
MetaData
sync.Mutex
initialized bool
root string
devicePrefix string
TransactionId uint64
NewTransactionId uint64
nextFreeDevice int
activeMounts map[string]int
}
type DiskUsage struct {
Used uint64
Total uint64
}
type Status struct {
PoolName string
DataLoopback string
MetadataLoopback string
Data DiskUsage
Metadata DiskUsage
}
func getDevName(name string) string {
return "/dev/mapper/" + name
}
func (info *DevInfo) Name() string {
hash := info.Hash
if hash == "" {
hash = "base"
}
return fmt.Sprintf("%s-%s", info.devices.devicePrefix, hash)
}
func (info *DevInfo) DevName() string {
return getDevName(info.Name())
}
func (devices *DeviceSet) loopbackDir() string {
return path.Join(devices.root, "devicemapper")
}
func (devices *DeviceSet) jsonFile() string {
return path.Join(devices.loopbackDir(), "json")
}
func (devices *DeviceSet) getPoolName() string {
return devices.devicePrefix + "-pool"
}
func (devices *DeviceSet) getPoolDevName() string {
return getDevName(devices.getPoolName())
}
func (devices *DeviceSet) hasImage(name string) bool {
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)
_, err := os.Stat(filename)
return err == nil
}
// ensureImage creates a sparse file of <size> bytes at the path
// <root>/devicemapper/<name>.
// If the file already exists, it does nothing.
// Either way it returns the full path.
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) {
return "", err
}
if _, err := os.Stat(filename); err != nil {
if !os.IsNotExist(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)
if err != nil {
return "", err
}
if err = file.Truncate(size); err != nil {
return "", err
}
}
return filename, nil
}
func (devices *DeviceSet) allocateDeviceId() int {
// TODO: Add smarter reuse of deleted devices
id := devices.nextFreeDevice
devices.nextFreeDevice = devices.nextFreeDevice + 1
return id
}
func (devices *DeviceSet) allocateTransactionId() uint64 {
devices.NewTransactionId = devices.NewTransactionId + 1
return devices.NewTransactionId
}
func (devices *DeviceSet) saveMetadata() error {
jsonData, err := json.Marshal(devices.MetaData)
if err != nil {
return fmt.Errorf("Error encoding metaadata to json: %s", err)
}
tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json")
if err != nil {
return fmt.Errorf("Error creating metadata file: %s", err)
}
n, err := tmpFile.Write(jsonData)
if err != nil {
return fmt.Errorf("Error writing metadata to %s: %s", tmpFile.Name(), err)
}
if n < len(jsonData) {
return io.ErrShortWrite
}
if err := tmpFile.Sync(); err != nil {
return fmt.Errorf("Error syncing metadata file %s: %s", tmpFile.Name(), err)
}
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 {
return fmt.Errorf("Error committing metadata file", err)
}
if devices.NewTransactionId != devices.TransactionId {
if err = setTransactionId(devices.getPoolDevName(), devices.TransactionId, devices.NewTransactionId); err != nil {
return fmt.Errorf("Error setting devmapper transition ID: %s", err)
}
devices.TransactionId = devices.NewTransactionId
}
return nil
}
func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*DevInfo, error) {
utils.Debugf("registerDevice(%v, %v)", id, hash)
info := &DevInfo{
Hash: hash,
DeviceId: id,
Size: size,
TransactionId: devices.allocateTransactionId(),
Initialized: false,
devices: devices,
}
devices.Devices[hash] = info
if err := devices.saveMetadata(); err != nil {
// Try to remove unused device
delete(devices.Devices, hash)
return nil, err
}
return info, nil
}
func (devices *DeviceSet) activateDeviceIfNeeded(hash string) error {
utils.Debugf("activateDeviceIfNeeded(%v)", hash)
info := devices.Devices[hash]
if info == nil {
return fmt.Errorf("Unknown device %s", hash)
}
if devinfo, _ := getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 {
return nil
}
return activateDevice(devices.getPoolDevName(), info.Name(), info.DeviceId, info.Size)
}
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()
if err != nil {
err = exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname).Run()
}
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) loadMetaData() error {
utils.Debugf("loadMetadata()")
defer utils.Debugf("loadMetadata END")
_, _, _, params, err := getStatus(devices.getPoolName())
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
devices.NewTransactionId = devices.TransactionId
jsonData, err := ioutil.ReadFile(devices.jsonFile())
if err != nil && !os.IsNotExist(err) {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
devices.MetaData.Devices = make(map[string]*DevInfo)
if jsonData != nil {
if err := json.Unmarshal(jsonData, &devices.MetaData); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
for hash, d := range devices.Devices {
d.Hash = hash
d.devices = devices
if d.DeviceId >= devices.nextFreeDevice {
devices.nextFreeDevice = d.DeviceId + 1
}
// If the transaction id is larger than the actual one we lost the device due to some crash
if d.TransactionId > devices.TransactionId {
utils.Debugf("Removing lost device %s with id %d", hash, d.TransactionId)
delete(devices.Devices, hash)
}
}
return nil
}
func (devices *DeviceSet) setupBaseImage() error {
oldInfo := devices.Devices[""]
if oldInfo != nil && oldInfo.Initialized {
return nil
}
if oldInfo != nil && !oldInfo.Initialized {
utils.Debugf("Removing uninitialized base image")
if err := devices.removeDevice(""); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
utils.Debugf("Initializing base device-manager snapshot")
id := devices.allocateDeviceId()
// Create initial device
if err := createDevice(devices.getPoolDevName(), id); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize)
info, err := devices.registerDevice(id, "", DefaultBaseFsSize)
if err != nil {
_ = deleteDevice(devices.getPoolDevName(), id)
utils.Debugf("\n--->Err: %s\n", err)
return err
}
utils.Debugf("Creating filesystem on base device-manager snapshot")
if err = devices.activateDeviceIfNeeded(""); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if err := devices.createFilesystem(info); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
info.Initialized = true
if err = devices.saveMetadata(); err != nil {
info.Initialized = false
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return nil
}
func setCloseOnExec(name string) {
fileInfos, _ := ioutil.ReadDir("/proc/self/fd")
if fileInfos != nil {
for _, i := range fileInfos {
link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name()))
if link == name {
fd, err := strconv.Atoi(i.Name())
if err == nil {
syscall.CloseOnExec(fd)
}
}
}
}
}
func (devices *DeviceSet) log(level int, file string, line int, dmError int, message string) {
if level >= 7 {
return // Ignore _LOG_DEBUG
}
utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
}
func major(device uint64) uint64 {
return (device >> 8) & 0xfff
}
func minor(device uint64) uint64 {
return (device & 0xff) | ((device >> 12) & 0xfff00)
}
func (devices *DeviceSet) initDevmapper() error {
logInit(devices)
// Make sure the sparse images exist in <root>/devicemapper/data and
// <root>/devicemapper/metadata
createdLoopback := !devices.hasImage("data") || !devices.hasImage("metadata")
data, err := devices.ensureImage("data", DefaultDataLoopbackSize)
if err != nil {
utils.Debugf("Error device ensureImage (data): %s\n", err)
return err
}
metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize)
if err != nil {
utils.Debugf("Error device ensureImage (metadata): %s\n", err)
return err
}
// Set the device prefix from the device id and inode of the docker root dir
st, err := os.Stat(devices.root)
if err != nil {
return fmt.Errorf("Error looking up dir %s: %s", devices.root, err)
}
sysSt := st.Sys().(*syscall.Stat_t)
// "reg-" stands for "regular file".
// In the future we might use "dev-" for "device file", etc.
// docker-maj,min[-inode] stands for:
// - Managed by docker
// - The target of this device is at major <maj> and minor <min>
// - If <inode> is defined, use that file inside the device as a loopback image. Otherwise use the device itself.
devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino)
utils.Debugf("Generated prefix: %s", devices.devicePrefix)
// Check for the existence of the device <prefix>-pool
utils.Debugf("Checking for existence of the pool '%s'", devices.getPoolName())
info, err := getInfo(devices.getPoolName())
if info == nil {
utils.Debugf("Error device getInfo: %s", err)
return err
}
// It seems libdevmapper opens this without O_CLOEXEC, and go exec will not close files
// that are not Close-on-exec, and lxc-start will die if it inherits any unexpected files,
// so we add this badhack to make sure it closes itself
setCloseOnExec("/dev/mapper/control")
// If the pool doesn't exist, create it
if info.Exists == 0 {
utils.Debugf("Pool doesn't exist. Creating it.")
dataFile, err := AttachLoopDevice(data)
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
defer dataFile.Close()
metadataFile, err := AttachLoopDevice(metadata)
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
defer metadataFile.Close()
if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
// If we didn't just create the data or metadata image, we need to
// load the metadata from the existing file.
if !createdLoopback {
if err = devices.loadMetaData(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
// Setup the base image
if err := devices.setupBaseImage(); err != nil {
utils.Debugf("Error device setupBaseImage: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
utils.Debugf("Error init: %s\n", err)
return err
}
if devices.Devices[hash] != nil {
return fmt.Errorf("hash %s already exists", hash)
}
baseInfo := devices.Devices[baseHash]
if baseInfo == nil {
return fmt.Errorf("Error adding device for '%s': can't find device for parent '%s'", hash, baseHash)
}
deviceId := devices.allocateDeviceId()
if err := devices.createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil {
utils.Debugf("Error creating snap device: %s\n", err)
return err
}
if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size); err != nil {
deleteDevice(devices.getPoolDevName(), deviceId)
utils.Debugf("Error registering device: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) removeDevice(hash string) error {
info := devices.Devices[hash]
if info == nil {
return fmt.Errorf("hash %s doesn't exists", hash)
}
devinfo, _ := getInfo(info.Name())
if devinfo != nil && devinfo.Exists != 0 {
if err := removeDevice(info.Name()); err != nil {
utils.Debugf("Error removing device: %s\n", err)
return err
}
}
if info.Initialized {
info.Initialized = false
if err := devices.saveMetadata(); err != nil {
utils.Debugf("Error saving meta data: %s\n", err)
return err
}
}
if err := deleteDevice(devices.getPoolDevName(), info.DeviceId); err != nil {
utils.Debugf("Error deleting device: %s\n", err)
return err
}
devices.allocateTransactionId()
delete(devices.Devices, info.Hash)
if err := devices.saveMetadata(); err != nil {
devices.Devices[info.Hash] = info
utils.Debugf("Error saving meta data: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) RemoveDevice(hash string) error {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return devices.removeDevice(hash)
}
func (devices *DeviceSet) deactivateDevice(hash string) error {
utils.Debugf("[devmapper] deactivateDevice(%s)", hash)
defer utils.Debugf("[devmapper] deactivateDevice END")
var devname string
// FIXME: shouldn't we just register the pool into devices?
devname, err := devices.byHash(hash)
if err != nil {
return err
}
devinfo, err := getInfo(devname)
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if devinfo.Exists != 0 {
if err := removeDevice(devname); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if err := devices.waitRemove(hash); err != nil {
return err
}
}
return nil
}
// waitRemove blocks until either:
// a) the device registered at <device_set_prefix>-<hash> is removed,
// or b) the 1 second timeout expires.
func (devices *DeviceSet) waitRemove(hash string) error {
utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, hash)
defer utils.Debugf("[deviceset %s] waitRemove END", devices.devicePrefix, hash)
devname, err := devices.byHash(hash)
if err != nil {
return err
}
i := 0
for ; i < 1000; i += 1 {
devinfo, err := getInfo(devname)
if err != nil {
// If there is an error we assume the device doesn't exist.
// 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 devinfo.Exists == 0 {
break
}
time.Sleep(1 * time.Millisecond)
}
if i == 1000 {
return fmt.Errorf("Timeout while waiting for device %s to be removed", devname)
}
return nil
}
// waitClose blocks until either:
// a) the device registered at <device_set_prefix>-<hash> is closed,
// or b) the 1 second timeout expires.
func (devices *DeviceSet) waitClose(hash string) error {
devname, err := devices.byHash(hash)
if err != nil {
return err
}
i := 0
for ; i < 1000; i += 1 {
devinfo, err := getInfo(devname)
if err != nil {
return err
}
utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount)
if devinfo.OpenCount == 0 {
break
}
time.Sleep(1 * time.Millisecond)
}
if i == 1000 {
return fmt.Errorf("Timeout while waiting for device %s to close", devname)
}
return nil
}
// byHash is a hack to allow looking up the deviceset's pool by the hash "pool".
// FIXME: it seems probably cleaner to register the pool in devices.Devices,
// but I am afraid of arcane implications deep in the devicemapper code,
// so this will do.
func (devices *DeviceSet) byHash(hash string) (devname string, err error) {
if hash == "pool" {
return devices.getPoolDevName(), nil
}
info := devices.Devices[hash]
if info == nil {
return "", fmt.Errorf("hash %s doesn't exists", hash)
}
return info.Name(), nil
}
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()
if !devices.initialized {
return nil
}
for path, count := range devices.activeMounts {
for i := count; i > 0; i-- {
if err := syscall.Unmount(path, 0); err != nil {
utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err)
}
}
delete(devices.activeMounts, path)
}
for _, d := range devices.Devices {
if err := devices.waitClose(d.Hash); err != nil {
utils.Errorf("Warning: error waiting for device %s to unmount: %s\n", d.Hash, err)
}
if err := devices.deactivateDevice(d.Hash); err != nil {
utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err)
}
}
pool := devices.getPoolDevName()
if devinfo, err := getInfo(pool); err == nil && devinfo.Exists != 0 {
if err := devices.deactivateDevice("pool"); err != nil {
utils.Debugf("Shutdown deactivate %s , error: %s\n", pool, err)
}
}
return nil
}
func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
return fmt.Errorf("Error initializing devmapper: %s", err)
}
if err := devices.activateDeviceIfNeeded(hash); err != nil {
return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
}
info := devices.Devices[hash]
var flags uintptr = syscall.MS_MGC_VAL
if readOnly {
flags = flags | syscall.MS_RDONLY
}
err := syscall.Mount(info.DevName(), path, "ext4", flags, "discard")
if err != nil && err == syscall.EINVAL {
err = syscall.Mount(info.DevName(), path, "ext4", flags, "")
}
if err != nil {
return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
}
count := devices.activeMounts[path]
devices.activeMounts[path] = count + 1
return nil
}
func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) error {
utils.Debugf("[devmapper] UnmountDevice(hash=%s path=%s)", hash, path)
defer utils.Debugf("[devmapper] UnmountDevice END")
devices.Lock()
defer devices.Unlock()
utils.Debugf("[devmapper] Unmount(%s)", path)
if err := syscall.Unmount(path, 0); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
utils.Debugf("[devmapper] Unmount done")
// Wait for the unmount to be effective,
// by watching the value of Info.OpenCount for the device
if err := devices.waitClose(hash); err != nil {
return err
}
if count := devices.activeMounts[path]; count > 1 {
devices.activeMounts[path] = count - 1
} else {
delete(devices.activeMounts, path)
}
if deactivate {
devices.deactivateDevice(hash)
}
return nil
}
func (devices *DeviceSet) HasDevice(hash string) bool {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
return false
}
return devices.Devices[hash] != nil
}
func (devices *DeviceSet) HasInitializedDevice(hash string) bool {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
return false
}
info := devices.Devices[hash]
return info != nil && info.Initialized
}
func (devices *DeviceSet) HasActivatedDevice(hash string) bool {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
return false
}
info := devices.Devices[hash]
if info == nil {
return false
}
devinfo, _ := getInfo(info.Name())
return devinfo != nil && devinfo.Exists != 0
}
func (devices *DeviceSet) SetInitialized(hash string) error {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
info := devices.Devices[hash]
if info == nil {
return fmt.Errorf("Unknown device %s", hash)
}
info.Initialized = true
if err := devices.saveMetadata(); err != nil {
info.Initialized = false
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) Status() *Status {
devices.Lock()
defer devices.Unlock()
status := &Status{}
if err := devices.ensureInit(); err != nil {
return status
}
status.PoolName = devices.getPoolName()
status.DataLoopback = path.Join(devices.loopbackDir(), "data")
status.MetadataLoopback = path.Join(devices.loopbackDir(), "metadata")
_, totalSizeInSectors, _, params, err := getStatus(devices.getPoolName())
if err == nil {
var transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64
if _, err := fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal); err == nil {
// Convert from blocks to bytes
blockSizeInSectors := totalSizeInSectors / dataTotal
status.Data.Used = dataUsed * blockSizeInSectors * 512
status.Data.Total = dataTotal * blockSizeInSectors * 512
// metadata blocks are always 4k
status.Metadata.Used = metadataUsed * 4096
status.Metadata.Total = metadataTotal * 4096
}
}
return status
}
func (devices *DeviceSet) ensureInit() error {
if !devices.initialized {
devices.initialized = true
if err := devices.initDevmapper(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
return nil
}
func NewDeviceSet(root string) *DeviceSet {
SetDevDir("/dev")
return &DeviceSet{
initialized: false,
root: root,
MetaData: MetaData{Devices: make(map[string]*DevInfo)},
activeMounts: make(map[string]int),
}
}
-709
View File
@@ -1,709 +0,0 @@
package devmapper
/*
#cgo LDFLAGS: -L. -ldevmapper
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libdevmapper.h>
#include <linux/loop.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <errno.h>
#ifndef LOOP_CTL_GET_FREE
#define LOOP_CTL_GET_FREE 0x4C82
#endif
// FIXME: this could easily be rewritten in go
char* attach_loop_device(const char *filename, int *loop_fd_out)
{
struct loop_info64 loopinfo = {0};
struct stat st;
char buf[64];
int i, loop_fd, fd, start_index;
char* loopname;
*loop_fd_out = -1;
start_index = 0;
fd = open("/dev/loop-control", O_RDONLY);
if (fd >= 0) {
start_index = ioctl(fd, LOOP_CTL_GET_FREE);
close(fd);
if (start_index < 0)
start_index = 0;
}
fd = open(filename, O_RDWR);
if (fd < 0) {
perror("open");
return NULL;
}
loop_fd = -1;
for (i = start_index ; loop_fd < 0 ; i++ ) {
if (sprintf(buf, "/dev/loop%d", i) < 0) {
close(fd);
return NULL;
}
if (stat(buf, &st)) {
if (!S_ISBLK(st.st_mode)) {
fprintf(stderr, "[error] Loopback device %s is not a block device.\n", buf);
} else if (errno == ENOENT) {
fprintf(stderr, "[error] There are no more loopback device available.\n");
} else {
fprintf(stderr, "[error] Unkown error trying to stat the loopback device %s (errno: %d).\n", buf, errno);
}
close(fd);
return NULL;
}
loop_fd = open(buf, O_RDWR);
if (loop_fd < 0 && errno == ENOENT) {
fprintf(stderr, "[error] The loopback device %s does not exists.\n", buf);
close(fd);
return NULL;
} else if (loop_fd < 0) {
fprintf(stderr, "[error] Unkown error openning the loopback device %s. (errno: %d)\n", buf, errno);
continue;
}
if (ioctl(loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) {
int errsv = errno;
close(loop_fd);
loop_fd = -1;
if (errsv != EBUSY) {
close(fd);
fprintf(stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv));
return NULL;
}
continue;
}
close(fd);
strncpy((char*)loopinfo.lo_file_name, buf, LO_NAME_SIZE);
loopinfo.lo_offset = 0;
loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR;
if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) {
perror("ioctl LOOP_SET_STATUS64");
if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) {
perror("ioctl LOOP_CLR_FD");
}
close(loop_fd);
fprintf (stderr, "cannot set up loopback device info");
return (NULL);
}
loopname = strdup(buf);
if (loopname == NULL) {
close(loop_fd);
return (NULL);
}
*loop_fd_out = loop_fd;
return (loopname);
}
return (NULL);
}
static int64_t get_block_size(int fd)
{
uint64_t size;
if (ioctl(fd, BLKGETSIZE64, &size) == -1)
return -1;
return ((int64_t)size);
}
extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str);
static void
log_cb(int level, const char *file, int line,
int dm_errno_or_class, const char *f, ...)
{
char buffer[256];
va_list ap;
va_start(ap, f);
vsnprintf(buffer, 256, f, ap);
va_end(ap);
DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer);
}
static void
log_with_errno_init ()
{
dm_log_with_errno_init(log_cb);
}
*/
import "C"
import (
"errors"
"fmt"
"github.com/dotcloud/docker/utils"
"os"
"runtime"
"syscall"
"unsafe"
)
type DevmapperLogger interface {
log(level int, file string, line int, dmError int, message string)
}
const (
DeviceCreate TaskType = iota
DeviceReload
DeviceRemove
DeviceRemoveAll
DeviceSuspend
DeviceResume
DeviceInfo
DeviceDeps
DeviceRename
DeviceVersion
DeviceStatus
DeviceTable
DeviceWaitevent
DeviceList
DeviceClear
DeviceMknodes
DeviceListVersions
DeviceTargetMsg
DeviceSetGeometry
)
const (
AddNodeOnResume AddNodeType = iota
AddNodeOnCreate
)
var (
ErrTaskRun = errors.New("dm_task_run failed")
ErrTaskSetName = errors.New("dm_task_set_name failed")
ErrTaskSetMessage = errors.New("dm_task_set_message failed")
ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed")
ErrTaskSetRO = errors.New("dm_task_set_ro failed")
ErrTaskAddTarget = errors.New("dm_task_add_target failed")
ErrGetDriverVersion = errors.New("dm_task_get_driver_version failed")
ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
ErrGetBlockSize = errors.New("Can't get block size")
ErrUdevWait = errors.New("wait on udev cookie failed")
ErrSetDevDir = errors.New("dm_set_dev_dir failed")
ErrGetLibraryVersion = errors.New("dm_get_library_version failed")
ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove")
ErrRunRemoveDevice = errors.New("running removeDevice failed")
)
type (
Task struct {
unmanaged *C.struct_dm_task
}
Info struct {
Exists int
Suspended int
LiveTable int
InactiveTable int
OpenCount int32
EventNr uint32
Major uint32
Minor uint32
ReadOnly int
TargetCount int32
}
TaskType int
AddNodeType int
)
func (t *Task) destroy() {
if t != nil {
C.dm_task_destroy(t.unmanaged)
runtime.SetFinalizer(t, nil)
}
}
func TaskCreate(tasktype TaskType) *Task {
c_task := C.dm_task_create(C.int(tasktype))
if c_task == nil {
return nil
}
task := &Task{unmanaged: c_task}
runtime.SetFinalizer(task, (*Task).destroy)
return task
}
func (t *Task) Run() error {
if res := C.dm_task_run(t.unmanaged); res != 1 {
return ErrTaskRun
}
return nil
}
func (t *Task) SetName(name string) error {
c_name := C.CString(name)
defer free(c_name)
if res := C.dm_task_set_name(t.unmanaged, c_name); res != 1 {
return ErrTaskSetName
}
return nil
}
func (t *Task) SetMessage(message string) error {
c_message := C.CString(message)
defer free(c_message)
if res := C.dm_task_set_message(t.unmanaged, c_message); res != 1 {
return ErrTaskSetMessage
}
return nil
}
func (t *Task) SetSector(sector uint64) error {
if res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)); res != 1 {
return ErrTaskSetAddNode
}
return nil
}
func (t *Task) SetCookie(cookie *uint32, flags uint16) error {
c_cookie := C.uint32_t(*cookie)
if res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)); res != 1 {
return ErrTaskSetAddNode
}
*cookie = uint32(c_cookie)
return nil
}
func (t *Task) SetAddNode(add_node AddNodeType) error {
if res := C.dm_task_set_add_node(t.unmanaged, C.dm_add_node_t(add_node)); res != 1 {
return ErrTaskSetAddNode
}
return nil
}
func (t *Task) SetRo() error {
if res := C.dm_task_set_ro(t.unmanaged); res != 1 {
return ErrTaskSetRO
}
return nil
}
func (t *Task) AddTarget(start uint64, size uint64, ttype string, params string) error {
c_ttype := C.CString(ttype)
defer free(c_ttype)
c_params := C.CString(params)
defer free(c_params)
if res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params); res != 1 {
return ErrTaskAddTarget
}
return nil
}
func (t *Task) GetDriverVersion() (string, error) {
buffer := C.CString(string(make([]byte, 128)))
defer free(buffer)
if res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128); res != 1 {
return "", ErrGetDriverVersion
}
return C.GoString(buffer), nil
}
func (t *Task) GetInfo() (*Info, error) {
c_info := C.struct_dm_info{}
if res := C.dm_task_get_info(t.unmanaged, &c_info); res != 1 {
return nil, ErrGetDriverVersion
}
return &Info{
Exists: int(c_info.exists),
Suspended: int(c_info.suspended),
LiveTable: int(c_info.live_table),
InactiveTable: int(c_info.inactive_table),
OpenCount: int32(c_info.open_count),
EventNr: uint32(c_info.event_nr),
Major: uint32(c_info.major),
Minor: uint32(c_info.minor),
ReadOnly: int(c_info.read_only),
TargetCount: int32(c_info.target_count),
}, nil
}
func (t *Task) GetNextTarget(next uintptr) (uintptr, uint64, uint64, string, string) {
var (
c_start, c_length C.uint64_t
c_target_type, c_params *C.char
)
nextp := C.dm_get_next_target(t.unmanaged, unsafe.Pointer(next), &c_start, &c_length, &c_target_type, &c_params)
return uintptr(nextp), uint64(c_start), uint64(c_length), C.GoString(c_target_type), C.GoString(c_params)
}
func AttachLoopDevice(filename string) (*os.File, error) {
c_filename := C.CString(filename)
defer free(c_filename)
var fd C.int
res := C.attach_loop_device(c_filename, &fd)
if res == nil {
if os.Getenv("DEBUG") != "" {
C.perror(C.CString(fmt.Sprintf("[debug] Error attach_loop_device(%s, %d)", filename, int(fd))))
}
return nil, ErrAttachLoopbackDevice
}
defer free(res)
return os.NewFile(uintptr(fd), C.GoString(res)), nil
}
func getBlockSize(fd uintptr) int {
var size uint64
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); err != 0 {
utils.Debugf("Error ioctl: %s", err)
return -1
}
return int(size)
}
func GetBlockDeviceSize(file *os.File) (uint64, error) {
if size := C.get_block_size(C.int(file.Fd())); size == -1 {
return 0, ErrGetBlockSize
} else {
return uint64(size), nil
}
}
func UdevWait(cookie uint32) error {
if res := C.dm_udev_wait(C.uint32_t(cookie)); res != 1 {
utils.Debugf("Failed to wait on udev cookie %d", cookie)
return ErrUdevWait
}
return nil
}
func LogInitVerbose(level int) {
C.dm_log_init_verbose(C.int(level))
}
var dmLogger DevmapperLogger = nil
func logInit(logger DevmapperLogger) {
dmLogger = logger
C.log_with_errno_init()
}
func SetDevDir(dir string) error {
c_dir := C.CString(dir)
defer free(c_dir)
if res := C.dm_set_dev_dir(c_dir); res != 1 {
utils.Debugf("Error dm_set_dev_dir")
return ErrSetDevDir
}
return nil
}
func GetLibraryVersion() (string, error) {
buffer := C.CString(string(make([]byte, 128)))
defer free(buffer)
if res := C.dm_get_library_version(buffer, 128); res != 1 {
return "", ErrGetLibraryVersion
}
return C.GoString(buffer), nil
}
// Useful helper for cleanup
func RemoveDevice(name string) error {
task := TaskCreate(DeviceRemove)
if task == nil {
return ErrCreateRemoveTask
}
if err := task.SetName(name); err != nil {
utils.Debugf("Can't set task name %s", name)
return err
}
if err := task.Run(); err != nil {
return ErrRunRemoveDevice
}
return nil
}
func free(p *C.char) {
C.free(unsafe.Pointer(p))
}
// This is the programmatic example of "dmsetup create"
func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error {
task, err := createTask(DeviceCreate, poolName)
if task == nil {
return err
}
size, err := GetBlockDeviceSize(dataFile)
if err != nil {
return fmt.Errorf("Can't get data size")
}
params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768"
if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
return fmt.Errorf("Can't add target")
}
var cookie uint32 = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate")
}
UdevWait(cookie)
return nil
}
func createTask(t TaskType, name string) (*Task, error) {
task := TaskCreate(t)
if task == nil {
return nil, fmt.Errorf("Can't create task of type %d", int(t))
}
if err := task.SetName(name); err != nil {
return nil, fmt.Errorf("Can't set task name %s", name)
}
return task, nil
}
func getInfo(name string) (*Info, error) {
task, err := createTask(DeviceInfo, name)
if task == nil {
return nil, err
}
if err := task.Run(); err != nil {
return nil, err
}
return task.GetInfo()
}
func getStatus(name string) (uint64, uint64, string, string, error) {
task, err := createTask(DeviceStatus, name)
if task == nil {
utils.Debugf("getStatus: Error createTask: %s", err)
return 0, 0, "", "", err
}
if err := task.Run(); err != nil {
utils.Debugf("getStatus: Error Run: %s", err)
return 0, 0, "", "", err
}
devinfo, err := task.GetInfo()
if err != nil {
utils.Debugf("getStatus: Error GetInfo: %s", err)
return 0, 0, "", "", err
}
if devinfo.Exists == 0 {
utils.Debugf("getStatus: Non existing device %s", name)
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
}
func setTransactionId(poolName string, oldId uint64, newId uint64) error {
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil {
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running setTransactionId")
}
return nil
}
func suspendDevice(name string) error {
task, err := createTask(DeviceSuspend, name)
if task == nil {
return err
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceSuspend")
}
return nil
}
func resumeDevice(name string) error {
task, err := createTask(DeviceResume, name)
if task == nil {
return err
}
var cookie uint32 = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceSuspend")
}
UdevWait(cookie)
return nil
}
func createDevice(poolName string, deviceId int) error {
utils.Debugf("[devmapper] createDevice(poolName=%v, deviceId=%v)", poolName, deviceId)
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil {
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running createDevice")
}
return nil
}
func deleteDevice(poolName string, deviceId int) error {
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil {
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running deleteDevice")
}
return nil
}
func removeDevice(name string) error {
utils.Debugf("[devmapper] removeDevice START")
defer utils.Debugf("[devmapper] removeDevice END")
task, err := createTask(DeviceRemove, name)
if task == nil {
return err
}
if err = task.Run(); err != nil {
return fmt.Errorf("Error running removeDevice")
}
return nil
}
func activateDevice(poolName string, name string, deviceId int, size uint64) error {
task, err := createTask(DeviceCreate, name)
if task == nil {
return err
}
params := fmt.Sprintf("%s %d", poolName, deviceId)
if err := task.AddTarget(0, size/512, "thin", params); err != nil {
return fmt.Errorf("Can't add target")
}
if err := task.SetAddNode(AddNodeOnCreate); err != nil {
return fmt.Errorf("Can't add node")
}
var cookie uint32 = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate")
}
UdevWait(cookie)
return nil
}
func (devices *DeviceSet) createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error {
devinfo, _ := getInfo(baseName)
doSuspend := devinfo != nil && devinfo.Exists != 0
if doSuspend {
if err := suspendDevice(baseName); err != nil {
return err
}
}
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
if doSuspend {
resumeDevice(baseName)
}
return err
}
if err := task.SetSector(0); err != nil {
if doSuspend {
resumeDevice(baseName)
}
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseDeviceId)); err != nil {
if doSuspend {
resumeDevice(baseName)
}
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
if doSuspend {
resumeDevice(baseName)
}
return fmt.Errorf("Error running DeviceCreate")
}
if doSuspend {
if err := resumeDevice(baseName); err != nil {
return err
}
}
return nil
}
-13
View File
@@ -1,13 +0,0 @@
package devmapper
import "C"
// Due to the way cgo works this has to be in a separate file, as devmapper.go has
// definitions in the cgo block, which is incompatible with using "//export"
//export DevmapperLogCallback
func DevmapperLogCallback(level C.int, file *C.char, line C.int, dm_errno_or_class C.int, message *C.char) {
if dmLogger != nil {
dmLogger.log(int(level), C.GoString(file), int(line), int(dm_errno_or_class), C.GoString(message))
}
}
@@ -1,62 +0,0 @@
package main
import (
"fmt"
"github.com/dotcloud/docker/devmapper"
"os"
)
func usage() {
fmt.Printf("Usage: %s [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0])
os.Exit(1)
}
func main() {
devices := devmapper.NewDeviceSet("/var/lib/docker")
if len(os.Args) < 2 {
usage()
}
cmd := os.Args[1]
if cmd == "snap" {
if len(os.Args) < 4 {
usage()
}
err := devices.AddDevice(os.Args[2], os.Args[3])
if err != nil {
fmt.Println("Can't create snap device: ", err)
os.Exit(1)
}
} else if cmd == "remove" {
if len(os.Args) < 3 {
usage()
}
err := devices.RemoveDevice(os.Args[2])
if err != nil {
fmt.Println("Can't remove device: ", err)
os.Exit(1)
}
} else if cmd == "mount" {
if len(os.Args) < 4 {
usage()
}
err := devices.MountDevice(os.Args[2], os.Args[3])
if err != nil {
fmt.Println("Can't create snap device: ", err)
os.Exit(1)
}
} else {
fmt.Printf("Unknown command %s\n", cmd)
if len(os.Args) < 4 {
usage()
}
os.Exit(1)
}
return
}
+25 -106
View File
@@ -6,14 +6,10 @@ import (
"github.com/dotcloud/docker"
"github.com/dotcloud/docker/sysinit"
"github.com/dotcloud/docker/utils"
"io/ioutil"
"github.com/dotcloud/docker/engine"
"log"
"net"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
)
var (
@@ -34,14 +30,14 @@ func main() {
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")
flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.")
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")
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")
flInterContainerComm := flag.Bool("enable-container-comm", false, "Enable inter-container communication")
flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication")
flag.Parse()
@@ -53,13 +49,14 @@ func main() {
flHosts = flHosts[1:] //trick to display a nice default value in the usage
}
for i, flHost := range flHosts {
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
host, err := utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
if err == nil {
flHosts[i] = host
} else {
log.Fatal(err)
}
}
bridge := docker.DefaultNetworkBridge
if *bridgeName != "" {
bridge = *bridgeName
}
if *flDebug {
os.Setenv("DEBUG", "1")
}
@@ -70,33 +67,27 @@ func main() {
flag.Usage()
return
}
var dns []string
if *flDns != "" {
dns = []string{*flDns}
}
ip := net.ParseIP(*flDefaultIp)
config := &docker.DaemonConfig{
Pidfile: *pidfile,
GraphPath: *flGraphPath,
AutoRestart: *flAutoRestart,
EnableCors: *flEnableCors,
Dns: dns,
EnableIptables: *flEnableIptables,
BridgeIface: bridge,
ProtoAddresses: flHosts,
DefaultIp: ip,
InterContainerCommunication: *flInterContainerComm,
}
if err := daemon(config); err != nil {
eng, err := engine.New(*flRoot)
if err != nil {
log.Fatal(err)
}
job := eng.Job("serveapi")
job.Setenv("Pidfile", *pidfile)
job.Setenv("Root", *flRoot)
job.SetenvBool("AutoRestart", *flAutoRestart)
job.SetenvBool("EnableCors", *flEnableCors)
job.Setenv("Dns", *flDns)
job.SetenvBool("EnableIptables", *flEnableIptables)
job.Setenv("BridgeIface", *bridgeName)
job.SetenvList("ProtoAddresses", flHosts)
job.Setenv("DefaultIp", *flDefaultIp)
job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
if err := job.Run(); err != nil {
log.Fatal(err)
os.Exit(-1)
}
} else {
if len(flHosts) > 1 {
log.Fatal("Please specify only one -H")
return
}
protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
@@ -104,7 +95,6 @@ func main() {
os.Exit(sterr.Status)
}
log.Fatal(err)
os.Exit(-1)
}
}
}
@@ -112,74 +102,3 @@ func main() {
func showVersion() {
fmt.Printf("Docker version %s, build %s\n", VERSION, GITCOMMIT)
}
func createPidFile(pidfile string) error {
if pidString, err := ioutil.ReadFile(pidfile); err == nil {
pid, err := strconv.Atoi(string(pidString))
if err == nil {
if _, err := os.Stat(fmt.Sprintf("/proc/%d/", pid)); err == nil {
return fmt.Errorf("pid file found, ensure docker is not running or delete %s", pidfile)
}
}
}
file, err := os.Create(pidfile)
if err != nil {
return err
}
defer file.Close()
_, err = fmt.Fprintf(file, "%d", os.Getpid())
return err
}
func removePidFile(pidfile string) {
if err := os.Remove(pidfile); err != nil {
log.Printf("Error removing %s: %s", pidfile, err)
}
}
func daemon(config *docker.DaemonConfig) error {
if err := createPidFile(config.Pidfile); err != nil {
log.Fatal(err)
}
defer removePidFile(config.Pidfile)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM)
go func() {
sig := <-c
log.Printf("Received signal '%v', exiting\n", sig)
removePidFile(config.Pidfile)
os.Exit(0)
}()
server, err := docker.NewServer(config)
if err != nil {
return err
}
chErrors := make(chan error, len(config.ProtoAddresses))
for _, protoAddr := range config.ProtoAddresses {
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
if protoAddrParts[0] == "unix" {
syscall.Unlink(protoAddrParts[1])
} else if protoAddrParts[0] == "tcp" {
if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
}
} else {
log.Fatal("Invalid protocol format.")
os.Exit(-1)
}
go func() {
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
}()
}
for i := 0; i < len(config.ProtoAddresses); i += 1 {
err := <-chErrors
if err != nil {
return err
}
}
return nil
}
+6 -1
View File
@@ -1,11 +1,16 @@
from ubuntu:12.04
maintainer Nick Stinemates
#
# docker build -t docker:docs . && docker run -p 8000:8000 docker:docs
#
run apt-get update
run apt-get install -y python-setuptools make
run easy_install pip
#from docs/requirements.txt, but here to increase cacheability
run pip install Sphinx==1.1.3
run pip install sphinxcontrib-httpdomain==1.1.8
add . /docs
run pip install -r /docs/requirements.txt
run cd /docs; make docs
expose 8000
+5 -4
View File
@@ -39,8 +39,7 @@ Getting Started
To edit and test the docs, you'll need to install the Sphinx tool and
its dependencies. There are two main ways to install this tool:
Native Installation
...................
###Native Installation
* Install sphinx: `pip install sphinx`
* Mac OS X: `[sudo] pip-2.7 install sphinx`
@@ -48,8 +47,7 @@ Native Installation
* 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**
Alternative Installation: Docker Container
..........................................
###Alternative Installation: Docker Container
If you're running ``docker`` on your development machine then you may
find it easier and cleaner to use the Dockerfile. This installs Sphinx
@@ -59,6 +57,9 @@ docs inside the container, even starting a simple HTTP server on port
build .`` and run the resulting image. This is the equivalent to
``make clean server`` since each container starts clean.
In the ``docs/`` directory, run:
```docker build -t docker:docs . && docker run -p 8000:8000 docker:docs```
Usage
-----
* Follow the contribution guidelines (``../CONTRIBUTING.md``)
+21 -16
View File
@@ -970,28 +970,33 @@ Create a new image from a container's changes
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
**Example response**:
**Example response**:
.. sourcecode:: http
.. sourcecode:: http
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
{"Id":"596069db4bf5"}
{"Id":"596069db4bf5"}
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
3. Going further
+28 -23
View File
@@ -977,32 +977,37 @@ Create a new image from a container's changes
.. http:post:: /commit
Create a new image from a container's changes
Create a new image from a container's changes
**Example request**:
**Example request**:
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
.. sourcecode:: http
{"Id":"596069db4bf5"}
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
{"Id":"596069db4bf5"}
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
3. Going further
+22 -17
View File
@@ -985,32 +985,37 @@ Create a new image from a container's changes
.. http:post:: /commit
Create a new image from a container's changes
Create a new image from a container's changes
**Example request**:
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
**Example response**:
**Example response**:
.. sourcecode:: http
.. sourcecode:: http
HTTP/1.1 201 OK
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
{"Id":"596069db4bf5"}
{"Id":"596069db4bf5"}
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
3. Going further
+22 -17
View File
@@ -1034,32 +1034,37 @@ Create a new image from a container's changes
.. http:post:: /commit
Create a new image from a container's changes
Create a new image from a container's changes
**Example request**:
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
**Example response**:
**Example response**:
.. sourcecode:: http
.. sourcecode:: http
HTTP/1.1 201 OK
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
{"Id":"596069db4bf5"}
{"Id":"596069db4bf5"}
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
Monitor Docker's events
+29 -24
View File
@@ -943,36 +943,36 @@ Build an image from Dockerfile via stdin
.. http:post:: /build
Build an image from Dockerfile via stdin
Build an image from Dockerfile via stdin
**Example request**:
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /build HTTP/1.1
POST /build HTTP/1.1
{{ STREAM }}
{{ STREAM }}
**Example response**:
**Example response**:
.. sourcecode:: http
.. sourcecode:: http
HTTP/1.1 200 OK
HTTP/1.1 200 OK
{{ STREAM }}
{{ STREAM }}
The stream must be a tar archive compressed with one of the following algorithms:
identity (no compression), gzip, bzip2, xz. The archive must include a file called
`Dockerfile` at its root. It may include any number of other files, which will be
accessible in the build context (See the ADD build command).
The stream must be a tar archive compressed with one of the following algorithms:
identity (no compression), gzip, bzip2, xz. The archive must include a file called
`Dockerfile` at its root. It may include any number of other files, which will be
accessible in the build context (See the ADD build command).
The Content-type header should be set to "application/tar".
The Content-type header should be set to "application/tar".
:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
:query q: suppress verbose build output
:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
:query q: suppress verbose build output
:query nocache: do not use the cache when building the image
:statuscode 200: no error
:statuscode 200: no error
:statuscode 500: server error
@@ -1069,8 +1069,8 @@ Show the docker version information
"GoVersion":"go1.0.3"
}
:statuscode 200: no error
:statuscode 500: server error
:statuscode 200: no error
:statuscode 500: server error
Create a new image from a container's changes
@@ -1084,23 +1084,28 @@ Create a new image from a container's changes
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
{"Id":"596069db4bf5"}
{"Id":"596069db4bf5"}
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
+23 -18
View File
@@ -1050,32 +1050,37 @@ Create a new image from a container's changes
.. http:post:: /commit
Create a new image from a container's changes
Create a new image from a container's changes
**Example request**:
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
**Example response**:
**Example response**:
.. sourcecode:: http
.. sourcecode:: http
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
{"Id":"596069db4bf5"}
{"Id":"596069db4bf5"}
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
Monitor Docker's events
***********************
+34 -24
View File
@@ -13,9 +13,12 @@ Docker Remote API v1.6
1. Brief introduction
=====================
- The Remote API is replacing rcli
- Default port in the docker daemon is 4243
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
- The Remote API has replaced rcli
- The daemon listens on ``unix:///var/run/docker.sock``, but you can
:ref:`bind_docker`.
- The API tends to be REST, but for some complex commands, like
``attach`` or ``pull``, the HTTP connection is hijacked to transport
``stdout, stdin`` and ``stderr``
2. Endpoints
============
@@ -148,6 +151,7 @@ Create a container
}
:jsonparam config: the container's configuration
:query name: container name to use
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 406: impossible to attach (container not running)
@@ -442,7 +446,8 @@ Kill a container
.. sourcecode:: http
HTTP/1.1 204 OK
:query signal: Signal to send to the container (integer). When not set, SIGKILL is assumed and the call will waits for the container to exit.
:statuscode 204: no error
:statuscode 404: no such container
:statuscode 500: server error
@@ -990,36 +995,36 @@ Build an image from Dockerfile via stdin
.. http:post:: /build
Build an image from Dockerfile via stdin
Build an image from Dockerfile via stdin
**Example request**:
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /build HTTP/1.1
POST /build HTTP/1.1
{{ STREAM }}
{{ STREAM }}
**Example response**:
**Example response**:
.. sourcecode:: http
.. sourcecode:: http
HTTP/1.1 200 OK
HTTP/1.1 200 OK
{{ STREAM }}
{{ STREAM }}
The stream must be a tar archive compressed with one of the following algorithms:
identity (no compression), gzip, bzip2, xz. The archive must include a file called
`Dockerfile` at its root. It may include any number of other files, which will be
accessible in the build context (See the ADD build command).
The stream must be a tar archive compressed with one of the following algorithms:
identity (no compression), gzip, bzip2, xz. The archive must include a file called
`Dockerfile` at its root. It may include any number of other files, which will be
accessible in the build context (See the ADD build command).
The Content-type header should be set to "application/tar".
The Content-type header should be set to "application/tar".
:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
:query q: suppress verbose build output
:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
:query q: suppress verbose build output
:query nocache: do not use the cache when building the image
:statuscode 200: no error
:statuscode 200: no error
:statuscode 500: server error
@@ -1131,7 +1136,13 @@ Create a new image from a container's changes
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
**Example response**:
@@ -1147,7 +1158,6 @@ Create a new image from a container's changes
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
+85 -75
View File
@@ -29,6 +29,9 @@ To list available commands, either run ``docker`` with no parameters or execute
Attach to a running container.
-nostdin=false: Do not attach stdin
-sig-proxy=true: Proxify all received signal to the process (even in non-tty mode)
You can detach from the container again (and leave it running) with
``CTRL-c`` (for a quiet exit) or ``CTRL-\`` to get a stacktrace of
the Docker client when it quits.
@@ -38,7 +41,7 @@ To stop a container, use ``docker stop``
To kill the container, use ``docker kill``
.. _cli_attach_examples:
Examples:
~~~~~~~~~
@@ -52,8 +55,8 @@ Examples:
Mem: 373572k total, 355560k used, 18012k free, 27872k buffers
Swap: 786428k total, 0k used, 786428k free, 221740k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 17200 1116 912 R 0 0.3 0:00.03 top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 17200 1116 912 R 0 0.3 0:00.03 top
top - 02:05:55 up 3:05, 0 users, load average: 0.01, 0.02, 0.05
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
@@ -61,8 +64,8 @@ Examples:
Mem: 373572k total, 355244k used, 18328k free, 27872k buffers
Swap: 786428k total, 0k used, 786428k free, 221776k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
top - 02:05:58 up 3:06, 0 users, load average: 0.01, 0.02, 0.05
@@ -71,9 +74,9 @@ Examples:
Mem: 373572k total, 355780k used, 17792k free, 27880k buffers
Swap: 786428k total, 0k used, 786428k free, 221776k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
^C$
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
^C$
$ sudo docker stop $ID
.. _cli_build:
@@ -130,7 +133,6 @@ 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.
.. code-block:: bash
sudo docker build github.com/creack/docker-firefox
@@ -148,16 +150,35 @@ by using the ``git://`` schema.
::
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY [TAG]]
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
Create a new image from a container's changes
-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`.
-run="": Configuration to be applied when the image is launched with `docker run`.
(ex: '{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
Full -run example (multiline is ok within a single quote ``'``)
Simple commit of an existing container
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: bash
$ docker ps
ID IMAGE COMMAND CREATED STATUS PORTS
c3f279d17e0a ubuntu:12.04 /bin/bash 7 days ago Up 25 hours
197387f1b436 ubuntu:12.04 /bin/bash 7 days ago Up 25 hours
$ docker commit c3f279d17e0a SvenDowideit/testimage:version3
f5283438590d
$ 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
.................
(multiline is ok within a single quote ``'``)
::
@@ -236,7 +257,7 @@ Shell 1: Listening for events
.............................
.. code-block:: bash
$ sudo docker events
Shell 2: Start and Stop a Container
@@ -279,6 +300,9 @@ Shell 1: (Again .. now showing events)
Show the history of an image
-notrunc=false: Don't truncate output
-q=false: only show numeric IDs
.. _cli_images:
``images``
@@ -311,7 +335,7 @@ Displaying images visually
::
Usage: docker import URL|- [REPOSITORY [TAG]]
Usage: docker import URL|- [REPOSITORY[:TAG]]
Create a new filesystem image from the contents of a tarball
@@ -327,14 +351,16 @@ Examples
Import from a remote location
.............................
``$ sudo docker import http://example.com/exampleimage.tgz exampleimagerepo``
This will create a new untagged image.
``$ sudo docker import http://example.com/exampleimage.tgz``
Import from a local file
........................
Import to docker via pipe and standard in
``$ cat exampleimage.tgz | sudo docker import - exampleimagelocal``
``$ cat exampleimage.tgz | sudo docker import - exampleimagelocal:new``
Import from a local directory
.............................
@@ -396,36 +422,11 @@ Insert file from github
::
Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
Usage: docker kill CONTAINER [CONTAINER...]
Kill a running container
Kill a running container (Send SIGKILL)
.. _cli_link:
``link``
--------
::
Usage: docker link CURRENT_NAME NEW_NAME
Link a container to a new name.
Examples:
~~~~~~~~~
.. code-block:: bash
$ docker link /59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc /redis
$ docker ls
NAME ID IMAGE
/redis 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest
/59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest
This will create a new link for the existing name ``/59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc``
with the new name ``/redis`` so that we can new reference the same container under the new name ``/redis``.
The main process inside the container will be sent SIGKILL.
.. _cli_login:
@@ -534,7 +535,7 @@ with the new name ``/redis`` so that we can new reference the same container und
Remove one or more containers
-link="": Remove the link instead of the actual container
Examples:
~~~~~~~~~
@@ -599,8 +600,11 @@ network communication.
-entrypoint="": Overwrite the default entrypoint set by the image
-w="": Working directory inside the container
-lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
-sig-proxy=true: Proxify all received signal to the process (even in non-tty mode)
-expose=[]: Expose a port from the container without publishing it to your host
-link="": Add link to another container (containerid:alias)
-link="": Add link to another container (name:alias)
-name="": Assign the specified name to the container. If no name is specific docker will generate a random name
-P=false: Publish all exposed ports to the host interfaces
Examples
--------
@@ -637,51 +641,52 @@ use-cases, like running Docker within Docker.
docker run -w /path/to/dir/ -i -t ubuntu pwd
The ``-w`` lets the command being executed inside directory given,
here /path/to/dir/. If the path does not exists it is created inside the
The ``-w`` lets the command being executed inside directory given,
here /path/to/dir/. If the path does not exists it is created inside the
container.
.. code-block:: bash
docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd
The ``-v`` flag mounts the current working directory into the container.
The ``-w`` lets the command being executed inside the current
The ``-v`` flag mounts the current working directory into the container.
The ``-w`` lets the command being executed inside the current
working directory, by changing into the directory to the value
returned by ``pwd``. So this combination executes the command
using the container, but inside the current working directory.
.. code-block:: bash
docker run -p 127.0.0.0::80 ubuntu bash
docker run -p 127.0.0.1:80:8080 ubuntu bash
This the ``-p`` flag now allows you to bind a port to a specific
interface of the host machine. In this example port ``80`` of the
container will have a dynamically allocated port bound to 127.0.0.1
of the host.
.. code-block:: bash
docker run -p 127.0.0.1:80:80 ubuntu bash
This will bind port ``80`` of the container to port ``80`` on 127.0.0.1 of your
host machine.
This binds port ``8080`` of the container to port ``80`` on 127.0.0.1 of the
host machine. :ref:`port_redirection` explains in detail how to manipulate ports
in Docker.
.. code-block:: bash
docker run -expose 80 ubuntu bash
This will expose port ``80`` of the container for use within a link
without publishing the port to the host system's interfaces.
This exposes port ``80`` of the container for use within a link without
publishing the port to the host system's interfaces. :ref:`port_redirection`
explains in detail how to manipulate ports in Docker.
.. code-block:: bash
docker run -link /redis:redis ubuntu bash
docker run -name console -t -i ubuntu bash
The ``-link`` flag will link the container named ``/redis`` into the
This will create and run a new container with the container name
being ``console``.
.. code-block:: bash
docker run -link /redis:redis -name console ubuntu bash
The ``-link`` flag will link the container named ``/redis`` into the
newly created container with the alias ``redis``. The new container
can access the network and environment of the redis container via
environment variables.
environment variables. The ``-name`` flag will assign the name ``console``
to the newly created container.
.. _cli_search:
@@ -692,8 +697,11 @@ environment variables.
Usage: docker search TERM
Searches for the TERM parameter on the Docker index and prints out
a list of repositories that match.
Search the docker index for images
-notrunc=false: Don't truncate output
-stars=0: Only displays with at least xxx stars
-trusted=false: Only show trusted builds
.. _cli_start:
@@ -706,6 +714,9 @@ environment variables.
Start a stopped container
-a=false: Attach container's stdout/stderr and forward all signals to the process
-i=false: Attach container's stdin
.. _cli_stop:
``stop``
@@ -715,10 +726,12 @@ environment variables.
Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
Stop a running container
Stop a running container (Send SIGTERM, and then SIGKILL after grace period)
-t=10: Number of seconds to wait for the container to stop before killing it.
The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL
.. _cli_tag:
``tag``
@@ -726,7 +739,7 @@ environment variables.
::
Usage: docker tag [OPTIONS] IMAGE REPOSITORY [TAG]
Usage: docker tag [OPTIONS] IMAGE REPOSITORY[:TAG]
Tag an image into a repository
@@ -739,7 +752,7 @@ environment variables.
::
Usage: docker top CONTAINER
Usage: docker top CONTAINER [ps OPTIONS]
Lookup the running processes of a container
@@ -761,6 +774,3 @@ Show the version of the docker client, daemon, and latest released version.
Usage: docker wait [OPTIONS] NAME
Block until a container stops, then print its exit code.
+14 -1
View File
@@ -5,5 +5,18 @@
Contributing to Docker
======================
Want to hack on Docker? Awesome! The repository includes `all the instructions you need to get started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_.
Want to hack on Docker? Awesome!
The repository includes `all the instructions you need to get
started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_.
The developer environment `Dockerfile <https://github.com/dotcloud/docker/blob/master/Dockerfile>`_
specifies the tools and versions used to test and build Docker.
If you're making changes to the documentation, see the
`README.md <https://github.com/dotcloud/docker/blob/master/docs/README.md>`_.
The documentation environment `Dockerfile <https://github.com/dotcloud/docker/blob/master/docs/Dockerfile>`_
specifies the tools and versions used to build the Documentation.
Further interesting details can be found in the `Packaging hints <https://github.com/dotcloud/docker/blob/master/hack/PACKAGERS.md>`_.
+4 -9
View File
@@ -56,7 +56,7 @@ To create the Docker binary, run this command:
.. code-block:: bash
sudo docker run -lxc-conf=lxc.aa_profile=unconfined -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh binary
sudo docker run -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh binary
This will create the Docker binary in ``./bundles/<version>-dev/binary/``
@@ -64,20 +64,15 @@ This will create the Docker binary in ``./bundles/<version>-dev/binary/``
Step 5: Run the Tests
---------------------
To run the Docker test cases you first need to disable `AppArmor <https://wiki.ubuntu.com/AppArmor>`_ using the following commands
.. code-block:: bash
sudo /etc/init.d/apparmor stop
sudo /etc/init.d/apparmor teardown
To execute the test cases, run this command:
.. code-block:: bash
sudo docker run -lxc-conf=lxc.aa_profile=unconfined -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh test
sudo docker run -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh test
Note: if you're running the tests in vagrant, you need to specify a dns entry in the command: `-dns 8.8.8.8`
If the test are successful then the tail of the output should look something like this
.. code-block:: bash
@@ -10,7 +10,7 @@ CouchDB Service
.. include:: example_header.inc
Here's an example of using data volumes to share the same data between
2 CouchDB containers. This could be used for hot upgrades, testing
two CouchDB containers. This could be used for hot upgrades, testing
different versions of CouchDB on the same data, etc.
Create first database
@@ -25,8 +25,8 @@ Note that we're marking ``/var/lib/couchdb`` as a data volume.
Add data to the first database
------------------------------
We're assuming your docker host is reachable at `localhost`. If not,
replace `localhost` with the public IP of your docker host.
We're assuming your Docker host is reachable at ``localhost``. If not,
replace ``localhost`` with the public IP of your Docker host.
.. code-block:: bash
@@ -37,7 +37,7 @@ replace `localhost` with the public IP of your docker host.
Create second database
----------------------
This time, we're requesting shared access to $COUCH1's volumes.
This time, we're requesting shared access to ``$COUCH1``'s volumes.
.. code-block:: bash
@@ -52,5 +52,5 @@ Browse data on the second database
URL="http://$HOST:$(sudo docker port $COUCH2 5984)/_utils/"
echo "Navigate to $URL in your browser. You should see the same data as in the first database"'!'
Congratulations, you are running 2 Couchdb containers, completely
Congratulations, you are now running two Couchdb containers, completely
isolated from each other *except* for their data.
+10 -9
View File
@@ -12,16 +12,16 @@ Hello World
Running the Examples
====================
All the examples assume your machine is running the docker daemon. To
run the docker daemon in the background, simply type:
All the examples assume your machine is running the ``docker`` daemon. To
run the ``docker`` daemon in the background, simply type:
.. code-block:: bash
sudo docker -d &
Now you can run docker in client mode: by default all commands will be
Now you can run Docker in client mode: by default all commands will be
forwarded to the ``docker`` daemon via a protected Unix socket, so you
must run as root.
must run as the ``root`` or via the ``sudo`` command.
.. code-block:: bash
@@ -38,23 +38,24 @@ Hello World
This is the most basic example available for using Docker.
Download the base image (named "ubuntu"):
Download the base image which is named ``ubuntu``:
.. code-block:: bash
# Download an ubuntu image
sudo docker pull ubuntu
Alternatively to the *ubuntu* image, you can select *busybox*, a bare
Alternatively to the ``ubuntu`` image, you can select ``busybox``, a bare
minimal Linux system. The images are retrieved from the Docker
repository.
.. code-block:: bash
#run a simple echo command, that will echo hello world back to the console over standard out.
sudo docker run ubuntu /bin/echo hello world
This command will run a simple ``echo`` command, that will echo ``hello world`` back to the console over standard out.
**Explanation:**
- **"sudo"** execute the following commands as user *root*
@@ -100,9 +101,9 @@ we stop it.
CONTAINER_ID=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done")
We are going to run a simple hello world daemon in a new container
made from the *ubuntu* image.
made from the ``ubuntu`` image.
- **"docker run -d "** run a command in a new container. We pass "-d"
- **"sudo docker run -d "** run a command in a new container. We pass "-d"
so it runs as a daemon.
- **"ubuntu"** is the image we want to run the command inside of.
- **"/bin/sh -c"** is the command we want to run in the container
+1 -1
View File
@@ -10,7 +10,7 @@ Examples
Here are some examples of how to use Docker to create running
processes, starting from a very simple *Hello World* and progressing
to more substantial services like you might find in production.
to more substantial services like those which you might find in production.
.. toctree::
:maxdepth: 1
+53 -48
View File
@@ -9,17 +9,26 @@ Linking Redis
.. include:: example_header.inc
Building a redis container to link as a child of our web application.
Building a Redis container to link as a child of our web application.
Building the redis container
Building the Redis container
----------------------------
We will use a pre-build version of redis from the index under
the name ``crosbymichael/redis``. If you are interested in the
Dockerfile that was used to build this container here it is.
Lets build a Redis image with the following Dockerfile.
First checkout the Redis source code.
.. code-block:: bash
git clone https://github.com/antirez/redis.git
cd redis
git checkout 2.6
Now let's create a Dockerfile in the root of the Redis repository.
.. code-block:: bash
# Build redis from source
# Make sure you have the redis source code checked out in
# the same directory as this Dockerfile
@@ -34,7 +43,6 @@ Dockerfile that was used to build this container here it is.
ADD . /redis
RUN (cd /redis && make)
RUN (cd /redis && make test)
RUN mkdir -p /redis-data
VOLUME ["/redis-data"]
@@ -43,65 +51,56 @@ Dockerfile that was used to build this container here it is.
ENTRYPOINT ["/redis/src/redis-server"]
CMD ["--dir", "/redis-data"]
# docker build our new redis image from source
docker build -t redis-2.6 .
We need to ``EXPOSE`` the default port of 6379 so that our link knows what ports
to connect to our redis container on. If you do not expose any ports for the
to connect to our Redis container on. If you do not expose any ports for the
image then docker will not be able to establish the link between containers.
Run the redis container
Run the Redis container
-----------------------
.. code-block:: bash
docker run -d -e PASSWORD=docker crosbymichael/redis --requirepass docker
This will run our redis container using the default port of 6379 and using
as password to secure our service. Next we will link the redis container to
a new name using ``docker link``.
sudo docker run -d -e PASSWORD=docker -name redis redis-2.6 --requirepass docker
Linking an existing container
-----------------------------
This will run our Redis container with the password docker
to secure our service. By specifying the ``-name`` flag on run
we will assign the name ``redis`` to this container. If we do not specify a name for
our container via the ``-name`` flag docker will automatically generate a name for us.
We can issue all the commands that you would expect; start, stop, attach, using the name for our container.
The name also allows us to link other containers into this one.
Docker will automatically create an initial link with the container's id but
because the is long and not very user friendly we can link the container with
a new name.
.. code-block:: bash
docker link /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 /redis
Now we can reference our running redis service using the friendly name ``/redis``.
We can issue all the commands that you would expect; start, stop, attach, using the new name.
Linking redis as a child
Linking Redis as a child
------------------------
Next we can start a new web application that has a dependency on redis and apply a link
to connect both containers. If you noticed when running our redis service we did not use
the ``-p`` option to publish the redis port to the host system. Redis exposed port 6379
but we did not publish the port. This allows docker to prevent all network traffic to
the redis container except when explicitly specified within a link. This is a big win
for security.
Next we can start a new web application that has a dependency on Redis and apply a link
to connect both containers. If you noticed when running our Redis server we did not use
the ``-p`` flag to publish the Redis port to the host system. Redis exposed port 6379 via the Dockerfile
and this is all we need to establish a link.
Now lets start our web application with a link into redis.
Now let's start our web application with a link into Redis.
.. code-block:: bash
docker run -t -i -link /redis:db ubuntu bash
sudo docker run -t -i -link redis:db -name webapp ubuntu bash
root@4c01db0b339c:/# env
HOSTNAME=4c01db0b339c
DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db
DB_NAME=/webapp/db
TERM=xterm
DB_PORT=tcp://172.17.0.8:6379
DB_PORT_6379_TCP=tcp://172.17.0.8:6379
DB_PORT_6379_TCP_PROTO=tcp
DB_PORT_6379_TCP_ADDR=172.17.0.8
DB_PORT_6379_TCP_PORT=6379
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
DB_ENV_PASSWORD=dockerpass
DB_ENV_PASSWORD=docker
SHLVL=1
HOME=/
container=lxc
@@ -110,23 +109,29 @@ Now lets start our web application with a link into redis.
When we inspect the environment of the linked container we can see a few extra environment
variables have been added. When you specified ``-link /redis:db`` you are telling docker
to link the container named ``/redis`` into this new container with the alias ``db``.
variables have been added. When you specified ``-link redis:db`` you are telling docker
to link the container named ``redis`` into this new container with the alias ``db``.
Environment variables are prefixed with the alias so that the parent container can access
network and environment information from the child.
network and environment information from the containers that are linked into it.
.. code-block:: bash
# The name of the child container
DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db
DB_NAME=/webapp/db
# The default protocol, ip, and port of the service running in the container
DB_PORT=tcp://172.17.0.8:6379
# A specific protocol, ip, and port of various services
DB_PORT_6379_TCP=tcp://172.17.0.8:6379
DB_PORT_6379_TCP_PROTO=tcp
DB_PORT_6379_TCP_ADDR=172.17.0.8
DB_PORT_6379_TCP_PORT=6379
# Get environment variables of the container
DB_ENV_PASSWORD=dockerpass
DB_ENV_PASSWORD=docker
Accessing the network information along with the environment of the child container allows
us to easily connect to the redis service on the specific ip and port and use the password
specified in the environment.
us to easily connect to the Redis service on the specific IP and port and use the password
specified in the environment.
+9 -9
View File
@@ -10,8 +10,8 @@ Building an Image with MongoDB
.. include:: example_header.inc
The goal of this example is to show how you can build your own
docker images with MongoDB preinstalled. We will do that by
constructing a Dockerfile that downloads a base image, adds an
Docker images with MongoDB pre-installed. We will do that by
constructing a ``Dockerfile`` that downloads a base image, adds an
apt source and installs the database software on Ubuntu.
Creating a ``Dockerfile``
@@ -41,7 +41,7 @@ Since we want to be running the latest version of MongoDB we'll need to add the
RUN echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | tee /etc/apt/sources.list.d/10gen.list
Then, we don't want Ubuntu to complain about init not being available so we'll
divert /sbin/initctl to /bin/true so it thinks everything is working.
divert ``/sbin/initctl`` to ``/bin/true`` so it thinks everything is working.
.. code-block:: bash
@@ -65,8 +65,8 @@ run without needing to provide a special configuration file)
# Create the MongoDB data directory
RUN mkdir -p /data/db
Finally, we'll expose the standard port that MongoDB runs on (27107) as well as
define an ENTRYPOINT for the container.
Finally, we'll expose the standard port that MongoDB runs on, 27107, as well as
define an ``ENTRYPOINT`` instruction for the container.
.. code-block:: bash
@@ -78,7 +78,7 @@ run all of the commands.
.. code-block:: bash
docker build -t <yourname>/mongodb .
sudo docker build -t <yourname>/mongodb .
Now you should be able to run ``mongod`` as a daemon and be able to connect on
the local port!
@@ -86,13 +86,13 @@ the local port!
.. code-block:: bash
# Regular style
MONGO_ID=$(docker run -d <yourname>/mongodb)
MONGO_ID=$(sudo docker run -d <yourname>/mongodb)
# Lean and mean
MONGO_ID=$(docker run -d <yourname>/mongodb --noprealloc --smallfiles)
MONGO_ID=$(sudo docker run -d <yourname>/mongodb --noprealloc --smallfiles)
# Check the logs out
docker logs $MONGO_ID
sudo docker logs $MONGO_ID
# Connect and play around
mongo --port <port you get from `docker ps`>
+16 -16
View File
@@ -10,7 +10,7 @@ Node.js Web App
.. include:: example_header.inc
The goal of this example is to show you how you can build your own
docker images from a parent image using a ``Dockerfile`` . We will do
Docker images from a parent image using a ``Dockerfile`` . We will do
that by making a simple Node.js hello world web application running on
CentOS. You can get the full source code at
https://github.com/gasi/docker-node-hello.
@@ -55,7 +55,7 @@ Then, create an ``index.js`` file that defines a web app using the
In the next steps, well look at how you can run this app inside a CentOS
container using docker. First, youll need to build a docker image of your app.
container using Docker. First, youll need to build a Docker image of your app.
Creating a ``Dockerfile``
+++++++++++++++++++++++++
@@ -67,8 +67,8 @@ Create an empty file called ``Dockerfile``:
touch Dockerfile
Open the ``Dockerfile`` in your favorite text editor and add the following line
that defines the version of docker the image requires to build
(this example uses docker 0.3.4):
that defines the version of Docker the image requires to build
(this example uses Docker 0.3.4):
.. code-block:: bash
@@ -76,7 +76,7 @@ that defines the version of docker the image requires to build
Next, define the parent image you want to use to build your own image on top of.
Here, well use `CentOS <https://index.docker.io/_/centos/>`_ (tag: ``6.4``)
available on the `docker index`_:
available on the `Docker index`_:
.. code-block:: bash
@@ -95,23 +95,23 @@ To install the right package for CentOS, well use the instructions from the
# Install Node.js and npm
RUN yum install -y npm
To bundle your apps source code inside the docker image, use the ``ADD``
command:
To bundle your apps source code inside the Docker image, use the ``ADD``
instruction:
.. code-block:: bash
# Bundle app source
ADD . /src
Install your app dependencies using npm:
Install your app dependencies using the ``npm`` binary:
.. code-block:: bash
# Install app dependencies
RUN cd /src; npm install
Your app binds to port ``8080`` so youll use the ``EXPOSE`` command
to have it mapped by the docker daemon:
Your app binds to port ``8080`` so youll use the ``EXPOSE`` instruction
to have it mapped by the ``docker`` daemon:
.. code-block:: bash
@@ -152,7 +152,7 @@ Building your image
+++++++++++++++++++
Go to the directory that has your ``Dockerfile`` and run the following
command to build a docker image. The ``-t`` flag lets you tag your
command to build a Docker image. The ``-t`` flag lets you tag your
image so its easier to find later using the ``docker images``
command:
@@ -160,7 +160,7 @@ command:
sudo docker build -t <your username>/centos-node-hello .
Your image will now be listed by docker:
Your image will now be listed by Docker:
.. code-block:: bash
@@ -199,17 +199,17 @@ Print the output of your app:
Test
++++
To test your app, get the the port of your app that docker mapped:
To test your app, get the the port of your app that Docker mapped:
.. code-block:: bash
docker ps
sudo docker ps
> # Example
> ID IMAGE COMMAND ... PORTS
> ecce33b30ebf gasi/centos-node-hello:latest node /src/index.js 49160->8080
In the example above, docker mapped the ``8080`` port of the container to
In the example above, Docker mapped the ``8080`` port of the container to
``49160``.
Now you can call your app using ``curl`` (install if needed via:
@@ -229,7 +229,7 @@ Now you can call your app using ``curl`` (install if needed via:
> Hello World
We hope this tutorial helped you get up and running with Node.js and
CentOS on docker. You can get the full source code at
CentOS on Docker. You can get the full source code at
https://github.com/gasi/docker-node-hello.
Continue to :ref:`running_redis_service`.
+4 -5
View File
@@ -13,7 +13,7 @@ PostgreSQL Service
.. note::
As of version 0.5.2, docker requires root privileges to run.
As of version 0.5.2, Docker requires root privileges to run.
You have to either manually adjust your system configuration (permissions on
/var/run/docker.sock or sudo config), or prefix `docker` with `sudo`. Check
`this thread`_ for details.
@@ -24,8 +24,7 @@ PostgreSQL Service
Installing PostgreSQL on Docker
-------------------------------
For clarity I won't be showing commands output.
For clarity I won't be showing command output.
Run an interactive shell in Docker container.
@@ -62,7 +61,7 @@ Finally, install PostgreSQL 9.2
Now, create a PostgreSQL superuser role that can create databases and
other roles. Following Vagrant's convention the role will be named
`docker` with `docker` password assigned to it.
``docker`` with ``docker`` password assigned to it.
.. code-block:: bash
@@ -108,7 +107,7 @@ Bash prompt; you can also locate it using ``docker ps -a``.
.. code-block:: bash
docker commit <container_id> <your username>/postgresql
sudo docker commit <container_id> <your username>/postgresql
Finally, run PostgreSQL server via ``docker``.
+20 -20
View File
@@ -10,9 +10,9 @@ Python Web App
.. include:: example_header.inc
The goal of this example is to show you how you can author your own
docker images using a parent image, making changes to it, and then
Docker images using a parent image, making changes to it, and then
saving the results as a new image. We will do that by making a simple
hello flask web application image.
hello Flask web application image.
**Steps:**
@@ -20,22 +20,22 @@ hello flask web application image.
sudo docker pull shykes/pybuilder
We are downloading the "shykes/pybuilder" docker image
We are downloading the ``shykes/pybuilder`` Docker image
.. code-block:: bash
URL=http://github.com/shykes/helloflask/archive/master.tar.gz
We set a URL variable that points to a tarball of a simple helloflask web app
We set a ``URL`` variable that points to a tarball of a simple helloflask web app
.. code-block:: bash
BUILD_JOB=$(sudo docker run -d -t shykes/pybuilder:latest /usr/local/bin/buildapp $URL)
Inside of the "shykes/pybuilder" image there is a command called
buildapp, we are running that command and passing the $URL variable
Inside of the ``shykes/pybuilder`` image there is a command called
``buildapp``, we are running that command and passing the ``$URL`` variable
from step 2 to it, and running the whole thing inside of a new
container. BUILD_JOB will be set with the new container_id.
container. The ``BUILD_JOB`` environment variable will be set with the new container ID.
.. code-block:: bash
@@ -43,13 +43,13 @@ container. BUILD_JOB will be set with the new container_id.
[...]
While this container is running, we can attach to the new container to
see what is going on. Ctrl-C to disconnect.
see what is going on. You can use Ctrl-C to disconnect.
.. code-block:: bash
sudo docker ps -a
List all docker containers. If this container has already finished
List all Docker containers. If this container has already finished
running, it will still be listed here.
.. code-block:: bash
@@ -57,8 +57,8 @@ running, it will still be listed here.
BUILD_IMG=$(sudo docker commit $BUILD_JOB _/builds/github.com/shykes/helloflask/master)
Save the changes we just made in the container to a new image called
``_/builds/github.com/hykes/helloflask/master`` and save the image id in
the BUILD_IMG variable name.
``_/builds/github.com/hykes/helloflask/master`` and save the image ID in
the ``BUILD_IMG`` variable name.
.. code-block:: bash
@@ -72,24 +72,24 @@ the BUILD_IMG variable name.
- **/usr/local/bin/runapp** is the command which starts the web app.
Use the new image we just created and create a new container with
network port 5000, and return the container id and store in the
WEB_WORKER variable.
network port 5000, and return the container ID and store in the
``WEB_WORKER`` variable.
.. code-block:: bash
sudo docker logs $WEB_WORKER
* Running on http://0.0.0.0:5000/
View the logs for the new container using the WEB_WORKER variable, and
if everything worked as planned you should see the line "Running on
http://0.0.0.0:5000/" in the log output.
View the logs for the new container using the ``WEB_WORKER`` variable, and
if everything worked as planned you should see the line ``Running on
http://0.0.0.0:5000/`` in the log output.
.. code-block:: bash
WEB_PORT=$(sudo docker port $WEB_WORKER 5000)
Look up the public-facing port which is NAT-ed. Find the private port
used by the container and store it inside of the WEB_PORT variable.
used by the container and store it inside of the ``WEB_PORT`` variable.
.. code-block:: bash
@@ -97,8 +97,8 @@ used by the container and store it inside of the WEB_PORT variable.
curl http://127.0.0.1:$WEB_PORT
Hello world!
Access the web app using curl. If everything worked as planned you
should see the line "Hello world!" inside of your console.
Access the web app using the ``curl`` binary. If everything worked as planned you
should see the line ``Hello world!`` inside of your console.
**Video:**
@@ -9,7 +9,7 @@ Redis Service
.. include:: example_header.inc
Very simple, no frills, redis service.
Very simple, no frills, Redis service.
Open a docker container
-----------------------
@@ -35,13 +35,13 @@ Snapshot the installation
.. code-block:: bash
docker ps -a # grab the container id (this will be the first one in the list)
docker commit <container_id> <your username>/redis
sudo docker ps -a # grab the container id (this will be the first one in the list)
sudo docker commit <container_id> <your username>/redis
Run the service
---------------
Running the service with `-d` runs the container in detached mode, leaving the
Running the service with ``-d`` runs the container in detached mode, leaving the
container running in the background. Use your snapshot.
.. code-block:: bash
@@ -51,7 +51,7 @@ container running in the background. Use your snapshot.
Test 1
++++++
Connect to the container with the redis-cli.
Connect to the container with the ``redis-cli`` binary.
.. code-block:: bash
@@ -67,7 +67,7 @@ Connect to the container with the redis-cli.
Test 2
++++++
Connect to the host os with the redis-cli.
Connect to the host os with the ``redis-cli`` binary.
.. code-block:: bash
@@ -107,7 +107,7 @@ Create a ``supervisord`` configuration file
+++++++++++++++++++++++++++++++++++++++++++
Create an empty file called ``supervisord.conf``. Make sure it's at the same
level as your ``Dockerfile``:
directory level as your ``Dockerfile``:
.. code-block:: bash
@@ -12,14 +12,14 @@ SSH Daemon Service
**Video:**
I've create a little screencast to show how to create a sshd service
I've create a little screencast to show how to create a SSHd service
and connect to it. It is something like 11 minutes and not entirely
smooth, but gives you a good idea.
.. note::
This screencast was created before ``docker`` version 0.5.2, so the
This screencast was created before Docker version 0.5.2, so the
daemon is unprotected and available via a TCP port. When you run
through the same steps in a newer version of ``docker``, you will
through the same steps in a newer version of Docker, you will
need to add ``sudo`` in front of each ``docker`` command in order
to reach the daemon over its protected Unix socket.
@@ -29,13 +29,14 @@ smooth, but gives you a good idea.
<iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe>
</div>
You can also get this sshd container by using
::
You can also get this sshd container by using:
.. code-block:: bash
sudo docker pull dhrp/sshd
The password is 'screencast'
The password is ``screencast``.
**Video's Transcription:**
+1 -1
View File
@@ -69,7 +69,7 @@ to run LXC containers. Note that 2.6.32 has some documented issues regarding
network namespace setup and teardown; those issues are not a risk if you
run containers in a private environment, but can lead to denial-of-service
attacks if you want to run untrusted code in your containers. For more details,
see `[LP#720095 <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/720095>`_.
see `LP#720095 <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/720095>`_.
Kernels 2.6.38, and every version since 3.2, have been deployed successfully
to run containerized production workloads. Feature-wise, there is no huge
+6 -4
View File
@@ -78,7 +78,8 @@ Docker is available as a Debian package, which makes installation easy.
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"
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
# Update your sources
sudo apt-get update
@@ -132,7 +133,8 @@ to follow them again.*
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"
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
# update
sudo apt-get update
@@ -160,8 +162,8 @@ Verify it worked
Docker and UFW
^^^^^^^^^^^^^^
Docker uses a bridge to manage containers networking, by default UFW
drop all `forwarding`, a first step is to enable forwarding:
Docker uses a bridge to manage container networking. By default, UFW
drops all `forwarding`, thus a first step is to enable UFW forwarding:
.. code-block:: bash
+1 -1
View File
@@ -9,7 +9,7 @@ Using Vagrant (Mac, Linux)
This guide will setup a new virtualbox virtual machine with docker
installed on your computer. This works on most operating systems,
including MacOX, Windows, Linux, FreeBSD and others. If you can
including MacOSX, Windows, Linux, FreeBSD and others. If you can
install these and have at least 400MB RAM to spare you should be good.
Install Vagrant and Virtualbox
+4
View File
@@ -208,6 +208,10 @@ configuration / Device configuration)
.. image:: images/win/hp_bios_vm.JPG
On some machines the BIOS menu can only be accessed before startup.
To access BIOS in this scenario you should restart your computer and
press ESC/Enter when prompted to access the boot and BIOS controls. Typically
the option to allow virtualization is contained within the BIOS/Security menu.
Docker is not installed
```````````````````````
+1 -1
View File
@@ -40,4 +40,4 @@ Docker Github Repo:
* `CentOS
<https://github.com/dotcloud/docker/blob/master/contrib/mkimage-centos.sh>`_
* `Debian/Ubuntu
<https://github.com/dotcloud/docker/blob/master/contrib/mkimage-debian.sh>`_
<https://github.com/dotcloud/docker/blob/master/contrib/mkimage-debootstrap.sh>`_
+8 -10
View File
@@ -67,6 +67,8 @@ you don't need to add ``sudo`` to all the client commands.
# Restart the docker daemon
sudo service docker restart
.. _bind_docker:
Bind Docker to another host/port or a Unix socket
-------------------------------------------------
@@ -136,22 +138,19 @@ Listing all running containers
sudo docker ps
Expose a service on a TCP port
Bind a service on a TCP port
------------------------------
.. code-block:: bash
# Expose port 4444 of this container, and tell netcat to listen on it
JOB=$(sudo docker run -d -p 4444 ubuntu:12.10 /bin/nc -l -p 4444)
# Bind port 4444 of this container, and tell netcat to listen on it
JOB=$(sudo docker run -d -p 4444 ubuntu:12.10 /bin/nc -l 4444)
# Which public port is NATed to my container?
PORT=$(sudo docker port $JOB 4444)
PORT=$(sudo docker port $JOB 4444 | awk -F: '{ print $2 }')
# Connect to the public port via the host's public address
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
# Replace *eth0* according to your local interface name.
IP=$(ip -o -4 addr list eth0 | perl -n -e 'if (m{inet\s([\d\.]+)\/\d+\s}xms) { print $1 }')
echo hello world | nc $IP $PORT
# Connect to the public port
echo hello world | nc 127.0.0.1 $PORT
# Verify that the network connection worked
echo "Daemon received: $(sudo docker logs $JOB)"
@@ -181,4 +180,3 @@ You now have a image state from which you can create new instances.
Read more about :ref:`working_with_the_repository` or continue to the
complete :ref:`cli`
+13 -10
View File
@@ -174,10 +174,10 @@ override the default specified in CMD.
``EXPOSE <port> [<port>...]``
The ``EXPOSE`` instruction sets ports to be publicly exposed when
running the image. This is functionally equivalent to running ``docker
commit -run '{"PortSpecs": ["<port>", "<port2>"]}'`` outside the
builder. Take a look at :ref:`port_redirection` for more information.
The ``EXPOSE`` instruction exposes ports for use within links. This is
functionally equivalent to running ``docker commit -run '{"PortSpecs":
["<port>", "<port2>"]}'`` outside the builder. Refer to
:ref:`port_redirection` for detailed information.
3.6 ENV
-------
@@ -208,6 +208,9 @@ a remote file URL.
``<dest>`` is the path at which the source will be copied in the
destination container.
All new files and directories are created with mode 0755, uid and gid
0.
The copy obeys the following rules:
* If ``<src>`` is a URL and ``<dest>`` does not end with a trailing slash,
@@ -220,8 +223,9 @@ The copy obeys the following rules:
(``http://example.com`` will not work).
* If ``<src>`` is a directory, the entire directory is copied,
including filesystem metadata.
* If ``<src>``` is a tar archive in a recognized compression format
(identity, gzip, bzip2 or xz), it is unpacked as a directory.
* If ``<src>`` is a *local* tar archive in a recognized compression
format (identity, gzip, bzip2 or xz) then it is unpacked as a
directory. Resources from *remote* URLs are **not** decompressed.
When a directory is copied or unpacked, it has the same behavior as
``tar -x``: the result is the union of
@@ -229,7 +233,7 @@ The copy obeys the following rules:
1. whatever existed at the destination path and
2. the contents of the source tree,
with conflicts resolved in favor of 2) on a file-by-file basis.
with conflicts resolved in favor of "2." on a file-by-file basis.
* If ``<src>`` is any other kind of file, it is copied individually
along with its metadata. In this case, if ``<dest>`` ends with a
@@ -237,10 +241,9 @@ The copy obeys the following rules:
contents of ``<src>`` will be written at ``<dest>/base(<src>)``.
* If ``<dest>`` does not end with a trailing slash, it will be
considered a regular file and the contents of ``<src>`` will be
written at ``<dst>``.
written at ``<dest>``.
* If ``<dest>`` doesn't exist, it is created along with all missing
directories in its path. All new files and directories are created
with mode 0755, uid and gid 0.
directories in its path.
.. _entrypoint_def:
+59
View File
@@ -0,0 +1,59 @@
:title: Host Integration
:description: How to generate scripts for upstart, systemd, etc.
:keywords: systemd, upstart, supervisor, docker, documentation, host integration
Host Integration
================
You can use your Docker containers with process managers like ``upstart``,
``systemd`` and ``supervisor``.
Introduction
------------
If you want a process manager to manage your containers you will need to run
the docker daemon with the ``-r=false`` so that docker will not automatically
restart your containers when the host is restarted.
When you have finished setting up your image and are happy with your
running container, you may want to use a process manager to manage
it. When your run ``docker start -a`` docker will automatically attach
to the process and forward all signals so that the process manager can
detect when a container stops and correctly restart it.
Here are a few sample scripts for systemd and upstart to integrate with docker.
Sample Upstart Script
---------------------
.. code-block:: bash
description "Redis container"
author "Me"
start on filesystem and started lxc-net and started docker
stop on runlevel [!2345]
respawn
exec docker start -a 0a7e070b698b
Sample systemd Script
---------------------
.. code-block:: bash
[Unit]
Description=Redis container
Author=Me
After=docker.service
[Service]
Restart=always
ExecStart=/usr/bin/docker start -a 0a7e070b698b
ExecStop=/usr/bin/docker stop -t 2 0a7e070b698b
[Install]
WantedBy=local.target
+2
View File
@@ -18,3 +18,5 @@ Contents:
baseimages
port_redirection
puppet
host_integration
working_with_volumes
+124 -17
View File
@@ -8,29 +8,136 @@
Port redirection
================
Docker can redirect public TCP and UDP ports to your container, so it can be
reached over the network. Port redirection is done on ``docker run``
using the -p flag.
A port redirect is specified as *PUBLIC:PRIVATE*, where TCP port
*PUBLIC* will be redirected to TCP port *PRIVATE*. As a special case,
the public port can be omitted, in which case a random public port
will be allocated.
Interacting with a service is commonly done through a connection to a
port. When this service runs inside a container, one can connect to
the port after finding the IP address of the container as follows:
.. code-block:: bash
# A random PUBLIC port is redirected to PRIVATE port 80 on the container
sudo docker run -p 80 <image> <cmd>
# Find IP address of container with ID <container_id>
docker inspect <container_id> | grep IPAddress | cut -d '"' -f 4
# PUBLIC port 80 is redirected to PRIVATE port 80
sudo docker run -p 80:80 <image> <cmd>
However, this IP address is local to the host system and the container
port is not reachable by the outside world. Furthermore, even if the
port is used locally, e.g. by another container, this method is
tedious as the IP address of the container changes every time it
starts.
To redirect a UDP port the redirection must be expressed as *PUBLIC:PRIVATE/udp*:
Docker addresses these two problems and give a simple and robust way
to access services running inside containers.
To allow non-local clients to reach the service running inside the
container, Docker provide ways to bind the container port to an
interface of the host system. To simplify communication between
containers, Docker provides the linking mechanism.
Binding a port to an host interface
-----------------------------------
To bind a port of the container to a specific interface of the host
system, use the ``-p`` parameter of the ``docker run`` command:
.. code-block:: bash
# PUBLIC port 5300 is redirected to the PRIVATE port 53 using UDP
sudo docker run -p 5300:53/udp <image> <cmd>
# General syntax
docker run -p [([<host_interface>:[host_port]])|(<host_port>):]<container_port>[/udp] <image> <cmd>
Default port redirects can be built into a container with the
``EXPOSE`` build command.
When no host interface is provided, the port is bound to all available
interfaces of the host machine (aka INADDR_ANY, or 0.0.0.0).When no host port is
provided, one is dynamically allocated. The possible combinations of options for
TCP port are the following:
.. code-block:: bash
# Bind TCP port 8080 of the container to TCP port 80 on 127.0.0.1 of the host machine.
docker run -p 127.0.0.1:80:8080 <image> <cmd>
# Bind TCP port 8080 of the container to a dynamically allocated TCP port on 127.0.0.1 of the host machine.
docker run -p 127.0.0.1::8080 <image> <cmd>
# Bind TCP port 8080 of the container to TCP port 80 on all available interfaces of the host machine.
docker run -p 80:8080 <image> <cmd>
# Bind TCP port 8080 of the container to a dynamically allocated TCP port on all available interfaces of the host machine.
docker run -p 8080 <image> <cmd>
UDP ports can also be bound by adding a trailing ``/udp``. All the
combinations described for TCP work. Here is only one example:
.. code-block:: bash
# Bind UDP port 5353 of the container to UDP port 53 on 127.0.0.1 of the host machine.
docker run -p 127.0.0.1:53:5353/udp <image> <cmd>
The command ``docker port`` lists the interface and port on the host
machine bound to a given container port. It is useful when using
dynamically allocated ports:
.. code-block:: bash
# Bind to a dynamically allocated port
docker run -p 127.0.0.1::8080 -name dyn-bound <image> <cmd>
# Lookup the actual port
docker port dyn-bound 8080
127.0.0.1:49160
Linking a container
-------------------
Communication between two containers can also be established in a
docker-specific way called linking.
To briefly present the concept of linking, let us consider two
containers: ``server``, containing the service, and ``client``,
accessing the service. Once ``server`` is running, ``client`` is
started and links to server. Linking sets environment variables in
``client`` giving it some information about ``server``. In this sense,
linking is a method of service discovery.
Let us now get back to our topic of interest; communication between
the two containers. We mentioned that the tricky part about this
communication was that the IP address of ``server`` was not
fixed. Therefore, some of the environment variables are going to be
used to inform ``client`` about this IP address. This process called
exposure, is possible because ``client`` is started after ``server``
has been started.
Here is a full example. On ``server``, the port of interest is
exposed. The exposure is done either through the ``-expose`` parameter
to the ``docker run`` command, or the ``EXPOSE`` build command in a
Dockerfile:
.. code-block:: bash
# Expose port 80
docker run -expose 80 -name server <image> <cmd>
The ``client`` then links to the ``server``:
.. code-block:: bash
# Link
docker run -name client -link server:linked-server <image> <cmd>
``client`` locally refers to ``server`` as ``linked-server``. The
following environment variables, among others, are available on
``client``:
.. code-block:: bash
# The default protocol, ip, and port of the service running in the container
LINKED-SERVER_PORT=tcp://172.17.0.8:80
# A specific protocol, ip, and port of various services
LINKED-SERVER_PORT_80_TCP=tcp://172.17.0.8:80
LINKED-SERVER_PORT_80_TCP_PROTO=tcp
LINKED-SERVER_PORT_80_TCP_ADDR=172.17.0.8
LINKED-SERVER_PORT_80_TCP_PORT=80
This tells ``client`` that a service is running on port 80 of
``server`` and that ``server`` is accessible at the IP address
172.17.0.8
Note: Using the ``-p`` parameter also exposes the port..
+73
View File
@@ -0,0 +1,73 @@
:title: Working with Volumes
:description: How to create and share volumes
:keywords: Examples, Usage, volume, docker, documentation, examples
.. _volume_def:
Data Volume
===========
.. versionadded:: v0.3.0
Data volumes have been available since version 1 of the
:doc:`../api/docker_remote_api`
A *data volume* is a specially-designated directory within one or more
containers that bypasses the :ref:`ufs_def` to provide several useful
features for persistant or shared data:
* **Data volumes can be shared and reused between containers.** This
is the feature that makes data volumes so powerful. You can use it
for anything from hot database upgrades to custom backup or
replication tools. See the example below.
* **Changes to a data volume are made directly**, without the overhead
of a copy-on-write mechanism. This is good for very large files.
* **Changes to a data volume will not be included at the next commit**
because they are not recorded as regular filesystem changes in the
top layer of the :ref:`ufs_def`
Each container can have zero or more data volumes.
Getting Started
...............
Using data volumes is as simple as adding a new flag: ``-v``. The parameter ``-v`` can be used more than once in order to create more volumes within the new container. The example below shows the instruction to create a container with two new volumes::
docker run -v /var/volume1 -v /var/volume2 shykes/couchdb
For a Dockerfile, the VOLUME instruction will add one or more new volumes to any container created from the image::
VOLUME ["/var/volume1", "/var/volume2"]
Create a new container using existing volumes from an existing container:
---------------------------------------------------------------------------
The command below creates a new container which is runnning as daemon ``-d`` and with one volume ``/var/lib/couchdb``::
COUCH1=$(sudo docker run -d -v /var/lib/couchdb shykes/couchdb:2013-05-03)
From the container id of that previous container ``$COUCH1`` it's possible to create new container sharing the same volume using the parameter ``-volumes-from container_id``::
COUCH2=$(sudo docker run -d -volumes-from $COUCH1 shykes/couchdb:2013-05-03)
Now, the second container has the all the information from the first volume.
Create a new container which mounts a host directory into it:
-------------------------------------------------------------
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro].
If "host-dir" is missing, then docker creates a new volume.
This is not available for a Dockerfile due the portability and sharing purpose of it. The [host-dir] volumes is something 100% host dependent and will break on any other machine.
For example::
sudo docker run -v /var/logs:/var/host_logs:ro shykes/couchdb:2013-05-03
The command above mounts the host directory ``/var/logs`` into the container with read only permissions as ``/var/host_logs``.
.. versionadded:: v0.5.0
+7 -1
View File
@@ -13,6 +13,12 @@
{%- set url_root = pathto('', 1) %}
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
{%- if current_version == 'latest' %}
{% set github_tag = 'master' %}
{% else %}
{% set github_tag = current_version %}
{% endif %}
<script type="text/javascript">
// This is included here for Javascript that doesn't have access to the templates.
var doc_version = "{{ current_version }}";
@@ -107,7 +113,7 @@
{% block body %}{% endblock %}
</section>
<div class="pull-right"><a href="https://github.com/dotcloud/docker/blob/{{ current_version }}/docs/sources/{{ pagename }}.rst" title="edit this article">Edit this article on GitHub</a></div>
<div class="pull-right"><a href="https://github.com/dotcloud/docker/blob/{{ github_tag }}/docs/sources/{{ pagename }}.rst" title="edit this article">Edit this article on GitHub</a></div>
</div>
</div>
</div>
+1
View File
@@ -0,0 +1 @@
Solomon Hykes <solomon@dotcloud.com>
+82
View File
@@ -0,0 +1,82 @@
package engine
import (
"fmt"
"os"
"log"
"runtime"
"github.com/dotcloud/docker/utils"
)
type Handler func(*Job) string
var globalHandlers map[string]Handler
func Register(name string, handler Handler) error {
if globalHandlers == nil {
globalHandlers = make(map[string]Handler)
}
globalHandlers[name] = handler
return nil
}
// The Engine is the core of Docker.
// It acts as a store for *containers*, and allows manipulation of these
// containers by executing *jobs*.
type Engine struct {
root string
handlers map[string]Handler
}
// New initializes a new engine managing the directory specified at `root`.
// `root` is used to store containers and any other state private to the engine.
// Changing the contents of the root without executing a job will cause unspecified
// behavior.
func New(root string) (*Engine, error) {
// Check for unsupported architectures
if runtime.GOARCH != "amd64" {
return nil, fmt.Errorf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
}
// Check for unsupported kernel versions
// FIXME: it would be cleaner to not test for specific versions, but rather
// test for specific functionalities.
// Unfortunately we can't test for the feature "does not cause a kernel panic"
// without actually causing a kernel panic, so we need this workaround until
// the circumstances of pre-3.8 crashes are clearer.
// For details see http://github.com/dotcloud/docker/issues/407
if k, err := utils.GetKernelVersion(); err != nil {
log.Printf("WARNING: %s\n", err)
} else {
if utils.CompareKernelVersion(k, &utils.KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 {
log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String())
}
}
if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) {
return nil, err
}
eng := &Engine{
root: root,
handlers: globalHandlers,
}
return eng, nil
}
// Job creates a new job which can later be executed.
// This function mimics `Command` from the standard os/exec package.
func (eng *Engine) Job(name string, args ...string) *Job {
job := &Job{
eng: eng,
Name: name,
Args: args,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
handler, exists := eng.handlers[name]
if exists {
job.handler = handler
}
return job
}
+29
View File
@@ -0,0 +1,29 @@
package engine
import (
"testing"
)
func TestNewJob(t *testing.T) {
job := mkJob(t, "dummy", "--level=awesome")
if job.Name != "dummy" {
t.Fatalf("Wrong job name: %s", job.Name)
}
if len(job.Args) != 1 {
t.Fatalf("Wrong number of job arguments: %d", len(job.Args))
}
if job.Args[0] != "--level=awesome" {
t.Fatalf("Wrong job arguments: %s", job.Args[0])
}
}
func TestSetenv(t *testing.T) {
job := mkJob(t, "dummy")
job.Setenv("foo", "bar")
if val := job.Getenv("foo"); val != "bar" {
t.Fatalf("Getenv returns incorrect value: %s", val)
}
if val := job.Getenv("nonexistent"); val != "" {
t.Fatalf("Getenv returns incorrect value: %s", val)
}
}
+42
View File
@@ -0,0 +1,42 @@
package engine
import (
"testing"
"runtime"
"strings"
"fmt"
"io/ioutil"
"github.com/dotcloud/docker/utils"
)
var globalTestID string
func init() {
Register("dummy", func(job *Job) string { return ""; })
}
func mkEngine(t *testing.T) *Engine {
// Use the caller function name as a prefix.
// This helps trace temp directories back to their test.
pc, _, _, _ := runtime.Caller(1)
callerLongName := runtime.FuncForPC(pc).Name()
parts := strings.Split(callerLongName, ".")
callerShortName := parts[len(parts)-1]
if globalTestID == "" {
globalTestID = utils.RandomString()[:4]
}
prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, callerShortName)
root, err := ioutil.TempDir("", prefix)
if err != nil {
t.Fatal(err)
}
eng, err := New(root)
if err != nil {
t.Fatal(err)
}
return eng
}
func mkJob(t *testing.T, name string, args ...string) *Job {
return mkEngine(t).Job(name, args...)
}
+113
View File
@@ -0,0 +1,113 @@
package engine
import (
"io"
"strings"
"fmt"
"encoding/json"
"github.com/dotcloud/docker/utils"
)
// A job is the fundamental unit of work in the docker engine.
// Everything docker can do should eventually be exposed as a job.
// For example: execute a process in a container, create a new container,
// download an archive from the internet, serve the http api, etc.
//
// The job API is designed after unix processes: a job has a name, arguments,
// environment variables, standard streams for input, output and error, and
// an exit status which can indicate success (0) or error (anything else).
//
// One slight variation is that jobs report their status as a string. The
// string "0" indicates success, and any other strings indicates an error.
// This allows for richer error reporting.
//
type Job struct {
eng *Engine
Name string
Args []string
env []string
Stdin io.ReadCloser
Stdout io.WriteCloser
Stderr io.WriteCloser
handler func(*Job) string
status string
}
// Run executes the job and blocks until the job completes.
// If the job returns a failure status, an error is returned
// which includes the status.
func (job *Job) Run() error {
randId := utils.RandomString()[:4]
fmt.Printf("Job #%s: %s\n", randId, job)
defer fmt.Printf("Job #%s: %s = '%s'", randId, job, job.status)
if job.handler == nil {
job.status = "command not found"
} else {
job.status = job.handler(job)
}
if job.status != "0" {
return fmt.Errorf("%s: %s", job.Name, job.status)
}
return nil
}
// String returns a human-readable description of `job`
func (job *Job) String() string {
return strings.Join(append([]string{job.Name}, job.Args...), " ")
}
func (job *Job) Getenv(key string) (value string) {
for _, kv := range job.env {
if strings.Index(kv, "=") == -1 {
continue
}
parts := strings.SplitN(kv, "=", 2)
if parts[0] != key {
continue
}
if len(parts) < 2 {
value = ""
} else {
value = parts[1]
}
}
return
}
func (job *Job) GetenvBool(key string) (value bool) {
s := strings.ToLower(strings.Trim(job.Getenv(key), " \t"))
if s == "" || s == "0" || s == "no" || s == "false" || s == "none" {
return false
}
return true
}
func (job *Job) SetenvBool(key string, value bool) {
if value {
job.Setenv(key, "1")
} else {
job.Setenv(key, "0")
}
}
func (job *Job) GetenvList(key string) []string {
sval := job.Getenv(key)
l := make([]string, 0, 1)
if err := json.Unmarshal([]byte(sval), &l); err != nil {
l = append(l, sval)
}
return l
}
func (job *Job) SetenvList(key string, value []string) error {
sval, err := json.Marshal(value)
if err != nil {
return err
}
job.Setenv(key, string(sval))
return nil
}
func (job *Job) Setenv(key, value string) {
job.env = append(job.env, key + "=" + value)
}
+94 -120
View File
@@ -1,11 +1,10 @@
package gograph
import (
_ "code.google.com/p/gosqlite/sqlite3"
"database/sql"
"fmt"
"os"
"path"
"sync"
)
const (
@@ -25,7 +24,7 @@ const (
`
createEdgeIndices = `
CREATE UNIQUE INDEX "name_parent_ix" ON "edge" (parent_id, name);
CREATE UNIQUE INDEX IF NOT EXISTS "name_parent_ix" ON "edge" (parent_id, name);
`
)
@@ -48,74 +47,73 @@ type WalkFunc func(fullPath string, entity *Entity) error
// Graph database for storing entities and their relationships
type Database struct {
dbPath string
conn *sql.DB
mux sync.Mutex
}
// Create a new graph database initialized with a root entity
func NewDatabase(dbPath string) (*Database, error) {
db := &Database{dbPath}
if _, err := os.Stat(dbPath); err == nil {
return db, nil
func NewDatabase(conn *sql.DB, init bool) (*Database, error) {
if conn == nil {
return nil, fmt.Errorf("Database connection cannot be nil")
}
conn, err := db.openConn()
if err != nil {
return nil, err
}
defer conn.Close()
db := &Database{conn: conn}
if _, err := conn.Exec(createEntityTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeIndices); err != nil {
return nil, err
}
if init {
if _, err := conn.Exec(createEntityTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeIndices); err != nil {
return nil, err
}
rollback := func() {
conn.Exec("ROLLBACK")
}
rollback := func() {
conn.Exec("ROLLBACK")
}
// Create root entities
if _, err := conn.Exec("BEGIN"); err != nil {
return nil, err
}
if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
rollback()
return nil, err
}
// Create root entities
if _, err := conn.Exec("BEGIN"); err != nil {
return nil, err
}
if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("COMMIT"); err != nil {
return nil, err
if _, err := conn.Exec("COMMIT"); err != nil {
return nil, err
}
}
return db, nil
}
// Close the underlying connection to the database
func (db *Database) Close() error {
return db.conn.Close()
}
// Set the entity id for a given path
func (db *Database) Set(fullPath, id string) (*Entity, error) {
conn, err := db.openConn()
if err != nil {
return nil, err
}
defer conn.Close()
// FIXME: is rollback implicit when closing the connection?
db.mux.Lock()
defer db.mux.Unlock()
rollback := func() {
conn.Exec("ROLLBACK")
db.conn.Exec("ROLLBACK")
}
// FIXME: use exclusive transactions to avoid race conditions
if _, err := conn.Exec("BEGIN"); err != nil {
if _, err := db.conn.Exec("BEGIN EXCLUSIVE"); err != nil {
return nil, err
}
var entityId string
if err := conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
if err := db.conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
if err == sql.ErrNoRows {
if _, err := conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
if _, err := db.conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
rollback()
return nil, err
}
@@ -127,19 +125,24 @@ func (db *Database) Set(fullPath, id string) (*Entity, error) {
e := &Entity{id}
parentPath, name := splitPath(fullPath)
if err := db.setEdge(conn, parentPath, name, e); err != nil {
if err := db.setEdge(parentPath, name, e); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("COMMIT"); err != nil {
if _, err := db.conn.Exec("COMMIT"); err != nil {
return nil, err
}
return e, nil
}
func (db *Database) setEdge(conn *sql.DB, parentPath, name string, e *Entity) error {
parent, err := db.get(conn, parentPath)
// Return true if a name already exists in the database
func (db *Database) Exists(name string) bool {
return db.Get(name) != nil
}
func (db *Database) setEdge(parentPath, name string, e *Entity) error {
parent, err := db.get(parentPath)
if err != nil {
return err
}
@@ -147,7 +150,7 @@ func (db *Database) setEdge(conn *sql.DB, parentPath, name string, e *Entity) er
return fmt.Errorf("Cannot set self as child")
}
if _, err := conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
if _, err := db.conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
return err
}
return nil
@@ -162,18 +165,14 @@ func (db *Database) RootEntity() *Entity {
// Return the entity for a given path
func (db *Database) Get(name string) *Entity {
conn, err := db.openConn()
if err != nil {
return nil
}
e, err := db.get(conn, name)
e, err := db.get(name)
if err != nil {
return nil
}
return e
}
func (db *Database) get(conn *sql.DB, name string) (*Entity, error) {
func (db *Database) get(name string) (*Entity, error) {
e := db.RootEntity()
// We always know the root name so return it if
// it is requested
@@ -184,10 +183,13 @@ func (db *Database) get(conn *sql.DB, name string) (*Entity, error) {
parts := split(name)
for i := 1; i < len(parts); i++ {
p := parts[i]
if p == "" {
continue
}
next := db.child(conn, e, p)
next := db.child(e, p)
if next == nil {
return nil, fmt.Errorf("Cannot find child")
return nil, fmt.Errorf("Cannot find child for %s", name)
}
e = next
}
@@ -199,26 +201,22 @@ func (db *Database) get(conn *sql.DB, name string) (*Entity, error) {
// The key will be the full path of the entity
func (db *Database) List(name string, depth int) Entities {
out := Entities{}
conn, err := db.openConn()
e, err := db.get(name)
if err != nil {
return out
}
defer conn.Close()
for c := range db.children(conn, name, depth) {
for c := range db.children(e, name, depth) {
out[c.FullPath] = c.Entity
}
return out
}
func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
conn, err := db.openConn()
e, err := db.get(name)
if err != nil {
return err
}
defer conn.Close()
for c := range db.children(conn, name, depth) {
for c := range db.children(e, name, depth) {
if err := walkFunc(c.FullPath, c.Entity); err != nil {
return err
}
@@ -228,14 +226,8 @@ func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
// Return the refrence count for a specified id
func (db *Database) Refs(id string) int {
conn, err := db.openConn()
if err != nil {
return -1
}
defer conn.Close()
var count int
if err := conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
return 0
}
return count
@@ -244,13 +236,8 @@ func (db *Database) Refs(id string) int {
// Return all the id's path references
func (db *Database) RefPaths(id string) Edges {
refs := Edges{}
conn, err := db.openConn()
if err != nil {
return refs
}
defer conn.Close()
rows, err := conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
if err != nil {
return refs
}
@@ -273,22 +260,20 @@ func (db *Database) RefPaths(id string) Edges {
// Delete the reference to an entity at a given path
func (db *Database) Delete(name string) error {
db.mux.Lock()
defer db.mux.Unlock()
if name == "/" {
return fmt.Errorf("Cannot delete root entity")
}
conn, err := db.openConn()
if err != nil {
return err
}
defer conn.Close()
parentPath, n := splitPath(name)
parent, err := db.get(conn, parentPath)
parent, err := db.get(parentPath)
if err != nil {
return err
}
if _, err := conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, n+"%"); err != nil {
if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
return err
}
return nil
@@ -298,22 +283,19 @@ func (db *Database) Delete(name string) error {
// Walk the graph to make sure all references to the entity
// are removed and return the number of references removed
func (db *Database) Purge(id string) (int, error) {
conn, err := db.openConn()
if err != nil {
return -1, err
}
defer conn.Close()
db.mux.Lock()
defer db.mux.Unlock()
rollback := func() {
conn.Exec("ROLLBACK")
db.conn.Exec("ROLLBACK")
}
if _, err := conn.Exec("BEGIN"); err != nil {
if _, err := db.conn.Exec("BEGIN"); err != nil {
return -1, err
}
// Delete all edges
rows, err := conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
rows, err := db.conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
if err != nil {
rollback()
return -1, err
@@ -325,12 +307,12 @@ func (db *Database) Purge(id string) (int, error) {
}
// Delete entity
if _, err := conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
if _, err := db.conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
rollback()
return -1, err
}
if _, err := conn.Exec("COMMIT"); err != nil {
if _, err := db.conn.Exec("COMMIT"); err != nil {
return -1, err
}
return int(changes), nil
@@ -338,6 +320,9 @@ func (db *Database) Purge(id string) (int, error) {
// Rename an edge for a given path
func (db *Database) Rename(currentName, newName string) error {
db.mux.Lock()
defer db.mux.Unlock()
parentPath, name := splitPath(currentName)
newParentPath, newEdgeName := splitPath(newName)
@@ -345,18 +330,12 @@ func (db *Database) Rename(currentName, newName string) error {
return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
}
conn, err := db.openConn()
if err != nil {
return err
}
defer conn.Close()
parent, err := db.get(conn, parentPath)
parent, err := db.get(parentPath)
if err != nil {
return err
}
rows, err := conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name LIKE ?;", newEdgeName, parent.id, name+"%")
rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
if err != nil {
return err
}
@@ -377,16 +356,15 @@ type WalkMeta struct {
Edge *Edge
}
func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMeta {
func (db *Database) children(e *Entity, name string, depth int) <-chan WalkMeta {
out := make(chan WalkMeta)
e, err := db.get(conn, name)
if err != nil {
if e == nil {
close(out)
return out
}
go func() {
rows, err := conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
if err != nil {
close(out)
}
@@ -420,7 +398,7 @@ func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMe
if depth != -1 {
nDepth -= 1
}
sc := db.children(conn, meta.FullPath, nDepth)
sc := db.children(child, meta.FullPath, nDepth)
for c := range sc {
out <- c
}
@@ -431,18 +409,14 @@ func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMe
}
// Return the entity based on the parent path and name
func (db *Database) child(conn *sql.DB, parent *Entity, name string) *Entity {
func (db *Database) child(parent *Entity, name string) *Entity {
var id string
if err := conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, name+"%").Scan(&id); err != nil {
if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
return nil
}
return &Entity{id}
}
func (db *Database) openConn() (*sql.DB, error) {
return sql.Open("sqlite3", db.dbPath)
}
// Return the id used to reference this entity
func (e *Entity) ID() string {
return e.id
+121 -45
View File
@@ -1,35 +1,41 @@
package gograph
import (
_ "code.google.com/p/gosqlite/sqlite3"
"database/sql"
"fmt"
"os"
"path"
"strconv"
"testing"
)
func newTestDb(t *testing.T) *Database {
db, err := NewDatabase(path.Join(os.TempDir(), "sqlite.db"))
func newTestDb(t *testing.T) (*Database, string) {
p := path.Join(os.TempDir(), "sqlite.db")
conn, err := sql.Open("sqlite3", p)
db, err := NewDatabase(conn, true)
if err != nil {
t.Fatal(err)
}
return db
return db, p
}
func destroyTestDb(db *Database) {
os.Remove(db.dbPath)
func destroyTestDb(dbPath string) {
os.Remove(dbPath)
}
func TestNewDatabase(t *testing.T) {
db := newTestDb(t)
db, dbpath := newTestDb(t)
if db == nil {
t.Fatal("Database should not be nil")
}
defer destroyTestDb(db)
db.Close()
defer destroyTestDb(dbpath)
}
func TestCreateRootEnity(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
root := db.RootEntity()
if root == nil {
t.Fatal("Root entity should not be nil")
@@ -37,8 +43,8 @@ func TestCreateRootEnity(t *testing.T) {
}
func TestGetRootEntity(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
e := db.Get("/")
if e == nil {
@@ -50,8 +56,8 @@ func TestGetRootEntity(t *testing.T) {
}
func TestSetEntityWithDifferentName(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db.Set("/test", "1")
if _, err := db.Set("/other", "1"); err != nil {
@@ -60,8 +66,8 @@ func TestSetEntityWithDifferentName(t *testing.T) {
}
func TestSetDuplicateEntity(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
if _, err := db.Set("/foo", "42"); err != nil {
t.Fatal(err)
@@ -72,8 +78,8 @@ func TestSetDuplicateEntity(t *testing.T) {
}
func TestCreateChild(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
child, err := db.Set("/db", "1")
if err != nil {
@@ -88,8 +94,8 @@ func TestCreateChild(t *testing.T) {
}
func TestListAllRootChildren(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
for i := 1; i < 6; i++ {
a := strconv.Itoa(i)
@@ -104,8 +110,8 @@ func TestListAllRootChildren(t *testing.T) {
}
func TestListAllSubChildren(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
_, err := db.Set("/webapp", "1")
if err != nil {
@@ -146,8 +152,8 @@ func TestListAllSubChildren(t *testing.T) {
}
func TestAddSelfAsChild(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
child, err := db.Set("/test", "1")
if err != nil {
@@ -159,8 +165,8 @@ func TestAddSelfAsChild(t *testing.T) {
}
func TestAddChildToNonExistantRoot(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
if _, err := db.Set("/myapp", "1"); err != nil {
t.Fatal(err)
@@ -172,8 +178,8 @@ func TestAddChildToNonExistantRoot(t *testing.T) {
}
func TestWalkAll(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
@@ -218,8 +224,8 @@ func TestWalkAll(t *testing.T) {
}
func TestGetEntityByPath(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
@@ -265,8 +271,8 @@ func TestGetEntityByPath(t *testing.T) {
}
func TestEnitiesPaths(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
@@ -309,8 +315,8 @@ func TestEnitiesPaths(t *testing.T) {
}
func TestDeleteRootEntity(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
if err := db.Delete("/"); err == nil {
t.Fatal("Error should not be nil")
@@ -318,8 +324,8 @@ func TestDeleteRootEntity(t *testing.T) {
}
func TestDeleteEntity(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
@@ -365,8 +371,8 @@ func TestDeleteEntity(t *testing.T) {
}
func TestCountRefs(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db.Set("/webapp", "1")
@@ -382,8 +388,8 @@ func TestCountRefs(t *testing.T) {
}
func TestPurgeId(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db.Set("/webapp", "1")
@@ -404,8 +410,8 @@ func TestPurgeId(t *testing.T) {
}
func TestRename(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db.Set("/webapp", "1")
@@ -433,8 +439,8 @@ func TestRename(t *testing.T) {
}
func TestCreateMultipleNames(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db.Set("/db", "1")
if _, err := db.Set("/myapp", "1"); err != nil {
@@ -448,8 +454,8 @@ func TestCreateMultipleNames(t *testing.T) {
}
func TestRefPaths(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db.Set("/webapp", "1")
@@ -460,5 +466,75 @@ func TestRefPaths(t *testing.T) {
if len(refs) != 2 {
t.Fatalf("Expected reference count to be 2, got %d", len(refs))
}
}
func TestExistsTrue(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db.Set("/testing", "1")
if !db.Exists("/testing") {
t.Fatalf("/tesing should exist")
}
}
func TestExistsFalse(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db.Set("/toerhe", "1")
if db.Exists("/testing") {
t.Fatalf("/tesing should not exist")
}
}
func TestGetNameWithTrailingSlash(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db.Set("/todo", "1")
e := db.Get("/todo/")
if e == nil {
t.Fatalf("Entity should not be nil")
}
}
func TestConcurrentWrites(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
errs := make(chan error, 2)
save := func(name string, id string) {
if _, err := db.Set(fmt.Sprintf("/%s", name), id); err != nil {
errs <- err
}
errs <- nil
}
purge := func(id string) {
if _, err := db.Purge(id); err != nil {
errs <- err
}
errs <- nil
}
save("/1", "1")
go purge("1")
go save("/2", "2")
any := false
for i := 0; i < 2; i++ {
if err := <-errs; err != nil {
any = true
t.Log(err)
}
}
if any {
t.Fatal()
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ type pathSorter struct {
func sortByDepth(paths []string) {
s := &pathSorter{paths, func(i, j string) bool {
return pathDepth(i) > pathDepth(j)
return PathDepth(i) > PathDepth(j)
}}
sort.Sort(s)
}
+1 -1
View File
@@ -11,7 +11,7 @@ func split(p string) []string {
}
// Returns the depth or number of / in a given path
func pathDepth(p string) int {
func PathDepth(p string) int {
parts := split(p)
if len(parts) == 2 && parts[1] == "" {
return 1
+7 -5
View File
@@ -2,6 +2,7 @@ package docker
import (
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
@@ -94,7 +95,7 @@ func (graph *Graph) Get(name string) (*Image, error) {
}
// Create creates a new image and registers it in the graph.
func (graph *Graph) Create(layerData Archive, container *Container, comment, author string, config *Config) (*Image, error) {
func (graph *Graph) Create(layerData archive.Archive, container *Container, comment, author string, config *Config) (*Image, error) {
img := &Image{
ID: GenerateID(),
Comment: comment,
@@ -117,7 +118,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut
// Register imports a pre-existing image into the graph.
// FIXME: pass img as first argument
func (graph *Graph) Register(jsonData []byte, layerData Archive, img *Image) error {
func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Image) error {
if err := ValidateID(img.ID); err != nil {
return err
}
@@ -146,7 +147,7 @@ func (graph *Graph) Register(jsonData []byte, layerData Archive, img *Image) err
// The archive is stored on disk and will be automatically deleted as soon as has been read.
// If output is not nil, a human-readable progress bar will be written to it.
// FIXME: does this belong in Graph? How about MktempFile, let the caller use it for archives?
func (graph *Graph) TempLayerArchive(id string, compression Compression, sf *utils.StreamFormatter, output io.Writer) (*TempArchive, error) {
func (graph *Graph) TempLayerArchive(id string, compression archive.Compression, sf *utils.StreamFormatter, output io.Writer) (*archive.TempArchive, error) {
image, err := graph.Get(id)
if err != nil {
return nil, err
@@ -155,11 +156,11 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, sf *uti
if err != nil {
return nil, err
}
archive, err := image.TarLayer(compression)
a, err := image.TarLayer(compression)
if err != nil {
return nil, err
}
return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf, true), tmp.Root)
return archive.NewTempArchive(utils.ProgressReader(ioutil.NopCloser(a), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf, true), tmp.Root)
}
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
@@ -201,6 +202,7 @@ func (graph *Graph) getDockerInitLayer() (string, error) {
"/proc": "dir",
"/sys": "dir",
"/.dockerinit": "file",
"/.dockerenv": "file",
"/etc/resolv.conf": "file",
"/etc/hosts": "file",
"/etc/hostname": "file",

Some files were not shown because too many files have changed in this diff Show More