Compare commits

...

252 Commits

Author SHA1 Message Date
Daniel Mizyrycki
2e7649beda Merge pull request #920 from dotcloud/919-packaging
Packaging, issue 919: Bump version to 0.4.2
2013-06-18 01:08:03 -07:00
Daniel Mizyrycki
e664a46ff3 Packaging, issue 919: Bump version to 0.4.2 2013-06-17 19:50:31 -07:00
Daniel Mizyrycki
bd9bf9b646 Packaging|dockerbuilder, issue #761: use docker-golang PPA. Add Dockerfile header 2013-06-17 14:08:50 -07:00
Victor Vieux
7b6f50772c Merge pull request #912 from dotcloud/bump_0.4.1
Bumped version to 0.4.1
2013-06-17 14:06:17 -07:00
Solomon Hykes
6e2c32eb9a Merge pull request #911 from dotcloud/add_port_redirection_doc
* Documentation: add port redirection doc
2013-06-17 13:57:08 -07:00
Solomon Hykes
22b0a38df5 Merge pull request #897 from dotcloud/fix-overlapping-add
* Builder: ADD improvements: use tar for copy + automatically unpack local archives
2013-06-17 13:32:34 -07:00
Solomon Hykes
cb58e63fc5 Typo 2013-06-17 14:28:04 -06:00
Solomon Hykes
8626598753 Added content to port redirect doc 2013-06-17 13:25:50 -07:00
Victor Vieux
36231345f1 add port redirection doc 2013-06-17 22:05:58 +02:00
Victor Vieux
e8f001d451 Bumped version to 0.4.1 2013-06-17 19:15:21 +00:00
Daniel Mizyrycki
389db5f598 Merge pull request #887 from dotcloud/761-packaging-dockerbuilder
Packaging, dockerbuilder: Automate pushing docker binary packages
2013-06-17 08:40:01 -07:00
Solomon Hykes
6746c385bd FIXMEs 2013-06-15 12:24:20 -07:00
Solomon Hykes
30f604517a Merge pull request #895 from dotcloud/build-fixes
- Builder: fix a bug which caused builds to fail if ADD was the first command
2013-06-15 09:23:41 -07:00
Solomon Hykes
080f35fe65 Fix a bug which caused builds to fail if ADD was the first command 2013-06-15 09:16:35 -07:00
Solomon Hykes
5b8287617d + Builder: ADD of a local file will detect tar archives and unpack them
into the container instead of copying them as a regular file.

* Builder: ADD uses tar/untar for copies instead of calling 'cp -ar'.
	This is more consistent, reduces the number of dependencies, and
	fixe #896.
2013-06-14 16:43:39 -07:00
Solomon Hykes
5799806414 FIXMEs 2013-06-14 16:29:19 -07:00
Solomon Hykes
14265d9a18 Various FIXME items 2013-06-14 15:11:34 -07:00
Solomon Hykes
17235eb089 Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-06-14 15:07:05 -07:00
Solomon Hykes
7f118519eb Remove duplicate 'WARNING' 2013-06-14 14:46:08 -07:00
Solomon Hykes
250e47e2eb Merge branch 'dns_server_side'
+ Configure dns configuration host-wide with 'docker -d -dns'
+ Detect faulty DNS configuration and replace it with a public default
2013-06-14 14:39:05 -07:00
Guillaume J. Charmes
f413fb8e56 Merge pull request #857 from edx/856-vagrant-port-forwarding
* Vagrantfile: Add an option to forward all ports to the vagrant host that have been ex...
2013-06-14 14:37:55 -07:00
Guillaume J. Charmes
f0e43dcdb1 Merge pull request #607 from dotcloud/expose_api_port_vagrant-feature
* Vagrantfile: Add the rest api port to vagrantfile's port_forward
2013-06-14 14:35:53 -07:00
Guillaume J. Charmes
813771e6b7 Merge pull request #892 from unclejack/validate_memory_limits
* Runtime: validate memory limits & error out if it's less than 524288
2013-06-14 14:32:28 -07:00
unclejack
d3f83a6592 add test: fail to create container if mem limit < 512KB 2013-06-14 22:55:00 +03:00
Andy Rothfusz
7958f1f694 Add examples for local import. 2013-06-14 13:42:59 -06:00
Guillaume J. Charmes
4a02c6dab1 Merge pull request #816 from unclejack/524-fix_aufs_links_related_warnings
524 fix aufs links related warnings
2013-06-14 12:32:47 -07:00
Guillaume J. Charmes
165d343d06 Merge pull request #663 from dotcloud/662-fix_push_html_404-fix
* Registry: add regexp check on repo's name
2013-06-14 12:30:44 -07:00
Solomon Hykes
c701de939f Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-06-14 11:58:46 -07:00
Guillaume J. Charmes
05b87d2d5b Merge pull request #868 from dotcloud/postupload-endpoints-header
- Registry: Send X-Docker-Endpoints at the end of a push
2013-06-14 11:53:54 -07:00
Guillaume J. Charmes
78e4a385f7 Merge branch 'master' into postupload-endpoints-header
Conflicts:
	server.go
2013-06-14 11:50:58 -07:00
unclejack
822abab17e install aufs-tools when setting up the testing vagrant box 2013-06-14 21:41:12 +03:00
unclejack
f1d16ea003 install aufs-tools when setting up the hack vagrant box 2013-06-14 21:41:12 +03:00
unclejack
fb7eaf67d1 add aufs-tools package to dev env docs page 2013-06-14 21:41:12 +03:00
unclejack
e53721ef69 add aufs-tools to lxc-docker dependencies 2013-06-14 21:38:15 +03:00
unclejack
2f67a62b5b run auplink before unmounting aufs 2013-06-14 21:38:15 +03:00
unclejack
9ee11161bf validate memory limits & error out if less than 512 KB 2013-06-14 19:52:44 +03:00
Victor Vieux
e49f82b9e1 update docs 2013-06-14 10:10:26 +00:00
Victor Vieux
ddf5a1940f Merge branch 'master' into 22-add_sizes_images_and_containers-feature 2013-06-14 10:05:06 +00:00
Victor Vieux
00cf2a1fa2 fix virtual size on images 2013-06-14 10:05:01 +00:00
Daniel Mizyrycki
3384943cd3 Packaging, dockerbuilder: Automate pushing docker binary packages 2013-06-13 22:14:43 -07:00
Guillaume J. Charmes
452128f0da Remove run() where it is not needed within the builder 2013-06-13 15:18:15 -07:00
Guillaume J. Charmes
2eaa0a1dd7 Fix non-tty run issue 2013-06-13 12:57:35 -07:00
Guillaume J. Charmes
8085754507 Merge pull request #751 from dotcloud/660-auth_client-feature
* Registry: Move auth to the client
2013-06-13 11:52:40 -07:00
Victor Vieux
c46382ba29 rebase master 2013-06-13 17:58:06 +00:00
Guillaume J. Charmes
42d1c36a5c Fix merge issue 2013-06-13 10:25:43 -07:00
Guillaume J. Charmes
51a4b65101 Merge pull request #883 from unclejack/build-docker-with-go1.1.1
use Go 1.1.1 to build docker
2013-06-13 10:20:38 -07:00
Guillaume J. Charmes
30fb45c494 Merge pull request #799 from dotcloud/691-run_id-feature
* Runtime: allow docker run <name>:<id>
2013-06-13 10:14:40 -07:00
Victor Vieux
9cdd39e0d7 Merge branch 'master' into 691-run_id-feature 2013-06-13 13:18:43 +00:00
Victor Vieux
45a8945746 added test 2013-06-13 13:17:56 +00:00
Victor Vieux
697282d6ad Merge pull request #804 from dotcloud/no_stdout_stale-fix
*Runtime: Fix stale command when stdout is not allocated
2013-06-13 04:22:29 -07:00
unclejack
78a76ad50e use Go 1.1.1 to build docker 2013-06-13 08:59:41 +03:00
Solomon Hykes
5ecfe13be9 Merge branch '610-improve_rmi-feature'
* Runtime: improved image removal to garbage-collect unreferenced parents
- Runtime: fixed image removal to cleanly remove tags and repositories
2013-06-12 20:30:07 -07:00
Guillaume J. Charmes
0bc1c6d57a Merge pull request #826 from dotcloud/825-move_xino_shm-fix
- Runtime: fix aufs mount on ubuntu13.04+btrfs
2013-06-12 17:20:42 -07:00
Solomon Hykes
f57175cbad FIXME: a loose collection of FIXMEs for internal use by the maintainers 2013-06-12 15:28:59 -07:00
Sam Alba
81a11a3c30 Update NOTICE 2013-06-12 15:50:30 -06:00
Sam Alba
04cca097ae Update README.md 2013-06-12 15:50:09 -06:00
Andy Rothfusz
48897b5fa1 Merge pull request #845 from unclejack/841-update_docs_no_add_without_context
841 - docs: warn about the transmission of data to the docker daemon & ADD without context
2013-06-12 14:25:44 -07:00
Solomon Hykes
ecae342434 New roadmap item: advanced port redirections 2013-06-12 10:50:47 -07:00
Victor Vieux
f2383151cb bump to master 2013-06-12 17:39:32 +00:00
Solomon Hykes
b4565af256 Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-06-12 10:23:14 -07:00
Guillaume J. Charmes
c85e775162 Merge pull request #844 from dotcloud/843-inspect_multiple_params-feature
* Runtime: allow multiple params in inspect
2013-06-12 10:18:42 -07:00
Guillaume J. Charmes
3491df6edb Merge pull request #852 from dotcloud/556-docker-search-fmt
Remove CR/NL from description in docker CLI
2013-06-12 10:17:05 -07:00
Guillaume J. Charmes
0e6ec57996 Merge pull request #874 from fsouza/fix-build-newline
- Builder: don't ignore last line in Dockerfile when it doesn't end with \n
2013-06-12 10:15:00 -07:00
Guillaume J. Charmes
f37b158982 Merge pull request #877 from fsouza/fix-hijack
- Runtime: use in instead of os.Stdin in hijack
2013-06-12 10:14:37 -07:00
Francisco Souza
da54abaf2e commands: use in instead of os.Stdin in hijack 2013-06-12 09:54:37 -03:00
Solomon Hykes
092c761cec Merge pull request #853 from kencochrane/registry-api-1.1-fix
* Documentation: separate the registry and index API's into their own docs
2013-06-11 18:34:10 -07:00
Solomon Hykes
5edafd6284 Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-06-11 11:21:57 -07:00
Solomon Hykes
d64f105b44 Added a readme explaining the role of each API 2013-06-11 10:39:02 -07:00
Victor Vieux
2d5eda5141 Merge pull request #864 from dotcloud/851-choose_public_port-feature
* Runtime: you can now specify public port (ex: -p 80:4500)
2013-06-11 10:11:10 -07:00
Solomon Hykes
be15d5f2d9 Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-06-11 10:09:34 -07:00
Solomon Hykes
5918a5a322 More principles. Raw and unstructured to spawn discussion. 2013-06-11 09:27:36 -07:00
Solomon Hykes
f8af296e6f Merge pull request #865 from dotcloud/errors_commands-fix
Display StatusText as error when empty body in commands.go
2013-06-11 09:16:54 -07:00
Solomon Hykes
432e18990b Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-06-11 08:58:23 -07:00
Francisco Souza
2e9403b047 build: don't ignore last line in Dockerfile when it doesn't end with \n 2013-06-11 11:39:06 -03:00
Victor Vieux
3ea6a2c7c3 add Michael Crosby to AUTHORS 2013-06-11 10:17:39 +00:00
Victor Vieux
20bf0e00e8 * Remote Api: Add flag to enable cross domain requests 2013-06-11 10:12:36 +00:00
Michael Crosby
dd53c457d7 Add OPTIONS to route map
Move the OPTIONS method registration into the existing
route map.  Also add support for empty paths in
the map.
2013-06-10 16:10:40 -09:00
Michael Crosby
ac599d6528 Add explicit status response to OPTIONS handler
Write the http.StatusOK header in the OPTIONS
handler and update the unit tests to refer to the
response code using the const from the http package.
2013-06-10 14:44:10 -09:00
Andy Rothfusz
ca4597e9d7 Add links to libraries, fix #800 2013-06-10 15:22:34 -07:00
Andy Rothfusz
eeea9ac946 Add list of Docker Remote API Client Libraries. Fixes #800. 2013-06-10 15:17:27 -07:00
Michael Crosby
0a28628c02 Add Cors and OPTIONS route unit tests
Move creating the router and populating the
routes to a separate function outside of
ListenAndServe to allow unit tests to make
assertions on the configured routes and handler
funcs.
2013-06-10 13:02:40 -09:00
Victor Vieux
bcc4754dc1 Merge pull request #869 from fsouza/fix-api-docs
docs/api/remote: fix rst syntax in the "Search images" section
2013-06-10 14:21:39 -07:00
Victor Vieux
66d9a73362 rebump 2013-06-10 21:05:54 +00:00
Andy Rothfusz
5712e37437 Merge pull request #840 from dhrp/just-fixed-some-links
Fixed some links. Closes #839 #838 #835
2013-06-10 13:51:43 -07:00
Francisco Souza
b1ed75078e docs/api/remote: fix rst syntax in the "Search images" section 2013-06-10 16:07:57 -03:00
Joffrey F
47d7486bbe Merge pull request #814 from dotcloud/708-pushpull-multislash
Support for special namespace 'src' (highland support)
2013-06-10 11:29:39 -07:00
shin-
d227af1edd Escape remote names on repo push/pull 2013-06-10 11:28:27 -07:00
shin-
4e18010731 Support for special namespace 'src' (highland support) 2013-06-10 11:28:26 -07:00
shin-
db3242e4bb Send X-Docker-Endpoints header when validating the images upload with the index at the end of a push 2013-06-10 11:21:56 -07:00
Guillaume J. Charmes
7169212683 Fix typo 2013-06-10 11:08:40 -07:00
Guillaume J. Charmes
2a6a1d439c Merge pull request #867 from Turbo87/patch-1
Fixed broken link in README
2013-06-10 10:57:09 -07:00
Tobias Bieniek
37c20fa64b Fixed broken link in README 2013-06-10 19:03:54 +03:00
Victor Vieux
ab0d0a28a8 fix errors when no body 2013-06-10 15:06:52 +00:00
Victor Vieux
0de3f1ca9a add tests 2013-06-10 14:14:54 +00:00
Victor Vieux
95d66ebc6b specify public port 2013-06-10 13:56:43 +00:00
Michael Crosby
393e873d25 Add Access-Control-Allow-Methods header
Add the Access-Control-Allow-Methods header so that
DELETE operations are allowed.

Also move the write CORS headers method before
docker writes a 404 not found so that the client
receives the correct response and not an invalid
CORS request.
2013-06-09 17:17:35 -09:00
Guillaume J. Charmes
956491f853 Merge pull request #855 from samjsharpe/fix_missing_hyphen
Build from Dockerfile on stdin requires a hyphen
2013-06-07 13:04:27 -07:00
Calen Pennington
302660e362 Add an option to forward all ports to the vagrant host that have been exported from docker containers 2013-06-07 15:56:39 -04:00
Sam J Sharpe
5e6cd21f8b Build from Dockerfile on stdin requires a hypen
There is a missing hypen in the documentation:
    `docker build < Dockerfile` will complain
    `docker build - < Dockerfile` will not complain
2013-06-07 20:35:34 +01:00
Ken Cochrane
9e1cd37bbc seperated the registry and index API's into their own docs
seperated the registry and index API's into their own docs and merged
in the index search api into the index api. Also renamed the original
registry api to registry_index_spec.
2013-06-07 13:42:52 -04:00
shin-
8d4282cd36 Remove CR/NL from description in docker CLI. Also moved description shortening to the client 2013-06-07 06:09:24 -07:00
Solomon Hykes
968e08a9ba Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-06-07 02:41:08 +02:00
unclejack
4b3a381f39 docs: build: ADD copies just needed data w/ full path 2013-06-07 00:39:43 +03:00
Solomon Hykes
56473d4cce Typo in MAINTAINERS file 2013-06-06 22:52:55 +02:00
unclejack
efa7ea592c docs: warn about build data tx & ADD w/o context
This updates the documentation to mention that:
1. a lot of data may get sent to the docker daemon if there is a lot of
data in the directory passed to docker build
2. ADD doesn't work in the absence of the context
3. running without a context doesn't send file data to the docker
daemon
4. explain that the data sent to the docker daemon will be used by ADD
commands
2013-06-06 22:06:12 +03:00
Guillaume J. Charmes
afd325a884 Solve an issue with the -dns in daemon mode 2013-06-06 11:01:29 -07:00
Guillaume J. Charmes
a3f6054f97 Check for local dns server and output a warning 2013-06-06 11:01:09 -07:00
Sam Alba
da937bf214 Update README.md 2013-06-06 11:09:11 -06:00
Solomon Hykes
42b63eb818 Daniel Mizyrycki is maintainer of dockerbuilder, the official build environment for docker binary releases 2013-06-06 19:06:54 +02:00
Solomon Hykes
0d6db333d6 docker-build contrib script is deprecated by the new 'build' command 2013-06-06 19:05:21 +02:00
Solomon Hykes
3999465c85 Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-06-06 19:04:17 +02:00
Daniel Mizyrycki
1cc4049e82 Merge pull request #827 from dotcloud/dev_environment_update
Update "Setting Up a Dev Environment" doc, with modern golang  PPA and stable lxc kernel
2013-06-06 09:48:19 -07:00
Victor Vieux
4107701062 add [] and move errors to stderr 2013-06-06 15:45:08 +00:00
Victor Vieux
a799cdad3e allow multiple params in inspect 2013-06-06 15:22:54 +00:00
Victor Vieux
a118ad90ed changed to 12.04 and add kernel 2013-06-06 12:36:28 +00:00
Thatcher Peskens
0f23fb949d Fixed some links
* Added Google group to FAQ on docs
* Changed IRC link
* Fixed link to contributing broken by 326faec
2013-06-05 18:06:51 -07:00
Thatcher
f1992eeea5 Merge pull request #817 from dhrp/blog-in-navigation
Modified the navigation in both website and documentaion to include the blog.
2013-06-05 17:28:19 -07:00
Guillaume J. Charmes
84d68007cb Add -dns to docker daemon 2013-06-05 14:20:54 -07:00
Victor Vieux
bf63cb9045 bump to master again 2013-06-05 16:01:36 +00:00
Victor Vieux
ce0041832c bump to master 2013-06-05 15:30:45 +00:00
Solomon Hykes
97d5f525f4 hack/PRINCIPLES.md: a list of principles guiding Docker's design. The goal is to scale the decision-making in the project and remove @shykes as a bottleneck as much as possible 2013-06-05 17:27:53 +02:00
Solomon Hykes
2ea29ce0ef hack/ROADMAP.md: a high-level roadmap. Make a pull request to suggest changes 2013-06-05 17:26:26 +02:00
Solomon Hykes
068076f775 Merge pull request #822 from lopter/master
* Client: Print the container id before the hijack in `docker run` (see also #804)
2013-06-05 08:08:30 -07:00
Solomon Hykes
34c8b24211 Merge pull request #812 from dotcloud/809-progress_message-fix
* Remote API: Fix progress message in client
2013-06-05 07:27:31 -07:00
Victor Vieux
e3cc625315 update doc to newer go 2013-06-05 13:19:49 +00:00
Victor Vieux
f67ea78cce move xino stuff to /dev/shm 2013-06-05 12:59:05 +00:00
Victor Vieux
6255112926 updated doc 2013-06-05 13:19:57 +02:00
Victor Vieux
c906239220 bump to master 2013-06-05 10:23:45 +00:00
Victor Vieux
b4682e6707 bump to master 2013-06-05 10:19:51 +00:00
Solomon Hykes
04050c4173 Merge pull request #818 from johncosta/ubuntu-1304-add-apt-repository
Remove provider specifc language
2013-06-05 02:40:09 -07:00
Louis Opter
7e6ede6379 Print the container id before the hijack in docker run (see also #804)
This is useful when you want to get the container id before you start to
interact with stdin (which is what I'm doing in dotcloud/sandbox).
2013-06-04 15:32:59 -07:00
Guillaume J. Charmes
63e80384ea Fix nil pointer on some situatuion 2013-06-04 14:35:32 -07:00
Guillaume J. Charmes
7ef9833dbb Put back panic for go1.0.3 compatibility 2013-06-04 14:26:40 -07:00
Guillaume J. Charmes
c1ee9bf881 Merge pull request #808 from dotcloud/795-lintify
Cleanup source
2013-06-04 14:20:38 -07:00
John Costa
c000ef194c Remove provider specifc language 2013-06-04 16:01:38 -04:00
Ken Cochrane
479ac9afa7 Merge pull request #802 from johncosta/ubuntu-1304-add-apt-repository
- Documentation: adding missing dependency to the ubuntu linux install page.
2013-06-04 11:55:34 -07:00
Thatcher Peskens
716892b95d Modified the navigation in both website and documentaion to include the Blog. 2013-06-04 11:41:54 -07:00
Thatcher
d7a6485dfe Merge pull request #796 from dhrp/added-and-fixed-links
Added and fixed some links (closes #502)
2013-06-04 11:28:40 -07:00
Victor Vieux
fd224ee590 linted names 2013-06-04 18:00:22 +00:00
Victor Vieux
3922691fb9 fix progress message in client 2013-06-04 16:09:08 +00:00
Sam Alba
c566c8efc7 Merge pull request #810 from dotcloud/proxy-fix
fix regression on proxy
2013-06-04 08:46:40 -07:00
Victor Vieux
06b585ce8a fix proxy 2013-06-04 15:44:27 +00:00
John Costa
e61af8bc62 some installations of ubuntu 13.04 (digital ocean Ubuntu 13.04 x64 Server in this case) require software-properties-common to be installed before being able to use add-apt-repository 2013-06-04 10:40:44 -04:00
Victor Vieux
b6825f98c0 bump to master 2013-06-04 14:00:18 +00:00
Victor Vieux
86ada2fa5d drop/omit 2013-06-04 13:51:12 +00:00
Victor Vieux
b515a5a9ec go vet 2013-06-04 13:24:58 +00:00
Michael Crosby
6d5bdff394 Add flag to enable cross domain requests in Api
Add the -api-enable-cors flag when running docker
in daemon mode to allow CORS requests to be made to
the Remote Api.  The default value is false for this
flag to not allow cross origin request to be made.

Also added a handler for OPTIONS requests the standard
for cross domain requests is to initially make an
OPTIONS request to the api.
2013-06-03 21:39:00 -04:00
Guillaume J. Charmes
0ca8844398 Fix stale command with stdout is not allocated 2013-06-03 17:39:29 -07:00
Sam Alba
10ef4f7f39 Merge pull request #797 from dotcloud/registry-fix-missing-body-close
registry.go: Fixed missing Body.Close()
2013-06-03 14:43:50 -07:00
Sam Alba
cff3b37a61 Disabled HTTP keep-alive in the default HTTP client for Registry calls 2013-06-03 14:42:21 -07:00
Victor Vieux
d26a3b37a6 allow docker run <name>:<id> 2013-06-03 20:00:15 +00:00
Guillaume J. Charmes
82dd963e08 Minor changes in registry.go 2013-06-03 12:20:52 -07:00
Sam Alba
830c458fe7 Fixed missing Body.Close when doing some HTTP requests. It should improve some request issues. 2013-06-03 12:14:57 -07:00
Thatcher Peskens
38f29f7d0c Changed some text on the website to include docker-club
Fixed a link in the FAQ on the docs
Fixed a detail in the Makefile for pushing the site to dotcloud
2013-06-03 11:45:19 -07:00
Solomon Hykes
a8ae398bf5 Bumped version to 0.4.0 2013-06-03 10:59:48 -07:00
Victor Vieux
7e59b83053 removed auth in pull 2013-06-03 17:51:52 +00:00
Guillaume J. Charmes
7a4408f608 Merge pull request #794 from dotcloud/780-diff-fix2
- Runtime: remove TrimLeft as it's go1.1
2013-06-03 10:41:08 -07:00
Victor Vieux
854039b6ba remove TrimLeft as it's go1.1 2013-06-03 17:25:51 +00:00
Guillaume J. Charmes
070923b14f Merge pull request #792 from dotcloud/780-diff-fix
- Runtime: fix Path corruption in 'docker diff'
2013-06-03 10:06:25 -07:00
Victor Vieux
71b1657e8d added test 2013-06-03 17:02:57 +00:00
Guillaume J. Charmes
1bafe9da26 Merge pull request #793 from dotcloud/774-remove_login_check_on_pull-fix
- Registry: remove login check on pull
2013-06-03 09:37:02 -07:00
Victor Vieux
1ce4ba6c9f remove check on login 2013-06-03 15:33:29 +00:00
Victor Vieux
a55a0d370d ([a-z0-9_]{4,30})/([a-zA-Z0-9-_.]+) 2013-06-03 14:23:57 +00:00
Guillaume J. Charmes
2b1b3c1270 Merge pull request #784 from dotcloud/remove_cgo_dependency
* Runtime: Remove cgo dependency
2013-06-03 07:03:17 -07:00
Guillaume J. Charmes
8243f2510e Update test to reflect new ApiInfo struct 2013-06-03 06:44:00 -07:00
Guillaume J. Charmes
0443cc351d Merge pull request #772 from dotcloud/improve_version_info_cmds
* API: Improve version info cmds
2013-06-03 06:36:09 -07:00
Victor Vieux
ca902b6be4 bump master 2013-06-03 12:37:51 +00:00
Victor Vieux
844a8db6c6 add debug 2013-06-03 12:21:22 +00:00
Victor Vieux
3dd1e4d58c added docs and moved to api version 1.2 2013-06-03 12:09:16 +00:00
Victor Vieux
62c78696cd bump to master 2013-06-03 11:06:13 +00:00
Victor Vieux
e16c93486d fix Path corruption in 'docker diff' 2013-06-03 10:19:20 +00:00
Solomon Hykes
e42eb7fa8c Meta: added Guillaume as primary maintainer for tty code 2013-06-02 23:42:18 -07:00
Solomon Hykes
cebfde9ea5 Merge pull request #787 from gasi/nodejs-centos-docs
* Documentation: Deploying a Node.js Web App on CentOS
* Documentation: small formatting improvements
2013-06-02 23:27:29 -07:00
Solomon Hykes
eff7a15bea Merge pull request #785 from jweede/master
* Documentation: spelling correction in website
2013-06-02 23:14:09 -07:00
Daniel Gasienica
82dadc2005 Document installation of npm dependencies /ht @grigio 2013-06-02 20:10:22 -07:00
Guillaume J. Charmes
2d52d4d614 Merge pull request #789 from samjsharpe/fix-extra-brace
Removes a brace in the description of the wait command
2013-06-02 15:22:11 -07:00
Sam J Sharpe
ca5ae266b7 Removes a brace in the description of the wait command 2013-06-02 22:40:56 +01:00
Daniel Gasienica
464765b940 Add link at the beginning 2013-06-01 22:16:26 -07:00
Daniel Gasienica
e9ffc1e499 Add Node.js web app example using CentOS 2013-06-01 22:06:53 -07:00
Daniel Gasienica
4fb9a6eafb Use code blocks 2013-06-01 22:03:41 -07:00
Daniel Gasienica
157547845a Name examples consistently 2013-06-01 22:03:28 -07:00
Daniel Gasienica
2935ca7ee2 Use title case for consistency 2013-06-01 22:03:12 -07:00
Daniel Gasienica
23452f1573 Use em dash in title 2013-06-01 22:02:24 -07:00
Daniel Gasienica
f6f345b1fe Fix typo 2013-06-01 21:55:01 -07:00
Daniel Gasienica
e3fd61ad74 Add more tags 2013-06-01 21:27:27 -07:00
Daniel Gasienica
01ce63aacd Make style consistent 2013-06-01 21:26:58 -07:00
Daniel Gasienica
3ca9c11110 Add Mac OS X instructions for doc tools 2013-06-01 21:26:18 -07:00
Daniel Gasienica
b4df0b17af Add make server command to preview docs 2013-06-01 21:25:51 -07:00
Jon Wedaman
7f65bf508e Spelling correction in docs 2013-06-01 21:48:32 -04:00
Guillaume J. Charmes
a70dd65964 Move Termios struct to os specific file 2013-06-01 16:19:50 -07:00
Guillaume J. Charmes
3cc0963ad1 Remove unused constants 2013-06-01 15:55:52 -07:00
Guillaume J. Charmes
31eb01ae8a Use uintptr instead of int for Fd 2013-06-01 15:55:05 -07:00
Guillaume J. Charmes
64f346779f Remove cgo from term 2013-06-01 15:51:45 -07:00
Guillaume J. Charmes
078a19d725 Merge pull request #777 from dotcloud/bsdtar-env
Pass a guaranteed environment to bsdtar for predictable behaviour
2013-06-01 11:28:55 -07:00
Solomon Hykes
561ceac55d * Runtime: pass a guaranteed environment to bsdtar for predictable behavior without depending on the underlying host configuration. 2013-05-31 22:25:48 -07:00
Guillaume J. Charmes
a373c770b6 Merge pull request #707 from unclejack/411-add-arch-field
411 add architecture field
2013-05-31 17:26:11 -07:00
Solomon Hykes
90b8c5ce67 Merge pull request #762 from dotcloud/761-packaging-dockerbuilder
Packaging: Ensure dockerbuilder can build docker PPA
2013-05-31 16:28:44 -07:00
Guillaume J. Charmes
9bc71c101c Merge pull request #719 from dotcloud/json_stream-feature
* API: push, pull, import, insert -> Json Stream
2013-05-31 16:05:15 -07:00
Guillaume J. Charmes
f41d2ec4d9 Update api docs 2013-05-31 15:56:30 -07:00
Guillaume J. Charmes
1dae7a25b9 Improve the docker version and docker info commands 2013-05-31 15:53:57 -07:00
Daniel Mizyrycki
926c1d45aa Merge pull request #533 from tianon/mkimage-debian
Update mkimage-debian.sh now that wheezy is officially the stable release
MD: Ref issue #447
2013-05-31 14:53:37 -07:00
Guillaume J. Charmes
80b8756da3 Merge pull request #727 from dotcloud/remove_hijack_logs-feature
* API: remove hijack on the client in logs, and split stdout / stderr
2013-05-31 14:43:59 -07:00
Guillaume J. Charmes
7d167590bc Merge pull request #766 from dotcloud/prevent_attach_stopped_container-feature
returns an error if the container we want to attach is not running
2013-05-31 14:41:57 -07:00
Solomon Hykes
76bb920449 Merge pull request #753 from dotcloud/registry_api_remove_teams
Remove teams from the registry API
2013-05-31 12:41:12 -07:00
Solomon Hykes
1ac36a3adf Merge pull request #767 from gasi/master
Fix minor documentation error in ‘Running Redis Service’ example
2013-05-31 12:24:25 -07:00
Solomon Hykes
46bdbbabba Merge pull request #755 from dotcloud/infrastructure2
+ Meta: organize the project infrastructure: servers, dns, email etc.
2013-05-31 12:23:55 -07:00
Daniel Gasienica
766a2db0d9 Add Daniel Gasienica to AUTHORS 2013-05-31 12:19:57 -07:00
Daniel Gasienica
fd0c501e6d Fix minor documentation error in ‘Running Redis Service’ example 2013-05-31 12:19:49 -07:00
Victor Vieux
468e4c4b56 returns an error if the container we want to attach is not running 2013-05-31 15:34:23 +00:00
Guillaume J. Charmes
9eda9154a7 Merge pull request #764 from dotcloud/add_t_parameter_doc_build-fix
add missing -t parameter in the doc
2013-05-31 07:49:29 -07:00
Victor Vieux
2baea24879 add -t parameter in the doc 2013-05-31 14:40:09 +00:00
Victor Vieux
9060b5c2f5 added proper returns type, move the auto-prune in v1.1 api 2013-05-31 14:37:02 +00:00
Daniel Mizyrycki
1040225e36 Packaging: Ensure dockerbuilder can build docker PPA 2013-05-31 00:59:18 -07:00
Andy Rothfusz
1c091657d4 Merge pull request #745 from rogaha/doc_ssh_service_example_uptd
Add screencast summary. Makes it easy to reproduce steps.
2013-05-30 18:49:32 -07:00
Victor Vieux
3afdd82e42 bump to master 2013-05-30 23:38:40 +00:00
Victor Vieux
bd38b47552 bump to master 2013-05-30 23:32:57 +00:00
Victor Vieux
5aa95b667c WIP needs to fix HTTP error codes 2013-05-30 22:53:45 +00:00
Guillaume J. Charmes
054451fd19 NON-WORKING: Beginning of rmi refactor 2013-05-30 12:30:21 -07:00
Solomon Hykes
43f369ea0c Organize the project infrastructure: servers, dns, email etc. 2013-05-30 12:28:24 -07:00
Solomon Hykes
531b30119a Remove special case for 'teams' from registry api 2013-05-30 11:37:58 -07:00
Victor Vieux
cd002a4d16 ensure progress downloader is well formated 2013-05-30 17:00:42 +00:00
Victor Vieux
49e656839f move auth to the client WIP 2013-05-30 15:39:43 +00:00
Roberto Gandolfo Hashioka
c7af917d13 updated the running ssh service example with the video's transcription 2013-05-29 11:29:30 -07:00
Guillaume J. Charmes
c05e9f856d Error output fix for docker rmi 2013-05-29 11:11:19 -07:00
Victor Vieux
7e92302c4f auto prune WIP 2013-05-29 17:27:32 +00:00
Victor Vieux
94f0d478de refacto 2013-05-29 17:01:54 +00:00
Victor Vieux
2eb4e2a0b8 removed the -f 2013-05-29 16:31:47 +00:00
Victor Vieux
f339fc2eb9 bump to master 2013-05-29 13:52:18 +00:00
Victor Vieux
ea9095c562 merge master 2013-05-29 11:49:39 +00:00
Victor Vieux
8699805756 documentation 2013-05-28 15:49:57 +00:00
Victor Vieux
fbcd8503b3 remove hijack on the client in logs, and split stdout / stderr 2013-05-27 16:07:05 +00:00
Victor Vieux
5a36efb61f fix json encoding, and use less casts 2013-05-26 23:45:45 +00:00
Victor Vieux
14212930e4 ensure valid json 2013-05-25 15:51:26 +00:00
Victor Vieux
c8c7094b2e imporved error, push, import insert 2013-05-25 15:09:46 +00:00
Victor Vieux
cb0bc4adc2 add error handling 2013-05-25 14:12:02 +00:00
unclejack
5f69a53dba set architecture to x86_64 by default
We're going to hardcode architecture to amd64 for now.
This is a stub and will have to be changed to set the actual arch.
2013-05-25 13:03:49 +03:00
unclejack
2cf92abf0e add arch field to image struct 2013-05-24 21:41:30 +03:00
Victor Vieux
1c946ef003 fix: Can't lookup root of unregistered image 2013-05-24 13:03:09 +00:00
Victor Vieux
b45143da9b switch to SI standard and add test 2013-05-23 10:29:09 +00:00
Victor Vieux
ed56b6a905 fix typo 2013-05-23 09:35:20 +00:00
Victor Vieux
6fce89e60b bump to master 2013-05-22 13:41:29 +00:00
Victor Vieux
4489005cb2 add regexp check on repo's name 2013-05-21 12:53:05 +00:00
Victor Vieux
67b20f2c8c add check to see if the image isn't parent of another and add -f to force 2013-05-20 18:31:45 +00:00
Victor Vieux
6102552d61 Merge branch 'master' into 610-improve_rmi-feature 2013-05-18 14:29:37 +00:00
Victor Vieux
d7673274d2 check if the image to delete isn't parent of another 2013-05-18 14:29:32 +00:00
Victor Vieux
f01990aad2 fix 2013-05-17 17:57:44 +00:00
Guillaume J. Charmes
db1e965b65 Merge fixes + cleanup 2013-05-16 11:27:50 -07:00
Guillaume J. Charmes
2ae8aaa106 Merge branch 'master' into 610-improve_rmi-feature 2013-05-16 11:15:16 -07:00
Victor Vieux
c80448c4d1 improve rmi 2013-05-15 13:51:50 +00:00
Victor Vieux
c75942c79d add 4243 port forward 2013-05-15 02:41:19 +02:00
Victor Vieux
a91b710961 add sizes in images and containers 2013-05-13 15:14:20 +02:00
Tianon Gravi
2f89315bf8 Update mkimage-debian.sh now that wheezy is officially the stable release - also, we can't rely on "release" versions for testing or unstable - only "stable" has reliable release versions 2013-05-06 17:00:21 -06:00
101 changed files with 7378 additions and 2871 deletions

View File

@@ -15,6 +15,7 @@ Brian McCallister <brianm@skife.org>
Bruno Bigras <bigras.bruno@gmail.com>
Caleb Spare <cespare@gmail.com>
Charles Hooper <charles.hooper@dotcloud.com>
Daniel Gasienica <daniel@gasienica.ch>
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
Daniel Robinson <gottagetmac@gmail.com>
Daniel Von Fange <daniel@leancoder.com>
@@ -41,6 +42,7 @@ Ken Cochrane <kencochrane@gmail.com>
Kevin J. Lynagh <kevin@keminglabs.com>
Louis Opter <kalessin@kalessin.fr>
Maxim Treskin <zerthurd@gmail.com>
Michael Crosby <crosby.michael@gmail.com>
Mikhail Sobolev <mss@mawhrin.net>
Nate Jones <nate@endot.org>
Nelson Chen <crazysim@gmail.com>

View File

@@ -1,5 +1,30 @@
# Changelog
## 0.4.2 (2013-06-17)
- Packaging: Bumped version to work around an Ubuntu bug
## 0.4.1 (2013-06-17)
+ Remote Api: Add flag to enable cross domain requests
+ Remote Api/Client: Add images and containers sizes in docker ps and docker images
+ Runtime: Configure dns configuration host-wide with 'docker -d -dns'
+ Runtime: Detect faulty DNS configuration and replace it with a public default
+ Runtime: allow docker run <name>:<id>
+ Runtime: you can now specify public port (ex: -p 80:4500)
* Client: allow multiple params in inspect
* Client: Print the container id before the hijack in `docker run`
* Registry: add regexp check on repo's name
* Registry: Move auth to the client
* Runtime: improved image removal to garbage-collect unreferenced parents
* Vagrantfile: Add the rest api port to vagrantfile's port_forward
* Upgrade to Go 1.1
- Builder: don't ignore last line in Dockerfile when it doesn't end with \n
- Registry: Remove login check on pull
## 0.4.0 (2013-06-03)
+ Introducing Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
+ Introducing Remote API: control Docker programmatically using a simple HTTP/json API
* Runtime: various reliability and usability improvements
## 0.3.4 (2013-05-30)
+ Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
+ Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.

36
FIXME Normal file
View File

@@ -0,0 +1,36 @@
## FIXME
This file is a loose collection of things to improve in the codebase, for the internal
use of the maintainers.
They are not big enough to be in the roadmap, not user-facing enough to be github issues,
and not important enough to be discussed in the mailing list.
They are just like FIXME comments in the source code, except we're not sure where in the source
to put them - so we put them here :)
* Merge Runtime, Server and Builder into Runtime
* Run linter on codebase
* Unify build commands and regular commands
* Move source code into src/ subdir for clarity
* Clean up the Makefile, it's a mess
* docker buidl: show short IDs
* docker build: on non-existent local path for ADD, don't show full absolute path on the host
* mount into /dockerinit rather than /sbin/init
* docker tag foo REPO:TAG
* use size header for progress bar in pull
* Clean up context upload in build!!!
* Parallel pull
* Ensure /proc/sys/net/ipv4/ip_forward is 1
* Force DNS to public!
* Always generate a resolv.conf per container, to avoid changing resolv.conf under thne container's feet
* Save metadata with import/export
* Upgrade dockerd without stopping containers
* bring back git revision info, looks like it was lost
* Simple command to remove all untagged images
* Simple command to clean up containers for disk space
* Caching after an ADD
* entry point config
* bring back git revision info, looks like it was lost

View File

@@ -46,6 +46,7 @@ whichrelease:
release: $(BINRELEASE)
s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz
s3cmd -P put docker-latest.tgz s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-latest.tgz
srcrelease: $(SRCRELEASE)
deps: $(DOCKER_DIR)
@@ -60,6 +61,7 @@ $(SRCRELEASE):
$(BINRELEASE): $(SRCRELEASE)
rm -f $(BINRELEASE)
cd $(SRCRELEASE); make; cp -R bin docker-$(RELEASE_VERSION); tar -f ../$(BINRELEASE) -zv -c docker-$(RELEASE_VERSION)
cd $(SRCRELEASE); cp -R bin docker-latest; tar -f ../docker-latest.tgz -zv -c docker-latest
clean:
@rm -rf $(dir $(DOCKER_BIN))

7
NOTICE
View File

@@ -3,4 +3,9 @@ Copyright 2012-2013 dotCloud, inc.
This product includes software developed at dotCloud, inc. (http://www.dotcloud.com).
This product contains software (https://github.com/kr/pty) developed by Keith Rarick, licensed under the MIT License.
This product contains software (https://github.com/kr/pty) developed by Keith Rarick, licensed under the MIT License.
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.

View File

@@ -251,7 +251,7 @@ Note
----
We also keep the documentation in this repository. The website documentation is generated using sphinx using these sources.
Please find it under docs/sources/ and read more about it https://github.com/dotcloud/docker/master/docs/README.md
Please find it under docs/sources/ and read more about it https://github.com/dotcloud/docker/tree/master/docs/README.md
Please feel free to fix / update the documentation and send us pull requests. More tutorials are also welcome.
@@ -371,4 +371,10 @@ Standard Container Specification
#### Security
### Legal
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.

16
Vagrantfile vendored
View File

@@ -5,11 +5,13 @@ BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
AWS_AMI = ENV['AWS_AMI'] || "ami-d0f89fb9"
FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
Vagrant::Config.run do |config|
# Setup virtual machine box. This VM configuration code is always executed.
config.vm.box = BOX_NAME
config.vm.box_url = BOX_URI
config.vm.forward_port 4243, 4243
# Provision docker and new kernel if deployment was not done
if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty?
@@ -70,3 +72,17 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
config.vm.box_url = BOX_URI
end
end
if !FORWARD_DOCKER_PORTS.nil?
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
(49000..49900).each do |port|
config.vm.forward_port port, port
end
end
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
(49000..49900).each do |port|
config.vm.network :forwarded_port, :host => port, :guest => port
end
end
end

252
api.go
View File

@@ -13,7 +13,7 @@ import (
"strings"
)
const API_VERSION = 1.1
const APIVERSION = 1.2
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
conn, _, err := w.(http.Hijacker).Hijack()
@@ -45,12 +45,16 @@ func httpError(w http.ResponseWriter, err error) {
http.Error(w, err.Error(), http.StatusNotFound)
} else if strings.HasPrefix(err.Error(), "Bad parameter") {
http.Error(w, err.Error(), http.StatusBadRequest)
} else if strings.HasPrefix(err.Error(), "Conflict") {
http.Error(w, err.Error(), http.StatusConflict)
} else if strings.HasPrefix(err.Error(), "Impossible") {
http.Error(w, err.Error(), http.StatusNotAcceptable)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func writeJson(w http.ResponseWriter, b []byte) {
func writeJSON(w http.ResponseWriter, b []byte) {
w.Header().Set("Content-Type", "application/json")
w.Write(b)
}
@@ -67,8 +71,10 @@ func getBoolParam(value string) (bool, error) {
}
func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
// FIXME: Handle multiple login at once
// FIXME: return specific error code if config file missing?
if version > 1.1 {
w.WriteHeader(http.StatusNotFound)
return nil
}
authConfig, err := auth.LoadConfig(srv.runtime.root)
if err != nil {
if err != auth.ErrConfigFileMissing {
@@ -80,40 +86,45 @@ func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Reques
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
// FIXME: Handle multiple login at once
config := &auth.AuthConfig{}
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
return err
}
authConfig, err := auth.LoadConfig(srv.runtime.root)
if err != nil {
if err != auth.ErrConfigFileMissing {
return err
}
authConfig = &auth.AuthConfig{}
}
if config.Username == authConfig.Username {
config.Password = authConfig.Password
}
newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root)
status, err := auth.Login(newAuthConfig)
authConfig := &auth.AuthConfig{}
err := json.NewDecoder(r.Body).Decode(authConfig)
if err != nil {
return err
}
if status != "" {
b, err := json.Marshal(&ApiAuth{Status: status})
status := ""
if version > 1.1 {
status, err = auth.Login(authConfig, false)
if err != nil {
return err
}
writeJson(w, b)
} else {
localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
if err != nil {
if err != auth.ErrConfigFileMissing {
return err
}
}
if authConfig.Username == localAuthConfig.Username {
authConfig.Password = localAuthConfig.Password
}
newAuthConfig := auth.NewAuthConfig(authConfig.Username, authConfig.Password, authConfig.Email, srv.runtime.root)
status, err = auth.Login(newAuthConfig, true)
if err != nil {
return err
}
}
if status != "" {
b, err := json.Marshal(&APIAuth{Status: status})
if err != nil {
return err
}
writeJSON(w, b)
return nil
}
w.WriteHeader(http.StatusNoContent)
@@ -126,7 +137,7 @@ func getVersion(srv *Server, version float64, w http.ResponseWriter, r *http.Req
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@@ -155,7 +166,7 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r
return nil
}
func getImagesJson(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
@@ -174,7 +185,7 @@ func getImagesJson(srv *Server, version float64, w http.ResponseWriter, r *http.
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@@ -191,7 +202,7 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@@ -208,7 +219,7 @@ func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *ht
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@@ -225,11 +236,11 @@ func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
func getContainersJson(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
@@ -249,7 +260,7 @@ func getContainersJson(srv *Server, version float64, w http.ResponseWriter, r *h
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@@ -292,12 +303,12 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req
if err != nil {
return err
}
b, err := json.Marshal(&ApiId{id})
b, err := json.Marshal(&APIID{id})
if err != nil {
return err
}
w.WriteHeader(http.StatusCreated)
writeJson(w, b)
writeJSON(w, b)
return nil
}
@@ -312,16 +323,25 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht
tag := r.Form.Get("tag")
repo := r.Form.Get("repo")
if version > 1.0 {
w.Header().Set("Content-Type", "application/json")
}
sf := utils.NewStreamFormatter(version > 1.0)
if image != "" { //pull
registry := r.Form.Get("registry")
if version > 1.0 {
w.Header().Set("Content-Type", "application/json")
}
if err := srv.ImagePull(image, tag, registry, w, version > 1.0); err != nil {
if err := srv.ImagePull(image, tag, registry, w, sf, &auth.AuthConfig{}); err != nil {
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
}
return err
}
} else { //import
if err := srv.ImageImport(src, repo, tag, r.Body, w); err != nil {
if err := srv.ImageImport(src, repo, tag, r.Body, w, sf); err != nil {
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
}
return err
}
}
@@ -342,7 +362,7 @@ func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *htt
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@@ -357,20 +377,38 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
imgId, err := srv.ImageInsert(name, url, path, w)
if version > 1.0 {
w.Header().Set("Content-Type", "application/json")
}
sf := utils.NewStreamFormatter(version > 1.0)
imgID, err := srv.ImageInsert(name, url, path, w, sf)
if err != nil {
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
}
}
b, err := json.Marshal(&APIID{ID: imgID})
if err != nil {
return err
}
b, err := json.Marshal(&ApiId{Id: imgId})
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
authConfig := &auth.AuthConfig{}
if version > 1.1 {
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
return err
}
} else {
localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
if err != nil && err != auth.ErrConfigFileMissing {
return err
}
authConfig = localAuthConfig
}
if err := parseForm(r); err != nil {
return err
}
@@ -380,8 +418,15 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
if err := srv.ImagePush(name, registry, w); err != nil {
if version > 1.0 {
w.Header().Set("Content-Type", "application/json")
}
sf := utils.NewStreamFormatter(version > 1.0)
if err := srv.ImagePush(name, registry, w, sf, authConfig); err != nil {
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
}
return err
}
return nil
@@ -389,17 +434,23 @@ 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 {
config := &Config{}
out := &APIRun{}
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
return err
}
if len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns() {
out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
config.Dns = defaultDns
}
id, err := srv.ContainerCreate(config)
if err != nil {
return err
}
out.ID = id
out := &ApiRun{
Id: id,
}
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
@@ -408,12 +459,13 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
}
b, err := json.Marshal(out)
if err != nil {
return err
}
w.WriteHeader(http.StatusCreated)
writeJson(w, b)
writeJSON(w, b)
return nil
}
@@ -457,14 +509,30 @@ func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *ht
}
func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
if vars == nil {
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
if err := srv.ImageDelete(name); err != nil {
imgs, err := srv.ImageDelete(name, version > 1.1)
if err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)
if imgs != nil {
if len(*imgs) != 0 {
b, err := json.Marshal(imgs)
if err != nil {
return err
}
writeJSON(w, b)
} else {
return fmt.Errorf("Conflict, %s wasn't deleted", name)
}
} else {
w.WriteHeader(http.StatusNoContent)
}
return nil
}
@@ -510,11 +578,11 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *
if err != nil {
return err
}
b, err := json.Marshal(&ApiWait{StatusCode: status})
b, err := json.Marshal(&APIWait{StatusCode: status})
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@@ -601,7 +669,7 @@ func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@@ -619,17 +687,17 @@ func getImagesByName(srv *Server, version float64, w http.ResponseWriter, r *htt
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
apiConfig := &ApiImageConfig{}
apiConfig := &APIImageConfig{}
if err := json.NewDecoder(r.Body).Decode(apiConfig); err != nil {
return err
}
image, err := srv.ImageGetCached(apiConfig.Id, apiConfig.Config)
image, err := srv.ImageGetCached(apiConfig.ID, apiConfig.Config)
if err != nil {
return err
}
@@ -637,12 +705,12 @@ func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *
w.WriteHeader(http.StatusNotFound)
return nil
}
apiId := &ApiId{Id: image.Id}
b, err := json.Marshal(apiId)
apiID := &APIID{ID: image.ID}
b, err := json.Marshal(apiID)
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@@ -679,22 +747,31 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
return nil
}
func ListenAndServe(addr string, srv *Server, logging bool) error {
func optionsHandler(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
w.WriteHeader(http.StatusOK)
return nil
}
func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Access-Control-Allow-Origin", "*")
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
}
func createRouter(srv *Server, logging bool) (*mux.Router, error) {
r := mux.NewRouter()
log.Printf("Listening for HTTP on %s\n", addr)
m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
"GET": {
"/auth": getAuth,
"/version": getVersion,
"/info": getInfo,
"/images/json": getImagesJson,
"/images/json": getImagesJSON,
"/images/viz": getImagesViz,
"/images/search": getImagesSearch,
"/images/{name:.*}/history": getImagesHistory,
"/images/{name:.*}/json": getImagesByName,
"/containers/ps": getContainersJson,
"/containers/json": getContainersJson,
"/containers/ps": getContainersJSON,
"/containers/json": getContainersJSON,
"/containers/{name:.*}/export": getContainersExport,
"/containers/{name:.*}/changes": getContainersChanges,
"/containers/{name:.*}/json": getContainersByName,
@@ -721,6 +798,9 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
"/containers/{name:.*}": deleteContainers,
"/images/{name:.*}": deleteImages,
},
"OPTIONS": {
"": optionsHandler,
},
}
for method, routes := range m {
@@ -743,9 +823,12 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
}
version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
if err != nil {
version = API_VERSION
version = APIVERSION
}
if version == 0 || version > API_VERSION {
if srv.enableCors {
writeCorsHeaders(w, r)
}
if version == 0 || version > APIVERSION {
w.WriteHeader(http.StatusNotFound)
return
}
@@ -753,9 +836,24 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
httpError(w, err)
}
}
r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
if localRoute == "" {
r.Methods(localMethod).HandlerFunc(f)
} else {
r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
}
}
}
return r, nil
}
func ListenAndServe(addr string, srv *Server, logging bool) error {
log.Printf("Listening for HTTP on %s\n", addr)
r, err := createRouter(srv, logging)
if err != nil {
return err
}
return http.ListenAndServe(addr, r)
}

View File

@@ -1,71 +1,79 @@
package docker
type ApiHistory struct {
Id string
type APIHistory struct {
ID string `json:"Id"`
Created int64
CreatedBy string
CreatedBy string `json:",omitempty"`
}
type ApiImages struct {
Repository string `json:",omitempty"`
Tag string `json:",omitempty"`
Id string
Created int64
type APIImages struct {
Repository string `json:",omitempty"`
Tag string `json:",omitempty"`
ID string `json:"Id"`
Created int64
Size int64
VirtualSize int64
}
type ApiInfo struct {
Containers int
Version string
Images int
type APIInfo struct {
Debug bool
GoVersion string
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
Containers int
Images int
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
MemoryLimit bool `json:",omitempty"`
SwapLimit bool `json:",omitempty"`
}
type ApiContainers struct {
Id string
Image string
Command string
Created int64
Status string
Ports string
type APIRmi struct {
Deleted string `json:",omitempty"`
Untagged string `json:",omitempty"`
}
type ApiSearch struct {
type APIContainers struct {
ID string `json:"Id"`
Image string
Command string
Created int64
Status string
Ports string
SizeRw int64
SizeRootFs int64
}
type APISearch struct {
Name string
Description string
}
type ApiId struct {
Id string
type APIID struct {
ID string `json:"Id"`
}
type ApiRun struct {
Id string
Warnings []string
type APIRun struct {
ID string `json:"Id"`
Warnings []string `json:",omitempty"`
}
type ApiPort struct {
type APIPort struct {
Port string
}
type ApiVersion struct {
Version string
GitCommit string
MemoryLimit bool
SwapLimit bool
type APIVersion struct {
Version string
GitCommit string `json:",omitempty"`
GoVersion string `json:",omitempty"`
}
type ApiWait struct {
type APIWait struct {
StatusCode int
}
type ApiAuth struct {
type APIAuth struct {
Status string
}
type ApiImageConfig struct {
Id string
type APIImageConfig struct {
ID string `json:"Id"`
*Config
}

View File

@@ -6,7 +6,6 @@ import (
"bytes"
"encoding/json"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/registry"
"github.com/dotcloud/docker/utils"
"io"
"net"
@@ -18,7 +17,7 @@ import (
"time"
)
func TestGetAuth(t *testing.T) {
func TestPostAuth(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
@@ -37,29 +36,23 @@ func TestGetAuth(t *testing.T) {
Email: "utest@yopmail.com",
}
authConfigJson, err := json.Marshal(authConfig)
authConfigJSON, err := json.Marshal(authConfig)
if err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "/auth", bytes.NewReader(authConfigJson))
req, err := http.NewRequest("POST", "/auth", bytes.NewReader(authConfigJSON))
if err != nil {
t.Fatal(err)
}
if err := postAuth(srv, API_VERSION, r, req, nil); err != nil {
if err := postAuth(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusOK && r.Code != 0 {
t.Fatalf("%d OK or 0 expected, received %d\n", http.StatusOK, r.Code)
}
newAuthConfig := registry.NewRegistry(runtime.root).GetAuthConfig(false)
if newAuthConfig.Username != authConfig.Username ||
newAuthConfig.Email != authConfig.Email {
t.Fatalf("The auth configuration hasn't been set correctly")
}
}
func TestGetVersion(t *testing.T) {
@@ -73,11 +66,11 @@ func TestGetVersion(t *testing.T) {
r := httptest.NewRecorder()
if err := getVersion(srv, API_VERSION, r, nil, nil); err != nil {
if err := getVersion(srv, APIVERSION, r, nil, nil); err != nil {
t.Fatal(err)
}
v := &ApiVersion{}
v := &APIVersion{}
if err = json.Unmarshal(r.Body.Bytes(), v); err != nil {
t.Fatal(err)
}
@@ -97,21 +90,21 @@ func TestGetInfo(t *testing.T) {
r := httptest.NewRecorder()
if err := getInfo(srv, API_VERSION, r, nil, nil); err != nil {
if err := getInfo(srv, APIVERSION, r, nil, nil); err != nil {
t.Fatal(err)
}
infos := &ApiInfo{}
infos := &APIInfo{}
err = json.Unmarshal(r.Body.Bytes(), infos)
if err != nil {
t.Fatal(err)
}
if infos.Version != VERSION {
t.Errorf("Excepted version %s, %s found", VERSION, infos.Version)
if infos.Images != 1 {
t.Errorf("Excepted images: %d, %d found", 1, infos.Images)
}
}
func TestGetImagesJson(t *testing.T) {
func TestGetImagesJSON(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
@@ -128,11 +121,11 @@ func TestGetImagesJson(t *testing.T) {
r := httptest.NewRecorder()
if err := getImagesJson(srv, API_VERSION, r, req, nil); err != nil {
if err := getImagesJSON(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
images := []ApiImages{}
images := []APIImages{}
if err := json.Unmarshal(r.Body.Bytes(), &images); err != nil {
t.Fatal(err)
}
@@ -153,11 +146,11 @@ func TestGetImagesJson(t *testing.T) {
t.Fatal(err)
}
if err := getImagesJson(srv, API_VERSION, r2, req2, nil); err != nil {
if err := getImagesJSON(srv, APIVERSION, r2, req2, nil); err != nil {
t.Fatal(err)
}
images2 := []ApiImages{}
images2 := []APIImages{}
if err := json.Unmarshal(r2.Body.Bytes(), &images2); err != nil {
t.Fatal(err)
}
@@ -166,8 +159,8 @@ func TestGetImagesJson(t *testing.T) {
t.Errorf("Excepted 1 image, %d found", len(images2))
}
if images2[0].Id != GetTestImage(runtime).Id {
t.Errorf("Retrieved image Id differs, expected %s, received %s", GetTestImage(runtime).Id, images2[0].Id)
if images2[0].ID != GetTestImage(runtime).ID {
t.Errorf("Retrieved image Id differs, expected %s, received %s", GetTestImage(runtime).ID, images2[0].ID)
}
r3 := httptest.NewRecorder()
@@ -178,11 +171,11 @@ func TestGetImagesJson(t *testing.T) {
t.Fatal(err)
}
if err := getImagesJson(srv, API_VERSION, r3, req3, nil); err != nil {
if err := getImagesJSON(srv, APIVERSION, r3, req3, nil); err != nil {
t.Fatal(err)
}
images3 := []ApiImages{}
images3 := []APIImages{}
if err := json.Unmarshal(r3.Body.Bytes(), &images3); err != nil {
t.Fatal(err)
}
@@ -199,7 +192,7 @@ func TestGetImagesJson(t *testing.T) {
t.Fatal(err)
}
err = getImagesJson(srv, API_VERSION, r4, req4, nil)
err = getImagesJSON(srv, APIVERSION, r4, req4, nil)
if err == nil {
t.Fatalf("Error expected, received none")
}
@@ -220,7 +213,7 @@ func TestGetImagesViz(t *testing.T) {
srv := &Server{runtime: runtime}
r := httptest.NewRecorder()
if err := getImagesViz(srv, API_VERSION, r, nil, nil); err != nil {
if err := getImagesViz(srv, APIVERSION, r, nil, nil); err != nil {
t.Fatal(err)
}
@@ -256,11 +249,11 @@ func TestGetImagesSearch(t *testing.T) {
t.Fatal(err)
}
if err := getImagesSearch(srv, API_VERSION, r, req, nil); err != nil {
if err := getImagesSearch(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
results := []ApiSearch{}
results := []APISearch{}
if err := json.Unmarshal(r.Body.Bytes(), &results); err != nil {
t.Fatal(err)
}
@@ -280,11 +273,11 @@ func TestGetImagesHistory(t *testing.T) {
r := httptest.NewRecorder()
if err := getImagesHistory(srv, API_VERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
if err := getImagesHistory(srv, APIVERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
t.Fatal(err)
}
history := []ApiHistory{}
history := []APIHistory{}
if err := json.Unmarshal(r.Body.Bytes(), &history); err != nil {
t.Fatal(err)
}
@@ -303,7 +296,7 @@ func TestGetImagesByName(t *testing.T) {
srv := &Server{runtime: runtime}
r := httptest.NewRecorder()
if err := getImagesByName(srv, API_VERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
if err := getImagesByName(srv, APIVERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
t.Fatal(err)
}
@@ -311,12 +304,12 @@ func TestGetImagesByName(t *testing.T) {
if err := json.Unmarshal(r.Body.Bytes(), img); err != nil {
t.Fatal(err)
}
if img.Id != GetTestImage(runtime).Id || img.Comment != "Imported from http://get.docker.io/images/busybox" {
if img.ID != GetTestImage(runtime).ID || img.Comment != "Imported from http://get.docker.io/images/busybox" {
t.Errorf("Error inspecting image")
}
}
func TestGetContainersJson(t *testing.T) {
func TestGetContainersJSON(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
@@ -326,7 +319,7 @@ func TestGetContainersJson(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "test"},
})
if err != nil {
@@ -340,18 +333,18 @@ func TestGetContainersJson(t *testing.T) {
}
r := httptest.NewRecorder()
if err := getContainersJson(srv, API_VERSION, r, req, nil); err != nil {
if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
containers := []ApiContainers{}
containers := []APIContainers{}
if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil {
t.Fatal(err)
}
if len(containers) != 1 {
t.Fatalf("Excepted %d container, %d found", 1, len(containers))
}
if containers[0].Id != container.Id {
t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.Id, containers[0].Id)
if containers[0].ID != container.ID {
t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ID, containers[0].ID)
}
}
@@ -369,7 +362,7 @@ func TestGetContainersExport(t *testing.T) {
// Create a container and remove a file
container, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
},
)
@@ -383,7 +376,7 @@ func TestGetContainersExport(t *testing.T) {
}
r := httptest.NewRecorder()
if err = getContainersExport(srv, API_VERSION, 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)
}
@@ -424,7 +417,7 @@ func TestGetContainersChanges(t *testing.T) {
// Create a container and remove a file
container, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/rm", "/etc/passwd"},
},
)
@@ -438,7 +431,7 @@ func TestGetContainersChanges(t *testing.T) {
}
r := httptest.NewRecorder()
if err := getContainersChanges(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
if err := getContainersChanges(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
changes := []Change{}
@@ -472,7 +465,7 @@ func TestGetContainersByName(t *testing.T) {
// Create a container and remove a file
container, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "test"},
},
)
@@ -482,49 +475,15 @@ func TestGetContainersByName(t *testing.T) {
defer runtime.Destroy(container)
r := httptest.NewRecorder()
if err := getContainersByName(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
if err := getContainersByName(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
outContainer := &Container{}
if err := json.Unmarshal(r.Body.Bytes(), outContainer); err != nil {
t.Fatal(err)
}
if outContainer.Id != container.Id {
t.Fatalf("Wrong containers retrieved. Expected %s, recieved %s", container.Id, outContainer.Id)
}
}
func TestPostAuth(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{
runtime: runtime,
}
config := &auth.AuthConfig{
Username: "utest",
Email: "utest@yopmail.com",
}
authStr := auth.EncodeAuth(config)
auth.SaveConfig(runtime.root, authStr, config.Email)
r := httptest.NewRecorder()
if err := getAuth(srv, API_VERSION, r, nil, nil); err != nil {
t.Fatal(err)
}
authConfig := &auth.AuthConfig{}
if err := json.Unmarshal(r.Body.Bytes(), authConfig); err != nil {
t.Fatal(err)
}
if authConfig.Username != config.Username || authConfig.Email != config.Email {
t.Errorf("The retrieve auth mismatch with the one set.")
if outContainer.ID != container.ID {
t.Fatalf("Wrong containers retrieved. Expected %s, recieved %s", container.ID, outContainer.ID)
}
}
@@ -542,7 +501,7 @@ func TestPostCommit(t *testing.T) {
// Create a container and remove a file
container, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
},
)
@@ -555,24 +514,24 @@ func TestPostCommit(t *testing.T) {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "/commit?repo=testrepo&testtag=tag&container="+container.Id, bytes.NewReader([]byte{}))
req, err := http.NewRequest("POST", "/commit?repo=testrepo&testtag=tag&container="+container.ID, bytes.NewReader([]byte{}))
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := postCommit(srv, API_VERSION, r, req, nil); err != nil {
if err := postCommit(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusCreated {
t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
}
apiId := &ApiId{}
if err := json.Unmarshal(r.Body.Bytes(), apiId); err != nil {
apiID := &APIID{}
if err := json.Unmarshal(r.Body.Bytes(), apiID); err != nil {
t.Fatal(err)
}
if _, err := runtime.graph.Get(apiId.Id); err != nil {
if _, err := runtime.graph.Get(apiID.ID); err != nil {
t.Fatalf("The image has not been commited")
}
}
@@ -715,7 +674,7 @@ func TestPostImagesInsert(t *testing.T) {
// t.Fatalf("The test file has not been found")
// }
// if err := srv.runtime.graph.Delete(img.Id); err != nil {
// if err := srv.runtime.graph.Delete(img.ID); err != nil {
// t.Fatal(err)
// }
}
@@ -824,8 +783,8 @@ func TestPostContainersCreate(t *testing.T) {
srv := &Server{runtime: runtime}
configJson, err := json.Marshal(&Config{
Image: GetTestImage(runtime).Id,
configJSON, err := json.Marshal(&Config{
Image: GetTestImage(runtime).ID,
Memory: 33554432,
Cmd: []string{"touch", "/test"},
})
@@ -833,25 +792,25 @@ func TestPostContainersCreate(t *testing.T) {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJson))
req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJSON))
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := postContainersCreate(srv, API_VERSION, r, req, nil); err != nil {
if err := postContainersCreate(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusCreated {
t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
}
apiRun := &ApiRun{}
apiRun := &APIRun{}
if err := json.Unmarshal(r.Body.Bytes(), apiRun); err != nil {
t.Fatal(err)
}
container := srv.runtime.Get(apiRun.Id)
container := srv.runtime.Get(apiRun.ID)
if container == nil {
t.Fatalf("Container not created")
}
@@ -880,7 +839,7 @@ func TestPostContainersKill(t *testing.T) {
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
@@ -902,7 +861,7 @@ func TestPostContainersKill(t *testing.T) {
}
r := httptest.NewRecorder()
if err := postContainersKill(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
if err := postContainersKill(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusNoContent {
@@ -924,7 +883,7 @@ func TestPostContainersRestart(t *testing.T) {
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
@@ -945,12 +904,12 @@ func TestPostContainersRestart(t *testing.T) {
t.Errorf("Container should be running")
}
req, err := http.NewRequest("POST", "/containers/"+container.Id+"/restart?t=1", bytes.NewReader([]byte{}))
req, err := http.NewRequest("POST", "/containers/"+container.ID+"/restart?t=1", bytes.NewReader([]byte{}))
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := postContainersRestart(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
if err := postContainersRestart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusNoContent {
@@ -980,7 +939,7 @@ func TestPostContainersStart(t *testing.T) {
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
@@ -991,7 +950,7 @@ func TestPostContainersStart(t *testing.T) {
defer runtime.Destroy(container)
r := httptest.NewRecorder()
if err := postContainersStart(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
if err := postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusNoContent {
@@ -1006,7 +965,7 @@ func TestPostContainersStart(t *testing.T) {
}
r = httptest.NewRecorder()
if err = postContainersStart(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err == nil {
if err = postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err == nil {
t.Fatalf("A running containter should be able to be started")
}
@@ -1026,7 +985,7 @@ func TestPostContainersStop(t *testing.T) {
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
@@ -1048,12 +1007,12 @@ func TestPostContainersStop(t *testing.T) {
}
// Note: as it is a POST request, it requires a body.
req, err := http.NewRequest("POST", "/containers/"+container.Id+"/stop?t=1", bytes.NewReader([]byte{}))
req, err := http.NewRequest("POST", "/containers/"+container.ID+"/stop?t=1", bytes.NewReader([]byte{}))
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := postContainersStop(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
if err := postContainersStop(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusNoContent {
@@ -1075,7 +1034,7 @@ func TestPostContainersWait(t *testing.T) {
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sleep", "1"},
OpenStdin: true,
},
@@ -1091,10 +1050,10 @@ func TestPostContainersWait(t *testing.T) {
setTimeout(t, "Wait timed out", 3*time.Second, func() {
r := httptest.NewRecorder()
if err := postContainersWait(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
if err := postContainersWait(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
apiWait := &ApiWait{}
apiWait := &APIWait{}
if err := json.Unmarshal(r.Body.Bytes(), apiWait); err != nil {
t.Fatal(err)
}
@@ -1119,7 +1078,7 @@ func TestPostContainersAttach(t *testing.T) {
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
@@ -1148,12 +1107,12 @@ func TestPostContainersAttach(t *testing.T) {
out: stdoutPipe,
}
req, err := http.NewRequest("POST", "/containers/"+container.Id+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{}))
req, err := http.NewRequest("POST", "/containers/"+container.ID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{}))
if err != nil {
t.Fatal(err)
}
if err := postContainersAttach(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
if err := postContainersAttach(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
}()
@@ -1206,7 +1165,7 @@ func TestDeleteContainers(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
})
if err != nil {
@@ -1218,19 +1177,19 @@ func TestDeleteContainers(t *testing.T) {
t.Fatal(err)
}
req, err := http.NewRequest("DELETE", "/containers/"+container.Id, nil)
req, err := http.NewRequest("DELETE", "/containers/"+container.ID, nil)
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := deleteContainers(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
if err := deleteContainers(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusNoContent {
t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
}
if c := runtime.Get(container.Id); c != nil {
if c := runtime.Get(container.ID); c != nil {
t.Fatalf("The container as not been deleted")
}
@@ -1239,9 +1198,131 @@ func TestDeleteContainers(t *testing.T) {
}
}
func TestOptionsRoute(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime, enableCors: true}
r := httptest.NewRecorder()
router, err := createRouter(srv, false)
if err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("OPTIONS", "/", nil)
if err != nil {
t.Fatal(err)
}
router.ServeHTTP(r, req)
if r.Code != http.StatusOK {
t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code)
}
}
func TestGetEnabledCors(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime, enableCors: true}
r := httptest.NewRecorder()
router, err := createRouter(srv, false)
if err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("GET", "/version", nil)
if err != nil {
t.Fatal(err)
}
router.ServeHTTP(r, req)
if r.Code != http.StatusOK {
t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code)
}
allowOrigin := r.Header().Get("Access-Control-Allow-Origin")
allowHeaders := r.Header().Get("Access-Control-Allow-Headers")
allowMethods := r.Header().Get("Access-Control-Allow-Methods")
if allowOrigin != "*" {
t.Errorf("Expected header Access-Control-Allow-Origin to be \"*\", %s found.", allowOrigin)
}
if allowHeaders != "Origin, X-Requested-With, Content-Type, Accept" {
t.Errorf("Expected header Access-Control-Allow-Headers to be \"Origin, X-Requested-With, Content-Type, Accept\", %s found.", allowHeaders)
}
if allowMethods != "GET, POST, DELETE, PUT, OPTIONS" {
t.Errorf("Expected hearder Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods)
}
}
func TestDeleteImages(t *testing.T) {
//FIXME: Implement this test
t.Log("Test not implemented")
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
if err := srv.runtime.repositories.Set("test", "test", unitTestImageName, true); err != nil {
t.Fatal(err)
}
images, err := srv.Images(false, "")
if err != nil {
t.Fatal(err)
}
if len(images) != 2 {
t.Errorf("Excepted 2 images, %d found", len(images))
}
req, err := http.NewRequest("DELETE", "/images/test:test", nil)
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := deleteImages(srv, APIVERSION, r, req, map[string]string{"name": "test:test"}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusOK {
t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
}
var outs []APIRmi
if err := json.Unmarshal(r.Body.Bytes(), &outs); err != nil {
t.Fatal(err)
}
if len(outs) != 1 {
t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs))
}
images, err = srv.Images(false, "")
if err != nil {
t.Fatal(err)
}
if len(images) != 1 {
t.Errorf("Excepted 1 image, %d found", len(images))
}
/* if c := runtime.Get(container.Id); c != nil {
t.Fatalf("The container as not been deleted")
}
if _, err := os.Stat(path.Join(container.rwPath(), "test")); err == nil {
t.Fatalf("The test file has not been deleted")
} */
}
// Mocked types for tests

View File

@@ -51,9 +51,13 @@ func Tar(path string, compression Compression) (io.Reader, error) {
return CmdStream(cmd)
}
// FIXME: specify behavior when target path exists vs. doesn't exist.
func Untar(archive io.Reader, path string) error {
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
cmd.Stdin = archive
// Hardcode locale environment for predictable outcome regardless of host configuration.
// (see https://github.com/dotcloud/docker/issues/355)
cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%s: %s", err, output)
@@ -61,6 +65,30 @@ func Untar(archive io.Reader, path string) error {
return nil
}
// UntarPath is a convenience function which looks for an archive
// at filesystem path `src`, and unpacks it at `dst`.
func UntarPath(src, dst string) error {
if archive, err := os.Open(src); err != nil {
return err
} else if err := Untar(archive, dst); err != nil {
return err
}
return nil
}
// CopyWithTar creates a tar archive of filesystem path `src`, and
// unpacks it at filesystem path `dst`.
// The archive is streamed directly with fixed buffering and no
// intermediary disk IO.
//
func CopyWithTar(src, dst string) error {
archive, err := Tar(src, Uncompressed)
if err != nil {
return err
}
return Untar(archive, dst)
}
// 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.

View File

@@ -16,12 +16,12 @@ import (
const CONFIGFILE = ".dockercfg"
// the registry server we want to login against
const INDEX_SERVER = "https://index.docker.io/v1"
const INDEXSERVER = "https://index.docker.io/v1"
//const INDEX_SERVER = "http://indexstaging-docker.dotcloud.com/"
//const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/"
var (
ErrConfigFileMissing error = errors.New("The Auth config file is missing")
ErrConfigFileMissing = errors.New("The Auth config file is missing")
)
type AuthConfig struct {
@@ -44,11 +44,11 @@ func IndexServerAddress() string {
if os.Getenv("DOCKER_INDEX_URL") != "" {
return os.Getenv("DOCKER_INDEX_URL") + "/v1"
}
return INDEX_SERVER
return INDEXSERVER
}
// create a base64 encoded auth string to store in config
func EncodeAuth(authConfig *AuthConfig) string {
func encodeAuth(authConfig *AuthConfig) string {
authStr := authConfig.Username + ":" + authConfig.Password
msg := []byte(authStr)
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
@@ -57,7 +57,7 @@ func EncodeAuth(authConfig *AuthConfig) string {
}
// decode the auth string
func DecodeAuth(authStr string) (*AuthConfig, error) {
func decodeAuth(authStr string) (*AuthConfig, error) {
decLen := base64.StdEncoding.DecodedLen(len(authStr))
decoded := make([]byte, decLen)
authByte := []byte(authStr)
@@ -82,7 +82,7 @@ func DecodeAuth(authStr string) (*AuthConfig, error) {
func LoadConfig(rootPath string) (*AuthConfig, error) {
confFile := path.Join(rootPath, CONFIGFILE)
if _, err := os.Stat(confFile); err != nil {
return nil, ErrConfigFileMissing
return &AuthConfig{rootPath:rootPath}, ErrConfigFileMissing
}
b, err := ioutil.ReadFile(confFile)
if err != nil {
@@ -94,7 +94,7 @@ func LoadConfig(rootPath string) (*AuthConfig, error) {
}
origAuth := strings.Split(arr[0], " = ")
origEmail := strings.Split(arr[1], " = ")
authConfig, err := DecodeAuth(origAuth[1])
authConfig, err := decodeAuth(origAuth[1])
if err != nil {
return nil, err
}
@@ -104,13 +104,13 @@ func LoadConfig(rootPath string) (*AuthConfig, error) {
}
// save the auth config
func SaveConfig(rootPath, authStr string, email string) error {
confFile := path.Join(rootPath, CONFIGFILE)
if len(email) == 0 {
func SaveConfig(authConfig *AuthConfig) error {
confFile := path.Join(authConfig.rootPath, CONFIGFILE)
if len(authConfig.Email) == 0 {
os.Remove(confFile)
return nil
}
lines := "auth = " + authStr + "\n" + "email = " + email + "\n"
lines := "auth = " + encodeAuth(authConfig) + "\n" + "email = " + authConfig.Email + "\n"
b := []byte(lines)
err := ioutil.WriteFile(confFile, b, 0600)
if err != nil {
@@ -120,7 +120,7 @@ func SaveConfig(rootPath, authStr string, email string) error {
}
// try to register/login to the registry server
func Login(authConfig *AuthConfig) (string, error) {
func Login(authConfig *AuthConfig, store bool) (string, error) {
storeConfig := false
client := &http.Client{}
reqStatusCode := 0
@@ -168,8 +168,10 @@ func Login(authConfig *AuthConfig) (string, error) {
status = "Login Succeeded\n"
storeConfig = true
} else if resp.StatusCode == 401 {
if err := SaveConfig(authConfig.rootPath, "", ""); err != nil {
return "", err
if store {
if err := SaveConfig(authConfig); err != nil {
return "", err
}
}
return "", fmt.Errorf("Wrong login/password, please try again")
} else {
@@ -182,9 +184,8 @@ func Login(authConfig *AuthConfig) (string, error) {
} else {
return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody)
}
if storeConfig {
authStr := EncodeAuth(authConfig)
if err := SaveConfig(authConfig.rootPath, authStr, authConfig.Email); err != nil {
if storeConfig && store {
if err := SaveConfig(authConfig); err != nil {
return "", err
}
}

View File

@@ -2,11 +2,14 @@ package docker
import (
"fmt"
"github.com/dotcloud/docker/utils"
"os"
"path"
"time"
)
var defaultDns = []string{"8.8.8.8", "8.8.4.4"}
type Builder struct {
runtime *Runtime
repositories *TagStore
@@ -40,7 +43,7 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
}
// Generate id
id := GenerateId()
id := GenerateID()
// Generate default hostname
// FIXME: the lxc template no longer needs to set a default hostname
if config.Hostname == "" {
@@ -49,32 +52,43 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
container := &Container{
// FIXME: we should generate the ID here instead of receiving it as an argument
Id: id,
ID: id,
Created: time.Now(),
Path: config.Cmd[0],
Args: config.Cmd[1:], //FIXME: de-duplicate from config
Config: config,
Image: img.Id, // Always use the resolved image id
Image: img.ID, // Always use the resolved image id
NetworkSettings: &NetworkSettings{},
// FIXME: do we need to store this in the container?
SysInitPath: sysInitPath,
}
container.root = builder.runtime.containerRoot(container.Id)
container.root = builder.runtime.containerRoot(container.ID)
// Step 1: create the container directory.
// This doubles as a barrier to avoid race conditions.
if err := os.Mkdir(container.root, 0700); err != nil {
return nil, err
}
if len(config.Dns) == 0 && len(builder.runtime.Dns) == 0 && utils.CheckLocalDns() {
//"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns
builder.runtime.Dns = defaultDns
}
// If custom dns exists, then create a resolv.conf for the container
if len(config.Dns) > 0 {
if len(config.Dns) > 0 || len(builder.runtime.Dns) > 0 {
var dns []string
if len(config.Dns) > 0 {
dns = config.Dns
} else {
dns = builder.runtime.Dns
}
container.ResolvConfPath = path.Join(container.root, "resolv.conf")
f, err := os.Create(container.ResolvConfPath)
if err != nil {
return nil, err
}
defer f.Close()
for _, dns := range config.Dns {
for _, dns := range dns {
if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
return nil, err
}
@@ -110,7 +124,7 @@ func (builder *Builder) Commit(container *Container, repository, tag, comment, a
}
// Register the image if needed
if repository != "" {
if err := builder.repositories.Set(repository, tag, img.Id, true); err != nil {
if err := builder.repositories.Set(repository, tag, img.ID, true); err != nil {
return img, err
}
}

View File

@@ -63,11 +63,11 @@ func (b *builderClient) CmdFrom(name string) error {
return err
}
img := &ApiId{}
img := &APIID{}
if err := json.Unmarshal(obj, img); err != nil {
return err
}
b.image = img.Id
b.image = img.ID
utils.Debugf("Using image %s", b.image)
return nil
}
@@ -91,19 +91,19 @@ func (b *builderClient) CmdRun(args string) error {
b.config.Cmd = nil
MergeConfig(b.config, config)
body, statusCode, err := b.cli.call("POST", "/images/getCache", &ApiImageConfig{Id: b.image, Config: b.config})
body, statusCode, err := b.cli.call("POST", "/images/getCache", &APIImageConfig{ID: b.image, Config: b.config})
if err != nil {
if statusCode != 404 {
return err
}
}
if statusCode != 404 {
apiId := &ApiId{}
if err := json.Unmarshal(body, apiId); err != nil {
apiID := &APIID{}
if err := json.Unmarshal(body, apiID); err != nil {
return err
}
utils.Debugf("Use cached version")
b.image = apiId.Id
b.image = apiID.ID
return nil
}
cid, err := b.run()
@@ -163,7 +163,7 @@ func (b *builderClient) CmdInsert(args string) error {
// return err
// }
// apiId := &ApiId{}
// apiId := &APIId{}
// if err := json.Unmarshal(body, apiId); err != nil {
// return err
// }
@@ -182,7 +182,7 @@ func (b *builderClient) run() (string, error) {
return "", err
}
apiRun := &ApiRun{}
apiRun := &APIRun{}
if err := json.Unmarshal(body, apiRun); err != nil {
return "", err
}
@@ -191,18 +191,18 @@ func (b *builderClient) run() (string, error) {
}
//start the container
_, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/start", nil)
_, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/start", nil)
if err != nil {
return "", err
}
b.tmpContainers[apiRun.Id] = struct{}{}
b.tmpContainers[apiRun.ID] = struct{}{}
// Wait for it to finish
body, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/wait", nil)
body, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/wait", nil)
if err != nil {
return "", err
}
apiWait := &ApiWait{}
apiWait := &APIWait{}
if err := json.Unmarshal(body, apiWait); err != nil {
return "", err
}
@@ -210,7 +210,7 @@ func (b *builderClient) run() (string, error) {
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode)
}
return apiRun.Id, nil
return apiRun.ID, nil
}
func (b *builderClient) commit(id string) error {
@@ -222,11 +222,11 @@ func (b *builderClient) commit(id string) error {
if id == "" {
cmd := b.config.Cmd
b.config.Cmd = []string{"true"}
if cid, err := b.run(); err != nil {
cid, err := b.run()
if err != nil {
return err
} else {
id = cid
}
id = cid
b.config.Cmd = cmd
}
@@ -239,12 +239,12 @@ func (b *builderClient) commit(id string) error {
if err != nil {
return err
}
apiId := &ApiId{}
if err := json.Unmarshal(body, apiId); err != nil {
apiID := &APIID{}
if err := json.Unmarshal(body, apiID); err != nil {
return err
}
b.tmpImages[apiId.Id] = struct{}{}
b.image = apiId.Id
b.tmpImages[apiID.ID] = struct{}{}
b.image = apiID.ID
b.needCommit = false
return nil
}

View File

@@ -61,7 +61,7 @@ func (b *buildFile) CmdFrom(name string) error {
remote = name
}
if err := b.srv.ImagePull(remote, tag, "", b.out, false); err != nil {
if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false), nil); err != nil {
return err
}
@@ -73,7 +73,7 @@ func (b *buildFile) CmdFrom(name string) error {
return err
}
}
b.image = image.Id
b.image = image.ID
b.config = &Config{}
return nil
}
@@ -102,7 +102,7 @@ func (b *buildFile) CmdRun(args string) error {
return err
} else if cache != nil {
utils.Debugf("[BUILDER] Use cached version")
b.image = cache.Id
b.image = cache.ID
return nil
} else {
utils.Debugf("[BUILDER] Cache miss")
@@ -176,16 +176,16 @@ func (b *buildFile) CmdAdd(args string) error {
dest := strings.Trim(tmp[1], " ")
cmd := b.config.Cmd
// Create the container and start it
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
cid, err := b.run()
b.config.Image = b.image
container, err := b.builder.Create(b.config)
if err != nil {
return err
}
b.tmpContainers[container.ID] = struct{}{}
container := b.runtime.Get(cid)
if container == nil {
return fmt.Errorf("Error while creating the container (CmdAdd)")
}
if err := container.EnsureMounted(); err != nil {
return err
}
@@ -202,25 +202,21 @@ func (b *buildFile) CmdAdd(args string) error {
if err := os.MkdirAll(destPath, 0700); err != nil {
return err
}
files, err := ioutil.ReadDir(path.Join(b.context, orig))
if err != nil {
if err := CopyWithTar(origPath, destPath); err != nil {
return err
}
for _, fi := range files {
if err := utils.CopyDirectory(path.Join(origPath, fi.Name()), path.Join(destPath, fi.Name())); err != nil {
return err
}
}
} else {
// First try to unpack the source as an archive
} else if err := 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), 0700); err != nil {
return err
}
if err := utils.CopyDirectory(origPath, destPath); err != nil {
if err := CopyWithTar(origPath, destPath); err != nil {
return err
}
}
if err := b.commit(cid, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
return err
}
b.config.Cmd = cmd
@@ -238,7 +234,7 @@ func (b *buildFile) run() (string, error) {
if err != nil {
return "", err
}
b.tmpContainers[c.Id] = struct{}{}
b.tmpContainers[c.ID] = struct{}{}
//start the container
if err := c.Start(); err != nil {
@@ -250,7 +246,7 @@ func (b *buildFile) run() (string, error) {
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret)
}
return c.Id, nil
return c.ID, nil
}
// Commit the container <id> with the autorun command <autoCmd>
@@ -266,17 +262,25 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
return err
} else if cache != nil {
utils.Debugf("[BUILDER] Use cached version")
b.image = cache.Id
b.image = cache.ID
return nil
} else {
utils.Debugf("[BUILDER] Cache miss")
}
if cid, err := b.run(); err != nil {
// Create the container and start it
container, err := b.builder.Create(b.config)
if err != nil {
return err
} else {
id = cid
}
b.tmpContainers[container.ID] = struct{}{}
if err := container.EnsureMounted(); err != nil {
return err
}
defer container.Unmount()
id = container.ID
}
container := b.runtime.Get(id)
@@ -292,8 +296,8 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
if err != nil {
return err
}
b.tmpImages[image.Id] = struct{}{}
b.image = image.Id
b.tmpImages[image.ID] = struct{}{}
b.image = image.ID
return nil
}
@@ -313,10 +317,11 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
for {
line, err := file.ReadString('\n')
if err != nil {
if err == io.EOF {
if err == io.EOF && line == "" {
break
} else if err != io.EOF {
return "", err
}
return "", err
}
line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
// Skip comments and empty line

View File

@@ -15,58 +15,75 @@ run sh -c 'echo root:testpass > /tmp/passwd'
run mkdir -p /var/run/sshd
`
const DockerfileNoNewLine = `
# VERSION 0.1
# DOCKER-VERSION 0.2
from ` + unitTestImageName + `
run sh -c 'echo root:testpass > /tmp/passwd'
run mkdir -p /var/run/sshd`
// FIXME: test building with a context
// FIXME: test building with a local ADD as first command
// FIXME: test building with 2 successive overlapping ADD commands
func TestBuild(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
dockerfiles := []string{Dockerfile, DockerfileNoNewLine}
for _, Dockerfile := range dockerfiles {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
srv := &Server{runtime: runtime}
buildfile := NewBuildFile(srv, &utils.NopWriter{})
buildfile := NewBuildFile(srv, &utils.NopWriter{})
imgId, err := buildfile.Build(strings.NewReader(Dockerfile), nil)
if err != nil {
t.Fatal(err)
}
imgID, err := buildfile.Build(strings.NewReader(Dockerfile), nil)
if err != nil {
t.Fatal(err)
}
builder := NewBuilder(runtime)
container, err := builder.Create(
&Config{
Image: imgId,
Cmd: []string{"cat", "/tmp/passwd"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
builder := NewBuilder(runtime)
container, err := builder.Create(
&Config{
Image: imgID,
Cmd: []string{"cat", "/tmp/passwd"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
output, err := container.Output()
if err != nil {
t.Fatal(err)
}
if string(output) != "root:testpass\n" {
t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, "root:testpass\n")
}
output, err := container.Output()
if err != nil {
t.Fatal(err)
}
if string(output) != "root:testpass\n" {
t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, "root:testpass\n")
}
container2, err := builder.Create(
&Config{
Image: imgId,
Cmd: []string{"ls", "-d", "/var/run/sshd"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container2)
container2, err := builder.Create(
&Config{
Image: imgID,
Cmd: []string{"ls", "-d", "/var/run/sshd"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container2)
output, err = container2.Output()
if err != nil {
t.Fatal(err)
}
if string(output) != "/var/run/sshd\n" {
t.Fatal("/var/run/sshd has not been created")
output, err = container2.Output()
if err != nil {
t.Fatal(err)
}
if string(output) != "/var/run/sshd\n" {
t.Fatal("/var/run/sshd has not been created")
}
}
}

View File

@@ -65,7 +65,7 @@ func Changes(layers []string, rw string) ([]Change, error) {
file := filepath.Base(path)
// If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") {
originalFile := strings.TrimLeft(file, ".wh.")
originalFile := file[len(".wh."):]
change.Path = filepath.Join(filepath.Dir(path), originalFile)
change.Kind = ChangeDelete
} else {

View File

@@ -20,6 +20,7 @@ import (
"path"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"syscall"
@@ -28,10 +29,10 @@ import (
"unicode"
)
const VERSION = "0.3.4"
const VERSION = "0.4.2"
var (
GIT_COMMIT string
GITCOMMIT string
)
func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
@@ -102,7 +103,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"stop", "Stop a running container"},
{"tag", "Tag an image into a repository"},
{"version", "Show the docker version information"},
{"wait", "Block until a container stops}, then print its exit code"},
{"wait", "Block until a container stops, then print its exit code"},
} {
help += fmt.Sprintf(" %-10.10s%s\n", command[0], command[1])
}
@@ -159,11 +160,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
file = os.Stdin
} else {
// Send Dockerfile from arg/Dockerfile (deprecate later)
if f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile")); err != nil {
f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile"))
if err != nil {
return err
} else {
file = f
}
file = f
// Send context from arg
// Create a FormFile multipart for the context if needed
// FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage?
@@ -176,20 +177,21 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
if err != nil {
return err
}
if wField, err := w.CreateFormFile("Context", filepath.Base(absPath)+"."+compression.Extension()); err != nil {
wField, err := w.CreateFormFile("Context", filepath.Base(absPath)+"."+compression.Extension())
if err != nil {
return err
} else {
// FIXME: Find a way to have a progressbar for the upload too
io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Caching Context %v/%v (%v)\r", false))
}
// FIXME: Find a way to have a progressbar for the upload too
sf := utils.NewStreamFormatter(false)
io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf))
multipartBody = io.MultiReader(multipartBody, boundary)
}
// Create a FormFile multipart for the Dockerfile
if wField, err := w.CreateFormFile("Dockerfile", "Dockerfile"); err != nil {
wField, err := w.CreateFormFile("Dockerfile", "Dockerfile")
if err != nil {
return err
} else {
io.Copy(wField, file)
}
io.Copy(wField, file)
multipartBody = io.MultiReader(multipartBody, boundary)
v := &url.Values{}
@@ -217,7 +219,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
if err != nil {
return err
}
return fmt.Errorf("error: %s", body)
if len(body) == 0 {
return fmt.Errorf("Error: %s", http.StatusText(resp.StatusCode))
}
return fmt.Errorf("Error: %s", body)
}
// Output the result
@@ -275,36 +280,24 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
oldState, err := term.SetRawTerminal()
if err != nil {
return err
} else {
defer term.RestoreTerminal(oldState)
}
defer term.RestoreTerminal(oldState)
cmd := Subcmd("login", "", "Register or Login to the docker registry server")
if err := cmd.Parse(args); err != nil {
return nil
}
body, _, err := cli.call("GET", "/auth", nil)
if err != nil {
return err
}
var out auth.AuthConfig
err = json.Unmarshal(body, &out)
if err != nil {
return err
}
var username string
var password string
var email string
fmt.Print("Username (", out.Username, "): ")
fmt.Print("Username (", cli.authConfig.Username, "): ")
username = readAndEchoString(os.Stdin, os.Stdout)
if username == "" {
username = out.Username
username = cli.authConfig.Username
}
if username != out.Username {
if username != cli.authConfig.Username {
fmt.Print("Password: ")
password = readString(os.Stdin, os.Stdout)
@@ -312,31 +305,33 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
return fmt.Errorf("Error : Password Required")
}
fmt.Print("Email (", out.Email, "): ")
fmt.Print("Email (", cli.authConfig.Email, "): ")
email = readAndEchoString(os.Stdin, os.Stdout)
if email == "" {
email = out.Email
email = cli.authConfig.Email
}
} else {
email = out.Email
email = cli.authConfig.Email
}
term.RestoreTerminal(oldState)
out.Username = username
out.Password = password
out.Email = email
cli.authConfig.Username = username
cli.authConfig.Password = password
cli.authConfig.Email = email
body, _, err = cli.call("POST", "/auth", out)
body, _, err := cli.call("POST", "/auth", cli.authConfig)
if err != nil {
return err
}
var out2 ApiAuth
var out2 APIAuth
err = json.Unmarshal(body, &out2)
if err != nil {
auth.LoadConfig(os.Getenv("HOME"))
return err
}
auth.SaveConfig(cli.authConfig)
if out2.Status != "" {
term.RestoreTerminal(oldState)
fmt.Print(out2.Status)
}
return nil
@@ -357,7 +352,7 @@ func (cli *DockerCli) CmdWait(args ...string) error {
if err != nil {
fmt.Printf("%s", err)
} else {
var out ApiWait
var out APIWait
err = json.Unmarshal(body, &out)
if err != nil {
return err
@@ -385,21 +380,20 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
return err
}
var out ApiVersion
var out APIVersion
err = json.Unmarshal(body, &out)
if err != nil {
utils.Debugf("Error unmarshal: body: %s, err: %s\n", body, err)
return err
}
fmt.Println("Version:", out.Version)
fmt.Println("Git Commit:", out.GitCommit)
if !out.MemoryLimit {
fmt.Println("WARNING: No memory limit support")
fmt.Println("Client version:", VERSION)
fmt.Println("Server version:", out.Version)
if out.GitCommit != "" {
fmt.Println("Git commit:", out.GitCommit)
}
if !out.SwapLimit {
fmt.Println("WARNING: No swap limit support")
if out.GoVersion != "" {
fmt.Println("Go version:", out.GoVersion)
}
return nil
}
@@ -419,15 +413,24 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
return err
}
var out ApiInfo
err = json.Unmarshal(body, &out)
if err != nil {
var out APIInfo
if err := json.Unmarshal(body, &out); err != nil {
return err
}
fmt.Printf("containers: %d\nversion: %s\nimages: %d\nGo version: %s\n", out.Containers, out.Version, out.Images, out.GoVersion)
if out.Debug {
fmt.Println("debug mode enabled")
fmt.Printf("fds: %d\ngoroutines: %d\n", out.NFd, out.NGoroutines)
fmt.Printf("Containers: %d\n", out.Containers)
fmt.Printf("Images: %d\n", out.Images)
if out.Debug || os.Getenv("DEBUG") != "" {
fmt.Printf("Debug mode (server): %v\n", out.Debug)
fmt.Printf("Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
fmt.Printf("Fds: %d\n", out.NFd)
fmt.Printf("Goroutines: %d\n", out.NGoroutines)
}
if !out.MemoryLimit {
fmt.Println("WARNING: No memory limit support")
}
if !out.SwapLimit {
fmt.Println("WARNING: No swap limit support")
}
return nil
}
@@ -449,7 +452,7 @@ func (cli *DockerCli) CmdStop(args ...string) error {
for _, name := range cmd.Args() {
_, _, err := cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil)
if err != nil {
fmt.Printf("%s", err)
fmt.Fprintf(os.Stderr, "%s", err)
} else {
fmt.Println(name)
}
@@ -474,7 +477,7 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
for _, name := range cmd.Args() {
_, _, err := cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil)
if err != nil {
fmt.Printf("%s", err)
fmt.Fprintf(os.Stderr, "%s", err)
} else {
fmt.Println(name)
}
@@ -495,7 +498,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
for _, name := range args {
_, _, err := cli.call("POST", "/containers/"+name+"/start", nil)
if err != nil {
fmt.Printf("%s", err)
fmt.Fprintf(os.Stderr, "%s", err)
} else {
fmt.Println(name)
}
@@ -504,29 +507,38 @@ func (cli *DockerCli) CmdStart(args ...string) error {
}
func (cli *DockerCli) CmdInspect(args ...string) error {
cmd := Subcmd("inspect", "CONTAINER|IMAGE", "Return low-level information on a container/image")
cmd := Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image")
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() != 1 {
if cmd.NArg() < 1 {
cmd.Usage()
return nil
}
obj, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
if err != nil {
obj, _, err = cli.call("GET", "/images/"+cmd.Arg(0)+"/json", nil)
fmt.Printf("[")
for i, name := range args {
if i > 0 {
fmt.Printf(",")
}
obj, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
if err != nil {
return err
obj, _, err = cli.call("GET", "/images/"+name+"/json", nil)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
continue
}
}
indented := new(bytes.Buffer)
if err = json.Indent(indented, obj, "", " "); err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
continue
}
if _, err := io.Copy(os.Stdout, indented); err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
}
}
indented := new(bytes.Buffer)
if err = json.Indent(indented, obj, "", " "); err != nil {
return err
}
if _, err := io.Copy(os.Stdout, indented); err != nil {
return err
}
fmt.Printf("]")
return nil
}
@@ -553,7 +565,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
if frontend, exists := out.NetworkSettings.PortMapping[cmd.Arg(1)]; exists {
fmt.Println(frontend)
} else {
return fmt.Errorf("error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0))
return fmt.Errorf("Error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0))
}
return nil
}
@@ -570,11 +582,22 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
}
for _, name := range cmd.Args() {
_, _, err := cli.call("DELETE", "/images/"+name, nil)
body, _, err := cli.call("DELETE", "/images/"+name, nil)
if err != nil {
fmt.Printf("%s", err)
fmt.Fprintf(os.Stderr, "%s", err)
} else {
fmt.Println(name)
var outs []APIRmi
err = json.Unmarshal(body, &outs)
if err != nil {
return err
}
for _, out := range outs {
if out.Deleted != "" {
fmt.Println("Deleted:", out.Deleted)
} else {
fmt.Println("Untagged:", out.Untagged)
}
}
}
}
return nil
@@ -595,7 +618,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
return err
}
var outs []ApiHistory
var outs []APIHistory
err = json.Unmarshal(body, &outs)
if err != nil {
return err
@@ -604,7 +627,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
fmt.Fprintln(w, "ID\tCREATED\tCREATED BY")
for _, out := range outs {
fmt.Fprintf(w, "%s\t%s ago\t%s\n", out.Id, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy)
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
@@ -693,18 +716,31 @@ func (cli *DockerCli) CmdPush(args ...string) error {
return nil
}
username, err := cli.checkIfLogged(*registry == "", "push")
if err != nil {
if err := cli.checkIfLogged(*registry == "", "push"); err != nil {
return err
}
if len(strings.SplitN(name, "/", 2)) == 1 {
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", username, name)
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.authConfig.Username, name)
}
buf, err := json.Marshal(cli.authConfig)
if err != nil {
return err
}
nameParts := strings.SplitN(name, "/", 2)
validNamespace := regexp.MustCompile(`^([a-z0-9_]{4,30})$`)
if !validNamespace.MatchString(nameParts[0]) {
return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", nameParts[0])
}
validRepo := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)$`)
if !validRepo.MatchString(nameParts[1]) {
return fmt.Errorf("Invalid repository name (%s), only [a-zA-Z0-9-_.] are allowed", nameParts[1])
}
v := url.Values{}
v.Set("registry", *registry)
if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, os.Stdout); err != nil {
if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), os.Stdout); err != nil {
return err
}
return nil
@@ -730,12 +766,6 @@ func (cli *DockerCli) CmdPull(args ...string) error {
remote = remoteParts[0]
}
if strings.Contains(remote, "/") {
if _, err := cli.checkIfLogged(true, "pull"); err != nil {
return err
}
}
v := url.Values{}
v.Set("fromImage", remote)
v.Set("tag", *tag)
@@ -783,7 +813,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
return err
}
var outs []ApiImages
var outs []APIImages
err = json.Unmarshal(body, &outs)
if err != nil {
return err
@@ -791,7 +821,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED")
fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED\tSIZE")
}
for _, out := range outs {
@@ -805,16 +835,21 @@ func (cli *DockerCli) CmdImages(args ...string) error {
if !*quiet {
fmt.Fprintf(w, "%s\t%s\t", out.Repository, out.Tag)
if *noTrunc {
fmt.Fprintf(w, "%s\t", out.Id)
fmt.Fprintf(w, "%s\t", out.ID)
} else {
fmt.Fprintf(w, "%s\t", utils.TruncateId(out.Id))
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 out.VirtualSize > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
} else {
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
}
fmt.Fprintf(w, "%s ago\n", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
} else {
if *noTrunc {
fmt.Fprintln(w, out.Id)
fmt.Fprintln(w, out.ID)
} else {
fmt.Fprintln(w, utils.TruncateId(out.Id))
fmt.Fprintln(w, utils.TruncateID(out.ID))
}
}
}
@@ -861,28 +896,33 @@ func (cli *DockerCli) CmdPs(args ...string) error {
return err
}
var outs []ApiContainers
var outs []APIContainers
err = json.Unmarshal(body, &outs)
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS")
fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tSIZE")
}
for _, out := range outs {
if !*quiet {
if *noTrunc {
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", out.Id, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
fmt.Fprintf(w, "%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, out.Ports)
} else {
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", utils.TruncateId(out.Id), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
}
if out.SizeRootFs > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
} else {
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.SizeRw))
}
} else {
if *noTrunc {
fmt.Fprintln(w, out.Id)
fmt.Fprintln(w, out.ID)
} else {
fmt.Fprintln(w, utils.TruncateId(out.Id))
fmt.Fprintln(w, utils.TruncateID(out.ID))
}
}
}
@@ -925,13 +965,13 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
return err
}
apiId := &ApiId{}
err = json.Unmarshal(body, apiId)
apiID := &APIID{}
err = json.Unmarshal(body, apiID)
if err != nil {
return err
}
fmt.Println(apiId.Id)
fmt.Println(apiID.ID)
return nil
}
@@ -988,12 +1028,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
return nil
}
v := url.Values{}
v.Set("logs", "1")
v.Set("stdout", "1")
v.Set("stderr", "1")
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false, nil, os.Stdout); err != nil {
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", nil, os.Stdout); err != nil {
return err
}
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", nil, os.Stderr); err != nil {
return err
}
return nil
@@ -1027,7 +1065,9 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
connections += 1
}
chErrors := make(chan error, connections)
cli.monitorTtySize(cmd.Arg(0))
if container.Config.Tty {
cli.monitorTtySize(cmd.Arg(0))
}
if splitStderr {
go func() {
chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?stream=1&stderr=1", false, nil, os.Stderr)
@@ -1070,7 +1110,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
return err
}
outs := []ApiSearch{}
outs := []APISearch{}
err = json.Unmarshal(body, &outs)
if err != nil {
return err
@@ -1079,7 +1119,12 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\n")
for _, out := range outs {
fmt.Fprintf(w, "%s\t%s\n", out.Name, out.Description)
desc := strings.Replace(out.Description, "\n", " ", -1)
desc = strings.Replace(desc, "\r", " ", -1)
if len(desc) > 45 {
desc = utils.Trunc(desc, 42) + "..."
}
fmt.Fprintf(w, "%s\t%s\n", out.Name, desc)
}
w.Flush()
return nil
@@ -1202,7 +1247,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
return err
}
out := &ApiRun{}
out := &APIRun{}
err = json.Unmarshal(body, out)
if err != nil {
return err
@@ -1223,18 +1268,23 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}
//start the container
_, _, err = cli.call("POST", "/containers/"+out.Id+"/start", nil)
_, _, err = cli.call("POST", "/containers/"+out.ID+"/start", nil)
if err != nil {
return err
}
if !config.AttachStdout && !config.AttachStderr {
fmt.Println(out.ID)
}
if connections > 0 {
chErrors := make(chan error, connections)
cli.monitorTtySize(out.Id)
if config.Tty {
cli.monitorTtySize(out.ID)
}
if splitStderr && config.AttachStderr {
go func() {
chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr)
chErrors <- cli.hijack("POST", "/containers/"+out.ID+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr)
}()
}
@@ -1252,54 +1302,31 @@ func (cli *DockerCli) CmdRun(args ...string) error {
v.Set("stderr", "1")
}
go func() {
chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout)
chErrors <- cli.hijack("POST", "/containers/"+out.ID+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout)
}()
for connections > 0 {
err := <-chErrors
if err != nil {
utils.Debugf("Error hijack: %s", err)
return err
}
connections -= 1
}
}
if !config.AttachStdout && !config.AttachStderr {
fmt.Println(out.Id)
}
return nil
}
func (cli *DockerCli) checkIfLogged(condition bool, action string) (string, error) {
body, _, err := cli.call("GET", "/auth", nil)
if err != nil {
return "", err
}
var out auth.AuthConfig
err = json.Unmarshal(body, &out)
if err != nil {
return "", err
}
func (cli *DockerCli) checkIfLogged(condition bool, action string) error {
// If condition AND the login failed
if condition && out.Username == "" {
if condition && cli.authConfig.Username == "" {
if err := cli.CmdLogin(""); err != nil {
return "", err
return err
}
body, _, err = cli.call("GET", "/auth", nil)
if err != nil {
return "", err
}
err = json.Unmarshal(body, &out)
if err != nil {
return "", err
}
if out.Username == "" {
return "", fmt.Errorf("Please login prior to %s. ('docker login')", action)
if cli.authConfig.Username == "" {
return fmt.Errorf("Please login prior to %s. ('docker login')", action)
}
}
return out.Username, nil
return nil
}
func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) {
@@ -1312,7 +1339,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
params = bytes.NewBuffer(buf)
}
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, API_VERSION, path), params)
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), params)
if err != nil {
return nil, -1, err
}
@@ -1335,7 +1362,10 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
return nil, -1, err
}
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return nil, resp.StatusCode, fmt.Errorf("error: %s", body)
if len(body) == 0 {
return nil, resp.StatusCode, fmt.Errorf("Error: %s", http.StatusText(resp.StatusCode))
}
return nil, resp.StatusCode, fmt.Errorf("Error: %s", body)
}
return body, resp.StatusCode, nil
}
@@ -1344,7 +1374,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
if (method == "POST" || method == "PUT") && in == nil {
in = bytes.NewReader([]byte{})
}
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, API_VERSION, path), in)
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), in)
if err != nil {
return err
}
@@ -1365,24 +1395,25 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
if err != nil {
return err
}
return fmt.Errorf("error: %s", body)
if len(body) == 0 {
return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
}
return fmt.Errorf("Error: %s", body)
}
if resp.Header.Get("Content-Type") == "application/json" {
type Message struct {
Status string `json:"status,omitempty"`
Progress string `json:"progress,omitempty"`
}
dec := json.NewDecoder(resp.Body)
for {
var m Message
var m utils.JSONMessage
if err := dec.Decode(&m); err == io.EOF {
break
} else if err != nil {
return err
}
if m.Progress != "" {
fmt.Fprintf(out, "Downloading %s\r", m.Progress)
fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress)
} else if m.Error != "" {
return fmt.Errorf(m.Error)
} else {
fmt.Fprintf(out, "%s\n", m.Status)
}
@@ -1396,7 +1427,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
}
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", API_VERSION, path), nil)
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
if err != nil {
return err
}
@@ -1417,27 +1448,30 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.Fi
return err
})
if in != nil && setRawTerminal && term.IsTerminal(int(in.Fd())) && os.Getenv("NORAW") == "" {
if oldState, err := term.SetRawTerminal(); err != nil {
if in != nil && setRawTerminal && term.IsTerminal(in.Fd()) && os.Getenv("NORAW") == "" {
oldState, err := term.SetRawTerminal()
if err != nil {
return err
} else {
defer term.RestoreTerminal(oldState)
}
defer term.RestoreTerminal(oldState)
}
sendStdin := utils.Go(func() error {
_, err := io.Copy(rwc, in)
io.Copy(rwc, in)
if err := rwc.(*net.TCPConn).CloseWrite(); err != nil {
fmt.Fprintf(os.Stderr, "Couldn't send EOF: %s\n", err)
utils.Debugf("Couldn't send EOF: %s\n", err)
}
return err
// Discard errors due to pipe interruption
return nil
})
if err := <-receiveStdout; err != nil {
utils.Debugf("Error receiveStdout: %s", err)
return err
}
if !term.IsTerminal(int(os.Stdin.Fd())) {
if !term.IsTerminal(in.Fd()) {
if err := <-sendStdin; err != nil {
utils.Debugf("Error sendStdin: %s", err)
return err
}
}
@@ -1482,10 +1516,12 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
}
func NewDockerCli(addr string, port int) *DockerCli {
return &DockerCli{addr, port}
authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
return &DockerCli{addr, port, authConfig}
}
type DockerCli struct {
host string
port int
host string
port int
authConfig *auth.AuthConfig
}

View File

@@ -13,6 +13,7 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -23,7 +24,7 @@ import (
type Container struct {
root string
Id string
ID string
Created time.Time
@@ -167,8 +168,8 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
}
type NetworkSettings struct {
IpAddress string
IpPrefixLen int
IPAddress string
IPPrefixLen int
Gateway string
Bridge string
PortMapping map[string]string
@@ -355,6 +356,18 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
errors <- err
}()
}
} else {
go func() {
if stdinCloser != nil {
defer stdinCloser.Close()
}
if cStdout, err := container.StdoutPipe(); err != nil {
utils.Debugf("Error stdout pipe")
} else {
io.Copy(&utils.NopWriter{}, cStdout)
}
}()
}
if stderr != nil {
nJobs += 1
@@ -381,7 +394,20 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
errors <- err
}()
}
} else {
go func() {
if stdinCloser != nil {
defer stdinCloser.Close()
}
if cStderr, err := container.StderrPipe(); err != nil {
utils.Debugf("Error stdout pipe")
} else {
io.Copy(&utils.NopWriter{}, cStderr)
}
}()
}
return utils.Go(func() error {
if cStdout != nil {
defer cStdout.Close()
@@ -409,7 +435,7 @@ func (container *Container) Start() error {
defer container.State.unlock()
if container.State.Running {
return fmt.Errorf("The container %s is already running.", container.Id)
return fmt.Errorf("The container %s is already running.", container.ID)
}
if err := container.EnsureMounted(); err != nil {
return err
@@ -431,24 +457,24 @@ func (container *Container) Start() error {
// Create the requested volumes volumes
for volPath := range container.Config.Volumes {
if c, err := container.runtime.volumes.Create(nil, container, "", "", nil); err != nil {
c, err := container.runtime.volumes.Create(nil, container, "", "", nil)
if err != nil {
return err
} else {
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return nil
}
container.Volumes[volPath] = c.Id
}
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return nil
}
container.Volumes[volPath] = c.ID
}
if container.Config.VolumesFrom != "" {
c := container.runtime.Get(container.Config.VolumesFrom)
if c == nil {
return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.Id)
return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.ID)
}
for volPath, id := range c.Volumes {
if _, exists := container.Volumes[volPath]; exists {
return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.Id)
return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.ID)
}
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return nil
@@ -462,7 +488,7 @@ func (container *Container) Start() error {
}
params := []string{
"-n", container.Id,
"-n", container.ID,
"-f", container.lxcConfigPath(),
"--",
"/sbin/init",
@@ -573,17 +599,17 @@ func (container *Container) allocateNetwork() error {
}
container.NetworkSettings.PortMapping = make(map[string]string)
for _, spec := range container.Config.PortSpecs {
if nat, err := iface.AllocatePort(spec); err != nil {
nat, err := iface.AllocatePort(spec)
if err != nil {
iface.Release()
return err
} else {
container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend)
}
container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend)
}
container.network = iface
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
container.NetworkSettings.IpAddress = iface.IPNet.IP.String()
container.NetworkSettings.IpPrefixLen, _ = iface.IPNet.Mask.Size()
container.NetworkSettings.IPAddress = iface.IPNet.IP.String()
container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size()
container.NetworkSettings.Gateway = iface.Gateway.String()
return nil
}
@@ -597,16 +623,16 @@ func (container *Container) releaseNetwork() {
// FIXME: replace this with a control socket within docker-init
func (container *Container) waitLxc() error {
for {
if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput()
if err != nil {
return err
} else {
if !strings.Contains(string(output), "RUNNING") {
return nil
}
}
if !strings.Contains(string(output), "RUNNING") {
return nil
}
time.Sleep(500 * time.Millisecond)
}
return nil
panic("Unreachable")
}
func (container *Container) monitor() {
@@ -616,17 +642,17 @@ func (container *Container) monitor() {
// If the command does not exists, try to wait via lxc
if container.cmd == nil {
if err := container.waitLxc(); err != nil {
utils.Debugf("%s: Process: %s", container.Id, err)
utils.Debugf("%s: Process: %s", container.ID, err)
}
} else {
if err := container.cmd.Wait(); err != nil {
// Discard the error as any signals or non 0 returns will generate an error
utils.Debugf("%s: Process: %s", container.Id, err)
utils.Debugf("%s: Process: %s", container.ID, err)
}
}
utils.Debugf("Process finished")
var exitCode int = -1
exitCode := -1
if container.cmd != nil {
exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
}
@@ -635,24 +661,24 @@ func (container *Container) monitor() {
container.releaseNetwork()
if container.Config.OpenStdin {
if err := container.stdin.Close(); err != nil {
utils.Debugf("%s: Error close stdin: %s", container.Id, err)
utils.Debugf("%s: Error close stdin: %s", container.ID, err)
}
}
if err := container.stdout.CloseWriters(); err != nil {
utils.Debugf("%s: Error close stdout: %s", container.Id, err)
utils.Debugf("%s: Error close stdout: %s", container.ID, err)
}
if err := container.stderr.CloseWriters(); err != nil {
utils.Debugf("%s: Error close stderr: %s", container.Id, err)
utils.Debugf("%s: Error close stderr: %s", container.ID, err)
}
if container.ptyMaster != nil {
if err := container.ptyMaster.Close(); err != nil {
utils.Debugf("%s: Error closing Pty master: %s", container.Id, err)
utils.Debugf("%s: Error closing Pty master: %s", container.ID, err)
}
}
if err := container.Unmount(); err != nil {
log.Printf("%v: Failed to umount filesystem: %v", container.Id, err)
log.Printf("%v: Failed to umount filesystem: %v", container.ID, err)
}
// Re-create a brand new stdin pipe once the container exited
@@ -673,7 +699,7 @@ func (container *Container) monitor() {
// This is because State.setStopped() has already been called, and has caused Wait()
// to return.
// FIXME: why are we serializing running state to disk in the first place?
//log.Printf("%s: Failed to dump configuration to the disk: %s", container.Id, err)
//log.Printf("%s: Failed to dump configuration to the disk: %s", container.ID, err)
}
}
@@ -683,17 +709,17 @@ func (container *Container) kill() error {
}
// Sending SIGKILL to the process via lxc
output, err := exec.Command("lxc-kill", "-n", container.Id, "9").CombinedOutput()
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)
log.Printf("error killing container %s (%s, %s)", container.ID, output, 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.ID)
}
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 SIGKILL - trying direct SIGKILL", container.ID)
if err := container.cmd.Process.Kill(); err != nil {
return err
}
@@ -721,7 +747,7 @@ func (container *Container) Stop(seconds int) error {
}
// 1. Send a SIGTERM
if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
if output, err := exec.Command("lxc-kill", "-n", container.ID, "15").CombinedOutput(); err != nil {
log.Print(string(output))
log.Print("Failed to send SIGTERM to the process, force killing")
if err := container.kill(); err != nil {
@@ -731,7 +757,7 @@ 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)
log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds)
if err := container.kill(); err != nil {
return err
}
@@ -795,7 +821,8 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
case <-done:
return nil
}
panic("unreachable")
panic("Unreachable")
}
func (container *Container) EnsureMounted() error {
@@ -838,16 +865,16 @@ func (container *Container) Unmount() error {
return Unmount(container.RootfsPath())
}
// ShortId returns a shorthand version of the container's id for convenience.
// ShortID returns a shorthand version of the container's id for convenience.
// A collision with other container shorthands is very unlikely, but possible.
// In case of a collision a lookup with Runtime.Get() will fail, and the caller
// will need to use a langer prefix, or the full-length container Id.
func (container *Container) ShortId() string {
return utils.TruncateId(container.Id)
func (container *Container) ShortID() string {
return utils.TruncateID(container.ID)
}
func (container *Container) logPath(name string) string {
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.Id, name))
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.ID, name))
}
func (container *Container) ReadLog(name string) (io.Reader, error) {
@@ -887,9 +914,32 @@ func (container *Container) rwPath() string {
return path.Join(container.root, "rw")
}
func validateId(id string) error {
func validateID(id string) error {
if id == "" {
return fmt.Errorf("Invalid empty id")
}
return nil
}
// GetSize, return real size, virtual size
func (container *Container) GetSize() (int64, int64) {
var sizeRw, sizeRootfs int64
filepath.Walk(container.rwPath(), func(path string, fileInfo os.FileInfo, err error) error {
if fileInfo != nil {
sizeRw += fileInfo.Size()
}
return nil
})
_, err := os.Stat(container.RootfsPath())
if err == nil {
filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error {
if fileInfo != nil {
sizeRootfs += fileInfo.Size()
}
return nil
})
}
return sizeRw, sizeRootfs
}

View File

@@ -14,7 +14,7 @@ import (
"time"
)
func TestIdFormat(t *testing.T) {
func TestIDFormat(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
@@ -22,19 +22,19 @@ func TestIdFormat(t *testing.T) {
defer nuke(runtime)
container1, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "echo hello world"},
},
)
if err != nil {
t.Fatal(err)
}
match, err := regexp.Match("^[0-9a-f]{64}$", []byte(container1.Id))
match, err := regexp.Match("^[0-9a-f]{64}$", []byte(container1.ID))
if err != nil {
t.Fatal(err)
}
if !match {
t.Fatalf("Invalid container ID: %s", container1.Id)
t.Fatalf("Invalid container ID: %s", container1.ID)
}
}
@@ -46,7 +46,7 @@ func TestMultipleAttachRestart(t *testing.T) {
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c",
"i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"},
},
@@ -153,7 +153,7 @@ func TestDiff(t *testing.T) {
// Create a container and remove a file
container1, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/rm", "/etc/passwd"},
},
)
@@ -194,7 +194,7 @@ func TestDiff(t *testing.T) {
// Create a new container from the commited image
container2, err := builder.Create(
&Config{
Image: img.Id,
Image: img.ID,
Cmd: []string{"cat", "/etc/passwd"},
},
)
@@ -217,6 +217,37 @@ func TestDiff(t *testing.T) {
t.Fatalf("/etc/passwd should not be present in the diff after commit.")
}
}
// Create a new containere
container3, err := builder.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"rm", "/bin/httpd"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container3)
if err := container3.Run(); err != nil {
t.Fatal(err)
}
// Check the changelog
c, err = container3.Changes()
if err != nil {
t.Fatal(err)
}
success = false
for _, elem := range c {
if elem.Path == "/bin/httpd" && elem.Kind == 2 {
success = true
}
}
if !success {
t.Fatalf("/bin/httpd should be present in the diff after commit.")
}
}
func TestCommitAutoRun(t *testing.T) {
@@ -229,7 +260,7 @@ func TestCommitAutoRun(t *testing.T) {
builder := NewBuilder(runtime)
container1, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
},
)
@@ -260,7 +291,7 @@ func TestCommitAutoRun(t *testing.T) {
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
container2, err := builder.Create(
&Config{
Image: img.Id,
Image: img.ID,
},
)
if err != nil {
@@ -309,7 +340,7 @@ func TestCommitRun(t *testing.T) {
container1, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
},
)
@@ -341,7 +372,7 @@ func TestCommitRun(t *testing.T) {
container2, err := builder.Create(
&Config{
Image: img.Id,
Image: img.ID,
Cmd: []string{"cat", "/world"},
},
)
@@ -388,7 +419,7 @@ func TestStart(t *testing.T) {
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Memory: 33554432,
CpuShares: 1000,
Cmd: []string{"/bin/cat"},
@@ -432,7 +463,7 @@ func TestRun(t *testing.T) {
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
@@ -460,7 +491,7 @@ func TestOutput(t *testing.T) {
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"},
},
)
@@ -484,7 +515,7 @@ func TestKillDifferentUser(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"tail", "-f", "/etc/resolv.conf"},
User: "daemon",
},
@@ -532,7 +563,7 @@ func TestKill(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/dev/zero"},
},
)
@@ -580,7 +611,7 @@ func TestExitCode(t *testing.T) {
builder := NewBuilder(runtime)
trueContainer, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true", ""},
})
if err != nil {
@@ -595,7 +626,7 @@ func TestExitCode(t *testing.T) {
}
falseContainer, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/false", ""},
})
if err != nil {
@@ -617,7 +648,7 @@ func TestRestart(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"},
},
)
@@ -650,7 +681,7 @@ func TestRestartStdin(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"},
OpenStdin: true,
@@ -732,7 +763,7 @@ func TestUser(t *testing.T) {
// Default user must be root
container, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
},
)
@@ -750,7 +781,7 @@ func TestUser(t *testing.T) {
// Set a username
container, err = builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
User: "root",
@@ -770,7 +801,7 @@ func TestUser(t *testing.T) {
// Set a UID
container, err = builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
User: "0",
@@ -790,7 +821,7 @@ func TestUser(t *testing.T) {
// Set a different user by uid
container, err = builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
User: "1",
@@ -812,7 +843,7 @@ func TestUser(t *testing.T) {
// Set a different user by username
container, err = builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
User: "daemon",
@@ -841,7 +872,7 @@ func TestMultipleContainers(t *testing.T) {
builder := NewBuilder(runtime)
container1, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/dev/zero"},
},
)
@@ -851,7 +882,7 @@ func TestMultipleContainers(t *testing.T) {
defer runtime.Destroy(container1)
container2, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/dev/zero"},
},
)
@@ -897,7 +928,7 @@ func TestStdin(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"},
OpenStdin: true,
@@ -944,7 +975,7 @@ func TestTty(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"},
OpenStdin: true,
@@ -991,7 +1022,7 @@ func TestEnv(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/usr/bin/env"},
},
)
@@ -1069,7 +1100,7 @@ func TestLXCConfig(t *testing.T) {
cpuMax := 10000
cpu := cpuMin + rand.Intn(cpuMax-cpuMin)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true"},
Hostname: "foobar",
@@ -1097,7 +1128,7 @@ func BenchmarkRunSequencial(b *testing.B) {
defer nuke(runtime)
for i := 0; i < b.N; i++ {
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foo"},
},
)
@@ -1132,7 +1163,7 @@ func BenchmarkRunParallel(b *testing.B) {
tasks = append(tasks, complete)
go func(i int, complete chan error) {
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foo"},
},
)

View File

@@ -11,13 +11,13 @@ import (
"time"
)
var DOCKER_PATH string = path.Join(os.Getenv("DOCKERPATH"), "docker")
var DOCKERPATH = path.Join(os.Getenv("DOCKERPATH"), "docker")
// WARNING: this crashTest will 1) crash your host, 2) remove all containers
func runDaemon() (*exec.Cmd, error) {
os.Remove("/var/run/docker.pid")
exec.Command("rm", "-rf", "/var/lib/docker/containers").Run()
cmd := exec.Command(DOCKER_PATH, "-d")
cmd := exec.Command(DOCKERPATH, "-d")
outPipe, err := cmd.StdoutPipe()
if err != nil {
return nil, err
@@ -77,7 +77,7 @@ func crashTest() error {
stop = false
for i := 0; i < 100 && !stop; {
func() error {
cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", fmt.Sprintf("%d", totalTestCount))
cmd := exec.Command(DOCKERPATH, "run", "base", "echo", fmt.Sprintf("%d", totalTestCount))
i++
totalTestCount++
outPipe, err := cmd.StdoutPipe()

View File

@@ -1 +0,0 @@
Solomon Hykes <solomon@dotcloud.com>

View File

@@ -1,68 +0,0 @@
# docker-build: build your software with docker
## Description
docker-build is a script to build docker images from source. It will be deprecated once the 'build' feature is incorporated into docker itself (See https://github.com/dotcloud/docker/issues/278)
Author: Solomon Hykes <solomon@dotcloud.com>
## Install
docker-builder requires:
1) A reasonably recent Python setup (tested on 2.7.2).
2) A running docker daemon at version 0.1.4 or more recent (http://www.docker.io/gettingstarted)
## Usage
First create a valid Changefile, which defines a sequence of changes to apply to a base image.
$ cat Changefile
# Start build from a know base image
from base:ubuntu-12.10
# Update ubuntu sources
run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
run apt-get update
# Install system packages
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
# Insert files from the host (./myscript must be present in the current directory)
copy myscript /usr/local/bin/myscript
Run docker-build, and pass the contents of your Changefile as standard input.
$ IMG=$(./docker-build < Changefile)
This will take a while: for each line of the changefile, docker-build will:
1. Create a new container to execute the given command or insert the given file
2. Wait for the container to complete execution
3. Commit the resulting changes as a new image
4. Use the resulting image as the input of the next step
If all the steps succeed, the result will be an image containing the combined results of each build step.
You can trace back those build steps by inspecting the image's history:
$ docker history $IMG
ID CREATED CREATED BY
1e9e2045de86 A few seconds ago /bin/sh -c cat > /usr/local/bin/myscript; chmod +x /usr/local/bin/git
77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
83e85d155451 A few seconds ago /bin/sh -c apt-get update
bfd53b36d9d3 A few seconds ago /bin/sh -c echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
base 2 weeks ago /bin/bash
27cf78414709 2 weeks ago
Note that your build started from 'base', as instructed by your Changefile. But that base image itself seems to have been built in 2 steps - hence the extra step in the history.
You can use this build technique to create any image you want: a database, a web application, or anything else that can be build by a sequence of unix commands - in other words, anything else.

View File

@@ -1,142 +0,0 @@
#!/usr/bin/env python
# docker-build is a script to build docker images from source.
# It will be deprecated once the 'build' feature is incorporated into docker itself.
# (See https://github.com/dotcloud/docker/issues/278)
#
# Author: Solomon Hykes <solomon@dotcloud.com>
# First create a valid Changefile, which defines a sequence of changes to apply to a base image.
#
# $ cat Changefile
# # Start build from a know base image
# from base:ubuntu-12.10
# # Update ubuntu sources
# run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
# run apt-get update
# # Install system packages
# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
# # Insert files from the host (./myscript must be present in the current directory)
# copy myscript /usr/local/bin/myscript
#
#
# Run docker-build, and pass the contents of your Changefile as standard input.
#
# $ IMG=$(./docker-build < Changefile)
#
# This will take a while: for each line of the changefile, docker-build will:
#
# 1. Create a new container to execute the given command or insert the given file
# 2. Wait for the container to complete execution
# 3. Commit the resulting changes as a new image
# 4. Use the resulting image as the input of the next step
import sys
import subprocess
import json
import hashlib
def docker(args, stdin=None):
print "# docker " + " ".join(args)
p = subprocess.Popen(["docker"] + list(args), stdin=stdin, stdout=subprocess.PIPE)
return p.stdout
def image_exists(img):
return docker(["inspect", img]).read().strip() != ""
def image_config(img):
return json.loads(docker(["inspect", img]).read()).get("config", {})
def run_and_commit(img_in, cmd, stdin=None, author=None, run=None):
run_id = docker(["run"] + (["-i", "-a", "stdin"] if stdin else ["-d"]) + [img_in, "/bin/sh", "-c", cmd], stdin=stdin).read().rstrip()
print "---> Waiting for " + run_id
result=int(docker(["wait", run_id]).read().rstrip())
if result != 0:
print "!!! '{}' return non-zero exit code '{}'. Aborting.".format(cmd, result)
sys.exit(1)
return docker(["commit"] + (["-author", author] if author else []) + (["-run", json.dumps(run)] if run is not None else []) + [run_id]).read().rstrip()
def insert(base, src, dst, author=None):
print "COPY {} to {} in {}".format(src, dst, base)
if dst == "":
raise Exception("Missing destination path")
stdin = file(src)
stdin.seek(0)
return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin, author=author)
def add(base, src, dst, author=None):
print "PUSH to {} in {}".format(dst, base)
if src == ".":
tar = subprocess.Popen(["tar", "-c", "."], stdout=subprocess.PIPE).stdout
else:
tar = subprocess.Popen(["curl", src], stdout=subprocess.PIPE).stdout
if dst == "":
raise Exception("Missing argument to push")
return run_and_commit(base, "mkdir -p '{0}' && tar -C '{0}' -x".format(dst), stdin=tar, author=author)
def main():
base=""
maintainer=""
steps = []
try:
for line in sys.stdin.readlines():
line = line.strip()
# Skip comments and empty lines
if line == "" or line[0] == "#":
continue
op, param = line.split(None, 1)
print op.upper() + " " + param
if op == "from":
base = param
steps.append(base)
elif op == "maintainer":
maintainer = param
elif op == "run":
result = run_and_commit(base, param, author=maintainer)
steps.append(result)
base = result
print "===> " + base
elif op == "copy":
src, dst = param.split(" ", 1)
result = insert(base, src, dst, author=maintainer)
steps.append(result)
base = result
print "===> " + base
elif op == "add":
src, dst = param.split(" ", 1)
result = add(base, src, dst, author=maintainer)
steps.append(result)
base=result
print "===> " + base
elif op == "expose":
config = image_config(base)
if config.get("PortSpecs") is None:
config["PortSpecs"] = []
portspec = param.strip()
config["PortSpecs"].append(portspec)
result = run_and_commit(base, "# (nop) expose port {}".format(portspec), author=maintainer, run=config)
steps.append(result)
base=result
print "===> " + base
elif op == "cmd":
config = image_config(base)
cmd = list(json.loads(param))
config["Cmd"] = cmd
result = run_and_commit(base, "# (nop) set default command to '{}'".format(" ".join(cmd)), author=maintainer, run=config)
steps.append(result)
base=result
print "===> " + base
else:
print "Skipping uknown op " + op
except:
docker(["rmi"] + steps[1:])
raise
print base
if __name__ == "__main__":
main()

View File

@@ -1,13 +0,0 @@
# Start build from a know base image
maintainer Solomon Hykes <solomon@dotcloud.com>
from base:ubuntu-12.10
# Update ubuntu sources
run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
run apt-get update
# Install system packages
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
# Insert files from the host (./myscript must be present in the current directory)
copy myscript /usr/local/bin/myscript
push /src

View File

@@ -1,3 +0,0 @@
#!/bin/sh
echo hello, world!

View File

@@ -2,18 +2,15 @@
set -e
# these should match the names found at http://www.debian.org/releases/
stableSuite='squeeze'
testingSuite='wheezy'
stableSuite='wheezy'
testingSuite='jessie'
unstableSuite='sid'
# if suite is equal to this, it gets the "latest" tag
latestSuite="$testingSuite"
variant='minbase'
include='iproute,iputils-ping'
repo="$1"
suite="${2:-$latestSuite}"
suite="${2:-$stableSuite}"
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
if [ ! "$repo" ]; then
@@ -41,17 +38,14 @@ img=$(sudo tar -c . | docker import -)
# tag suite
docker tag $img $repo $suite
if [ "$suite" = "$latestSuite" ]; then
# tag latest
docker tag $img $repo latest
fi
# test the image
docker run -i -t $repo:$suite echo success
# unstable's version numbers match testing (since it's mostly just a sandbox for testing), so it doesn't get a version number tag
if [ "$suite" != "$unstableSuite" -a "$suite" != 'unstable' ]; then
# tag the specific version
if [ "$suite" = "$stableSuite" -o "$suite" = 'stable' ]; then
# tag latest
docker tag $img $repo latest
# tag the specific debian release version
ver=$(docker run $repo:$suite cat /etc/debian_version)
docker tag $img $repo $ver
fi

View File

@@ -15,7 +15,7 @@ import (
)
var (
GIT_COMMIT string
GITCOMMIT string
)
func main() {
@@ -33,6 +33,8 @@ func main() {
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
flDns := flag.String("dns", "", "Set custom dns servers")
flag.Parse()
if *bridgeName != "" {
docker.NetworkBridgeIface = *bridgeName
@@ -59,13 +61,13 @@ func main() {
if *flDebug {
os.Setenv("DEBUG", "1")
}
docker.GIT_COMMIT = GIT_COMMIT
docker.GITCOMMIT = GITCOMMIT
if *flDaemon {
if flag.NArg() != 0 {
flag.Usage()
return
}
if err := daemon(*pidfile, host, port, *flAutoRestart); err != nil {
if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors, *flDns); err != nil {
log.Fatal(err)
os.Exit(-1)
}
@@ -104,7 +106,7 @@ func removePidFile(pidfile string) {
}
}
func daemon(pidfile, addr string, port int, autoRestart bool) error {
func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns string) error {
if addr != "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 /!\\")
}
@@ -121,8 +123,11 @@ func daemon(pidfile, addr string, port int, autoRestart bool) error {
removePidFile(pidfile)
os.Exit(0)
}()
server, err := docker.NewServer(autoRestart)
var dns []string
if flDns != "" {
dns = []string{flDns}
}
server, err := docker.NewServer(autoRestart, enableCors, dns)
if err != nil {
return err
}

View File

@@ -6,6 +6,7 @@ SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
PYTHON = python
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
@@ -38,17 +39,19 @@ help:
# @echo " linkcheck to check all external links for integrity"
# @echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " docs to build the docs and copy the static files to the outputdir"
@echo " server to serve the docs in your browser under \`http://localhost:8000\`"
@echo " publish to publish the app to dotcloud"
clean:
-rm -rf $(BUILDDIR)/*
docs:
#-rm -rf $(BUILDDIR)/*
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The documentation pages are now in $(BUILDDIR)/html."
server: docs
@cd $(BUILDDIR)/html; $(PYTHON) -m SimpleHTTPServer 8000
site:
cp -r website $(BUILDDIR)/
@@ -58,12 +61,13 @@ site:
connect:
@echo connecting dotcloud to www.docker.io website, make sure to use user 1
@cd _build/website/ ; \
dotcloud connect dockerwebsite ;
@echo or create your own "dockerwebsite" app
@cd $(BUILDDIR)/website/ ; \
dotcloud connect dockerwebsite ; \
dotcloud list
push:
@cd _build/website/ ; \
@cd $(BUILDDIR)/website/ ; \
dotcloud push
$(VERSIONS):

View File

@@ -14,20 +14,22 @@ Installation
------------
* Work in your own fork of the code, we accept pull requests.
* Install sphinx: ``pip install sphinx``
* Install sphinx httpdomain contrib package ``sphinxcontrib-httpdomain``
* Install sphinx: `pip install sphinx`
* Mac OS X: `[sudo] pip-2.7 install sphinx`)
* Install sphinx httpdomain contrib package: `pip install sphinxcontrib-httpdomain`
* Mac OS X: `[sudo] pip-2.7 install sphinxcontrib-httpdomain`
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
Usage
-----
* change the .rst files with your favorite editor to your liking
* run *make docs* to clean up old files and generate new ones
* your static website can now be found in the _build dir
* to preview what you have generated, cd into _build/html and then run 'python -m SimpleHTTPServer 8000'
* Change the `.rst` files with your favorite editor to your liking.
* Run `make docs` to clean up old files and generate new ones.
* Your static website can now be found in the `_build` directory.
* To preview what you have generated run `make server` and open <http://localhost:8000/> in your favorite browser.
Working using github's file editor
Working using GitHub's file editor
----------------------------------
Alternatively, for small changes and typo's you might want to use github's built in file editor. It allows
Alternatively, for small changes and typo's you might want to use GitHub's built in file editor. It allows
you to preview your changes right online. Just be carefull not to create many commits.
Images
@@ -72,4 +74,4 @@ Guides on using sphinx
* Code examples
Start without $, so it's easy to copy and paste.
Start without $, so it's easy to copy and paste.

View File

@@ -0,0 +1,5 @@
This directory holds the authoritative specifications of APIs defined and implemented by Docker. Currently this includes:
* The remote API by which a docker node can be queried over HTTP
* The registry API by which a docker node can download and upload container images for storage and sharing
* The index search API by which a docker node can search the public index for images to download

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,16 +2,17 @@
:description: docker documentation
:keywords: docker, ipa, documentation
API's
=============
APIs
====
This following :
Your programs and scripts can access Docker's functionality via these interfaces:
.. toctree::
:maxdepth: 3
registry_index_spec
registry_api
index_search_api
index_api
docker_remote_api

View File

@@ -0,0 +1,553 @@
:title: Index API
:description: API Documentation for Docker Index
:keywords: API, Docker, index, REST, documentation
=================
Docker Index API
=================
.. contents:: Table of Contents
1. Brief introduction
=====================
- This is the REST API for the Docker index
- Authorization is done with basic auth over SSL
- Not all commands require authentication, only those noted as such.
2. Endpoints
============
2.1 Repository
^^^^^^^^^^^^^^
Repositories
*************
User Repo
~~~~~~~~~
.. http:put:: /v1/repositories/(namespace)/(repo_name)/
Create a user repository with the given ``namespace`` and ``repo_name``.
**Example Request**:
.. sourcecode:: http
PUT /v1/repositories/foo/bar/ HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
X-Docker-Token: true
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}]
:parameter namespace: the namespace for the repo
:parameter repo_name: the name for the repo
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=write
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
""
:statuscode 200: Created
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
:statuscode 401: Unauthorized
:statuscode 403: Account is not Active
.. http:delete:: /v1/repositories/(namespace)/(repo_name)/
Delete a user repository with the given ``namespace`` and ``repo_name``.
**Example Request**:
.. sourcecode:: http
DELETE /v1/repositories/foo/bar/ HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
X-Docker-Token: true
""
:parameter namespace: the namespace for the repo
:parameter repo_name: the name for the repo
**Example Response**:
.. sourcecode:: http
HTTP/1.1 202
Vary: Accept
Content-Type: application/json
WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=delete
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
""
:statuscode 200: Deleted
:statuscode 202: Accepted
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
:statuscode 401: Unauthorized
:statuscode 403: Account is not Active
Library Repo
~~~~~~~~~~~~
.. http:put:: /v1/repositories/(repo_name)/
Create a library repository with the given ``repo_name``.
This is a restricted feature only available to docker admins.
When namespace is missing, it is assumed to be ``library``
**Example Request**:
.. sourcecode:: http
PUT /v1/repositories/foobar/ HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
X-Docker-Token: true
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}]
:parameter repo_name: the library name for the repo
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
WWW-Authenticate: Token signature=123abc,repository=”library/foobar”,access=write
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
""
:statuscode 200: Created
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
:statuscode 401: Unauthorized
:statuscode 403: Account is not Active
.. http:delete:: /v1/repositories/(repo_name)/
Delete a library repository with the given ``repo_name``.
This is a restricted feature only available to docker admins.
When namespace is missing, it is assumed to be ``library``
**Example Request**:
.. sourcecode:: http
DELETE /v1/repositories/foobar/ HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
X-Docker-Token: true
""
:parameter repo_name: the library name for the repo
**Example Response**:
.. sourcecode:: http
HTTP/1.1 202
Vary: Accept
Content-Type: application/json
WWW-Authenticate: Token signature=123abc,repository=”library/foobar”,access=delete
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
""
:statuscode 200: Deleted
:statuscode 202: Accepted
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
:statuscode 401: Unauthorized
:statuscode 403: Account is not Active
Repository Images
*****************
User Repo Images
~~~~~~~~~~~~~~~~
.. http:put:: /v1/repositories/(namespace)/(repo_name)/images
Update the images for a user repo.
**Example Request**:
.. sourcecode:: http
PUT /v1/repositories/foo/bar/images HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
:parameter namespace: the namespace for the repo
:parameter repo_name: the name for the repo
**Example Response**:
.. sourcecode:: http
HTTP/1.1 204
Vary: Accept
Content-Type: application/json
""
:statuscode 204: Created
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
:statuscode 401: Unauthorized
:statuscode 403: Account is not Active or permission denied
.. http:get:: /v1/repositories/(namespace)/(repo_name)/images
get the images for a user repo.
**Example Request**:
.. sourcecode:: http
GET /v1/repositories/foo/bar/images HTTP/1.1
Host: index.docker.io
Accept: application/json
:parameter namespace: the namespace for the repo
:parameter repo_name: the name for the repo
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”},
{“id”: “ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds”,
“checksum”: “34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew”}]
:statuscode 200: OK
:statuscode 404: Not found
Library Repo Images
~~~~~~~~~~~~~~~~~~~
.. http:put:: /v1/repositories/(repo_name)/images
Update the images for a library repo.
**Example Request**:
.. sourcecode:: http
PUT /v1/repositories/foobar/images HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
:parameter repo_name: the library name for the repo
**Example Response**:
.. sourcecode:: http
HTTP/1.1 204
Vary: Accept
Content-Type: application/json
""
:statuscode 204: Created
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
:statuscode 401: Unauthorized
:statuscode 403: Account is not Active or permission denied
.. http:get:: /v1/repositories/(repo_name)/images
get the images for a library repo.
**Example Request**:
.. sourcecode:: http
GET /v1/repositories/foobar/images HTTP/1.1
Host: index.docker.io
Accept: application/json
:parameter repo_name: the library name for the repo
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”},
{“id”: “ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds”,
“checksum”: “34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew”}]
:statuscode 200: OK
:statuscode 404: Not found
Repository Authorization
************************
Library Repo
~~~~~~~~~~~~
.. http:put:: /v1/repositories/(repo_name)/auth
authorize a token for a library repo
**Example Request**:
.. sourcecode:: http
PUT /v1/repositories/foobar/auth HTTP/1.1
Host: index.docker.io
Accept: application/json
Authorization: Token signature=123abc,repository="library/foobar",access=write
:parameter repo_name: the library name for the repo
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
"OK"
:statuscode 200: OK
:statuscode 403: Permission denied
:statuscode 404: Not found
User Repo
~~~~~~~~~
.. http:put:: /v1/repositories/(namespace)/(repo_name)/auth
authorize a token for a user repo
**Example Request**:
.. sourcecode:: http
PUT /v1/repositories/foo/bar/auth HTTP/1.1
Host: index.docker.io
Accept: application/json
Authorization: Token signature=123abc,repository="foo/bar",access=write
:parameter namespace: the namespace for the repo
:parameter repo_name: the name for the repo
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
"OK"
:statuscode 200: OK
:statuscode 403: Permission denied
:statuscode 404: Not found
2.2 Users
^^^^^^^^^
User Login
**********
.. http:get:: /v1/users
If you want to check your login, you can try this endpoint
**Example Request**:
.. sourcecode:: http
GET /v1/users HTTP/1.1
Host: index.docker.io
Accept: application/json
Authorization: Basic akmklmasadalkm==
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
OK
:statuscode 200: no error
:statuscode 401: Unauthorized
:statuscode 403: Account is not Active
User Register
*************
.. http:post:: /v1/users
Registering a new account.
**Example request**:
.. sourcecode:: http
POST /v1/users HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
{"email": "sam@dotcloud.com",
"password": "toto42",
"username": "foobar"'}
:jsonparameter email: valid email address, that needs to be confirmed
:jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
:jsonparameter password: min 5 characters
**Example Response**:
.. sourcecode:: http
HTTP/1.1 201 OK
Vary: Accept
Content-Type: application/json
"User Created"
:statuscode 201: User Created
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
Update User
***********
.. http:put:: /v1/users/(username)/
Change a password or email address for given user. If you pass in an email,
it will add it to your account, it will not remove the old one. Passwords will
be updated.
It is up to the client to verify that that password that is sent is the one that
they want. Common approach is to have them type it twice.
**Example Request**:
.. sourcecode:: http
PUT /v1/users/fakeuser/ HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
{"email": "sam@dotcloud.com",
"password": "toto42"}
:parameter username: username for the person you want to update
**Example Response**:
.. sourcecode:: http
HTTP/1.1 204
Vary: Accept
Content-Type: application/json
""
:statuscode 204: User Updated
:statuscode 400: Errors (invalid json, missing or invalid fields, etc)
:statuscode 401: Unauthorized
:statuscode 403: Account is not Active
:statuscode 404: User not found
2.3 Search
^^^^^^^^^^
If you need to search the index, this is the endpoint you would use.
Search
******
.. http:get:: /v1/search
Search the Index given a search term. It accepts :http:method:`get` only.
**Example request**:
.. sourcecode:: http
GET /v1/search?q=search_term HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{"query":"search_term",
"num_results": 2,
"results" : [
{"name": "dotcloud/base", "description": "A base ubuntu64 image..."},
{"name": "base2", "description": "A base ubuntu64 image..."},
]
}
:query q: what you want to search for
:statuscode 200: no error
:statuscode 500: server error

View File

@@ -1,43 +0,0 @@
:title: Docker Index documentation
:description: Documentation for docker Index
:keywords: docker, index, api
=======================
Docker Index Search API
=======================
Search
------
.. http:get:: /v1/search
Search the Index given a search term. It accepts :http:method:`get` only.
**Example request**:
.. sourcecode:: http
GET /v1/search?q=search_term HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{"query":"search_term",
"num_results": 2,
"results" : [
{"name": "dotcloud/base", "description": "A base ubuntu64 image..."},
{"name": "base2", "description": "A base ubuntu64 image..."},
]
}
:query q: what you want to search for
:statuscode 200: no error
:statuscode 500: server error

View File

@@ -1,7 +1,6 @@
:title: Registry Documentation
:description: Documentation for docker Registry and Registry API
:keywords: docker, registry, api, index
:title: Registry API
:description: API Documentation for Docker Registry
:keywords: API, Docker, index, registry, REST, documentation
===================
Docker Registry API
@@ -9,29 +8,10 @@ Docker Registry API
.. contents:: Table of Contents
1. The 3 roles
===============
1. Brief introduction
=====================
1.1 Index
---------
The Index is responsible for centralizing information about:
- User accounts
- Checksums of the images
- Public namespaces
The Index has different components:
- Web UI
- Meta-data store (comments, stars, list public repositories)
- Authentication service
- Tokenization
The index is authoritative for those information.
We expect that there will be only one instance of the index, run and managed by dotCloud.
1.2 Registry
------------
- This is the REST API for the Docker Registry
- It stores the images and the graph for a set of repositories
- It does not have user accounts data
- It has no notion of user accounts or authorization
@@ -60,419 +40,424 @@ We expect that there will be multiple registries out there. To help to grasp the
The latter would only require two new commands in docker, e.g. “registryget” and “registryput”, wrapping access to the local filesystem (and optionally doing consistency checks). Authentication and authorization are then delegated to SSH (e.g. with public keys).
1.3 Docker
2. Endpoints
============
2.1 Images
----------
On top of being a runtime for LXC, Docker is the Registry client. It supports:
- Push / Pull on the registry
- Client authentication on the Index
Layer
*****
2. Workflow
===========
.. http:get:: /v1/images/(image_id)/layer
2.1 Pull
get image layer for a given ``image_id``
**Example Request**:
.. sourcecode:: http
GET /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/layer HTTP/1.1
Host: registry-1.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Token akmklmasadalkmsdfgsdgdge33
:parameter image_id: the id for the layer you want to get
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
Cookie: (Cookie provided by the Registry)
{
id: "088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c",
parent: "aeee6396d62273d180a49c96c62e45438d87c7da4a5cf5d2be6bee4e21bc226f",
created: "2013-04-30T17:46:10.843673+03:00",
container: "8305672a76cc5e3d168f97221106ced35a76ec7ddbb03209b0f0d96bf74f6ef7",
container_config: {
Hostname: "host-test",
User: "",
Memory: 0,
MemorySwap: 0,
AttachStdin: false,
AttachStdout: false,
AttachStderr: false,
PortSpecs: null,
Tty: false,
OpenStdin: false,
StdinOnce: false,
Env: null,
Cmd: [
"/bin/bash",
"-c",
"apt-get -q -yy -f install libevent-dev"
],
Dns: null,
Image: "imagename/blah",
Volumes: { },
VolumesFrom: ""
},
docker_version: "0.1.7"
}
:statuscode 200: OK
:statuscode 401: Requires authorization
:statuscode 404: Image not found
.. http:put:: /v1/images/(image_id)/layer
put image layer for a given ``image_id``
**Example Request**:
.. sourcecode:: http
PUT /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/layer HTTP/1.1
Host: registry-1.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Token akmklmasadalkmsdfgsdgdge33
{
id: "088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c",
parent: "aeee6396d62273d180a49c96c62e45438d87c7da4a5cf5d2be6bee4e21bc226f",
created: "2013-04-30T17:46:10.843673+03:00",
container: "8305672a76cc5e3d168f97221106ced35a76ec7ddbb03209b0f0d96bf74f6ef7",
container_config: {
Hostname: "host-test",
User: "",
Memory: 0,
MemorySwap: 0,
AttachStdin: false,
AttachStdout: false,
AttachStderr: false,
PortSpecs: null,
Tty: false,
OpenStdin: false,
StdinOnce: false,
Env: null,
Cmd: [
"/bin/bash",
"-c",
"apt-get -q -yy -f install libevent-dev"
],
Dns: null,
Image: "imagename/blah",
Volumes: { },
VolumesFrom: ""
},
docker_version: "0.1.7"
}
:parameter image_id: the id for the layer you want to get
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
""
:statuscode 200: OK
:statuscode 401: Requires authorization
:statuscode 404: Image not found
Image
*****
.. http:put:: /v1/images/(image_id)/json
put image for a given ``image_id``
**Example Request**:
.. sourcecode:: http
PUT /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/json HTTP/1.1
Host: registry-1.docker.io
Accept: application/json
Content-Type: application/json
Cookie: (Cookie provided by the Registry)
{
“id”: “088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c”,
“checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”
}
:parameter image_id: the id for the layer you want to get
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
""
:statuscode 200: OK
:statuscode 401: Requires authorization
.. http:get:: /v1/images/(image_id)/json
get image for a given ``image_id``
**Example Request**:
.. sourcecode:: http
GET /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/json HTTP/1.1
Host: registry-1.docker.io
Accept: application/json
Content-Type: application/json
Cookie: (Cookie provided by the Registry)
:parameter image_id: the id for the layer you want to get
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
{
“id”: “088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c”,
“checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”
}
:statuscode 200: OK
:statuscode 401: Requires authorization
:statuscode 404: Image not found
Ancestry
********
.. http:get:: /v1/images/(image_id)/ancestry
get ancestry for an image given an ``image_id``
**Example Request**:
.. sourcecode:: http
GET /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/ancestry HTTP/1.1
Host: registry-1.docker.io
Accept: application/json
Content-Type: application/json
Cookie: (Cookie provided by the Registry)
:parameter image_id: the id for the layer you want to get
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
["088b4502f51920fbd9b7c503e87c7a2c05aa3adc3d35e79c031fa126b403200f",
"aeee63968d87c7da4a5cf5d2be6bee4e21bc226fd62273d180a49c96c62e4543",
"bfa4c5326bc764280b0863b46a4b20d940bc1897ef9c1dfec060604bdc383280",
"6ab5893c6927c15a15665191f2c6cf751f5056d8b95ceee32e43c5e8a3648544"]
:statuscode 200: OK
:statuscode 401: Requires authorization
:statuscode 404: Image not found
2.2 Tags
--------
.. image:: /static_files/docker_pull_chart.png
.. http:get:: /v1/repositories/(namespace)/(repository)/tags
1. Contact the Index to know where I should download “samalba/busybox”
2. Index replies:
a. “samalba/busybox” is on Registry A
b. here are the checksums for “samalba/busybox” (for all layers)
c. token
3. Contact Registry A to receive the layers for “samalba/busybox” (all of them to the base image). Registry A is authoritative for “samalba/busybox” but keeps a copy of all inherited layers and serve them all from the same location.
4. registry contacts index to verify if token/user is allowed to download images
5. Index returns true/false lettings registry know if it should proceed or error out
6. Get the payload for all layers
get all of the tags for the given repo.
Its possible to run docker pull \https://<registry>/repositories/samalba/busybox. In this case, docker bypasses the Index. However the security is not guaranteed (in case Registry A is corrupted) because there wont be any checksum checks.
**Example Request**:
Currently registry redirects to s3 urls for downloads, going forward all downloads need to be streamed through the registry. The Registry will then abstract the calls to S3 by a top-level class which implements sub-classes for S3 and local storage.
.. sourcecode:: http
Token is only returned when the 'X-Docker-Token' header is sent with request.
Basic Auth is required to pull private repos. Basic auth isn't required for pulling public repos, but if one is provided, it needs to be valid and for an active account.
API (pulling repository foo/bar):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1. (Docker -> Index) GET /v1/repositories/foo/bar/images
**Headers**:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
X-Docker-Token: true
**Action**:
(looking up the foo/bar in db and gets images and checksums for that repo (all if no tag is specified, if tag, only checksums for those tags) see part 4.4.1)
2. (Index -> Docker) HTTP 200 OK
**Headers**:
- Authorization: Token signature=123abc,repository=”foo/bar”,access=write
- X-Docker-Endpoints: registry.docker.io [, registry2.docker.io]
**Body**:
Jsonified checksums (see part 4.4.1)
3. (Docker -> Registry) GET /v1/repositories/foo/bar/tags/latest
**Headers**:
Authorization: Token signature=123abc,repository=”foo/bar”,access=write
4. (Registry -> Index) GET /v1/repositories/foo/bar/images
**Headers**:
Authorization: Token signature=123abc,repository=”foo/bar”,access=read
**Body**:
<ids and checksums in payload>
**Action**:
( Lookup token see if they have access to pull.)
If good:
HTTP 200 OK
Index will invalidate the token
If bad:
HTTP 401 Unauthorized
5. (Docker -> Registry) GET /v1/images/928374982374/ancestry
**Action**:
(for each image id returned in the registry, fetch /json + /layer)
.. note::
If someone makes a second request, then we will always give a new token, never reuse tokens.
2.2 Push
--------
.. image:: /static_files/docker_push_chart.png
1. Contact the index to allocate the repository name “samalba/busybox” (authentication required with user credentials)
2. If authentication works and namespace available, “samalba/busybox” is allocated and a temporary token is returned (namespace is marked as initialized in index)
3. Push the image on the registry (along with the token)
4. Registry A contacts the Index to verify the token (token must corresponds to the repository name)
5. Index validates the token. Registry A starts reading the stream pushed by docker and store the repository (with its images)
6. docker contacts the index to give checksums for upload images
.. note::
**Its possible not to use the Index at all!** In this case, a deployed version of the Registry is deployed to store and serve images. Those images are not authentified and the security is not guaranteed.
.. note::
**Index can be replaced!** For a private Registry deployed, a custom Index can be used to serve and validate token according to different policies.
Docker computes the checksums and submit them to the Index at the end of the push. When a repository name does not have checksums on the Index, it means that the push is in progress (since checksums are submitted at the end).
API (pushing repos foo/bar):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1. (Docker -> Index) PUT /v1/repositories/foo/bar/
**Headers**:
Authorization: Basic sdkjfskdjfhsdkjfh==
X-Docker-Token: true
**Action**::
- in index, we allocated a new repository, and set to initialized
**Body**::
(The body contains the list of images that are going to be pushed, with empty checksums. The checksums will be set at the end of the push)::
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}]
2. (Index -> Docker) 200 Created
**Headers**:
- WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=write
- X-Docker-Endpoints: registry.docker.io [, registry2.docker.io]
3. (Docker -> Registry) PUT /v1/images/98765432_parent/json
**Headers**:
Authorization: Token signature=123abc,repository=”foo/bar”,access=write
4. (Registry->Index) GET /v1/repositories/foo/bar/images
**Headers**:
Authorization: Token signature=123abc,repository=”foo/bar”,access=write
**Action**::
- Index:
will invalidate the token.
- Registry:
grants a session (if token is approved) and fetches the images id
5. (Docker -> Registry) PUT /v1/images/98765432_parent/json
**Headers**::
- Authorization: Token signature=123abc,repository=”foo/bar”,access=write
- Cookie: (Cookie provided by the Registry)
6. (Docker -> Registry) PUT /v1/images/98765432/json
**Headers**:
GET /v1/repositories/foo/bar/tags HTTP/1.1
Host: registry-1.docker.io
Accept: application/json
Content-Type: application/json
Cookie: (Cookie provided by the Registry)
7. (Docker -> Registry) PUT /v1/images/98765432_parent/layer
**Headers**:
:parameter namespace: namespace for the repo
:parameter repository: name for the repo
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
{
"latest": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
“0.1.1”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”
}
:statuscode 200: OK
:statuscode 401: Requires authorization
:statuscode 404: Repository not found
.. http:get:: /v1/repositories/(namespace)/(repository)/tags/(tag)
get a tag for the given repo.
**Example Request**:
.. sourcecode:: http
GET /v1/repositories/foo/bar/tags/latest HTTP/1.1
Host: registry-1.docker.io
Accept: application/json
Content-Type: application/json
Cookie: (Cookie provided by the Registry)
8. (Docker -> Registry) PUT /v1/images/98765432/layer
**Headers**:
X-Docker-Checksum: sha256:436745873465fdjkhdfjkgh
:parameter namespace: namespace for the repo
:parameter repository: name for the repo
:parameter tag: name of tag you want to get
9. (Docker -> Registry) PUT /v1/repositories/foo/bar/tags/latest
**Headers**:
**Example Response**:
.. sourcecode:: http
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
"9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"
:statuscode 200: OK
:statuscode 401: Requires authorization
:statuscode 404: Tag not found
.. http:delete:: /v1/repositories/(namespace)/(repository)/tags/(tag)
delete the tag for the repo
**Example Request**:
.. sourcecode:: http
DELETE /v1/repositories/foo/bar/tags/latest HTTP/1.1
Host: registry-1.docker.io
Accept: application/json
Content-Type: application/json
Cookie: (Cookie provided by the Registry)
**Body**:
“98765432”
10. (Docker -> Index) PUT /v1/repositories/foo/bar/images
:parameter namespace: namespace for the repo
:parameter repository: name for the repo
:parameter tag: name of tag you want to delete
**Headers**:
Authorization: Basic 123oislifjsldfj==
X-Docker-Endpoints: registry1.docker.io (no validation on this right now)
**Example Response**:
**Body**:
(The image, ids, tags and checksums)
.. sourcecode:: http
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
**Return** HTTP 204
""
.. note::
:statuscode 200: OK
:statuscode 401: Requires authorization
:statuscode 404: Tag not found
If push fails and they need to start again, what happens in the index, there will already be a record for the namespace/name, but it will be initialized. Should we allow it, or mark as name already used? One edge case could be if someone pushes the same thing at the same time with two different shells.
If it's a retry on the Registry, Docker has a cookie (provided by the registry after token validation). So the Index wont have to provide a new token.
.. http:put:: /v1/repositories/(namespace)/(repository)/tags/(tag)
3. How to use the Registry in standalone mode
=============================================
put a tag for the given repo.
The Index has two main purposes (along with its fancy social features):
**Example Request**:
- Resolve short names (to avoid passing absolute URLs all the time)
- username/projectname -> \https://registry.docker.io/users/<username>/repositories/<projectname>/
- team/projectname -> \https://registry.docker.io/team/<team>/repositories/<projectname>/
- Authenticate a user as a repos owner (for a central referenced repository)
.. sourcecode:: http
3.1 Without an Index
--------------------
Using the Registry without the Index can be useful to store the images on a private network without having to rely on an external entity controlled by dotCloud.
PUT /v1/repositories/foo/bar/tags/latest HTTP/1.1
Host: registry-1.docker.io
Accept: application/json
Content-Type: application/json
Cookie: (Cookie provided by the Registry)
In this case, the registry will be launched in a special mode (--standalone? --no-index?). In this mode, the only thing which changes is that Registry will never contact the Index to verify a token. It will be the Registry owner responsibility to authenticate the user who pushes (or even pulls) an image using any mechanism (HTTP auth, IP based, etc...).
“9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”
In this scenario, the Registry is responsible for the security in case of data corruption since the checksums are not delivered by a trusted entity.
:parameter namespace: namespace for the repo
:parameter repository: name for the repo
:parameter tag: name of tag you want to add
As hinted previously, a standalone registry can also be implemented by any HTTP server handling GET/PUT requests (or even only GET requests if no write access is necessary).
**Example Response**:
3.2 With an Index
-----------------
.. sourcecode:: http
The Index data needed by the Registry are simple:
- Serve the checksums
- Provide and authorize a Token
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
In the scenario of a Registry running on a private network with the need of centralizing and authorizing, its easy to use a custom Index.
""
The only challenge will be to tell Docker to contact (and trust) this custom Index. Docker will be configurable at some point to use a specific Index, itll be the private entity responsibility (basically the organization who uses Docker in a private environment) to maintain the Index and the Dockers configuration among its consumers.
:statuscode 200: OK
:statuscode 400: Invalid data
:statuscode 401: Requires authorization
:statuscode 404: Image not found
4. The API
==========
2.3 Repositories
----------------
The first version of the api is available here: https://github.com/jpetazzo/docker/blob/acd51ecea8f5d3c02b00a08176171c59442df8b3/docs/images-repositories-push-pull.md
.. http:delete:: /v1/repositories/(namespace)/(repository)/
4.1 Images
----------
delete a repository
The format returned in the images is not defined here (for layer and json), basically because Registry stores exactly the same kind of information as Docker uses to manage them.
**Example Request**:
The format of ancestry is a line-separated list of image ids, in age order. I.e. the images parent is on the last line, the parent of the parent on the next-to-last line, etc.; if the image has no parent, the file is empty.
.. sourcecode:: http
GET /v1/images/<image_id>/layer
PUT /v1/images/<image_id>/layer
GET /v1/images/<image_id>/json
PUT /v1/images/<image_id>/json
GET /v1/images/<image_id>/ancestry
PUT /v1/images/<image_id>/ancestry
DELETE /v1/repositories/foo/bar/ HTTP/1.1
Host: registry-1.docker.io
Accept: application/json
Content-Type: application/json
Cookie: (Cookie provided by the Registry)
4.2 Users
---------
""
4.2.1 Create a user (Index)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
:parameter namespace: namespace for the repo
:parameter repository: name for the repo
POST /v1/users
**Example Response**:
**Body**:
{"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'}
.. sourcecode:: http
**Validation**:
- **username** : min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
- **password**: min 5 characters
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
**Valid**: return HTTP 200
""
Errors: HTTP 400 (we should create error codes for possible errors)
- invalid json
- missing field
- wrong format (username, password, email, etc)
- forbidden name
- name already exists
:statuscode 200: OK
:statuscode 401: Requires authorization
:statuscode 404: Repository not found
.. note::
3.0 Authorization
=================
This is where we describe the authorization process, including the tokens and cookies.
A user account will be valid only if the email has been validated (a validation link is sent to the email address).
4.2.2 Update a user (Index)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
PUT /v1/users/<username>
**Body**:
{"password": "toto"}
.. note::
We can also update email address, if they do, they will need to reverify their new email address.
4.2.3 Login (Index)
^^^^^^^^^^^^^^^^^^^
Does nothing else but asking for a user authentication. Can be used to validate credentials. HTTP Basic Auth for now, maybe change in future.
GET /v1/users
**Return**:
- Valid: HTTP 200
- Invalid login: HTTP 401
- Account inactive: HTTP 403 Account is not Active
4.3 Tags (Registry)
-------------------
The Registry does not know anything about users. Even though repositories are under usernames, its just a namespace for the registry. Allowing us to implement organizations or different namespaces per user later, without modifying the Registrys API.
The following naming restrictions apply:
- Namespaces must match the same regular expression as usernames (See 4.2.1.)
- Repository names must match the regular expression [a-zA-Z0-9-_.]
4.3.1 Get all tags
^^^^^^^^^^^^^^^^^^
GET /v1/repositories/<namespace>/<repository_name>/tags
**Return**: HTTP 200
{
"latest": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
“0.1.1”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”
}
4.3.2 Read the content of a tag (resolve the image id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
GET /v1/repositories/<namespace>/<repo_name>/tags/<tag>
**Return**:
"9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"
4.3.3 Delete a tag (registry)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DELETE /v1/repositories/<namespace>/<repo_name>/tags/<tag>
4.4 Images (Index)
------------------
For the Index to “resolve” the repository name to a Registry location, it uses the X-Docker-Endpoints header. In other terms, this requests always add a “X-Docker-Endpoints” to indicate the location of the registry which hosts this repository.
4.4.1 Get the images
^^^^^^^^^^^^^^^^^^^^^
GET /v1/repositories/<namespace>/<repo_name>/images
**Return**: HTTP 200
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “md5:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
4.4.2 Add/update the images
^^^^^^^^^^^^^^^^^^^^^^^^^^^
You always add images, you never remove them.
PUT /v1/repositories/<namespace>/<repo_name>/images
**Body**:
[ {“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”} ]
**Return** 204
5. Chaining Registries
======================
Its possible to chain Registries server for several reasons:
- Load balancing
- Delegate the next request to another server
When a Registry is a reference for a repository, it should host the entire images chain in order to avoid breaking the chain during the download.
The Index and Registry use this mechanism to redirect on one or the other.
Example with an image download:
On every request, a special header can be returned:
X-Docker-Endpoints: server1,server2
On the next request, the client will always pick a server from this list.
6. Authentication & Authorization
=================================
6.1 On the Index
-----------------
The Index supports both “Basic” and “Token” challenges. Usually when there is a “401 Unauthorized”, the Index replies this::
401 Unauthorized
WWW-Authenticate: Basic realm="auth required",Token
You have 3 options:
1. Provide user credentials and ask for a token
**Header**:
- Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
- X-Docker-Token: true
In this case, along with the 200 response, youll get a new token (if user auth is ok):
If authorization isn't correct you get a 401 response.
If account isn't active you will get a 403 response.
**Response**:
- 200 OK
- X-Docker-Token: Token signature=123abc,repository=”foo/bar”,access=read
2. Provide user credentials only
**Header**:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
3. Provide Token
**Header**:
Authorization: Token signature=123abc,repository=”foo/bar”,access=read
6.2 On the Registry
-------------------
The Registry only supports the Token challenge::
401 Unauthorized
WWW-Authenticate: Token
The only way is to provide a token on “401 Unauthorized” responses::
Authorization: Token signature=123abc,repository=”foo/bar”,access=read
Usually, the Registry provides a Cookie when a Token verification succeeded. Every time the Registry passes a Cookie, you have to pass it back the same cookie.::
200 OK
Set-Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=&timestamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="; Path=/; HttpOnly
Next request::
GET /(...)
Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=&timestamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="
TODO: add more info.

View File

@@ -0,0 +1,569 @@
:title: Registry Documentation
:description: Documentation for docker Registry and Registry API
:keywords: docker, registry, api, index
=====================
Registry & index Spec
=====================
.. contents:: Table of Contents
1. The 3 roles
===============
1.1 Index
---------
The Index is responsible for centralizing information about:
- User accounts
- Checksums of the images
- Public namespaces
The Index has different components:
- Web UI
- Meta-data store (comments, stars, list public repositories)
- Authentication service
- Tokenization
The index is authoritative for those information.
We expect that there will be only one instance of the index, run and managed by dotCloud.
1.2 Registry
------------
- It stores the images and the graph for a set of repositories
- It does not have user accounts data
- It has no notion of user accounts or authorization
- It delegates authentication and authorization to the Index Auth service using tokens
- It supports different storage backends (S3, cloud files, local FS)
- It doesnt have a local database
- It will be open-sourced at some point
We expect that there will be multiple registries out there. To help to grasp the context, here are some examples of registries:
- **sponsor registry**: such a registry is provided by a third-party hosting infrastructure as a convenience for their customers and the docker community as a whole. Its costs are supported by the third party, but the management and operation of the registry are supported by dotCloud. It features read/write access, and delegates authentication and authorization to the Index.
- **mirror registry**: such a registry is provided by a third-party hosting infrastructure but is targeted at their customers only. Some mechanism (unspecified to date) ensures that public images are pulled from a sponsor registry to the mirror registry, to make sure that the customers of the third-party provider can “docker pull” those images locally.
- **vendor registry**: such a registry is provided by a software vendor, who wants to distribute docker images. It would be operated and managed by the vendor. Only users authorized by the vendor would be able to get write access. Some images would be public (accessible for anyone), others private (accessible only for authorized users). Authentication and authorization would be delegated to the Index. The goal of vendor registries is to let someone do “docker pull basho/riak1.3” and automatically push from the vendor registry (instead of a sponsor registry); i.e. get all the convenience of a sponsor registry, while retaining control on the asset distribution.
- **private registry**: such a registry is located behind a firewall, or protected by an additional security layer (HTTP authorization, SSL client-side certificates, IP address authorization...). The registry is operated by a private entity, outside of dotClouds control. It can optionally delegate additional authorization to the Index, but it is not mandatory.
.. note::
Mirror registries and private registries which do not use the Index dont even need to run the registry code. They can be implemented by any kind of transport implementing HTTP GET and PUT. Read-only registries can be powered by a simple static HTTP server.
.. note::
The latter implies that while HTTP is the protocol of choice for a registry, multiple schemes are possible (and in some cases, trivial):
- HTTP with GET (and PUT for read-write registries);
- local mount point;
- remote docker addressed through SSH.
The latter would only require two new commands in docker, e.g. “registryget” and “registryput”, wrapping access to the local filesystem (and optionally doing consistency checks). Authentication and authorization are then delegated to SSH (e.g. with public keys).
1.3 Docker
----------
On top of being a runtime for LXC, Docker is the Registry client. It supports:
- Push / Pull on the registry
- Client authentication on the Index
2. Workflow
===========
2.1 Pull
--------
.. image:: /static_files/docker_pull_chart.png
1. Contact the Index to know where I should download “samalba/busybox”
2. Index replies:
a. “samalba/busybox” is on Registry A
b. here are the checksums for “samalba/busybox” (for all layers)
c. token
3. Contact Registry A to receive the layers for “samalba/busybox” (all of them to the base image). Registry A is authoritative for “samalba/busybox” but keeps a copy of all inherited layers and serve them all from the same location.
4. registry contacts index to verify if token/user is allowed to download images
5. Index returns true/false lettings registry know if it should proceed or error out
6. Get the payload for all layers
Its possible to run docker pull \https://<registry>/repositories/samalba/busybox. In this case, docker bypasses the Index. However the security is not guaranteed (in case Registry A is corrupted) because there wont be any checksum checks.
Currently registry redirects to s3 urls for downloads, going forward all downloads need to be streamed through the registry. The Registry will then abstract the calls to S3 by a top-level class which implements sub-classes for S3 and local storage.
Token is only returned when the 'X-Docker-Token' header is sent with request.
Basic Auth is required to pull private repos. Basic auth isn't required for pulling public repos, but if one is provided, it needs to be valid and for an active account.
API (pulling repository foo/bar):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1. (Docker -> Index) GET /v1/repositories/foo/bar/images
**Headers**:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
X-Docker-Token: true
**Action**:
(looking up the foo/bar in db and gets images and checksums for that repo (all if no tag is specified, if tag, only checksums for those tags) see part 4.4.1)
2. (Index -> Docker) HTTP 200 OK
**Headers**:
- Authorization: Token signature=123abc,repository=”foo/bar”,access=write
- X-Docker-Endpoints: registry.docker.io [, registry2.docker.io]
**Body**:
Jsonified checksums (see part 4.4.1)
3. (Docker -> Registry) GET /v1/repositories/foo/bar/tags/latest
**Headers**:
Authorization: Token signature=123abc,repository=”foo/bar”,access=write
4. (Registry -> Index) GET /v1/repositories/foo/bar/images
**Headers**:
Authorization: Token signature=123abc,repository=”foo/bar”,access=read
**Body**:
<ids and checksums in payload>
**Action**:
( Lookup token see if they have access to pull.)
If good:
HTTP 200 OK
Index will invalidate the token
If bad:
HTTP 401 Unauthorized
5. (Docker -> Registry) GET /v1/images/928374982374/ancestry
**Action**:
(for each image id returned in the registry, fetch /json + /layer)
.. note::
If someone makes a second request, then we will always give a new token, never reuse tokens.
2.2 Push
--------
.. image:: /static_files/docker_push_chart.png
1. Contact the index to allocate the repository name “samalba/busybox” (authentication required with user credentials)
2. If authentication works and namespace available, “samalba/busybox” is allocated and a temporary token is returned (namespace is marked as initialized in index)
3. Push the image on the registry (along with the token)
4. Registry A contacts the Index to verify the token (token must corresponds to the repository name)
5. Index validates the token. Registry A starts reading the stream pushed by docker and store the repository (with its images)
6. docker contacts the index to give checksums for upload images
.. note::
**Its possible not to use the Index at all!** In this case, a deployed version of the Registry is deployed to store and serve images. Those images are not authentified and the security is not guaranteed.
.. note::
**Index can be replaced!** For a private Registry deployed, a custom Index can be used to serve and validate token according to different policies.
Docker computes the checksums and submit them to the Index at the end of the push. When a repository name does not have checksums on the Index, it means that the push is in progress (since checksums are submitted at the end).
API (pushing repos foo/bar):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1. (Docker -> Index) PUT /v1/repositories/foo/bar/
**Headers**:
Authorization: Basic sdkjfskdjfhsdkjfh==
X-Docker-Token: true
**Action**::
- in index, we allocated a new repository, and set to initialized
**Body**::
(The body contains the list of images that are going to be pushed, with empty checksums. The checksums will be set at the end of the push)::
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}]
2. (Index -> Docker) 200 Created
**Headers**:
- WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=write
- X-Docker-Endpoints: registry.docker.io [, registry2.docker.io]
3. (Docker -> Registry) PUT /v1/images/98765432_parent/json
**Headers**:
Authorization: Token signature=123abc,repository=”foo/bar”,access=write
4. (Registry->Index) GET /v1/repositories/foo/bar/images
**Headers**:
Authorization: Token signature=123abc,repository=”foo/bar”,access=write
**Action**::
- Index:
will invalidate the token.
- Registry:
grants a session (if token is approved) and fetches the images id
5. (Docker -> Registry) PUT /v1/images/98765432_parent/json
**Headers**::
- Authorization: Token signature=123abc,repository=”foo/bar”,access=write
- Cookie: (Cookie provided by the Registry)
6. (Docker -> Registry) PUT /v1/images/98765432/json
**Headers**:
Cookie: (Cookie provided by the Registry)
7. (Docker -> Registry) PUT /v1/images/98765432_parent/layer
**Headers**:
Cookie: (Cookie provided by the Registry)
8. (Docker -> Registry) PUT /v1/images/98765432/layer
**Headers**:
X-Docker-Checksum: sha256:436745873465fdjkhdfjkgh
9. (Docker -> Registry) PUT /v1/repositories/foo/bar/tags/latest
**Headers**:
Cookie: (Cookie provided by the Registry)
**Body**:
“98765432”
10. (Docker -> Index) PUT /v1/repositories/foo/bar/images
**Headers**:
Authorization: Basic 123oislifjsldfj==
X-Docker-Endpoints: registry1.docker.io (no validation on this right now)
**Body**:
(The image, ids, tags and checksums)
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
“checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
**Return** HTTP 204
.. note::
If push fails and they need to start again, what happens in the index, there will already be a record for the namespace/name, but it will be initialized. Should we allow it, or mark as name already used? One edge case could be if someone pushes the same thing at the same time with two different shells.
If it's a retry on the Registry, Docker has a cookie (provided by the registry after token validation). So the Index wont have to provide a new token.
2.3 Delete
----------
If you need to delete something from the index or registry, we need a nice clean way to do that. Here is the workflow.
1. Docker contacts the index to request a delete of a repository “samalba/busybox” (authentication required with user credentials)
2. If authentication works and repository is valid, “samalba/busybox” is marked as deleted and a temporary token is returned
3. Send a delete request to the registry for the repository (along with the token)
4. Registry A contacts the Index to verify the token (token must corresponds to the repository name)
5. Index validates the token. Registry A deletes the repository and everything associated to it.
6. docker contacts the index to let it know it was removed from the registry, the index removes all records from the database.
.. note::
The Docker client should present an "Are you sure?" prompt to confirm the deletion before starting the process. Once it starts it can't be undone.
API (deleting repository foo/bar):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1. (Docker -> Index) DELETE /v1/repositories/foo/bar/
**Headers**:
Authorization: Basic sdkjfskdjfhsdkjfh==
X-Docker-Token: true
**Action**::
- in index, we make sure it is a valid repository, and set to deleted (logically)
**Body**::
Empty
2. (Index -> Docker) 202 Accepted
**Headers**:
- WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=delete
- X-Docker-Endpoints: registry.docker.io [, registry2.docker.io] # list of endpoints where this repo lives.
3. (Docker -> Registry) DELETE /v1/repositories/foo/bar/
**Headers**:
Authorization: Token signature=123abc,repository=”foo/bar”,access=delete
4. (Registry->Index) PUT /v1/repositories/foo/bar/auth
**Headers**:
Authorization: Token signature=123abc,repository=”foo/bar”,access=delete
**Action**::
- Index:
will invalidate the token.
- Registry:
deletes the repository (if token is approved)
5. (Registry -> Docker) 200 OK
200 If success
403 if forbidden
400 if bad request
404 if repository isn't found
6. (Docker -> Index) DELETE /v1/repositories/foo/bar/
**Headers**:
Authorization: Basic 123oislifjsldfj==
X-Docker-Endpoints: registry-1.docker.io (no validation on this right now)
**Body**:
Empty
**Return** HTTP 200
3. How to use the Registry in standalone mode
=============================================
The Index has two main purposes (along with its fancy social features):
- Resolve short names (to avoid passing absolute URLs all the time)
- username/projectname -> \https://registry.docker.io/users/<username>/repositories/<projectname>/
- team/projectname -> \https://registry.docker.io/team/<team>/repositories/<projectname>/
- Authenticate a user as a repos owner (for a central referenced repository)
3.1 Without an Index
--------------------
Using the Registry without the Index can be useful to store the images on a private network without having to rely on an external entity controlled by dotCloud.
In this case, the registry will be launched in a special mode (--standalone? --no-index?). In this mode, the only thing which changes is that Registry will never contact the Index to verify a token. It will be the Registry owner responsibility to authenticate the user who pushes (or even pulls) an image using any mechanism (HTTP auth, IP based, etc...).
In this scenario, the Registry is responsible for the security in case of data corruption since the checksums are not delivered by a trusted entity.
As hinted previously, a standalone registry can also be implemented by any HTTP server handling GET/PUT requests (or even only GET requests if no write access is necessary).
3.2 With an Index
-----------------
The Index data needed by the Registry are simple:
- Serve the checksums
- Provide and authorize a Token
In the scenario of a Registry running on a private network with the need of centralizing and authorizing, its easy to use a custom Index.
The only challenge will be to tell Docker to contact (and trust) this custom Index. Docker will be configurable at some point to use a specific Index, itll be the private entity responsibility (basically the organization who uses Docker in a private environment) to maintain the Index and the Dockers configuration among its consumers.
4. The API
==========
The first version of the api is available here: https://github.com/jpetazzo/docker/blob/acd51ecea8f5d3c02b00a08176171c59442df8b3/docs/images-repositories-push-pull.md
4.1 Images
----------
The format returned in the images is not defined here (for layer and json), basically because Registry stores exactly the same kind of information as Docker uses to manage them.
The format of ancestry is a line-separated list of image ids, in age order. I.e. the images parent is on the last line, the parent of the parent on the next-to-last line, etc.; if the image has no parent, the file is empty.
GET /v1/images/<image_id>/layer
PUT /v1/images/<image_id>/layer
GET /v1/images/<image_id>/json
PUT /v1/images/<image_id>/json
GET /v1/images/<image_id>/ancestry
PUT /v1/images/<image_id>/ancestry
4.2 Users
---------
4.2.1 Create a user (Index)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
POST /v1/users
**Body**:
{"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'}
**Validation**:
- **username** : min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
- **password**: min 5 characters
**Valid**: return HTTP 200
Errors: HTTP 400 (we should create error codes for possible errors)
- invalid json
- missing field
- wrong format (username, password, email, etc)
- forbidden name
- name already exists
.. note::
A user account will be valid only if the email has been validated (a validation link is sent to the email address).
4.2.2 Update a user (Index)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
PUT /v1/users/<username>
**Body**:
{"password": "toto"}
.. note::
We can also update email address, if they do, they will need to reverify their new email address.
4.2.3 Login (Index)
^^^^^^^^^^^^^^^^^^^
Does nothing else but asking for a user authentication. Can be used to validate credentials. HTTP Basic Auth for now, maybe change in future.
GET /v1/users
**Return**:
- Valid: HTTP 200
- Invalid login: HTTP 401
- Account inactive: HTTP 403 Account is not Active
4.3 Tags (Registry)
-------------------
The Registry does not know anything about users. Even though repositories are under usernames, its just a namespace for the registry. Allowing us to implement organizations or different namespaces per user later, without modifying the Registrys API.
The following naming restrictions apply:
- Namespaces must match the same regular expression as usernames (See 4.2.1.)
- Repository names must match the regular expression [a-zA-Z0-9-_.]
4.3.1 Get all tags
^^^^^^^^^^^^^^^^^^
GET /v1/repositories/<namespace>/<repository_name>/tags
**Return**: HTTP 200
{
"latest": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
“0.1.1”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”
}
4.3.2 Read the content of a tag (resolve the image id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
GET /v1/repositories/<namespace>/<repo_name>/tags/<tag>
**Return**:
"9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"
4.3.3 Delete a tag (registry)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DELETE /v1/repositories/<namespace>/<repo_name>/tags/<tag>
4.4 Images (Index)
------------------
For the Index to “resolve” the repository name to a Registry location, it uses the X-Docker-Endpoints header. In other terms, this requests always add a “X-Docker-Endpoints” to indicate the location of the registry which hosts this repository.
4.4.1 Get the images
^^^^^^^^^^^^^^^^^^^^^
GET /v1/repositories/<namespace>/<repo_name>/images
**Return**: HTTP 200
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “md5:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
4.4.2 Add/update the images
^^^^^^^^^^^^^^^^^^^^^^^^^^^
You always add images, you never remove them.
PUT /v1/repositories/<namespace>/<repo_name>/images
**Body**:
[ {“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”} ]
**Return** 204
4.5 Repositories
----------------
4.5.1 Remove a Repository (Registry)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DELETE /v1/repositories/<namespace>/<repo_name>
Return 200 OK
4.5.2 Remove a Repository (Index)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This starts the delete process. see 2.3 for more details.
DELETE /v1/repositories/<namespace>/<repo_name>
Return 202 OK
5. Chaining Registries
======================
Its possible to chain Registries server for several reasons:
- Load balancing
- Delegate the next request to another server
When a Registry is a reference for a repository, it should host the entire images chain in order to avoid breaking the chain during the download.
The Index and Registry use this mechanism to redirect on one or the other.
Example with an image download:
On every request, a special header can be returned:
X-Docker-Endpoints: server1,server2
On the next request, the client will always pick a server from this list.
6. Authentication & Authorization
=================================
6.1 On the Index
-----------------
The Index supports both “Basic” and “Token” challenges. Usually when there is a “401 Unauthorized”, the Index replies this::
401 Unauthorized
WWW-Authenticate: Basic realm="auth required",Token
You have 3 options:
1. Provide user credentials and ask for a token
**Header**:
- Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
- X-Docker-Token: true
In this case, along with the 200 response, youll get a new token (if user auth is ok):
If authorization isn't correct you get a 401 response.
If account isn't active you will get a 403 response.
**Response**:
- 200 OK
- X-Docker-Token: Token signature=123abc,repository=”foo/bar”,access=read
2. Provide user credentials only
**Header**:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
3. Provide Token
**Header**:
Authorization: Token signature=123abc,repository=”foo/bar”,access=read
6.2 On the Registry
-------------------
The Registry only supports the Token challenge::
401 Unauthorized
WWW-Authenticate: Token
The only way is to provide a token on “401 Unauthorized” responses::
Authorization: Token signature=123abc,repository=”foo/bar”,access=read
Usually, the Registry provides a Cookie when a Token verification succeeded. Every time the Registry passes a Cookie, you have to pass it back the same cookie.::
200 OK
Set-Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=&timestamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="; Path=/; HttpOnly
Next request::
GET /(...)
Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=&timestamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="
7.0 Document Version
---------------------
- 1.0 : May 6th 2013 : initial release
- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace.

View File

@@ -19,10 +19,15 @@ Examples
docker build .
This will take the local Dockerfile
| This will read the Dockerfile from the current directory. It will also send any other files and directories found in the current directory to the docker daemon.
| The contents of this directory would be used by ADD commands found within the Dockerfile.
| This will send a lot of data to the docker daemon if the current directory contains a lot of data.
| If the absolute path is provided instead of '.', only the files and directories required by the ADD commands from the Dockerfile will be added to the context and transferred to the docker daemon.
|
.. code-block:: bash
docker build -
This will read a Dockerfile form Stdin without context
| This will read a Dockerfile from Stdin without context. Due to the lack of a context, no contents of any local directory will be sent to the docker daemon.
| ADD doesn't work when running in this mode due to the absence of the context, thus having no source files to copy to the container.

View File

@@ -8,6 +8,33 @@
::
Usage: docker import [OPTIONS] URL|- [REPOSITORY [TAG]]
Usage: docker import URL|- [REPOSITORY [TAG]]
Create a new filesystem image from the contents of a tarball
At this time, the URL must start with ``http`` and point to a single file archive (.tar, .tar.gz, .bzip)
containing a root filesystem. If you would like to import from a local directory or archive,
you can use the ``-`` parameter to take the data from standard in.
Examples
--------
Import from a remote location
.............................
``$ docker import http://example.com/exampleimage.tgz exampleimagerepo``
Import from a local file
........................
Import to docker via pipe and standard in
``$ cat exampleimage.tgz | docker import - exampleimagelocal``
Import from a local directory
.............................
``$ sudo tar -c . | docker import - exampleimagedir``
Note the ``sudo`` in this example -- you must preserve the ownership of the files (especially root ownership)
during the archiving with tar. If you are not root (or sudo) when you tar, then the ownerships might not get preserved.

View File

@@ -5,8 +5,8 @@
Introduction
============
Docker - The Linux container runtime
------------------------------------
Docker -- The Linux container runtime
-------------------------------------
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.

View File

@@ -5,5 +5,5 @@
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>`_.

View File

@@ -1,15 +1,39 @@
:title: Setting up a dev environment
:title: Setting Up a Dev Environment
:description: Guides on how to contribute to docker
:keywords: Docker, documentation, developers, contributing, dev environment
Setting up a dev environment
Setting Up a Dev Environment
============================
Instructions that have been verified to work on Ubuntu 12.10,
Instructions that have been verified to work on Ubuntu Precise 12.04 (LTS) (64-bit),
Dependencies
------------
**Linux kernel 3.8**
Due to a bug in LXC docker works best on the 3.8 kernel. Precise comes with a 3.2 kernel, so we need to upgrade it. The kernel we install comes with AUFS built in.
.. code-block:: bash
sudo apt-get -y install lxc wget bsdtar curl golang git
# install the backported kernel
sudo apt-get update && sudo apt-get install linux-image-generic-lts-raring
# reboot
sudo reboot
Installation
------------
.. code-block:: bash
sudo apt-get install python-software-properties
sudo add-apt-repository ppa:gophers/go
sudo apt-get update
sudo apt-get -y install lxc wget bsdtar curl golang-stable git aufs-tools
export GOPATH=~/go/
export PATH=$GOPATH/bin:$PATH

View File

@@ -4,8 +4,8 @@
.. _running_couchdb_service:
Create a CouchDB service
========================
CouchDB Service
===============
.. include:: example_header.inc

View File

@@ -1,6 +1,6 @@
:title: Docker Examples
:description: Examples on how to use Docker
:keywords: docker, hello world, examples
:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples
@@ -16,6 +16,7 @@ Contents:
hello_world
hello_world_daemon
python_web_app
nodejs_web_app
running_redis_service
running_ssh_service
couchdb_data_volumes

View File

@@ -0,0 +1,236 @@
:title: Running a Node.js app on CentOS
:description: Installing and running a Node.js app on CentOS
:keywords: docker, example, package installation, node, centos
.. _nodejs_web_app:
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 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.
Create Node.js app
++++++++++++++++++
First, create a ``package.json`` file that describes your app and its
dependencies:
.. code-block:: json
{
"name": "docker-centos-hello",
"private": true,
"version": "0.0.1",
"description": "Node.js Hello World app on CentOS using docker",
"author": "Daniel Gasienica <daniel@gasienica.ch>",
"dependencies": {
"express": "3.2.4"
}
}
Then, create an ``index.js`` file that defines a web app using the
`Express.js <http://expressjs.com/>`_ framework:
.. code-block:: javascript
var express = require('express');
// Constants
var PORT = 8080;
// App
var app = express();
app.get('/', function (req, res) {
res.send('Hello World\n');
});
app.listen(PORT)
console.log('Running on http://localhost:' + PORT);
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.
Creating a ``Dockerfile``
+++++++++++++++++++++++++
Create an empty file called ``Dockerfile``:
.. code-block:: bash
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):
.. code-block:: bash
# DOCKER-VERSION 0.3.4
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`_:
.. code-block:: bash
FROM centos:6.4
Since were building a Node.js app, youll have to install Node.js as well as
npm on your CentOS image. Node.js is required to run your app and npm to install
your apps dependencies defined in ``package.json``.
To install the right package for CentOS, well use the instructions from the
`Node.js wiki`_:
.. code-block:: bash
# Enable EPEL for Node.js
RUN rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
# Install Node.js and npm
RUN yum install -y npm-1.2.17-5.el6
To bundle your apps source code inside the docker image, use the ``ADD``
command:
.. code-block:: bash
# Bundle app source
ADD . /src
Install your app dependencies using npm:
.. 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:
.. code-block:: bash
EXPOSE 8080
Last but not least, define the command to run your app using ``CMD`` which
defines your runtime, i.e. ``node``, and the path to our app, i.e.
``src/index.js`` (see the step where we added the source to the container):
.. code-block:: bash
CMD ["node", "/src/index.js"]
Your ``Dockerfile`` should now look like this:
.. code-block:: bash
# DOCKER-VERSION 0.3.4
FROM centos:6.4
# Enable EPEL for Node.js
RUN rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
# Install Node.js and npm
RUN yum install -y npm-1.2.17-5.el6
# Bundle app source
ADD . /src
# Install app dependencies
RUN cd /src; npm install
EXPOSE 8080
CMD ["node", "/src/index.js"]
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 image so its easier
to find later using the ``docker images`` command:
.. code-block:: bash
docker build -t <your username>/centos-node-hello .
Your image will now be listed by docker:
.. code-block:: bash
docker images
> # Example
> REPOSITORY TAG ID CREATED
> centos 6.4 539c0211cd76 8 weeks ago
> gasi/centos-node-hello latest d64d3505b0d2 2 hours ago
Run the image
+++++++++++++
Running your image with ``-d`` runs the container in detached mode, leaving the
container running in the background. Run the image you previously built:
.. code-block:: bash
docker run -d <your username>/centos-node-hello
Print the output of your app:
.. code-block:: bash
# Get container ID
docker ps
# Print app output
docker logs <container id>
> # Example
> Running on http://localhost:8080
Test
++++
To test your app, get the the port of your app that docker mapped:
.. code-block:: bash
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
``49160``.
Now you can call your app using ``curl`` (install if needed via:
``sudo apt-get install curl``):
.. code-block:: bash
curl -i localhost:49160
> HTTP/1.1 200 OK
> X-Powered-By: Express
> Content-Type: text/html; charset=utf-8
> Content-Length: 12
> Date: Sun, 02 Jun 2013 03:53:22 GMT
> Connection: keep-alive
>
> 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
https://github.com/gasi/docker-node-hello.
Continue to :ref:`running_redis_service`.
.. _Node.js wiki: https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#rhelcentosscientific-linux-6
.. _docker index: https://index.docker.io/

View File

@@ -4,8 +4,8 @@
.. _python_web_app:
Building a python web app
=========================
Python Web App
==============
.. include:: example_header.inc

View File

@@ -4,7 +4,7 @@
.. _running_examples:
Running The Examples
Running the Examples
--------------------
All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type:

View File

@@ -4,8 +4,8 @@
.. _running_redis_service:
Create a redis service
======================
Redis Service
=============
.. include:: example_header.inc
@@ -34,7 +34,7 @@ Snapshot the installation
.. code-block:: bash
docker ps -a # grab the container id (this will be the last one in the list)
docker ps -a # grab the container id (this will be the first one in the list)
docker commit <container_id> <your username>/redis
Run the service

View File

@@ -4,8 +4,8 @@
.. _running_ssh_service:
Create an ssh daemon service
============================
SSH Daemon Service
==================
.. include:: example_header.inc
@@ -20,8 +20,7 @@ minutes and not entirely smooth, but gives you a good idea.
<div style="margin-top:10px;">
<iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe>
</div>
You can also get this sshd container by using
::
@@ -30,3 +29,49 @@ You can also get this sshd container by using
The password is 'screencast'
**Video's Transcription:**
.. code-block:: bash
# Hello! We are going to try and install openssh on a container and run it as a servic
# let's pull base to get a base ubuntu image.
$ docker pull base
# I had it so it was quick
# now let's connect using -i for interactive and with -t for terminal
# we execute /bin/bash to get a prompt.
$ docker run -i -t base /bin/bash
# now let's commit it
# which container was it?
$ docker ps -a |more
$ docker commit a30a3a2f2b130749995f5902f079dc6ad31ea0621fac595128ec59c6da07feea dhrp/sshd
# I gave the name dhrp/sshd for the container
# now we can run it again
$ docker run -d dhrp/sshd /usr/sbin/sshd -D # D for daemon mode
# is it running?
$ docker ps
# yes!
# let's stop it
$ docker stop 0ebf7cec294755399d063f4b1627980d4cbff7d999f0bc82b59c300f8536a562
$ docker ps
# and reconnect, but now open a port to it
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
$ docker port b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 22
# it has now given us a port to connect to
# we have to connect using a public ip of our host
$ hostname
$ ifconfig
$ ssh root@192.168.33.10 -p 49153
# Ah! forgot to set root passwd
$ docker commit b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 dhrp/sshd
$ docker ps -a
$ docker run -i -t dhrp/sshd /bin/bash
$ passwd
$ exit
$ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
$ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22
$ ifconfig
$ ssh root@192.168.33.10 -p 49154
# Thanks for watching, Thatcher thatcher@dotcloud.com

View File

@@ -19,7 +19,8 @@ Most frequently asked questions.
3. **Does Docker run on Mac OS X or Windows?**
Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a virtual machine on your box, and get the best of both worlds. Check out the MacOSX_ and Windows_ installation guides.
Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a
virtual machine on your box, and get the best of both worlds. Check out the :ref:`install_using_vagrant` and :ref:`windows` installation guides.
4. **How do containers compare to virtual machines?**
@@ -34,15 +35,16 @@ Most frequently asked questions.
You can find more answers on:
* `IRC: docker on freenode`_
* `Docker club mailinglist`_
* `IRC, docker on freenode`_
* `Github`_
* `Ask questions on Stackoverflow`_
* `Join the conversation on Twitter`_
.. _Windows: ../installation/windows/
.. _MacOSX: ../installation/vagrant/
.. _Docker club mailinglist: https://groups.google.com/d/forum/docker-club
.. _the repo: http://www.github.com/dotcloud/docker
.. _IRC\: docker on freenode: irc://chat.freenode.net#docker
.. _IRC, docker on freenode: irc://chat.freenode.net#docker
.. _Github: http://www.github.com/dotcloud/docker
.. _Ask questions on Stackoverflow: http://stackoverflow.com/search?q=docker
.. _Join the conversation on Twitter: http://twitter.com/getdocker

View File

@@ -7,8 +7,8 @@
Introduction
============
Docker - The Linux container runtime
------------------------------------
Docker -- The Linux container runtime
-------------------------------------
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.

View File

@@ -92,6 +92,16 @@ have AUFS filesystem support enabled, so we need to install it.
sudo apt-get update
sudo apt-get install linux-image-extra-`uname -r`
**add-apt-repository support**
Some installations of Ubuntu 13.04 require ``software-properties-common`` to be
installed before being able to use add-apt-repository.
.. code-block:: bash
sudo apt-get install software-properties-common
Installation
------------

View File

@@ -2,6 +2,7 @@
:description: Docker's tutorial to run docker on Windows
:keywords: Docker, Docker documentation, Windows, requirements, virtualbox, vagrant, git, ssh, putty, cygwin
.. _windows:
Using Vagrant (Windows)
=======================

View File

@@ -3,8 +3,8 @@
:keywords: Examples, Usage, basic commands, docker, documentation, examples
The basics
=============
The Basics
==========
Starting Docker
---------------

View File

@@ -18,7 +18,7 @@ steps and commit them along the way, giving you a final image.
To use Docker Builder, assemble the steps into a text file (commonly referred to
as a Dockerfile) and supply this to `docker build` on STDIN, like so:
``docker build < Dockerfile``
``docker build - < Dockerfile``
Docker will run your steps one-by-one, committing the result if necessary,
before finally outputting the ID of your new image.

View File

@@ -14,6 +14,7 @@ Contents:
basics
workingwithrepository
port_redirection
builder
puppet

View File

@@ -0,0 +1,25 @@
:title: Port redirection
:description: usage about port redirection
:keywords: Usage, basic port, docker, documentation, examples
Port redirection
================
Docker can redirect public tcp 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.
.. code-block:: bash
# A random PUBLIC port is redirected to PRIVATE port 80 on the container
docker run -p 80 <image> <cmd>
# PUBLIC port 80 is redirected to PRIVATE port 80
docker run -p 80:80 <image> <cmd>
Default port redirects can be built into a container with the EXPOSE build command.

View File

@@ -4,8 +4,8 @@
.. _working_with_the_repository:
Working with the repository
============================
Working with the Repository
===========================
Top-level repositories and user repositories
@@ -14,9 +14,9 @@ Top-level repositories and user repositories
Generally, there are two types of repositories: Top-level repositories which are controlled by the people behind
Docker, and user repositories.
* Top-level repositories can easily be recognized by not having a / (slash) in their name. These repositories can
* Top-level repositories can easily be recognized by not having a ``/`` (slash) in their name. These repositories can
generally be trusted.
* User repositories always come in the form of <username>/<repo_name>. This is what your published images will look like.
* User repositories always come in the form of ``<username>/<repo_name>``. This is what your published images will look like.
* User images are not checked, it is therefore up to you whether or not you trust the creator of this image.

View File

@@ -64,14 +64,15 @@
<div style="float: right" class="pull-right">
<ul class="nav">
<li><a href="http://www.docker.io/">Introduction</a></li>
<li><a href="http://www.docker.io/gettingstarted/">Getting started</a></li>
<li class="active"><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
<li id="nav-introduction"><a href="http://www.docker.io/">Introduction</a></li>
<li id="nav-gettingstarted"><a href="http://www.docker.io/gettingstarted/">Getting started</a></li>
<li id="nav-documentation" class="active"><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
<li id="nav-blog"><a href="http://blog.docker.io/">Blog</a></li>
</ul>
<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
</div>
<!--<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">-->
<!--<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>-->
<!--<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>-->
<!--</div>-->
</div>
<div style="margin-left: -12px; float: left;">
@@ -86,8 +87,13 @@
<div class="container">
<div class="row">
<div class="span12 titlebar"><h1 class="pageheader">DOCUMENTATION</h1>
<div class="span12 titlebar">
<!--<span class="pull-right" style="margin-left: 20px; font-size: 20px">{{version}}</span>-->
<div class="pull-right" id="fork-us" style="margin-top: 16px; margin-right: 16px;">
<a href="http://github.com/dotcloud/docker/"><img src="{{ pathto('_static/img/fork-us.png', 1) }}"> Fork us on Github</a>
</div>
<h1 class="pageheader">DOCUMENTATION</h1>
</div>
</div>
@@ -123,8 +129,14 @@
<div class="row">
<div class="span12 footer">
<div class="tbox textright forceleftmargin social links pull-right">
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
</div>
Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
{# {%- if show_source and has_source and sourcename %}#}
{# ·#}
{# <a href="{{ pathto('_sources/' + sourcename, true)|e }}"#}
@@ -157,7 +169,7 @@
<!-- script which should be loaded after everything else -->
<script type="text/javascript">
// Function to make the sticky header possible
var shiftWindow = function() {
scrollBy(0, -70);
console.log("window shifted")

View File

@@ -285,6 +285,40 @@ section.header {
.social .github {
background-position: -59px 2px;
}
#fork-us {
/*font-family: 'Maven Pro';*/
/*font-weight: bold;*/
font-size: 12px;
/*text-transform: uppercase;*/
display: block;
padding: 0px 1em;
height: 28px;
line-height: 28px;
background-color: #43484c;
filter: progid:dximagetransform.microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35');
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #747474), color-stop(100%, #43484c));
background-image: -webkit-linear-gradient(top, #747474 0%, #43484c 100%);
background-image: -moz-linear-gradient(top, #747474 0%, #43484c 100%);
background-image: -o-linear-gradient(top, #747474 0%, #43484c 100%);
background-image: linear-gradient(top, #747474 0%, #43484c 100%);
border: 1px solid #43484c;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-ms-border-radius: 4px;
-o-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: inset rgba(255, 255, 255, 0.17) 0 1px 1px;
-moz-box-shadow: inset rgba(255, 255, 255, 0.17) 0 1px 1px;
box-shadow: inset rgba(255, 255, 255, 0.17) 0 1px 1px;
margin: 8px;
}
#fork-us a {
color: #faf2ee;
text-shadow: rgba(0, 0, 0, 0.3) 0px 1px 0px;
}
/* =======================
Media size overrides
======================= */
@@ -325,10 +359,15 @@ section.header {
padding-top: 600px;
}
#fork-us {
display: none;
}
}
/* Landscape phones and down */
@media (max-width: 480px) {
#nav-gettingstarted {
display: none;
}
}
/* Misc fixes */
table th {

View File

@@ -391,6 +391,38 @@ section.header {
}
#fork-us {
/*font-family: 'Maven Pro';*/
/*font-weight: bold;*/
font-size: 12px;
/*text-transform: uppercase;*/
display: block;
padding: 0px 1em;
height: 28px;
line-height: 28px;
background-color: #43484c;
filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35');
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #747474), color-stop(100%, #43484c));
background-image: -webkit-linear-gradient(top, #747474 0%, #43484c 100%);
background-image: -moz-linear-gradient(top, #747474 0%, #43484c 100%);
background-image: -o-linear-gradient(top, #747474 0%, #43484c 100%);
background-image: linear-gradient(top, #747474 0%, #43484c 100%);
border: 1px solid #43484c;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-ms-border-radius: 4px;
-o-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: inset rgba(255, 255, 255, 0.17) 0 1px 1px;
-moz-box-shadow: inset rgba(255, 255, 255, 0.17) 0 1px 1px;
box-shadow: inset rgba(255, 255, 255, 0.17) 0 1px 1px;
margin: 8px;
a {
color: #faf2ee;
text-shadow: rgba(0, 0, 0, 0.3) 0px 1px 0px;
}
}
/* =======================
Media size overrides
======================= */
@@ -441,14 +473,17 @@ section.header {
/* TODO: Fix this to be relative to the navigation size */
padding-top: 600px;
}
#fork-us {
display: none;
}
}
/* Landscape phones and down */
@media (max-width: 480px) {
#nav-gettingstarted {
display: none;
}
}
/* Misc fixes */

View File

@@ -34,15 +34,11 @@
<div style="float: right" class="pull-right">
<ul class="nav">
<li><a href="../">Introduction</a></li>
<li class="active"><a href="">Getting started</a></li>
<li class=""><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
<li id="nav-introduction"><a href="../">Introduction</a></li>
<li id="nav-gettingstarted" class="active"><a href="">Getting started</a></li>
<li id="nav-documentation" class=""><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
<li id="nav-blog"><a href="http://blog.docker.io/">Blog</a></li>
</ul>
<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
</div>
</div>
<div style="margin-left: -12px; float: left;">
@@ -55,14 +51,22 @@
<div class="container">
<div class="row">
<div class="span12 titlebar"><h1 class="pageheader">GETTING STARTED</h1>
<div class="span12 titlebar">
<div class="pull-right" id="fork-us" style="margin-top: 16px; margin-right: 16px;">
<a href="http://github.com/dotcloud/docker/"><img src="../static/img/fork-us.png"> Fork us on Github</a>
</div>
<h1 class="pageheader"> GETTING STARTED</h1>
</div>
</div>
</div>
<div class="container">
<div class="alert alert-info">
<div class="alert alert-info" style="margin-bottom: 0;">
<strong>Docker is still under heavy development.</strong> It should not yet be used in production. Check <a href="http://github.com/dotcloud/docker">the repo</a> for recent progress.
</div>
<div class="row">
@@ -133,13 +137,13 @@
</section>
<section class="contentblock">
<h2>More resources</h2>
<ul>
<li><a href="irc://chat.freenode.net#docker">IRC: docker on freenode</a></li>
<li><a href="http://www.github.com/dotcloud/docker">Github</a></li>
<li><a href="http://stackoverflow.com/tags/docker/">Ask questions on Stackoverflow</a></li>
<li><a href="http://twitter.com/getdocker/">Join the conversation on Twitter</a></li>
</ul>
<h2>Questions? Want to get in touch?</h2>
<p>There are several ways to get in touch:</p>
<p><strong>Join the discussion on IRC.</strong> We can be found in the <a href="irc://chat.freenode.net#docker">#docker</a> channel on chat.freenode.net</p>
<p><strong>Discussions</strong> happen on our google group: <a href="https://groups.google.com/d/forum/docker-club">docker-club at googlegroups.com</a></p>
<p>All our <strong>development and decisions</strong> are made out in the open on Github <a href="http://www.github.com/dotcloud/docker">github.com/dotcloud/docker</a></p>
<p><strong>Get help on using Docker</strong> by asking on <a href="http://stackoverflow.com/tags/docker/">Stackoverflow</a></p>
<p>And of course, <strong>tweet</strong> your tweets to <a href="http://twitter.com/getdocker/">twitter.com/getdocker</a></p>
</section>
@@ -172,7 +176,10 @@
<footer id="footer" class="footer">
<div class="row">
<div class="span12 social">
<div class="tbox textright forceleftmargin social links pull-right">
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
</div>
Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
</div>

View File

@@ -44,9 +44,18 @@
.debug {
border: 1px red dotted;
}
.twitterblock {
min-height: 75px;
}
.twitterblock img {
float: left;
margin-right: 10px;
}
</style>
</head>
@@ -56,17 +65,18 @@
<div class="navbar-dotcloud">
<div class="container" style="text-align: center;">
<div class="pull-left" id="fork-us" style="margin-top: 16px;">
<a href="http://github.com/dotcloud/docker/"><img src="static/img/fork-us.png" alt="fork-icon"> Fork us on Github</a>
</div>
<div class="pull-right" >
<ul class="nav">
<li class="active"><a href="/">Introduction</a></li>
<li ><a href="gettingstarted">Getting started</a></li>
<li class=""><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
<li id="nav-introduction" class="active"><a href="/">Introduction</a></li>
<li id="nav-gettingstarted"><a href="gettingstarted">Getting started</a></li>
<li id="nav-documentation" class=""><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
<li id="nav-blog"><a href="http://blog.docker.io/">Blog</a></li>
</ul>
<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
</div>
</div>
</div>
</div>
@@ -81,7 +91,7 @@
<div class="span5" style="margin-bottom: 15px;">
<div style="text-align: center;" >
<img src="static/img/docker_letters_500px.png">
<img src="static/img/docker_letters_500px.png" alt="docker letters">
<h2>The Linux container engine</h2>
</div>
@@ -130,7 +140,7 @@
<section class="contentblock">
<div class="container">
<div class="span2" style="margin-left: 0" >
<a href="http://dotcloud.theresumator.com/apply/mWjkD4/Software-Engineer.html" title="Job description"><img src="static/img/hiring_graphic.png" width="140px" style="margin-top: 25px"></a>
<a href="http://dotcloud.theresumator.com/apply/mWjkD4/Software-Engineer.html" title="Job description"><img src="static/img/hiring_graphic.png" alt="we're hiring" width="140" style="margin-top: 25px"></a>
</div>
<div class="span4" style="margin-left: 0">
<h4>Do you think it is cool to hack on docker? Join us!</h4>
@@ -156,7 +166,7 @@
</div>
</a>
&nbsp;
<input type="button" class="searchbutton" type="submit" value="Search images"
<input type="button" class="searchbutton" value="Search images"
onClick="window.open('https://index.docker.io')" />
</section>
@@ -184,32 +194,19 @@
</div>
<style>
.twitterblock {
min-height: 75px;
}
.twitterblock img {
float: left;
margin-right: 10px;
}
</style>
<div class="container">
<div class="row">
<div class="span6">
<section class="contentblock twitterblock">
<img src="https://si0.twimg.com/profile_images/2707460527/252a64411a339184ff375a96fb68dcb0_bigger.png">
<em>Mitchell Hashimoto@mitchellh:</em> Docker launched today. It is incredible. Theyre also working RIGHT NOW on a Vagrant provider. LXC is COMING!!
<em>Mitchell Hashimoto @mitchellh:</em> Docker launched today. It is incredible. Theyre also working RIGHT NOW on a Vagrant provider. LXC is COMING!!
</section>
</div>
<div class="span6">
<section class="contentblock twitterblock">
<img src="https://si0.twimg.com/profile_images/1108290260/Adam_Jacob-114x150_original_bigger.jpg">
<em>Adam Jacob@adamhjk:</em> Docker is clearly the right idea. @solomonstre absolutely killed it. Containerized app deployment is the future, I think.
<em>Adam Jacob @adamhjk:</em> Docker is clearly the right idea. @solomonstre absolutely killed it. Containerized app deployment is the future, I think.
</section>
</div>
</div>
@@ -217,13 +214,13 @@
<div class="span6">
<section class="contentblock twitterblock">
<img src="https://si0.twimg.com/profile_images/14872832/twitter_pic_bigger.jpg">
<em>Matt Townsend@mtownsend:</em> I have a serious code crush on docker.io - it's Lego for PaaS. Motherfucking awesome Lego.
<em>Matt Townsend @mtownsend:</em> I have a serious code crush on docker.io - it's Lego for PaaS. Motherfucking awesome Lego.
</section>
</div>
<div class="span6">
<section class="contentblock twitterblock">
<img src="https://si0.twimg.com/profile_images/1312352395/rupert-259x300_bigger.jpg">
<em>Rob Harrop@robertharrop:</em> Impressed by @getdocker - it's all kinds of magic. Serious rethink of AWS architecture happening @skillsmatter.
<em>Rob Harrop @robertharrop:</em> Impressed by @getdocker - it's all kinds of magic. Serious rethink of AWS architecture happening @skillsmatter.
</section>
</div>
</div>
@@ -270,7 +267,7 @@
<li>Filesystem isolation: each process container runs in a completely separate root filesystem.</li>
<li>Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.</li>
<li>Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.</li>
<li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremeley fast, memory-cheap and disk-cheap.</li>
<li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremely fast, memory-cheap and disk-cheap.</li>
<li>Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.</li>
<li>Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.</li>
<li>Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell.</li>
@@ -317,7 +314,10 @@
<footer id="footer" class="footer">
<div class="row">
<div class="span12">
<div class="tbox textright forceleftmargin social links pull-right">
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
</div>
Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
</div>

View File

@@ -86,14 +86,23 @@ func (graph *Graph) Get(name string) (*Image, error) {
if err != nil {
return nil, err
}
if img.Id != id {
return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.Id)
if img.ID != id {
return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.ID)
}
img.graph = graph
if img.Size == 0 {
root, err := img.root()
if err != nil {
return nil, err
}
if err := StoreSize(img, root); err != nil {
return nil, err
}
}
graph.lockSumMap.Lock()
defer graph.lockSumMap.Unlock()
if _, exists := graph.checksumLock[img.Id]; !exists {
graph.checksumLock[img.Id] = &sync.Mutex{}
if _, exists := graph.checksumLock[img.ID]; !exists {
graph.checksumLock[img.ID] = &sync.Mutex{}
}
return img, nil
}
@@ -101,16 +110,17 @@ 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) {
img := &Image{
Id: GenerateId(),
ID: GenerateID(),
Comment: comment,
Created: time.Now(),
DockerVersion: VERSION,
Author: author,
Config: config,
Architecture: "x86_64",
}
if container != nil {
img.Parent = container.Image
img.Container = container.Id
img.Container = container.ID
img.ContainerConfig = *container.Config
}
if err := graph.Register(layerData, layerData != nil, img); err != nil {
@@ -123,12 +133,12 @@ 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(layerData Archive, store bool, img *Image) error {
if err := ValidateId(img.Id); err != nil {
if err := ValidateID(img.ID); err != nil {
return err
}
// (This is a convenience to save time. Race conditions are taken care of by os.Rename)
if graph.Exists(img.Id) {
return fmt.Errorf("Image %s already exists", img.Id)
if graph.Exists(img.ID) {
return fmt.Errorf("Image %s already exists", img.ID)
}
tmp, err := graph.Mktemp("")
defer os.RemoveAll(tmp)
@@ -139,12 +149,12 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
return err
}
// Commit
if err := os.Rename(tmp, graph.imageRoot(img.Id)); err != nil {
if err := os.Rename(tmp, graph.imageRoot(img.ID)); err != nil {
return err
}
img.graph = graph
graph.idIndex.Add(img.Id)
graph.checksumLock[img.Id] = &sync.Mutex{}
graph.idIndex.Add(img.ID)
graph.checksumLock[img.ID] = &sync.Mutex{}
return nil
}
@@ -165,13 +175,14 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, output
if err != nil {
return nil, err
}
return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)", false), tmp.Root)
sf := utils.NewStreamFormatter(false)
return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)"), sf), tmp.Root)
}
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
func (graph *Graph) Mktemp(id string) (string, error) {
if id == "" {
id = GenerateId()
id = GenerateID()
}
tmp, err := graph.tmp()
if err != nil {
@@ -228,7 +239,7 @@ func (graph *Graph) Map() (map[string]*Image, error) {
}
images := make(map[string]*Image, len(all))
for _, image := range all {
images[image.Id] = image
images[image.ID] = image
}
return images, nil
}
@@ -271,10 +282,10 @@ func (graph *Graph) ByParent() (map[string][]*Image, error) {
if err != nil {
return
}
if children, exists := byParent[parent.Id]; exists {
byParent[parent.Id] = []*Image{image}
if children, exists := byParent[parent.ID]; exists {
byParent[parent.ID] = []*Image{image}
} else {
byParent[parent.Id] = append(children, image)
byParent[parent.ID] = append(children, image)
}
})
return byParent, err
@@ -291,8 +302,8 @@ func (graph *Graph) Heads() (map[string]*Image, error) {
err = graph.WalkAll(func(image *Image) {
// If it's not in the byParent lookup table, then
// it's not a parent -> so it's a head!
if _, exists := byParent[image.Id]; !exists {
heads[image.Id] = image
if _, exists := byParent[image.ID]; !exists {
heads[image.ID] = image
}
})
return heads, err
@@ -315,11 +326,11 @@ func (graph *Graph) getStoredChecksums() (map[string]string, error) {
}
func (graph *Graph) storeChecksums(checksums map[string]string) error {
checksumJson, err := json.Marshal(checksums)
checksumJSON, err := json.Marshal(checksums)
if err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(graph.Root, "checksums"), checksumJson, 0600); err != nil {
if err := ioutil.WriteFile(path.Join(graph.Root, "checksums"), checksumJSON, 0600); err != nil {
return err
}
return nil

View File

@@ -34,14 +34,14 @@ func TestInterruptedRegister(t *testing.T) {
defer os.RemoveAll(graph.Root)
badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data
image := &Image{
Id: GenerateId(),
ID: GenerateID(),
Comment: "testing",
Created: time.Now(),
}
go graph.Register(badArchive, false, image)
time.Sleep(200 * time.Millisecond)
w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling)
if _, err := graph.Get(image.Id); err == nil {
if _, err := graph.Get(image.ID); err == nil {
t.Fatal("Image should not exist after Register is interrupted")
}
// Registering the same image again should succeed if the first register was interrupted
@@ -67,7 +67,7 @@ func TestGraphCreate(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := ValidateId(image.Id); err != nil {
if err := ValidateID(image.ID); err != nil {
t.Fatal(err)
}
if image.Comment != "Testing" {
@@ -91,7 +91,7 @@ func TestRegister(t *testing.T) {
t.Fatal(err)
}
image := &Image{
Id: GenerateId(),
ID: GenerateID(),
Comment: "testing",
Created: time.Now(),
}
@@ -104,11 +104,11 @@ func TestRegister(t *testing.T) {
} else if l := len(images); l != 1 {
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
}
if resultImg, err := graph.Get(image.Id); err != nil {
if resultImg, err := graph.Get(image.ID); err != nil {
t.Fatal(err)
} else {
if resultImg.Id != image.Id {
t.Fatalf("Wrong image ID. Should be '%s', not '%s'", image.Id, resultImg.Id)
if resultImg.ID != image.ID {
t.Fatalf("Wrong image ID. Should be '%s', not '%s'", image.ID, resultImg.ID)
}
if resultImg.Comment != image.Comment {
t.Fatalf("Wrong image comment. Should be '%s', not '%s'", image.Comment, resultImg.Comment)
@@ -156,7 +156,7 @@ func TestDeletePrefix(t *testing.T) {
graph := tempGraph(t)
defer os.RemoveAll(graph.Root)
img := createTestImage(graph, t)
if err := graph.Delete(utils.TruncateId(img.Id)); err != nil {
if err := graph.Delete(utils.TruncateID(img.ID)); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 0)
@@ -187,7 +187,7 @@ func TestDelete(t *testing.T) {
t.Fatal(err)
}
assertNImages(graph, t, 1)
if err := graph.Delete(img.Id); err != nil {
if err := graph.Delete(img.ID); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 0)
@@ -201,7 +201,7 @@ func TestDelete(t *testing.T) {
t.Fatal(err)
}
assertNImages(graph, t, 2)
if err := graph.Delete(img1.Id); err != nil {
if err := graph.Delete(img1.ID); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 1)
@@ -216,7 +216,7 @@ func TestDelete(t *testing.T) {
if err := graph.Register(archive, false, img1); err != nil {
t.Fatal(err)
}
if err := graph.Delete(img1.Id); err != nil {
if err := graph.Delete(img1.ID); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 1)

19
hack/PRINCIPLES.md Normal file
View File

@@ -0,0 +1,19 @@
# Docker principles
In the design and development of Docker we try to follow these principles:
(Work in progress)
* Don't try to replace every tool. Instead, be an ingredient to improve them.
* Less code is better.
* Less components is better. Do you really need to add one more class?
* 50 lines of straightforward, readable code is better than 10 lines of magic that nobody can understand.
* Don't do later what you can do now. "//FIXME: refactor" is not acceptable in new code.
* When hesitating between 2 options, choose the one that is easier to reverse.
* No is temporary, Yes is forever. If you're not sure about a new feature, say no. You can change your mind later.
* Containers must be portable to the greatest possible number of machines. Be suspicious of any change which makes machines less interchangeable.
* The less moving parts in a container, the better.
* Don't merge it unless you document it.
* Don't document it unless you can keep it up-to-date.
* Don't merge it unless you test it!
* Everyone's problem is slightly different. Focus on the part that is the same for everyone, and solve that.

105
hack/ROADMAP.md Normal file
View File

@@ -0,0 +1,105 @@
# Docker: what's next?
This document is a high-level overview of where we want to take Docker next.
It is a curated selection of planned improvements which are either important, difficult, or both.
For a more complete view of planned and requested improvements, see [the Github issues](https://github.com/dotcloud/docker/issues).
Tu suggest changes to the roadmap, including additions, please write the change as if it were already in effect, and make a pull request.
Broader kernel support
----------------------
Our goal is to make Docker run everywhere, but currently Docker requires [Linux version 3.8 or higher with lxc and aufs support](http://docs.docker.io/en/latest/installation/kernel.html). If you're deploying new machines for the purpose of running Docker, this is a fairly easy requirement to meet.
However, if you're adding Docker to an existing deployment, you may not have the flexibility to update and patch the kernel.
Expanding Docker's kernel support is a priority. This includes running on older kernel versions,
but also on kernels with no AUFS support, or with incomplete lxc capabilities.
Cross-architecture support
--------------------------
Our goal is to make Docker run everywhere. However currently Docker only runs on x86_64 systems.
We plan on expanding architecture support, so that Docker containers can be created and used on more architectures.
Even more integrations
----------------------
We want Docker to be the secret ingredient that makes your existing tools more awesome.
Thanks to this philosophy, Docker has already been integrated with
[Puppet](http://forge.puppetlabs.com/garethr/docker), [Chef](http://www.opscode.com/chef),
[Openstack Nova](https://github.com/dotcloud/openstack-docker), [Jenkins](https://github.com/georgebashi/jenkins-docker-plugin),
[DotCloud sandbox](http://github.com/dotcloud/sandbox), [Pallet](https://github.com/pallet/pallet-docker),
[Strider CI](http://blog.frozenridge.co/next-generation-continuous-integration-deployment-with-dotclouds-docker-and-strider/)
and even [Heroku buildpacks](https://github.com/progrium/buildstep).
Expect Docker to integrate with even more of your favorite tools going forward, including:
* Alternative storage backends such as ZFS, LVM or [BTRFS](github.com/dotcloud/docker/issues/443)
* Alternative containerization backends such as [OpenVZ](http://openvz.org), Solaris Zones, BSD Jails and even plain Chroot.
* Process managers like [Supervisord](http://supervisord.org/), [Runit](http://smarden.org/runit/), [Gaffer](https://gaffer.readthedocs.org/en/latest/#gaffer) and [Systemd](http://www.freedesktop.org/wiki/Software/systemd/)
* Build and integration tools like Make, Maven, Scons, Jenkins, Buildbot and Cruise Control.
* Configuration management tools like [Puppet](http://puppetlabs.com), [Chef](http://www.opscode.com/chef/) and [Salt](http://saltstack.org)
* Personal development environments like [Vagrant](http://vagrantup.com), [Boxen](http://boxen.github.com/), [Koding](http://koding.com) and [Cloud9](http://c9.io).
* Orchestration tools like [Zookeeper](http://zookeeper.apache.org/), [Mesos](http://incubator.apache.org/mesos/) and [Galaxy](https://github.com/ning/galaxy)
* Infrastructure deployment tools like [Openstack](http://openstack.org), [Apache Cloudstack](http://apache.cloudstack.org), [Ganeti](https://code.google.com/p/ganeti/)
Plugin API
----------
We want Docker to run everywhere, and to integrate with every devops tool.
Those are ambitious goals, and the only way to reach them is with the Docker community.
For the community to participate fully, we need an API which allows Docker to be deeply and easily customized.
We are working on a plugin API which will make Docker very, very customization-friendly.
We believe it will facilitate the integrations listed above - and many more we didn't even think about.
Let us know if you want to start playing with the API before it's generally available.
Externally mounted volumes
--------------------------
In 0.3 we [introduced data volumes](https://github.com/dotcloud/docker/wiki/Docker-0.3.0-release-note%2C-May-6-2013#data-volumes),
a great mechanism for manipulating persistent data such as database files, log files, etc.
Data volumes can be shared between containers, a powerful capability [which allows many advanced use cases](http://docs.docker.io/en/latest/examples/couchdb_data_volumes.html). In the future it will also be possible to share volumes between a container and the underlying host. This will make certain scenarios much easier, such as using a high-performance storage backend for your production database,
making live development changes available to a container, etc.
Better documentation
--------------------
We believe that great documentation is worth 10 features. We are often told that "Docker's documentation is great for a 2-month old project".
Our goal is to make it great, period.
If you have feedback on how to improve our documentation, please get in touch by replying to this email,
or by [filing an issue](https://github.com/dotcloud/docker/issues). We always appreciate it!
Production-ready
----------------
Docker is still alpha software, and not suited for production.
We are working hard to get there, and we are confident that it will be possible within a few months.
Advanced port redirections
--------------------------
Docker currently supports 2 flavors of port redirection: STATIC->STATIC (eg. "redirect public port 80 to private port 80")
and RANDOM->STATIC (eg. "redirect any public port to private port 80").
With these 2 flavors, docker can support the majority of backend programs out there. But some applications have more exotic
requirements, generally to implement custom clustering techniques. These applications include Hadoop, MongoDB, Riak, RabbitMQ,
Disco, and all programs relying on Erlang's OTP.
To support these applications, Docker needs to support more advanced redirection flavors, including:
* RANDOM->RANDOM
* STATIC1->STATIC2
These flavors should be implemented without breaking existing semantics, if at all possible.

2
hack/Vagrantfile vendored
View File

@@ -22,7 +22,7 @@ Vagrant::Config.run do |config|
pkg_cmd = "touch #{DOCKER_PATH}; "
# Install docker dependencies
pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; " \
"apt-get install -q -y lxc bsdtar git golang make linux-image-extra-3.8.0-19-generic; " \
"apt-get install -q -y lxc bsdtar git aufs-tools golang make linux-image-extra-3.8.0-19-generic; " \
"chown -R #{USER}.#{USER} #{GOPATH}; " \
"install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile"
config.vm.provision :shell, :inline => pkg_cmd

View File

@@ -1,23 +1,38 @@
# This will build a container capable of producing an official binary build of docker and
# uploading it to S3
# DESCRIPTION Build a container capable of producing official binary and
# PPA packages and uploading them to S3 and Launchpad
# VERSION 1.2
# DOCKER_VERSION 0.4
# AUTHOR Solomon Hykes <solomon@dotcloud.com>
# Daniel Mizyrycki <daniel@dotcloud.net>
# BUILD_CMD docker build -t dockerbuilder .
# RUN_CMD docker run -e AWS_ID="$AWS_ID" -e AWS_KEY="$AWS_KEY" -e GPG_KEY="$GPG_KEY" dockerbuilder
#
#
from ubuntu:12.04
maintainer Solomon Hykes <solomon@dotcloud.com>
from ubuntu:12.10
# Workaround the upstart issue
run dpkg-divert --local --rename --add /sbin/initctl
run ln -s /bin/true /sbin/initctl
# Enable universe and gophers PPA
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q python-software-properties
run add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
run add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu
run apt-get update
# Packages required to checkout, build and upload docker
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
# Packages required to checkout and build docker
run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.linux-amd64.tar.gz
run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz
run tar -C /usr/local -xzf /go.tar.gz
run echo "export PATH=$PATH:/usr/local/go/bin" > /.bashrc
run echo "export PATH=$PATH:/usr/local/go/bin" > /.bash_profile
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential
# Packages required to build an ubuntu package
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev
copy fake_initctl /usr/local/bin/initctl
run apt-get install -y -q devscripts
add . /src
# Copy dockerbuilder files into the container
add . /src
run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder
run cp /src/s3cfg /.s3cfg
cmd ["dockerbuilder"]

View File

@@ -0,0 +1 @@
Daniel Mizyrycki <daniel@dotcloud.com>

View File

@@ -2,7 +2,7 @@
set -x
set -e
export PATH=$PATH:/usr/local/go/bin
export PATH=/usr/local/go/bin:$PATH
PACKAGE=github.com/dotcloud/docker
@@ -13,12 +13,10 @@ fi
export REVISION=$1
if [ -z "$AWS_ID" ]; then
echo "Warning: environment variable AWS_ID is not set. Won't upload to S3."
fi
if [ -z "$AWS_KEY" ]; then
echo "Warning: environment variable AWS_KEY is not set. Won't upload to S3."
if [ -z "$AWS_ID" -o -z "$AWS_KEY" ]; then
echo "Warning: either AWS_ID or AWS_KEY environment variable not set. Won't upload to S3."
else
/bin/echo -e "[default]\naccess_key = $AWS_ID\nsecret_key = $AWS_KEY\n" > /.s3cfg
fi
if [ -z "$GPG_KEY" ]; then
@@ -35,6 +33,10 @@ else
make release RELEASE_VERSION=$REVISION
fi
# Remove credentials from container
rm -f /.s3cfg
if [ -z "$NO_UBUNTU" ]; then
export PATH=`echo $PATH | sed 's#/usr/local/go/bin:##g'`
(cd packaging/ubuntu && make ubuntu)
fi

View File

@@ -1,3 +0,0 @@
#!/bin/sh
echo Whatever you say, man

View File

@@ -1,3 +0,0 @@
[default]
access_key = $AWS_ID
secret_key = $AWS_KEY

View File

@@ -0,0 +1,2 @@
Ken Cochrane <ken@dotcloud.com>
Jerome Petazzoni <jerome@dotcloud.com>

View File

@@ -0,0 +1,5 @@
# Docker project infrastructure
This directory holds all information about the technical infrastructure of the docker project; servers, dns, email, and all the corresponding tools and configuration.
Obviously credentials should not be stored in this repo, but how to obtain and use them should be documented here.

View File

@@ -13,12 +13,13 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"time"
)
type Image struct {
Id string `json:"id"`
ID string `json:"id"`
Parent string `json:"parent,omitempty"`
Comment string `json:"comment,omitempty"`
Created time.Time `json:"created"`
@@ -27,7 +28,9 @@ type Image struct {
DockerVersion string `json:"docker_version,omitempty"`
Author string `json:"author,omitempty"`
Config *Config `json:"config,omitempty"`
Architecture string `json:"architecture,omitempty"`
graph *Graph
Size int64
}
func LoadImage(root string) (*Image, error) {
@@ -41,18 +44,17 @@ func LoadImage(root string) (*Image, error) {
if err := json.Unmarshal(jsonData, img); err != nil {
return nil, err
}
if err := ValidateId(img.Id); err != nil {
if err := ValidateID(img.ID); err != nil {
return nil, err
}
// Check that the filesystem layer exists
if stat, err := os.Stat(layerPath(root)); err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.Id)
} else {
return nil, err
return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.ID)
}
return nil, err
} else if !stat.IsDir() {
return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.Id, layerPath(root))
return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.ID, layerPath(root))
}
return img, nil
}
@@ -60,7 +62,7 @@ func LoadImage(root string) (*Image, error) {
func StoreImage(img *Image, layerData Archive, root string, store bool) error {
// Check that root doesn't already exist
if _, err := os.Stat(root); err == nil {
return fmt.Errorf("Image %s already exists", img.Id)
return fmt.Errorf("Image %s already exists", img.ID)
} else if !os.IsNotExist(err) {
return err
}
@@ -94,6 +96,18 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error {
if err := Untar(layerData, layer); err != nil {
return err
}
return StoreSize(img, root)
}
func StoreSize(img *Image, root string) error {
layer := layerPath(root)
filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error {
img.Size += fileInfo.Size()
return nil
})
// Store the json ball
jsonData, err := json.Marshal(img)
if err != nil {
@@ -126,6 +140,8 @@ func MountAUFS(ro []string, rw string, target string) error {
}
branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
branches += ",xino=/dev/shm/aufs.xino"
//if error, try to load aufs kernel module
if err := mount("none", target, "aufs", 0, branches); err != nil {
log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...")
@@ -180,11 +196,11 @@ func (image *Image) Changes(rw string) ([]Change, error) {
return Changes(layers, rw)
}
func (image *Image) ShortId() string {
return utils.TruncateId(image.Id)
func (image *Image) ShortID() string {
return utils.TruncateID(image.ID)
}
func ValidateId(id string) error {
func ValidateID(id string) error {
if id == "" {
return fmt.Errorf("Image id can't be empty")
}
@@ -194,7 +210,7 @@ func ValidateId(id string) error {
return nil
}
func GenerateId() string {
func GenerateID() string {
id := make([]byte, 32)
_, err := io.ReadFull(rand.Reader, id)
if err != nil {
@@ -240,7 +256,7 @@ func (img *Image) layers() ([]string, error) {
return nil, e
}
if len(list) == 0 {
return nil, fmt.Errorf("No layer found for image %s\n", img.Id)
return nil, fmt.Errorf("No layer found for image %s\n", img.ID)
}
return list, nil
}
@@ -275,7 +291,7 @@ func (img *Image) root() (string, error) {
if img.graph == nil {
return "", fmt.Errorf("Can't lookup root of unregistered image")
}
return img.graph.imageRoot(img.Id), nil
return img.graph.imageRoot(img.ID), nil
}
// Return the path of an image's layer
@@ -288,8 +304,8 @@ func (img *Image) layer() (string, error) {
}
func (img *Image) Checksum() (string, error) {
img.graph.checksumLock[img.Id].Lock()
defer img.graph.checksumLock[img.Id].Unlock()
img.graph.checksumLock[img.ID].Lock()
defer img.graph.checksumLock[img.ID].Unlock()
root, err := img.root()
if err != nil {
@@ -300,7 +316,7 @@ func (img *Image) Checksum() (string, error) {
if err != nil {
return "", err
}
if checksum, ok := checksums[img.Id]; ok {
if checksum, ok := checksums[img.ID]; ok {
return checksum, nil
}
@@ -351,7 +367,7 @@ func (img *Image) Checksum() (string, error) {
return "", err
}
checksums[img.Id] = hash
checksums[img.ID] = hash
// Dump the checksums to disc
if err := img.graph.storeChecksums(checksums); err != nil {
@@ -361,8 +377,17 @@ func (img *Image) Checksum() (string, error) {
return hash, nil
}
func (img *Image) getParentsSize(size int64) int64 {
parentImage, err := img.GetParent()
if err != nil || parentImage == nil {
return size
}
size += parentImage.Size
return parentImage.getParentsSize(size)
}
// Build an Image object from raw json data
func NewImgJson(src []byte) (*Image, error) {
func NewImgJSON(src []byte) (*Image, error) {
ret := &Image{}
utils.Debugf("Json string: {%s}\n", src)

View File

@@ -19,7 +19,7 @@ lxc.network.flags = up
lxc.network.link = {{.NetworkSettings.Bridge}}
lxc.network.name = eth0
lxc.network.mtu = 1500
lxc.network.ipv4 = {{.NetworkSettings.IpAddress}}/{{.NetworkSettings.IpPrefixLen}}
lxc.network.ipv4 = {{.NetworkSettings.IPAddress}}/{{.NetworkSettings.IPPrefixLen}}
# root filesystem
{{$ROOTFS := .RootfsPath}}

View File

@@ -2,13 +2,18 @@ package docker
import (
"fmt"
"github.com/dotcloud/docker/utils"
"os"
"os/exec"
"path/filepath"
"syscall"
"time"
)
func Unmount(target string) error {
if err := exec.Command("auplink", target, "flush").Run(); err != nil {
utils.Debugf("[warning]: couldn't run auplink before unmount: %s", err)
}
if err := syscall.Unmount(target, 0); err != nil {
return err
}

View File

@@ -52,7 +52,7 @@ func ipToInt(ip net.IP) int32 {
}
// Converts 32 bit integer into a 4 bytes IP address
func intToIp(n int32) net.IP {
func intToIP(n int32) net.IP {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(n))
return net.IP(b)
@@ -132,9 +132,8 @@ func CreateBridgeIface(ifaceName string) error {
}
if ifaceAddr == "" {
return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", ifaceName, ifaceName)
} else {
utils.Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
}
utils.Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
if output, err := ip("link", "add", ifaceName, "type", "bridge"); err != nil {
return fmt.Errorf("Error creating bridge: %s (output: %s)", err, output)
@@ -258,7 +257,7 @@ func proxy(listener net.Listener, proto, address string) error {
utils.Debugf("Connected to backend, splicing")
splice(src, dst)
}
return nil
panic("Unreachable")
}
func halfSplice(dst, src net.Conn) error {
@@ -398,7 +397,7 @@ func (alloc *IPAllocator) run() {
}
}
ip := allocatedIP{ip: intToIp(newNum)}
ip := allocatedIP{ip: intToIP(newNum)}
if inUse {
ip.err = errors.New("No unallocated IP available")
}
@@ -465,11 +464,11 @@ func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
return nil, err
}
// Allocate a random port if Frontend==0
if extPort, err := iface.manager.portAllocator.Acquire(nat.Frontend); err != nil {
extPort, err := iface.manager.portAllocator.Acquire(nat.Frontend)
if err != nil {
return nil, err
} else {
nat.Frontend = extPort
}
nat.Frontend = extPort
if err := iface.manager.portMapper.Map(nat.Frontend, net.TCPAddr{IP: iface.IPNet.IP, Port: nat.Backend}); err != nil {
iface.manager.portAllocator.Release(nat.Frontend)
return nil, err
@@ -486,20 +485,38 @@ type Nat struct {
func parseNat(spec string) (*Nat, error) {
var nat Nat
// If spec starts with ':', external and internal ports must be the same.
// This might fail if the requested external port is not available.
var sameFrontend bool
if spec[0] == ':' {
sameFrontend = true
spec = spec[1:]
}
port, err := strconv.ParseUint(spec, 10, 16)
if err != nil {
return nil, err
}
nat.Backend = int(port)
if sameFrontend {
nat.Frontend = nat.Backend
if strings.Contains(spec, ":") {
specParts := strings.Split(spec, ":")
if len(specParts) != 2 {
return nil, fmt.Errorf("Invalid port format.")
}
// If spec starts with ':', external and internal ports must be the same.
// This might fail if the requested external port is not available.
var sameFrontend bool
if len(specParts[0]) == 0 {
sameFrontend = true
} else {
front, err := strconv.ParseUint(specParts[0], 10, 16)
if err != nil {
return nil, err
}
nat.Frontend = int(front)
}
back, err := strconv.ParseUint(specParts[1], 10, 16)
if err != nil {
return nil, err
}
nat.Backend = int(back)
if sameFrontend {
nat.Frontend = nat.Backend
}
} else {
port, err := strconv.ParseUint(spec, 10, 16)
if err != nil {
return nil, err
}
nat.Backend = int(port)
}
nat.Proto = "tcp"
return &nat, nil

View File

@@ -18,6 +18,32 @@ func TestIptables(t *testing.T) {
}
}
func TestParseNat(t *testing.T) {
if nat, err := parseNat("4500"); err == nil {
if nat.Frontend != 0 || nat.Backend != 4500 {
t.Errorf("-p 4500 should produce 0->4500, got %d->%d", nat.Frontend, nat.Backend)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat(":4501"); err == nil {
if nat.Frontend != 4501 || nat.Backend != 4501 {
t.Errorf("-p :4501 should produce 4501->4501, got %d->%d", nat.Frontend, nat.Backend)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat("4502:4503"); err == nil {
if nat.Frontend != 4502 || nat.Backend != 4503 {
t.Errorf("-p 4502:4503 should produce 4502->4503, got %d->%d", nat.Frontend, nat.Backend)
}
} else {
t.Fatal(err)
}
}
func TestPortAllocation(t *testing.T) {
allocator, err := newPortAllocator()
if err != nil {
@@ -137,7 +163,7 @@ func TestConversion(t *testing.T) {
if i == 0 {
t.Fatal("converted to zero")
}
conv := intToIp(i)
conv := intToIP(i)
if !ip.Equal(conv) {
t.Error(conv.String())
}

View File

@@ -10,7 +10,7 @@ Homepage: http://github.com/dotcloud/docker
Package: lxc-docker
Architecture: linux-any
Depends: ${shlibs:Depends}, ${misc:Depends}, lxc, bsdtar
Depends: ${shlibs:Depends}, ${misc:Depends}, lxc, bsdtar, aufs-tools
Description: Linux container runtime
Docker complements LXC with a high-level API which operates at the process
level. It runs unix processes with strong guarantees of isolation and

View File

@@ -1,3 +1,34 @@
lxc-docker (0.4.2-1) precise; urgency=low
- Packaging: Bumped version to work around an Ubuntu bug
-- dotCloud <ops@dotcloud.com> Mon, 17 Jun 2013 00:00:00 -0700
lxc-docker (0.4.1-1) precise; urgency=low
- Builder: don't ignore last line in Dockerfile when it doesn't end with \n
- Client: allow multiple params in inspect
- Client: Print the container id before the hijack in `docker run`
- Remote Api: Add flag to enable cross domain requests
- Remote Api/Client: Add images and containers sizes in docker ps and docker images
- Registry: add regexp check on repo's name
- Registry: Move auth to the client
- Registry: Remove login check on pull
- Runtime: Configure dns configuration host-wide with 'docker -d -dns'
- Runtime: Detect faulty DNS configuration and replace it with a public default
- Runtime: allow docker run <name>:<id>
- Runtime: you can now specify public port (ex: -p 80:4500)
- Runtime: improved image removal to garbage-collect unreferenced parents
- Vagrantfile: Add the rest api port to vagrantfile's port_forward
- Upgrade to Go 1.1
-- dotCloud <ops@dotcloud.com> Mon, 17 Jun 2013 00:00:00 -0700
lxc-docker (0.4.0-1) precise; urgency=low
- Introducing Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
- Introducing Remote API: control Docker programmatically using a simple HTTP/json API
- Runtime: various reliability and usability improvements
-- dotCloud <ops@dotcloud.com> Mon, 03 Jun 2013 00:00:00 -0700
lxc-docker (0.3.4-1) precise; urgency=low
- Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
- Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.

View File

@@ -8,7 +8,7 @@ Homepage: http://github.com/dotcloud/docker
Package: lxc-docker
Architecture: linux-any
Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar
Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar,aufs-tools
Conflicts: docker
Description: lxc-docker is a Linux container runtime
Docker complements LXC with a high-level API which operates at the process

View File

@@ -5,6 +5,5 @@ stop on starting rc RUNLEVEL=[016]
respawn
script
# FIXME: docker should not depend on the system having en_US.UTF-8
LC_ALL='en_US.UTF-8' /usr/bin/docker -d
/usr/bin/docker -d
end script

View File

@@ -15,7 +15,7 @@ import (
"strings"
)
var ErrAlreadyExists error = errors.New("Image already exists")
var ErrAlreadyExists = errors.New("Image already exists")
func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
for _, cookie := range c.Jar.Cookies(req.URL) {
@@ -64,7 +64,11 @@ func (r *Registry) LookupRemoteImage(imgId, registry string, authConfig *auth.Au
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
res, err := rt.RoundTrip(req)
return err == nil && res.StatusCode == 307
if err != nil {
return false
}
res.Body.Close()
return res.StatusCode == 307
}
func (r *Registry) getImagesInRepository(repository string, authConfig *auth.AuthConfig) ([]map[string]string, error) {
@@ -103,8 +107,8 @@ func (r *Registry) getImagesInRepository(repository string, authConfig *auth.Aut
// Retrieve an image from the Registry.
// Returns the Image object as well as the layer as an Archive (io.Reader)
func (r *Registry) GetRemoteImageJson(imgId, registry string, token []string) ([]byte, error) {
// Get the Json
func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, error) {
// Get the JSON
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
if err != nil {
return nil, fmt.Errorf("Failed to download json: %s", err)
@@ -152,21 +156,24 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
}
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
res, err := r.client.Do(req)
defer res.Body.Close()
utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
if err != nil || (res.StatusCode != 200 && res.StatusCode != 404) {
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 && res.StatusCode != 404 {
continue
} else if res.StatusCode == 404 {
return nil, fmt.Errorf("Repository not found")
}
result := make(map[string]string)
rawJson, err := ioutil.ReadAll(res.Body)
rawJSON, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
if err := json.Unmarshal(rawJson, &result); err != nil {
if err := json.Unmarshal(rawJSON, &result); err != nil {
return nil, err
}
return result, nil
@@ -212,19 +219,19 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
return nil, fmt.Errorf("Index response didn't contain any endpoints")
}
checksumsJson, err := ioutil.ReadAll(res.Body)
checksumsJSON, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
remoteChecksums := []*ImgData{}
if err := json.Unmarshal(checksumsJson, &remoteChecksums); err != nil {
if err := json.Unmarshal(checksumsJSON, &remoteChecksums); err != nil {
return nil, err
}
// Forge a better object from the retrieved data
imgsData := make(map[string]*ImgData)
for _, elem := range remoteChecksums {
imgsData[elem.Id] = elem
imgsData[elem.ID] = elem
}
return &RepositoryData{
@@ -235,10 +242,10 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
}
// Push a local image to the registry
func (r *Registry) PushImageJsonRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
registry = "https://" + registry + "/v1"
// FIXME: try json with UTF8
req, err := http.NewRequest("PUT", registry+"/images/"+imgData.Id+"/json", strings.NewReader(string(jsonRaw)))
req, err := http.NewRequest("PUT", registry+"/images/"+imgData.ID+"/json", strings.NewReader(string(jsonRaw)))
if err != nil {
return err
}
@@ -246,7 +253,7 @@ func (r *Registry) PushImageJsonRegistry(imgData *ImgData, jsonRaw []byte, regis
req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
req.Header.Set("X-Docker-Checksum", imgData.Checksum)
utils.Debugf("Setting checksum for %s: %s", imgData.Id, imgData.Checksum)
utils.Debugf("Setting checksum for %s: %s", imgData.ID, imgData.Checksum)
res, err := doWithCookies(r.client, req)
if err != nil {
return fmt.Errorf("Failed to upload metadata: %s", err)
@@ -321,8 +328,8 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token
return nil
}
func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validate bool) (*RepositoryData, error) {
imgListJson, err := json.Marshal(imgList)
func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
imgListJSON, err := json.Marshal(imgList)
if err != nil {
return nil, err
}
@@ -331,15 +338,18 @@ func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validat
suffix = "images"
}
utils.Debugf("Image list pushed to index:\n%s\n", imgListJson)
utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON)
req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJson))
req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJSON))
if err != nil {
return nil, err
}
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
req.ContentLength = int64(len(imgListJson))
req.ContentLength = int64(len(imgListJSON))
req.Header.Set("X-Docker-Token", "true")
if validate {
req.Header["X-Docker-Endpoints"] = regs
}
res, err := r.client.Do(req)
if err != nil {
@@ -350,14 +360,16 @@ func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validat
// Redirect if necessary
for res.StatusCode >= 300 && res.StatusCode < 400 {
utils.Debugf("Redirected to %s\n", res.Header.Get("Location"))
req, err = http.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJson))
req, err = http.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJSON))
if err != nil {
return nil, err
}
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
req.ContentLength = int64(len(imgListJson))
req.ContentLength = int64(len(imgListJSON))
req.Header.Set("X-Docker-Token", "true")
if validate {
req.Header["X-Docker-Endpoints"] = regs
}
res, err = r.client.Do(req)
if err != nil {
return nil, err
@@ -389,11 +401,11 @@ func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validat
}
if validate {
if res.StatusCode != 204 {
if errBody, err := ioutil.ReadAll(res.Body); err != nil {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
} else {
return nil, fmt.Errorf("Error: Status %d trying to push checksums %s: %s", res.StatusCode, remote, errBody)
}
return nil, fmt.Errorf("Error: Status %d trying to push checksums %s: %s", res.StatusCode, remote, errBody)
}
}
@@ -456,7 +468,7 @@ type RepositoryData struct {
}
type ImgData struct {
Id string `json:"id"`
ID string `json:"id"`
Checksum string `json:"checksum,omitempty"`
Tag string `json:",omitempty"`
}
@@ -466,13 +478,17 @@ type Registry struct {
authConfig *auth.AuthConfig
}
func NewRegistry(root string) *Registry {
// If the auth file does not exist, keep going
authConfig, _ := auth.LoadConfig(root)
func NewRegistry(root string, authConfig *auth.AuthConfig) *Registry {
httpTransport := &http.Transport{
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
}
r := &Registry{
authConfig: authConfig,
client: &http.Client{},
client: &http.Client{
Transport: httpTransport,
},
}
r.client.Jar = cookiejar.NewCookieJar()
return r

View File

@@ -32,6 +32,7 @@ type Runtime struct {
autoRestart bool
volumes *Graph
srv *Server
Dns []string
}
var sysInitPath string
@@ -51,7 +52,7 @@ func (runtime *Runtime) List() []*Container {
func (runtime *Runtime) getContainerElement(id string) *list.Element {
for e := runtime.containers.Front(); e != nil; e = e.Next() {
container := e.Value.(*Container)
if container.Id == id {
if container.ID == id {
return e
}
}
@@ -83,8 +84,8 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
if err := container.FromDisk(); err != nil {
return nil, err
}
if container.Id != id {
return container, fmt.Errorf("Container %s is stored at %s", container.Id, id)
if container.ID != id {
return container, fmt.Errorf("Container %s is stored at %s", container.ID, id)
}
if container.State.Running {
container.State.Ghost = true
@@ -95,12 +96,12 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
return container, nil
}
// Register makes a container object usable by the runtime as <container.Id>
// Register makes a container object usable by the runtime as <container.ID>
func (runtime *Runtime) Register(container *Container) error {
if container.runtime != nil || runtime.Exists(container.Id) {
if container.runtime != nil || runtime.Exists(container.ID) {
return fmt.Errorf("Container is already loaded")
}
if err := validateId(container.Id); err != nil {
if err := validateID(container.ID); err != nil {
return err
}
@@ -123,7 +124,7 @@ func (runtime *Runtime) Register(container *Container) error {
}
// done
runtime.containers.PushBack(container)
runtime.idIndex.Add(container.Id)
runtime.idIndex.Add(container.ID)
// When we actually restart, Start() do the monitoring.
// However, when we simply 'reattach', we have to restart a monitor
@@ -133,25 +134,25 @@ func (runtime *Runtime) Register(container *Container) error {
// if so, then we need to restart monitor and init a new lock
// If the container is supposed to be running, make sure of it
if container.State.Running {
if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput()
if err != nil {
return err
} else {
if !strings.Contains(string(output), "RUNNING") {
utils.Debugf("Container %s was supposed to be running be is not.", container.Id)
if runtime.autoRestart {
utils.Debugf("Restarting")
container.State.Ghost = false
container.State.setStopped(0)
if err := container.Start(); err != nil {
return err
}
nomonitor = true
} else {
utils.Debugf("Marking as stopped")
container.State.setStopped(-127)
if err := container.ToDisk(); err != nil {
return err
}
}
if !strings.Contains(string(output), "RUNNING") {
utils.Debugf("Container %s was supposed to be running be is not.", container.ID)
if runtime.autoRestart {
utils.Debugf("Restarting")
container.State.Ghost = false
container.State.setStopped(0)
if err := container.Start(); err != nil {
return err
}
nomonitor = true
} else {
utils.Debugf("Marking as stopped")
container.State.setStopped(-127)
if err := container.ToDisk(); err != nil {
return err
}
}
}
@@ -182,9 +183,9 @@ func (runtime *Runtime) Destroy(container *Container) error {
return fmt.Errorf("The given container is <nil>")
}
element := runtime.getContainerElement(container.Id)
element := runtime.getContainerElement(container.ID)
if element == nil {
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id)
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.ID)
}
if err := container.Stop(3); err != nil {
@@ -194,14 +195,14 @@ func (runtime *Runtime) Destroy(container *Container) error {
return err
} else if mounted {
if err := container.Unmount(); err != nil {
return fmt.Errorf("Unable to unmount container %v: %v", container.Id, err)
return fmt.Errorf("Unable to unmount container %v: %v", container.ID, err)
}
}
// Deregister the container before removing its directory, to avoid race conditions
runtime.idIndex.Delete(container.Id)
runtime.idIndex.Delete(container.ID)
runtime.containers.Remove(element)
if err := os.RemoveAll(container.root); err != nil {
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.Id, err)
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err)
}
return nil
}
@@ -218,7 +219,7 @@ func (runtime *Runtime) restore() error {
utils.Debugf("Failed to load container %v: %v", id, err)
continue
}
utils.Debugf("Loaded container %v", container.Id)
utils.Debugf("Loaded container %v", container.ID)
}
return nil
}
@@ -245,11 +246,12 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) {
}
// FIXME: harmonize with NewGraph()
func NewRuntime(autoRestart bool) (*Runtime, error) {
func NewRuntime(autoRestart bool, dns []string) (*Runtime, error) {
runtime, err := NewRuntimeFromDirectory("/var/lib/docker", autoRestart)
if err != nil {
return nil, err
}
runtime.Dns = dns
if k, err := utils.GetKernelVersion(); err != nil {
log.Printf("WARNING: %s\n", err)

View File

@@ -17,7 +17,7 @@ import (
)
const unitTestImageName string = "docker-ut"
const unitTestImageId string = "e9aa60c60128cad1"
const unitTestStoreBase string = "/var/lib/docker/unit-tests"
func nuke(runtime *Runtime) error {
@@ -68,7 +68,7 @@ func init() {
runtime: runtime,
}
// Retrieve the Image
if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, false); err != nil {
if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil {
panic(err)
}
}
@@ -120,7 +120,7 @@ func TestRuntimeCreate(t *testing.T) {
builder := NewBuilder(runtime)
container, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
@@ -140,29 +140,29 @@ func TestRuntimeCreate(t *testing.T) {
}
// Make sure the container List() returns is the right one
if runtime.List()[0].Id != container.Id {
if runtime.List()[0].ID != container.ID {
t.Errorf("Unexpected container %v returned by List", runtime.List()[0])
}
// Make sure we can get the container with Get()
if runtime.Get(container.Id) == nil {
if runtime.Get(container.ID) == nil {
t.Errorf("Unable to get newly created container")
}
// Make sure it is the right container
if runtime.Get(container.Id) != container {
if runtime.Get(container.ID) != container {
t.Errorf("Get() returned the wrong container")
}
// Make sure Exists returns it as existing
if !runtime.Exists(container.Id) {
if !runtime.Exists(container.ID) {
t.Errorf("Exists() returned false for a newly created container")
}
// Make sure crete with bad parameters returns an error
_, err = builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
},
)
if err == nil {
@@ -171,7 +171,7 @@ func TestRuntimeCreate(t *testing.T) {
_, err = builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{},
},
)
@@ -187,7 +187,7 @@ func TestDestroy(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
@@ -210,7 +210,7 @@ func TestDestroy(t *testing.T) {
}
// Make sure runtime.Get() refuses to return the unexisting container
if runtime.Get(container.Id) != nil {
if runtime.Get(container.ID) != nil {
t.Errorf("Unable to get newly created container")
}
@@ -237,7 +237,7 @@ func TestGet(t *testing.T) {
builder := NewBuilder(runtime)
container1, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
@@ -247,7 +247,7 @@ func TestGet(t *testing.T) {
defer runtime.Destroy(container1)
container2, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
@@ -257,7 +257,7 @@ func TestGet(t *testing.T) {
defer runtime.Destroy(container2)
container3, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
@@ -266,16 +266,16 @@ func TestGet(t *testing.T) {
}
defer runtime.Destroy(container3)
if runtime.Get(container1.Id) != container1 {
t.Errorf("Get(test1) returned %v while expecting %v", runtime.Get(container1.Id), container1)
if runtime.Get(container1.ID) != container1 {
t.Errorf("Get(test1) returned %v while expecting %v", runtime.Get(container1.ID), container1)
}
if runtime.Get(container2.Id) != container2 {
t.Errorf("Get(test2) returned %v while expecting %v", runtime.Get(container2.Id), container2)
if runtime.Get(container2.ID) != container2 {
t.Errorf("Get(test2) returned %v while expecting %v", runtime.Get(container2.ID), container2)
}
if runtime.Get(container3.Id) != container3 {
t.Errorf("Get(test3) returned %v while expecting %v", runtime.Get(container3.Id), container3)
if runtime.Get(container3.ID) != container3 {
t.Errorf("Get(test3) returned %v while expecting %v", runtime.Get(container3.ID), container3)
}
}
@@ -283,7 +283,7 @@ func TestGet(t *testing.T) {
func findAvailalblePort(runtime *Runtime, port int) (*Container, error) {
strPort := strconv.Itoa(port)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p " + strPort},
PortSpecs: []string{strPort},
},
@@ -379,7 +379,7 @@ func TestRestore(t *testing.T) {
// Create a container with one instance of docker
container1, err := builder.Create(&Config{
Image: GetTestImage(runtime1).Id,
Image: GetTestImage(runtime1).ID,
Cmd: []string{"ls", "-al"},
},
)
@@ -390,7 +390,7 @@ func TestRestore(t *testing.T) {
// Create a second container meant to be killed
container2, err := builder.Create(&Config{
Image: GetTestImage(runtime1).Id,
Image: GetTestImage(runtime1).ID,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
@@ -406,7 +406,7 @@ func TestRestore(t *testing.T) {
}
if !container2.State.Running {
t.Fatalf("Container %v should appear as running but isn't", container2.Id)
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
// Simulate a crash/manual quit of dockerd: process dies, states stays 'Running'
@@ -426,7 +426,7 @@ func TestRestore(t *testing.T) {
}
if !container2.State.Running {
t.Fatalf("Container %v should appear as running but isn't", container2.Id)
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
// Here are are simulating a docker restart - that is, reloading all containers
@@ -442,14 +442,14 @@ func TestRestore(t *testing.T) {
runningCount := 0
for _, c := range runtime2.List() {
if c.State.Running {
t.Errorf("Running container found: %v (%v)", c.Id, c.Path)
t.Errorf("Running container found: %v (%v)", c.ID, c.Path)
runningCount++
}
}
if runningCount != 0 {
t.Fatalf("Expected 0 container alive, %d found", runningCount)
}
container3 := runtime2.Get(container1.Id)
container3 := runtime2.Get(container1.ID)
if container3 == nil {
t.Fatal("Unable to Get container")
}

349
server.go
View File

@@ -1,6 +1,7 @@
package docker
import (
"errors"
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/registry"
@@ -16,8 +17,12 @@ import (
"strings"
)
func (srv *Server) DockerVersion() ApiVersion {
return ApiVersion{VERSION, GIT_COMMIT, srv.runtime.capabilities.MemoryLimit, srv.runtime.capabilities.SwapLimit}
func (srv *Server) DockerVersion() APIVersion {
return APIVersion{
Version: VERSION,
GitCommit: GITCOMMIT,
GoVersion: runtime.Version(),
}
}
func (srv *Server) ContainerKill(name string) error {
@@ -48,27 +53,24 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
return fmt.Errorf("No such container: %s", name)
}
func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
results, err := registry.NewRegistry(srv.runtime.root).SearchRepositories(term)
results, err := registry.NewRegistry(srv.runtime.root, nil).SearchRepositories(term)
if err != nil {
return nil, err
}
var outs []ApiSearch
var outs []APISearch
for _, repo := range results.Results {
var out ApiSearch
var out APISearch
out.Description = repo["description"]
if len(out.Description) > 45 {
out.Description = utils.Trunc(out.Description, 42) + "..."
}
out.Name = repo["name"]
outs = append(outs, out)
}
return outs, nil
}
func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, error) {
func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) (string, error) {
out = utils.NewWriteFlusher(out)
img, err := srv.runtime.repositories.LookupImage(name)
if err != nil {
@@ -81,7 +83,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e
}
defer file.Body.Close()
config, _, err := ParseRun([]string{img.Id, "echo", "insert", url, path}, srv.runtime.capabilities)
config, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities)
if err != nil {
return "", err
}
@@ -92,7 +94,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e
return "", err
}
if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)\r", false), path); err != nil {
if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), path); err != nil {
return "", err
}
// FIXME: Handle custom repo, tag comment, author
@@ -100,8 +102,8 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e
if err != nil {
return "", err
}
fmt.Fprintf(out, "%s\n", img.Id)
return img.ShortId(), nil
out.Write(sf.FormatStatus(img.ID))
return img.ShortID(), nil
}
func (srv *Server) ImagesViz(out io.Writer) error {
@@ -121,9 +123,9 @@ func (srv *Server) ImagesViz(out io.Writer) error {
return fmt.Errorf("Error while getting parent image: %v", err)
}
if parentImage != nil {
out.Write([]byte(" \"" + parentImage.ShortId() + "\" -> \"" + image.ShortId() + "\"\n"))
out.Write([]byte(" \"" + parentImage.ShortID() + "\" -> \"" + image.ShortID() + "\"\n"))
} else {
out.Write([]byte(" base -> \"" + image.ShortId() + "\" [style=invis]\n"))
out.Write([]byte(" base -> \"" + image.ShortID() + "\" [style=invis]\n"))
}
}
@@ -131,7 +133,7 @@ func (srv *Server) ImagesViz(out io.Writer) error {
for name, repository := range srv.runtime.repositories.Repositories {
for tag, id := range repository {
reporefs[utils.TruncateId(id)] = append(reporefs[utils.TruncateId(id)], fmt.Sprintf("%s:%s", name, tag))
reporefs[utils.TruncateID(id)] = append(reporefs[utils.TruncateID(id)], fmt.Sprintf("%s:%s", name, tag))
}
}
@@ -142,7 +144,7 @@ func (srv *Server) ImagesViz(out io.Writer) error {
return nil
}
func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
var (
allImages map[string]*Image
err error
@@ -155,13 +157,13 @@ func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
if err != nil {
return nil, err
}
outs := []ApiImages{} //produce [] when empty instead of 'null'
outs := []APIImages{} //produce [] when empty instead of 'null'
for name, repository := range srv.runtime.repositories.Repositories {
if filter != "" && name != filter {
continue
}
for tag, id := range repository {
var out ApiImages
var out APIImages
image, err := srv.runtime.graph.Get(id)
if err != nil {
log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
@@ -170,24 +172,28 @@ func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
delete(allImages, id)
out.Repository = name
out.Tag = tag
out.Id = image.Id
out.ID = image.ID
out.Created = image.Created.Unix()
out.Size = image.Size
out.VirtualSize = image.getParentsSize(0) + image.Size
outs = append(outs, out)
}
}
// Display images which aren't part of a
if filter == "" {
for _, image := range allImages {
var out ApiImages
out.Id = image.Id
var out APIImages
out.ID = image.ID
out.Created = image.Created.Unix()
out.Size = image.Size
out.VirtualSize = image.getParentsSize(0) + image.Size
outs = append(outs, out)
}
}
return outs, nil
}
func (srv *Server) DockerInfo() ApiInfo {
func (srv *Server) DockerInfo() *APIInfo {
images, _ := srv.runtime.graph.All()
var imgcount int
if images == nil {
@@ -195,29 +201,27 @@ func (srv *Server) DockerInfo() ApiInfo {
} else {
imgcount = len(images)
}
var out ApiInfo
out.Containers = len(srv.runtime.List())
out.Version = VERSION
out.Images = imgcount
out.GoVersion = runtime.Version()
if os.Getenv("DEBUG") != "" {
out.Debug = true
out.NFd = utils.GetTotalUsedFds()
out.NGoroutines = runtime.NumGoroutine()
return &APIInfo{
Containers: len(srv.runtime.List()),
Images: imgcount,
MemoryLimit: srv.runtime.capabilities.MemoryLimit,
SwapLimit: srv.runtime.capabilities.SwapLimit,
Debug: os.Getenv("DEBUG") != "",
NFd: utils.GetTotalUsedFds(),
NGoroutines: runtime.NumGoroutine(),
}
return out
}
func (srv *Server) ImageHistory(name string) ([]ApiHistory, error) {
func (srv *Server) ImageHistory(name string) ([]APIHistory, error) {
image, err := srv.runtime.repositories.LookupImage(name)
if err != nil {
return nil, err
}
var outs []ApiHistory = []ApiHistory{} //produce [] when empty instead of 'null'
outs := []APIHistory{} //produce [] when empty instead of 'null'
err = image.WalkHistory(func(img *Image) error {
var out ApiHistory
out.Id = srv.runtime.repositories.ImageName(img.ShortId())
var out APIHistory
out.ID = srv.runtime.repositories.ImageName(img.ShortID())
out.Created = img.Created.Unix()
out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ")
outs = append(outs, out)
@@ -234,17 +238,17 @@ func (srv *Server) ContainerChanges(name string) ([]Change, error) {
return nil, fmt.Errorf("No such container: %s", name)
}
func (srv *Server) Containers(all bool, n int, since, before string) []ApiContainers {
func (srv *Server) Containers(all bool, n int, since, before string) []APIContainers {
var foundBefore bool
var displayed int
retContainers := []ApiContainers{}
retContainers := []APIContainers{}
for _, container := range srv.runtime.List() {
if !container.State.Running && !all && n == -1 && since == "" && before == "" {
continue
}
if before != "" {
if container.ShortId() == before {
if container.ShortID() == before {
foundBefore = true
continue
}
@@ -255,19 +259,21 @@ func (srv *Server) Containers(all bool, n int, since, before string) []ApiContai
if displayed == n {
break
}
if container.ShortId() == since {
if container.ShortID() == since {
break
}
displayed++
c := ApiContainers{
Id: container.Id,
c := APIContainers{
ID: container.ID,
}
c.Image = srv.runtime.repositories.ImageName(container.Image)
c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
c.Created = container.Created.Unix()
c.Status = container.State.String()
c.Ports = container.NetworkSettings.PortMappingHuman()
c.SizeRw, c.SizeRootFs = container.GetSize()
retContainers = append(retContainers, c)
}
return retContainers
@@ -282,7 +288,7 @@ func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, conf
if err != nil {
return "", err
}
return img.ShortId(), err
return img.ShortID(), err
}
func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
@@ -292,7 +298,7 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
return nil
}
func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoint string, token []string, json bool) error {
func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoint string, token []string, sf *utils.StreamFormatter) error {
history, err := r.GetRemoteHistory(imgId, endpoint, token)
if err != nil {
return err
@@ -302,24 +308,25 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
// FIXME: Launch the getRemoteImage() in goroutines
for _, id := range history {
if !srv.runtime.graph.Exists(id) {
fmt.Fprintf(out, utils.FormatStatus("Pulling %s metadata", json), id)
imgJson, err := r.GetRemoteImageJson(id, endpoint, token)
out.Write(sf.FormatStatus("Pulling %s metadata", id))
imgJSON, err := r.GetRemoteImageJSON(id, endpoint, token)
if err != nil {
// FIXME: Keep goging in case of error?
return err
}
img, err := NewImgJson(imgJson)
img, err := NewImgJSON(imgJSON)
if err != nil {
return fmt.Errorf("Failed to parse json: %s", err)
}
// Get the layer
fmt.Fprintf(out, utils.FormatStatus("Pulling %s fs layer", json), id)
layer, contentLength, err := r.GetRemoteImageLayer(img.Id, endpoint, token)
out.Write(sf.FormatStatus("Pulling %s fs layer", id))
layer, contentLength, err := r.GetRemoteImageLayer(img.ID, endpoint, token)
if err != nil {
return err
}
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, utils.FormatProgress("%v/%v (%v)", json), json), false, img); err != nil {
defer layer.Close()
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil {
return err
}
}
@@ -327,8 +334,8 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
return nil
}
func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, askedTag string, json bool) error {
fmt.Fprintf(out, utils.FormatStatus("Pulling repository %s from %s", json), remote, auth.IndexServerAddress())
func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, remote, askedTag string, sf *utils.StreamFormatter) error {
out.Write(sf.FormatStatus("Pulling repository %s from %s", local, auth.IndexServerAddress()))
repoData, err := r.GetRepositoryData(remote)
if err != nil {
return err
@@ -353,23 +360,23 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, a
}
} else {
// Otherwise, check that the tag exists and use only that one
if id, exists := tagsList[askedTag]; !exists {
return fmt.Errorf("Tag %s not found in repositoy %s", askedTag, remote)
} else {
repoData.ImgList[id].Tag = askedTag
id, exists := tagsList[askedTag]
if !exists {
return fmt.Errorf("Tag %s not found in repositoy %s", askedTag, local)
}
repoData.ImgList[id].Tag = askedTag
}
for _, img := range repoData.ImgList {
if askedTag != "" && img.Tag != askedTag {
utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.Id)
utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.ID)
continue
}
fmt.Fprintf(out, utils.FormatStatus("Pulling image %s (%s) from %s", json), img.Id, img.Tag, remote)
out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, remote))
success := false
for _, ep := range repoData.Endpoints {
if err := srv.pullImage(r, out, img.Id, "https://"+ep+"/v1", repoData.Tokens, json); err != nil {
fmt.Fprintf(out, utils.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint\n", json), askedTag, err)
if err := srv.pullImage(r, out, img.ID, "https://"+ep+"/v1", repoData.Tokens, sf); err != nil {
out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err))
continue
}
success = true
@@ -383,7 +390,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, a
if askedTag != "" && tag != askedTag {
continue
}
if err := srv.runtime.repositories.Set(remote, tag, id, true); err != nil {
if err := srv.runtime.repositories.Set(local, tag, id, true); err != nil {
return err
}
}
@@ -394,17 +401,21 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, a
return nil
}
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, json bool) error {
r := registry.NewRegistry(srv.runtime.root)
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
r := registry.NewRegistry(srv.runtime.root, authConfig)
out = utils.NewWriteFlusher(out)
if endpoint != "" {
if err := srv.pullImage(r, out, name, endpoint, nil, json); err != nil {
if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
return err
}
return nil
}
if err := srv.pullRepository(r, out, name, tag, json); err != nil {
remote := name
parts := strings.Split(name, "/")
if len(parts) > 2 {
remote = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/")))
}
if err := srv.pullRepository(r, out, name, remote, tag, sf); err != nil {
return err
}
@@ -458,16 +469,16 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
return nil, err
}
img.WalkHistory(func(img *Image) error {
if _, exists := imageSet[img.Id]; exists {
if _, exists := imageSet[img.ID]; exists {
return nil
}
imageSet[img.Id] = struct{}{}
checksum, err := srv.getChecksum(img.Id)
imageSet[img.ID] = struct{}{}
checksum, err := srv.getChecksum(img.ID)
if err != nil {
return err
}
imgList = append([]*registry.ImgData{{
Id: img.Id,
ID: img.ID,
Checksum: checksum,
Tag: tag,
}}, imgList...)
@@ -477,52 +488,58 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
return imgList, nil
}
func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string) error {
func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string, sf *utils.StreamFormatter) error {
out = utils.NewWriteFlusher(out)
fmt.Fprintf(out, "Processing checksums\n")
out.Write(sf.FormatStatus("Processing checksums"))
imgList, err := srv.getImageList(localRepo)
if err != nil {
return err
}
fmt.Fprintf(out, "Sending images list\n")
out.Write(sf.FormatStatus("Sending image list"))
repoData, err := r.PushImageJsonIndex(name, imgList, false)
srvName := name
parts := strings.Split(name, "/")
if len(parts) > 2 {
srvName = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/")))
}
repoData, err := r.PushImageJSONIndex(srvName, imgList, false, nil)
if err != nil {
return err
}
for _, ep := range repoData.Endpoints {
fmt.Fprintf(out, "Pushing repository %s to %s (%d tags)\r\n", name, ep, len(localRepo))
out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo)))
// For each image within the repo, push them
for _, elem := range imgList {
if _, exists := repoData.ImgList[elem.Id]; exists {
fmt.Fprintf(out, "Image %s already on registry, skipping\n", name)
if _, exists := repoData.ImgList[elem.ID]; exists {
out.Write(sf.FormatStatus("Image %s already on registry, skipping", name))
continue
}
if err := srv.pushImage(r, out, name, elem.Id, ep, repoData.Tokens); err != nil {
if err := srv.pushImage(r, out, name, elem.ID, ep, repoData.Tokens, sf); err != nil {
// FIXME: Continue on error?
return err
}
fmt.Fprintf(out, "Pushing tags for rev [%s] on {%s}\n", elem.Id, ep+"/users/"+name+"/"+elem.Tag)
if err := r.PushRegistryTag(name, elem.Id, elem.Tag, ep, repoData.Tokens); err != nil {
out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"/users/"+srvName+"/"+elem.Tag))
if err := r.PushRegistryTag(srvName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil {
return err
}
}
}
if _, err := r.PushImageJsonIndex(name, imgList, true); err != nil {
if _, err := r.PushImageJSONIndex(srvName, imgList, true, repoData.Endpoints); err != nil {
return err
}
return nil
}
func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, ep string, token []string) error {
func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, ep string, token []string, sf *utils.StreamFormatter) error {
out = utils.NewWriteFlusher(out)
jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgId, "json"))
if err != nil {
return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgId, err)
}
fmt.Fprintf(out, "Pushing %s\r\n", imgId)
out.Write(sf.FormatStatus("Pushing %s", imgId))
// Make sure we have the image's checksum
checksum, err := srv.getChecksum(imgId)
@@ -530,14 +547,14 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
return err
}
imgData := &registry.ImgData{
Id: imgId,
ID: imgId,
Checksum: checksum,
}
// Send the json
if err := r.PushImageJsonRegistry(imgData, jsonRaw, ep, token); err != nil {
if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
if err == registry.ErrAlreadyExists {
fmt.Fprintf(out, "Image %s already uploaded ; skipping\n", imgData.Id)
out.Write(sf.FormatStatus("Image %s already uploaded ; skipping", imgData.ID))
return nil
}
return err
@@ -570,22 +587,22 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
}
// Send the layer
if err := r.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, "", false), ep, token); err != nil {
if err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%v/%v (%v)"), sf), ep, token); err != nil {
return err
}
return nil
}
func (srv *Server) ImagePush(name, endpoint string, out io.Writer) error {
func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
out = utils.NewWriteFlusher(out)
img, err := srv.runtime.graph.Get(name)
r := registry.NewRegistry(srv.runtime.root)
r := registry.NewRegistry(srv.runtime.root, authConfig)
if err != nil {
fmt.Fprintf(out, "The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name]))
out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
// If it fails, try to get the repository
if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
if err := srv.pushRepository(r, out, name, localRepo); err != nil {
if err := srv.pushRepository(r, out, name, localRepo, sf); err != nil {
return err
}
return nil
@@ -593,14 +610,14 @@ func (srv *Server) ImagePush(name, endpoint string, out io.Writer) error {
return err
}
fmt.Fprintf(out, "The push refers to an image: [%s]\n", name)
if err := srv.pushImage(r, out, name, img.Id, endpoint, nil); err != nil {
out.Write(sf.FormatStatus("The push refers to an image: [%s]", name))
if err := srv.pushImage(r, out, name, img.ID, endpoint, nil, sf); err != nil {
return err
}
return nil
}
func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer) error {
func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer, sf *utils.StreamFormatter) error {
var archive io.Reader
var resp *http.Response
@@ -609,21 +626,21 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
} else {
u, err := url.Parse(src)
if err != nil {
fmt.Fprintf(out, "Error: %s\n", err)
return err
}
if u.Scheme == "" {
u.Scheme = "http"
u.Host = src
u.Path = ""
}
fmt.Fprintf(out, "Downloading from %s\n", u)
out.Write(sf.FormatStatus("Downloading from %s", u))
// Download with curl (pretty progress bar)
// If curl is not available, fallback to http.Get()
resp, err = utils.Download(u.String(), out)
if err != nil {
return err
}
archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)\r", false)
archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("Importing", "%v/%v (%v)"), sf)
}
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
if err != nil {
@@ -631,16 +648,20 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
}
// Optionally register the image at REPO/TAG
if repo != "" {
if err := srv.runtime.repositories.Set(repo, tag, img.Id, true); err != nil {
if err := srv.runtime.repositories.Set(repo, tag, img.ID, true); err != nil {
return err
}
}
fmt.Fprintf(out, "%s\n", img.ShortId())
out.Write(sf.FormatStatus(img.ShortID()))
return nil
}
func (srv *Server) ContainerCreate(config *Config) (string, error) {
if config.Memory != 0 && config.Memory < 524288 {
return "", fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)")
}
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
config.Memory = 0
}
@@ -656,7 +677,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
}
return "", err
}
return container.ShortId(), nil
return container.ShortID(), nil
}
func (srv *Server) ContainerRestart(name string, t int) error {
@@ -693,7 +714,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
for volumeId := range volumes {
// If the requested volu
if c, exists := usedVolumes[volumeId]; exists {
log.Printf("The volume %s is used by the container %s. Impossible to remove it. Skipping.\n", volumeId, c.Id)
log.Printf("The volume %s is used by the container %s. Impossible to remove it. Skipping.\n", volumeId, c.ID)
continue
}
if err := srv.runtime.volumes.Delete(volumeId); err != nil {
@@ -707,18 +728,112 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
return nil
}
func (srv *Server) ImageDelete(name string) error {
img, err := srv.runtime.repositories.LookupImage(name)
var ErrImageReferenced = errors.New("Image referenced by a repository")
func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
// If the image is referenced by a repo, do not delete
if len(srv.runtime.repositories.ByID()[id]) != 0 {
return ErrImageReferenced
}
// If the image is not referenced but has children, go recursive
referenced := false
byParents, err := srv.runtime.graph.ByParent()
if err != nil {
return fmt.Errorf("No such image: %s", name)
} else {
if err := srv.runtime.graph.Delete(img.Id); err != nil {
return fmt.Errorf("Error deleting image %s: %s", name, err.Error())
return err
}
for _, img := range byParents[id] {
if err := srv.deleteImageAndChildren(img.ID, imgs); err != nil {
if err != ErrImageReferenced {
return err
}
referenced = true
}
}
if referenced {
return ErrImageReferenced
}
// If the image is not referenced and has no children, remove it
byParents, err = srv.runtime.graph.ByParent()
if err != nil {
return err
}
if len(byParents[id]) == 0 {
if err := srv.runtime.repositories.DeleteAll(id); err != nil {
return err
}
err := srv.runtime.graph.Delete(id)
if err != nil {
return err
}
*imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)})
return nil
}
return nil
}
func (srv *Server) deleteImageParents(img *Image, imgs *[]APIRmi) error {
if img.Parent != "" {
parent, err := srv.runtime.graph.Get(img.Parent)
if err != nil {
return err
}
// Remove all children images
if err := srv.deleteImageAndChildren(img.Parent, imgs); err != nil {
return err
}
return srv.deleteImageParents(parent, imgs)
}
return nil
}
func (srv *Server) deleteImage(img *Image, repoName, tag string) (*[]APIRmi, error) {
//Untag the current image
var imgs []APIRmi
tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag)
if err != nil {
return nil, err
}
if tagDeleted {
imgs = append(imgs, APIRmi{Untagged: img.ShortID()})
}
if len(srv.runtime.repositories.ByID()[img.ID]) == 0 {
if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil {
if err != ErrImageReferenced {
return &imgs, err
}
} else if err := srv.deleteImageParents(img, &imgs); err != nil {
if err != ErrImageReferenced {
return &imgs, err
}
}
}
return &imgs, nil
}
func (srv *Server) ImageDelete(name string, autoPrune bool) (*[]APIRmi, error) {
img, err := srv.runtime.repositories.LookupImage(name)
if err != nil {
return nil, fmt.Errorf("No such image: %s", name)
}
if !autoPrune {
if err := srv.runtime.graph.Delete(img.ID); err != nil {
return nil, fmt.Errorf("Error deleting image %s: %s", name, err.Error())
}
return nil, nil
}
var tag string
if strings.Contains(name, ":") {
nameParts := strings.Split(name, ":")
name = nameParts[0]
tag = nameParts[1]
}
return srv.deleteImage(img, name, tag)
}
func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error) {
// Retrieve all images
@@ -733,7 +848,7 @@ func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error)
if _, exists := imageMap[img.Parent]; !exists {
imageMap[img.Parent] = make(map[string]struct{})
}
imageMap[img.Parent][img.Id] = struct{}{}
imageMap[img.Parent][img.ID] = struct{}{}
}
// Loop on the children of the given image and check the config
@@ -790,7 +905,6 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
if container == nil {
return fmt.Errorf("No such container: %s", name)
}
//logs
if logs {
if stdout {
@@ -816,6 +930,9 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
if container.State.Ghost {
return fmt.Errorf("Impossible to attach to a ghost container")
}
if !container.State.Running {
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
}
var (
cStdin io.ReadCloser
@@ -865,21 +982,23 @@ func (srv *Server) ImageInspect(name string) (*Image, error) {
return nil, fmt.Errorf("No such image: %s", name)
}
func NewServer(autoRestart bool) (*Server, error) {
func NewServer(autoRestart, enableCors bool, dns ListOpts) (*Server, error) {
if runtime.GOARCH != "amd64" {
log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
}
runtime, err := NewRuntime(autoRestart)
runtime, err := NewRuntime(autoRestart, dns)
if err != nil {
return nil, err
}
srv := &Server{
runtime: runtime,
runtime: runtime,
enableCors: enableCors,
}
runtime.srv = srv
return srv, nil
}
type Server struct {
runtime *Runtime
runtime *Runtime
enableCors bool
}

View File

@@ -4,6 +4,58 @@ import (
"testing"
)
func TestContainerTagImageDelete(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
if err := srv.runtime.repositories.Set("utest", "tag1", unitTestImageName, false); err != nil {
t.Fatal(err)
}
if err := srv.runtime.repositories.Set("utest/docker", "tag2", unitTestImageName, false); err != nil {
t.Fatal(err)
}
images, err := srv.Images(false, "")
if err != nil {
t.Fatal(err)
}
if len(images) != 3 {
t.Errorf("Excepted 3 images, %d found", len(images))
}
if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil {
t.Fatal(err)
}
images, err = srv.Images(false, "")
if err != nil {
t.Fatal(err)
}
if len(images) != 2 {
t.Errorf("Excepted 2 images, %d found", len(images))
}
if _, err := srv.ImageDelete("utest:tag1", true); err != nil {
t.Fatal(err)
}
images, err = srv.Images(false, "")
if err != nil {
t.Fatal(err)
}
if len(images) != 1 {
t.Errorf("Excepted 1 image, %d found", len(images))
}
}
func TestCreateRm(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
@@ -13,7 +65,7 @@ func TestCreateRm(t *testing.T) {
srv := &Server{runtime: runtime}
config, _, err := ParseRun([]string{GetTestImage(runtime).Id, "echo test"}, nil)
config, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
@@ -46,7 +98,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
srv := &Server{runtime: runtime}
config, _, err := ParseRun([]string{GetTestImage(runtime).Id, "/bin/cat"}, nil)
config, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
if err != nil {
t.Fatal(err)
}
@@ -95,3 +147,25 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
}
}
func TestRunWithTooLowMemoryLimit(t *testing.T) {
runtime, err := newTestRuntime()
srv := &Server{runtime: runtime}
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
_, err = srv.ContainerCreate(
&Config{
Image: GetTestImage(runtime).ID,
Memory: 524287,
CpuShares: 1000,
Cmd: []string{"/bin/cat"},
},
)
if err == nil {
t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!")
}
}

84
tags.go
View File

@@ -11,7 +11,7 @@ import (
"strings"
)
const DEFAULT_TAG = "latest"
const DEFAULTTAG = "latest"
type TagStore struct {
path string
@@ -72,7 +72,7 @@ func (store *TagStore) LookupImage(name string) (*Image, error) {
// (so we can pass all errors here)
repoAndTag := strings.SplitN(name, ":", 2)
if len(repoAndTag) == 1 {
repoAndTag = append(repoAndTag, DEFAULT_TAG)
repoAndTag = append(repoAndTag, DEFAULTTAG)
}
if i, err := store.GetImage(repoAndTag[0], repoAndTag[1]); err != nil {
return nil, err
@@ -87,27 +87,73 @@ func (store *TagStore) LookupImage(name string) (*Image, error) {
// Return a reverse-lookup table of all the names which refer to each image
// Eg. {"43b5f19b10584": {"base:latest", "base:v1"}}
func (store *TagStore) ById() map[string][]string {
byId := make(map[string][]string)
func (store *TagStore) ByID() map[string][]string {
byID := make(map[string][]string)
for repoName, repository := range store.Repositories {
for tag, id := range repository {
name := repoName + ":" + tag
if _, exists := byId[id]; !exists {
byId[id] = []string{name}
if _, exists := byID[id]; !exists {
byID[id] = []string{name}
} else {
byId[id] = append(byId[id], name)
sort.Strings(byId[id])
byID[id] = append(byID[id], name)
sort.Strings(byID[id])
}
}
}
return byId
return byID
}
func (store *TagStore) ImageName(id string) string {
if names, exists := store.ById()[id]; exists && len(names) > 0 {
if names, exists := store.ByID()[id]; exists && len(names) > 0 {
return names[0]
}
return utils.TruncateId(id)
return utils.TruncateID(id)
}
func (store *TagStore) DeleteAll(id string) error {
names, exists := store.ByID()[id]
if !exists || len(names) == 0 {
return nil
}
for _, name := range names {
if strings.Contains(name, ":") {
nameParts := strings.Split(name, ":")
if _, err := store.Delete(nameParts[0], nameParts[1]); err != nil {
return err
}
} else {
if _, err := store.Delete(name, ""); err != nil {
return err
}
}
}
return nil
}
func (store *TagStore) Delete(repoName, tag string) (bool, error) {
deleted := false
if err := store.Reload(); err != nil {
return false, err
}
if r, exists := store.Repositories[repoName]; exists {
if tag != "" {
if _, exists2 := r[tag]; exists2 {
delete(r, tag)
if len(r) == 0 {
delete(store.Repositories, repoName)
}
deleted = true
} else {
return false, fmt.Errorf("No such tag: %s:%s", repoName, tag)
}
} else {
delete(store.Repositories, repoName)
deleted = true
}
} else {
fmt.Errorf("No such repository: %s", repoName)
}
return deleted, store.Save()
}
func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
@@ -116,7 +162,7 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
return err
}
if tag == "" {
tag = DEFAULT_TAG
tag = DEFAULTTAG
}
if err := validateRepoName(repoName); err != nil {
return err
@@ -133,11 +179,11 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
} else {
repo = make(map[string]string)
if old, exists := store.Repositories[repoName]; exists && !force {
return fmt.Errorf("Tag %s:%s is already set to %s", repoName, tag, old)
return fmt.Errorf("Conflict: Tag %s:%s is already set to %s", repoName, tag, old)
}
store.Repositories[repoName] = repo
}
repo[tag] = img.Id
repo[tag] = img.ID
return store.Save()
}
@@ -151,14 +197,20 @@ func (store *TagStore) Get(repoName string) (Repository, error) {
return nil, nil
}
func (store *TagStore) GetImage(repoName, tag string) (*Image, error) {
func (store *TagStore) GetImage(repoName, tagOrId string) (*Image, error) {
repo, err := store.Get(repoName)
if err != nil {
return nil, err
} else if repo == nil {
return nil, nil
}
if revision, exists := repo[tag]; exists {
//go through all the tags, to see if tag is in fact an ID
for _, revision := range repo {
if strings.HasPrefix(revision, tagOrId) {
return store.graph.Get(revision)
}
}
if revision, exists := repo[tagOrId]; exists {
return store.graph.Get(revision)
}
return nil, nil

49
tags_test.go Normal file
View File

@@ -0,0 +1,49 @@
package docker
import (
"testing"
)
func TestLookupImage(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil {
t.Fatal(err)
} else if img == nil {
t.Errorf("Expected 1 image, none found")
}
if img, err := runtime.repositories.LookupImage(unitTestImageName + ":" + DEFAULTTAG); err != nil {
t.Fatal(err)
} else if img == nil {
t.Errorf("Expected 1 image, none found")
}
if img, err := runtime.repositories.LookupImage(unitTestImageName + ":" + "fail"); err == nil {
t.Errorf("Expected error, none found")
} else if img != nil {
t.Errorf("Expected 0 image, 1 found")
}
if img, err := runtime.repositories.LookupImage("fail:fail"); err == nil {
t.Errorf("Expected error, none found")
} else if img != nil {
t.Errorf("Expected 0 image, 1 found")
}
if img, err := runtime.repositories.LookupImage(unitTestImageId); err != nil {
t.Fatal(err)
} else if img == nil {
t.Errorf("Expected 1 image, none found")
}
if img, err := runtime.repositories.LookupImage(unitTestImageName + ":" + unitTestImageId); err != nil {
t.Fatal(err)
} else if img == nil {
t.Errorf("Expected 1 image, none found")
}
}

2
term/MAINTAINERS Normal file
View File

@@ -0,0 +1,2 @@
Guillaume Charmes <guillaume@dotcloud.com>
Solomon Hykes <solomon@dotcloud.com>

View File

@@ -7,104 +7,6 @@ import (
"unsafe"
)
type Termios struct {
Iflag uintptr
Oflag uintptr
Cflag uintptr
Lflag uintptr
Cc [20]byte
Ispeed uintptr
Ospeed uintptr
}
const (
// Input flags
inpck = 0x010
istrip = 0x020
icrnl = 0x100
ixon = 0x200
// Output flags
opost = 0x1
// Control flags
cs8 = 0x300
// Local flags
icanon = 0x100
iexten = 0x400
)
const (
HUPCL = 0x4000
ICANON = 0x100
ICRNL = 0x100
IEXTEN = 0x400
BRKINT = 0x2
CFLUSH = 0xf
CLOCAL = 0x8000
CREAD = 0x800
CS5 = 0x0
CS6 = 0x100
CS7 = 0x200
CS8 = 0x300
CSIZE = 0x300
CSTART = 0x11
CSTATUS = 0x14
CSTOP = 0x13
CSTOPB = 0x400
CSUSP = 0x1a
IGNBRK = 0x1
IGNCR = 0x80
IGNPAR = 0x4
IMAXBEL = 0x2000
INLCR = 0x40
INPCK = 0x10
ISIG = 0x80
ISTRIP = 0x20
IUTF8 = 0x4000
IXANY = 0x800
IXOFF = 0x400
IXON = 0x200
NOFLSH = 0x80000000
OCRNL = 0x10
OFDEL = 0x20000
OFILL = 0x80
ONLCR = 0x2
ONLRET = 0x40
ONOCR = 0x20
ONOEOT = 0x8
OPOST = 0x1
RENB = 0x1000
PARMRK = 0x8
PARODD = 0x2000
TOSTOP = 0x400000
VDISCARD = 0xf
VDSUSP = 0xb
VEOF = 0x0
VEOL = 0x1
VEOL2 = 0x2
VERASE = 0x3
VINTR = 0x8
VKILL = 0x5
VLNEXT = 0xe
VMIN = 0x10
VQUIT = 0x9
VREPRINT = 0x6
VSTART = 0xc
VSTATUS = 0x12
VSTOP = 0xd
VSUSP = 0xa
VT0 = 0x0
VT1 = 0x10000
VTDLY = 0x10000
VTIME = 0x11
ECHO = 0x00000008
PENDIN = 0x20000000
)
type State struct {
termios Termios
}
@@ -128,21 +30,21 @@ func SetWinsize(fd uintptr, ws *Winsize) error {
}
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool {
func IsTerminal(fd uintptr) bool {
var termios Termios
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)))
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&termios)))
return err == 0
}
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(fd int, state *State) error {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)))
func Restore(fd uintptr, state *State) error {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)))
return err
}
func SetRawTerminal() (*State, error) {
oldState, err := MakeRaw(int(os.Stdin.Fd()))
oldState, err := MakeRaw(os.Stdin.Fd())
if err != nil {
return nil, err
}
@@ -150,12 +52,12 @@ func SetRawTerminal() (*State, error) {
signal.Notify(c, os.Interrupt)
go func() {
_ = <-c
Restore(int(os.Stdin.Fd()), oldState)
Restore(os.Stdin.Fd(), oldState)
os.Exit(0)
}()
return oldState, err
}
func RestoreTerminal(state *State) {
Restore(int(os.Stdin.Fd()), state)
Restore(os.Stdin.Fd(), state)
}

View File

@@ -8,23 +8,45 @@ import (
const (
getTermios = syscall.TIOCGETA
setTermios = syscall.TIOCSETA
ECHO = 0x00000008
ONLCR = 0x2
ISTRIP = 0x20
INLCR = 0x40
ISIG = 0x80
IGNCR = 0x80
ICANON = 0x100
ICRNL = 0x100
IXOFF = 0x400
IXON = 0x200
)
type Termios struct {
Iflag uint64
Oflag uint64
Cflag uint64
Lflag uint64
Cc [20]byte
Ispeed uint64
Ospeed uint64
}
// MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd int) (*State, error) {
func MakeRaw(fd uintptr) (*State, error) {
var oldState State
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
return nil, err
}
newState := oldState.termios
newState.Iflag &^= ISTRIP | INLCR | IGNCR | IXON | IXOFF
newState.Iflag &^= (ISTRIP | INLCR | IGNCR | IXON | IXOFF)
newState.Iflag |= ICRNL
newState.Oflag |= ONLCR
newState.Lflag &^= ECHO | ICANON | ISIG
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
newState.Lflag &^= (ECHO | ICANON | ISIG)
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 {
return nil, err
}

View File

@@ -5,54 +5,40 @@ import (
"unsafe"
)
// #include <termios.h>
// #include <sys/ioctl.h>
/*
void MakeRaw(int fd) {
struct termios t;
// FIXME: Handle errors?
ioctl(fd, TCGETS, &t);
t.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
t.c_oflag &= ~OPOST;
t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
t.c_cflag &= ~(CSIZE | PARENB);
t.c_cflag |= CS8;
ioctl(fd, TCSETS, &t);
}
*/
import "C"
const (
getTermios = syscall.TCGETS
setTermios = syscall.TCSETS
)
type Termios struct {
Iflag uint32
Oflag uint32
Cflag uint32
Lflag uint32
Cc [20]byte
Ispeed uint32
Ospeed uint32
}
// MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd int) (*State, error) {
func MakeRaw(fd uintptr) (*State, error) {
var oldState State
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TCGETS, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
return nil, err
}
newState := oldState.termios
newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON)
newState.Oflag &^= syscall.OPOST
newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN)
newState.Cflag &^= (syscall.CSIZE | syscall.PARENB)
newState.Cflag |= syscall.CS8
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
return nil, err
}
C.MakeRaw(C.int(fd))
return &oldState, nil
// FIXME: post on goland issues this: very same as the C function bug non-working
// newState := oldState.termios
// newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
// newState.Oflag &^= OPOST
// newState.Lflag &^= (ECHO | syscall.ECHONL | ICANON | ISIG | IEXTEN)
// newState.Cflag &^= (CSIZE | syscall.PARENB)
// newState.Cflag |= CS8
// if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TCSETS, uintptr(unsafe.Pointer(&newState))); err != 0 {
// return nil, err
// }
// return &oldState, nil
}

2
testing/Vagrantfile vendored
View File

@@ -30,7 +30,7 @@ Vagrant::Config.run do |config|
# Install docker dependencies
pkg_cmd << "apt-get install -q -y python-software-properties; " \
"add-apt-repository -y ppa:gophers/go/ubuntu; apt-get update -qq; " \
"DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc bsdtar git golang-stable make; "
"DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc bsdtar git golang-stable aufs-tools make; "
# Activate new kernel
pkg_cmd << "shutdown -r +1; "
config.vm.provision :shell, :inline => pkg_cmd

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"index/suffixarray"
@@ -33,7 +34,7 @@ func Go(f func() error) chan error {
// Request a given URL and return an io.Reader
func Download(url string, stderr io.Writer) (*http.Response, error) {
var resp *http.Response
var err error = nil
var err error
if resp, err = http.Get(url); err != nil {
return nil, err
}
@@ -69,7 +70,7 @@ type progressReader struct {
readProgress int // How much has been read so far (bytes)
lastUpdate int // How many bytes read at least update
template string // Template to print. Default "%v/%v (%v)"
json bool
sf *StreamFormatter
}
func (r *progressReader) Read(p []byte) (n int, err error) {
@@ -93,7 +94,7 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
}
// Send newline when complete
if err != nil {
fmt.Fprintf(r.output, FormatStatus("", r.json))
r.output.Write(r.sf.FormatStatus(""))
}
return read, err
@@ -101,11 +102,12 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
func (r *progressReader) Close() error {
return io.ReadCloser(r.reader).Close()
}
func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string, json bool) *progressReader {
if template == "" {
template = "%v/%v (%v)\r"
func ProgressReader(r io.ReadCloser, size int, output io.Writer, template []byte, sf *StreamFormatter) *progressReader {
tpl := string(template)
if tpl == "" {
tpl = string(sf.FormatProgress("", "%v/%v (%v)"))
}
return &progressReader{r, NewWriteFlusher(output), size, 0, 0, template, json}
return &progressReader{r, NewWriteFlusher(output), size, 0, 0, tpl, sf}
}
// HumanDuration returns a human-readable approximation of a duration
@@ -133,6 +135,20 @@ func HumanDuration(d time.Duration) string {
return fmt.Sprintf("%d years", d.Hours()/24/365)
}
// HumanSize returns a human-readable approximation of a size
// using SI standard (eg. "44kB", "17MB")
func HumanSize(size int64) string {
i := 0
var sizef float64
sizef = float64(size)
units := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
for sizef >= 1000.0 {
sizef = sizef / 1000.0
i++
}
return fmt.Sprintf("%.4g %s", sizef, units[i])
}
func Trunc(s string, maxlen int) string {
if len(s) <= maxlen {
return s
@@ -347,11 +363,11 @@ func (idx *TruncIndex) Get(s string) (string, error) {
return string(idx.bytes[before:after]), err
}
// TruncateId returns a shorthand version of a string identifier for convenience.
// TruncateID returns a shorthand version of a string identifier for convenience.
// A collision with other shorthands is very unlikely, but possible.
// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
// will need to use a langer prefix, or the full-length Id.
func TruncateId(id string) string {
func TruncateID(id string) string {
shortLen := 12
if len(id) < shortLen {
shortLen = len(id)
@@ -532,6 +548,7 @@ func GetKernelVersion() (*KernelVersionInfo, error) {
}, nil
}
// FIXME: this is deprecated by CopyWithTar in archive.go
func CopyDirectory(source, dest string) error {
if output, err := exec.Command("cp", "-ra", source, dest).CombinedOutput(); err != nil {
return fmt.Errorf("Error copy: %s (%s)", err, output)
@@ -564,16 +581,74 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher {
return &WriteFlusher{w: w, flusher: flusher}
}
func FormatStatus(str string, json bool) string {
if json {
return "{\"status\" : \"" + str + "\"}"
}
return str + "\r\n"
type JSONMessage struct {
Status string `json:"status,omitempty"`
Progress string `json:"progress,omitempty"`
Error string `json:"error,omitempty"`
}
func FormatProgress(str string, json bool) string {
if json {
return "{\"progress\" : \"" + str + "\"}"
}
return "Downloading " + str + "\r"
type StreamFormatter struct {
json bool
used bool
}
func NewStreamFormatter(json bool) *StreamFormatter {
return &StreamFormatter{json, false}
}
func (sf *StreamFormatter) FormatStatus(format string, a ...interface{}) []byte {
sf.used = true
str := fmt.Sprintf(format, a...)
if sf.json {
b, err := json.Marshal(&JSONMessage{Status: str})
if err != nil {
return sf.FormatError(err)
}
return b
}
return []byte(str + "\r\n")
}
func (sf *StreamFormatter) FormatError(err error) []byte {
sf.used = true
if sf.json {
if b, err := json.Marshal(&JSONMessage{Error: err.Error()}); err == nil {
return b
}
return []byte("{\"error\":\"format error\"}")
}
return []byte("Error: " + err.Error() + "\r\n")
}
func (sf *StreamFormatter) FormatProgress(action, str string) []byte {
sf.used = true
if sf.json {
b, err := json.Marshal(&JSONMessage{Status: action, Progress: str})
if err != nil {
return nil
}
return b
}
return []byte(action + " " + str + "\r")
}
func (sf *StreamFormatter) Used() bool {
return sf.used
}
func CheckLocalDns() bool {
resolv, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
Debugf("Error openning resolv.conf: %s", err)
return false
}
for _, ip := range []string{
"127.0.0.1",
"127.0.1.1",
} {
if strings.Contains(string(resolv), ip) {
return true
}
}
return false
}

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