Compare commits

...

366 Commits

Author SHA1 Message Date
Victor Vieux
51f6c4a737 Merge pull request #1227 from dotcloud/bump_0.5.0
Bump to 0.5.0
2013-07-18 11:51:29 -07:00
Andy Rothfusz
f4eaec3e1e Merge pull request #1226 from metalivedev/easydockerfile
Make dockerfile docs easier to find. Clean up formatting.
2013-07-18 10:14:22 -07:00
Victor Vieux
b083418257 change -b -> -v and add udp example 2013-07-18 16:25:14 +00:00
Victor Vieux
5794857f7a Merge pull request #1169 from crosbymichael/buildfile-tests
Add unit tests for buildfile config instructions
2013-07-18 08:35:23 -07:00
Michael Crosby
e7f3f6fa5a Add unit tests for buildfile config instructions
Add tests for instructions in the buildfile that
modify the config of the resulting image.
2013-07-18 05:37:28 -09:00
Andy Rothfusz
aa5671411b Make dockerfile docs easier to find. Clean up formatting. 2013-07-17 18:56:40 -07:00
Victor Vieux
f8dfd0aa5e Merge pull request #1225 from dotcloud/hotfix_docker_rmi
*Runtime: improve docker rmi via id
2013-07-17 14:31:56 -07:00
Andy Rothfusz
3dbf9c6560 Merge pull request #1219 from metalivedev/docs-repoupdate
Update docs with 0.5 repository information.
2013-07-17 13:53:12 -07:00
Guillaume J. Charmes
de563a3ea3 Merge pull request #1194 from crosbymichael/build-verbose
* Builder: Add verbose output to docker build
2013-07-17 12:53:06 -07:00
Victor Vieux
9cf2b41c05 change rm usage in docs 2013-07-17 19:24:54 +00:00
Victor Vieux
f310b875f8 Merge branch 'master' of https://github.com/kencochrane/docker into kencochrane-master 2013-07-17 19:23:06 +00:00
Solomon Hykes
ac14c463d5 Changed date on changelog 2013-07-17 11:51:26 -07:00
Guillaume J. Charmes
578e888915 Merge pull request #1212 from dotcloud/merge_v_b_options
* Runtime: Merge -b and -v options
2013-07-17 11:43:47 -07:00
Victor Vieux
5231bf3653 Merge pull request #1222 from lopter/master
Always stop the opposite goroutine in network_proxy.go (closes #1213)
2013-07-17 11:40:33 -07:00
Solomon Hykes
8af945f353 Small changes in changelog wording 2013-07-17 11:39:38 -07:00
Ken Cochrane
d0e8ca1257 updated with notes from @vieux 2013-07-17 13:46:11 -04:00
Victor Vieux
5a934fc923 fix docker rmi via id 2013-07-17 15:48:53 +00:00
Louis Opter
c766d064ac Always stop the opposite goroutine in network_proxy.go (closes #1213) 2013-07-17 01:05:11 -07:00
Andy Rothfusz
0356081c0a Update repository information. 2013-07-16 17:04:41 -07:00
Guillaume J. Charmes
18e91d5f85 Update docs 2013-07-16 10:14:21 -07:00
Guillaume J. Charmes
1004d57b85 Hotfix: make sure ./utils tests pass 2013-07-15 17:58:23 -07:00
Nick Stinemates
f9e4ef5eb0 Merge pull request #1210 from dotcloud/improve_configmerge
improve mergeconfig, ...
2013-07-15 18:04:12 -07:00
Guillaume J. Charmes
eefbadd230 Merge -b and -v options 2013-07-15 17:51:32 -07:00
Guillaume J. Charmes
bc21b3ebf0 Bump version to 0.5.0 2013-07-15 14:57:52 -07:00
Solomon Hykes
608fb2a21e Merge pull request #1184 from dotcloud/1176-packaging-release
Hack: document PPA release step
2013-07-15 13:59:55 -07:00
Solomon Hykes
45050d9887 Merge pull request #1188 from dotcloud/1174-packaging-binary
Packaging: add pure binary to docker release
2013-07-15 13:59:06 -07:00
Daniel Mizyrycki
75a0052e64 packaging, issue #1176: Document PPA release step 2013-07-15 12:13:51 -07:00
Guillaume J. Charmes
c8efd08384 Merge pull request #1208 from crosbymichael/1201-rw-volumes-from
- Volumes: Copy VolumesRW values when using --volumes-from
2013-07-15 10:59:51 -07:00
Guillaume J. Charmes
454cd147fb Merge pull request #1096 from dotcloud/remove_os_user
* Runtime: Remove the os.user dependency and manually lookup /etc/passwd instead
2013-07-15 10:19:09 -07:00
Guillaume J. Charmes
e41507bde2 Add unit test to check wrong uid case 2013-07-15 10:05:09 -07:00
Victor Vieux
193a7e1dc1 improve mergeconfig, if dns, portspec, env or volumes specify in docker run, apend and not replace 2013-07-15 13:12:33 +00:00
Michael Crosby
5ae8c7a985 Copy VolumesRW values when using --volumes-from
Fixes #1201
2013-07-14 18:23:20 -09:00
Victor Vieux
9b57f9187b Merge pull request #1200 from ToothlessGear/fix-whitespaces_progessbar
Fix progressbar, without messing up other outputs
2013-07-13 08:50:50 -07:00
Victor Vieux
50e45b485f Merge pull request #1190 from dotcloud/1189-add_debug_error
* RemoteAPI: Improve debug
2013-07-13 08:15:59 -07:00
Victor Vieux
2051ebc0eb Merge pull request #1198 from dotcloud/fix_pull_tag
Fixed tag option for "docker pull" (the option was ignored)
2013-07-13 08:14:47 -07:00
Guillaume J. Charmes
933b9d44e1 Merge pull request #1054 from nickstenning/getimage-by-tag
* Runtime: Reverse priority of tag lookup in TagStore.GetImage
2013-07-12 16:15:04 -07:00
Nick Stenning
44b3e8d51b Reverse priority of tag lookup in TagStore.GetImage
Currently, if you have the following images:

    foo/bar      1       23b27d50fb49
    foo/bar      2       f2b86ec3fcc4

And you issue the following command:

    docker tag foo/bar:2 foo/bar latest

docker will tag the "wrong" image, because the image id for foo/bar:1 starts
with a "2". That is, you'll end up with the following:

    foo/bar      1       23b27d50fb49
    foo/bar      2       f2b86ec3fcc4
    foo/bar      latest  23b27d50fb49

This commit reverses the priority given to tags vs. image ids in the
construction `<user>/<repo>:<tagOrId>`, meaning that if a tag that is an exact
match for the specified `tagOrId`, it will be tagged in preference to an image
with an id that happens to start with the correct character sequence.
2013-07-12 23:56:36 +01:00
Daniel Mizyrycki
9bf8ad741f Merge pull request #1083 from hukeli/debian
Keep debian package up-to-date
2013-07-12 15:24:37 -07:00
Daniel Mizyrycki
9913ebbe21 Merge pull request #1203 from dotcloud/1202-packaging-debian
Packaging, issue #1202: Upgrade vagrantfile go in debian packaging
2013-07-12 15:11:57 -07:00
Daniel Mizyrycki
c7a48e91d8 Packaging, issue #1202: Upgrade vagrantfile go in debian packaging 2013-07-12 15:06:12 -07:00
Solomon Hykes
2cbf2200ac Merge pull request #1195 from dotcloud/tests-cleanup
* Hack: tests cleanup
2013-07-12 14:51:59 -07:00
Marcus Farkas
bac5772312 *Client: Fix the progressbar, without manipulating other outputs
Prior this commit, 'docker images' and other cmd's, which used utils.HumanSize(),
showed unnecessary whitespaces.
Formatting of progress has been moved to FormatProgess(), justifing the string
directly in the template.
2013-07-12 20:15:25 +02:00
Marcus Farkas
a6e5a397bd Revert "Client: better progressbar output"
This reverts commit 3ac68f1966.
2013-07-12 20:08:45 +02:00
Ken Cochrane
364f48d6c7 updated the rmi command docs, the had typos 2013-07-12 14:05:26 -04:00
Ken Cochrane
4174e7aa7a updated the help commands on a few commands that were not correct 2013-07-12 13:55:26 -04:00
Guillaume J. Charmes
eb38750d99 Remove the os.user dependency and manually lookup /etc/passwd instead 2013-07-12 10:49:47 -07:00
Sam Alba
cd0fef633c Fixed tag option for "docker pull" (the option was ignored) 2013-07-12 10:42:54 -07:00
Michael Crosby
d0c73c28df Add param to api docs for verbose build output 2013-07-12 06:22:56 -09:00
Victor Vieux
8e6c249e48 Merge pull request #1197 from crosbymichael/buildfile-doc-ordering
Fix Docker Builder documentation section numbers
2013-07-12 05:27:47 -07:00
Victor Vieux
752f99e8a1 Merge pull request #977 from dotcloud/966-improve_docker_login_parameters-feature
* Client: Add options to docker login to be able to use it via script
2013-07-12 05:07:25 -07:00
Victor Vieux
a909223ee2 Merge pull request #1102 from dotcloud/1098-store_hostconfig_tmp
* Runtime: bind mounts are now preserved upon container restart
2013-07-12 05:04:10 -07:00
Victor Vieux
8ff271fc74 Merge pull request #1192 from dotcloud/docker_port-fix
hotfix: fix broken docker port
2013-07-12 04:57:53 -07:00
Victor Vieux
9dfac1dd65 Merge pull request #1055 from dotcloud/list_container_processes-feature
* RemoteApi: /top to list running processes in a container
* Client: docker top to list running processes in a container
2013-07-12 04:56:12 -07:00
Victor Vieux
a8a6848ce0 fix tests regarding the new test image 2013-07-12 11:54:53 +00:00
Victor Vieux
9232d1ef62 Merge branch 'master' into list_container_processes-feature 2013-07-12 11:47:27 +00:00
Michael Crosby
90483dc912 Fix Docker Builder documentation numbering 2013-07-11 16:41:19 -09:00
Solomon Hykes
6bdb6f226b Simplify unit tests code with mkRuntime() 2013-07-11 17:59:25 -07:00
Solomon Hykes
2ac1141980 Don't leave broken, commented out tests lying around. 2013-07-11 17:58:45 -07:00
Michael Crosby
1104d443cc Revert changes from PR 1030
With streaming output of the build
changes in 1030 are no longer required.
2013-07-11 15:52:08 -09:00
Michael Crosby
49044a9608 Fix buildfile tests after rebase 2013-07-11 15:37:26 -09:00
Guillaume J. Charmes
71d2ff4946 Hotfix: check the length of entrypoint before comparing. 2013-07-11 17:31:07 -07:00
Michael Crosby
474191dd7b Add verbose output to docker build
Verbose output is enabled by default and
the flag -q can be used to suppress the verbose output.
2013-07-11 15:27:33 -09:00
Guillaume J. Charmes
637eceb6a7 Merge pull request #1124 from crosbymichael/buildfile-volumes
+ Builder: Add VOLUME instruction to buildfile
2013-07-11 17:16:57 -07:00
Victor Vieux
976428f505 change output 2013-07-11 21:04:23 +02:00
Victor Vieux
affe7caf78 fix broken docker port 2013-07-11 19:28:15 +02:00
Victor Vieux
b7937e268f add debug for error in the server 2013-07-11 12:21:43 +00:00
Louis Opter
5a411fa38e Make the TestAllocate{UDP,TCP}PortLocalhost more reliable
- For the TCP test try again if socat wasn't listening yet;
- For the UDP test raise the timeout to a minute to workaround what
  seems to be an issue with Linux.
2013-07-10 18:25:53 -07:00
Daniel Mizyrycki
bf26ae03cf Packaging, issue #1174: Add pure binary to docker release 2013-07-10 17:39:00 -07:00
Andy Rothfusz
3363cd5cd0 Merge pull request #1178 from dotcloud/fix-dev-environment
Fix outdated docs explaining how to setup a dev environment
2013-07-10 16:53:22 -07:00
Daniel Mizyrycki
5c49a61353 Merge pull request #1183 from dotcloud/960-packaging-PPA
Packaging, issue #960: Document PUBLISH_PPA for staging/production release
2013-07-10 16:16:31 -07:00
Daniel Mizyrycki
f83c31e188 Packaging, issue #960: Document PUBLISH_PPA for staging/production release 2013-07-10 16:06:49 -07:00
Louis Opter
8f36467107 Raise the timeouts for the TCP/UDP localhost proxy tests
Sometimes these tests fail, let's see if that improves the situation.
2013-07-10 16:05:14 -07:00
Louis Opter
8e49cb453f Merge pull request #1181 from dotcloud/export_portmapping
Export PortMapping in container.go
2013-07-10 14:24:20 -07:00
Michael Crosby
40f1e4edbe Rebased changes buildfile_test 2013-07-10 07:12:57 -09:00
Michael Crosby
1267e15b0f Add unittest for volume config verification 2013-07-10 06:59:16 -09:00
Michael Crosby
eb9fef2c42 Add VOLUME instruction to buildfile 2013-07-10 06:59:16 -09:00
Victor Vieux
43b346d93b Merge pull request #1151 from alex/patch-1
Replaced gendered language in the README
2013-07-10 07:52:30 -07:00
Victor Vieux
d918c7d9de export portmapping in network.go 2013-07-10 14:09:35 +00:00
Victor Vieux
e962e9edcf Merge pull request #1168 from dotcloud/standalone_registry
* Server: Allow push on standalone registry
2013-07-10 04:14:23 -07:00
Victor Vieux
b7a62f1f1b Merge pull request #1177 from lopter/udp-support-final
* Network: Add UDP support
2013-07-10 03:55:18 -07:00
Victor Vieux
2e5d1a2d48 Merge pull request #1164 from dotcloud/1162-import_hangs-fix
* Runtime: Untar is now faster
2013-07-10 03:37:24 -07:00
Louis Opter
fac0d87d00 Add support for UDP (closes #33)
API Changes
-----------

The port notation is extended to support "/udp" or "/tcp" at the *end*
of the specifier string (and defaults to tcp if "/tcp" or "/udp" are
missing)

`docker ps` now shows UDP ports as "frontend->backend/udp". Nothing
changes for TCP ports.

`docker inspect` now displays two sub-dictionaries: "Tcp" and "Udp",
under "PortMapping" in "NetworkSettings".

Theses changes stand true for the values returned by the HTTP API too.

This changeset will definitely break tools built upon the API (or upon
`docker inspect`). A less intrusive way to add UDP ports in `docker
inspect` would be to simply add "/udp" for UDP ports but it will still
break existing applications which tries to convert the whole field to an
integer. I believe that having two TCP/UDP sub-dictionaries is better
because it makes the whole thing more clear and more easy to parse right
away (i.e: you don't have to check the format of the string, split it
and convert the right part to an integer)

Code Changes
------------

Significant changes in network.go:

- A second PortAllocator is instantiated for the UDP range;
- PortMapper maintains separate mapping for TCP and UDP;
- The extPorts array in NetworkInterface is now an array of Nat objects
  (so we can know on which protocol a given port was mapped when
  NetworkInterface.Release() is called);
- TCP proxying on localhost has been moved away in network_proxy.go.

localhost proxy code rewrite in network_proxy.go:

We have to proxy the traffic between localhost:frontend-port and
container:backend-port because Netfilter doesn't work properly on the
loopback interface and DNAT iptable rules aren't applied there.

- Goroutines in the TCP proxying code are now explicitly stopped when
  the proxy is stopped;
- UDP connection tracking using a map (more infos in [1]);
- Support for IPv6 (to be more accurate, the code is transparent to the
  Go net package, so you can use, tcp/tcp4/tcp6/udp/udp4/udp6);
- Single Proxy interface for both UDP and TCP proxying;
- Full test suite.

[1] https://github.com/dotcloud/docker/issues/33#issuecomment-20010400
2013-07-09 17:42:35 -07:00
Solomon Hykes
a839b36e55 Fix outdated docs explaining how to setup a dev environment. Building docker with docker ftw 2013-07-09 16:48:16 -07:00
Sam Alba
316c8328aa Hardened repos name validation 2013-07-09 16:46:55 -07:00
Sam Alba
e8db031112 Fixed tag parsing when the repos name contains both a port and a tag 2013-07-09 16:46:25 -07:00
Sam Alba
59b785a282 Fixing missing tag field when pulling containers which does not exist 2013-07-09 16:45:32 -07:00
Louis Opter
1a1daca621 Fix a typo in runtime_test.go: Availalble -> Available 2013-07-09 11:52:33 -07:00
Sam Alba
837be914ca Merge branch 'master' of github.com:dotcloud/docker into standalone_registry 2013-07-09 11:31:14 -07:00
Sam Alba
f44eac49fa Fixed potential security issue (never try http on official index when polling the endpoint). Also fixed local repos name when pulling index.docker.io/foo/bar 2013-07-09 11:30:12 -07:00
Guillaume J. Charmes
0acdef4549 Merge pull request #1166 from dotcloud/networkless_tests-2
* Tests: Remove all network dependencies from the test suite
2013-07-09 11:20:18 -07:00
Guillaume J. Charmes
7d8ef90ccb Merge pull request #1173 from dotcloud/1172-ghost_restart-fix
Make sure container is not marked as ghost when it starts
2013-07-09 10:49:17 -07:00
Guillaume J. Charmes
91520838fc Make sure container is not marked as ghost when it starts 2013-07-09 10:48:33 -07:00
Guillaume J. Charmes
ada0e1fb08 Merge pull request #1049 from dotcloud/1040_ignore_stderr_tests-fix
- Tests: Ignore stderr while doing tests
2013-07-09 10:32:24 -07:00
Sam Alba
33d97e81eb Removed DOCKER_INDEX_URL 2013-07-09 08:10:43 -07:00
Sam Alba
019324015b Moved parseRepositoryTag to the utils package 2013-07-09 08:06:10 -07:00
Victor Vieux
72d278fdac Merge pull request #1170 from dotcloud/fix_type_socket
Fix typo socket
2013-07-09 03:57:45 -07:00
Victor Vieux
05d7f85af9 fix typo 2013-07-09 10:55:28 +00:00
Solomon Hykes
7fba358ae2 Merge pull request #1013 from dotcloud/standardize-build
* Hack: standardized docker's build environment in a Dockerfile
2013-07-08 21:33:45 -07:00
Solomon Hykes
9f1fc40a64 * Hack: standardized docker's build environment in a Dockerfile 2013-07-08 21:30:29 -07:00
Sam Alba
3be7bc38e0 Fixed typo (thanks unit tests) 2013-07-08 17:42:18 -07:00
Sam Alba
31c66d5a00 Re-implemented a notion of local and private repos. This allows to consider the full qualified name of the repos as the name for the local repository without breaking the calls to the Registry API. 2013-07-08 17:26:50 -07:00
Sam Alba
e7d36c9590 It is now possible to include a ":" in a local repository name (it will not be the case for a remote name). This adds support for full qualified repository name in order to support private registry server 2013-07-08 17:22:41 -07:00
Sam Alba
3e8626c4a1 Changed the tag parsing to it will work even if there is a port in the repos registry url (full qualified name for pushing on a standalone registry) 2013-07-08 17:20:41 -07:00
Guillaume J. Charmes
e14dd4d33e Merge pull request #1157 from kstaken/1156-entrypoint-builder
Builder: Fix #1156 entrypoint override from base image
2013-07-08 16:57:26 -07:00
Kimbro Staken
87a69e6753 Merge branch '1156-entrypoint-builder' of github.com:kstaken/docker into 1156-entrypoint-builder 2013-07-08 16:06:09 -07:00
Kimbro Staken
f64dbdbe3a Override Entrypoint picked up from the base image that breaks run commands in builder 2013-07-08 16:04:39 -07:00
Kimbro Staken
2b5553144a Removing the save to disk as it was not really necessary 2013-07-08 16:03:18 -07:00
Guillaume J. Charmes
e43ef364cb Remove all network dependencies from the test suite 2013-07-08 15:23:04 -07:00
Guillaume J. Charmes
08a87d4b3b Fix #1162 - Remove bufio from Untar 2013-07-08 13:42:17 -07:00
Victor Vieux
90f372af5c Merge pull request #1163 from dotcloud/1137-change_search_size-feature
* Client : uses the terminal size to display search output, add -notrunc
2013-07-08 11:47:25 -07:00
Victor Vieux
3ec29eb5da Merge pull request #1066 from mhennings/fix-broken-streaming-result
* Server: Fix streaming status to the docker client while pushing images
2013-07-08 11:21:29 -07:00
Victor Vieux
3a20e4e15d add if to prevent crash 2013-07-08 18:19:12 +00:00
Victor Vieux
fd97190ee7 uses the terminal size to display search output, add -notrunc and fix bug in resize 2013-07-08 17:20:13 +00:00
Victor Vieux
70480ce7bc Merge pull request #1030 from dotcloud/builder_display_err_log
*Builder : Display containers logs in case of build failure
2013-07-08 07:26:46 -07:00
Victor Vieux
bf7d6cbb4a rebase master 2013-07-08 13:26:29 +00:00
Victor Vieux
c059785ffb Merge pull request #1161 from dotcloud/add_remote_addr_debug
Add remote addr in debug
2013-07-08 05:46:49 -07:00
Victor Vieux
a0f5fb7394 add remote addr in debug 2013-07-08 12:45:50 +00:00
Victor Vieux
ad33e9f388 Merge pull request #1138 from dotcloud/1123-rmi_conflict-fix
* Runtime: Fix error in rmi when conflict
2013-07-08 05:19:05 -07:00
Kimbro Staken
1d1d81b0bc Cleanup white space 2013-07-08 00:18:47 -07:00
Kimbro Staken
f3d2969560 Override Entrypoint picked up from the base image that breaks run commands in builder 2013-07-08 00:11:45 -07:00
Alex Gaynor
758ea61b77 Replaced gendered language in the README 2013-07-07 13:55:02 +10:00
Sam Alba
e2b8ee2723 Fixed runtime_test (ImagePull prototyped changed) 2013-07-05 16:03:22 -07:00
Guillaume J. Charmes
07dc0a5120 Merge pull request #1144 from dotcloud/standalone_registry
* Registry: Standalone registry
2013-07-05 15:56:48 -07:00
Sam Alba
d3125d8570 Code cleaning 2013-07-05 15:26:08 -07:00
Sam Alba
283ebf3ff9 fmt.Errorf instead of errors.New 2013-07-05 14:56:56 -07:00
Sam Alba
4c174e0bfb Fixed ping URL 2013-07-05 14:55:48 -07:00
Sam Alba
57a6c83547 Allowing namespaces in standalone registry 2013-07-05 14:30:43 -07:00
Sam Alba
cfc7684b7d Restoring old changeset lost by previous merge 2013-07-05 12:37:07 -07:00
Sam Alba
be49f0a118 Merging from master 2013-07-05 12:27:10 -07:00
Sam Alba
66a9d06d9f Adding support for nicer URLs to support standalone registry (+ some registry code cleaning) 2013-07-05 12:20:58 -07:00
Guillaume J. Charmes
6940cf1ecd Merge pull request #1127 from cespare/patch-1
Typo fix
2013-07-05 10:48:59 -07:00
Guillaume J. Charmes
4e0cdc016a Revert #1126. Remove mount shm 2013-07-05 10:47:00 -07:00
Guillaume J. Charmes
8a8109648a Merge pull request #1129 from cespare/style-fixes-2
Style fixes for fmt + err usage.
2013-07-05 10:31:53 -07:00
Guillaume J. Charmes
dc8b359319 Merge pull request #1126 from karanlyons/patch-1
* Runtime: Mount /dev/shm as a tmpfs.
2013-07-05 10:31:05 -07:00
Victor Vieux
dea29e7c99 Fix error in rmi when conflict 2013-07-05 16:58:39 +00:00
Daniel Mizyrycki
ab6379b3e0 Merge pull request #1133 from dotcloud/775-testing-notifications
testing, issue #775: Add automatic testing notifications to docker-ci
2013-07-04 21:48:00 -07:00
Daniel Mizyrycki
f7fed2ea5f testing, issue #775: Add automatic testing notifications to docker-ci 2013-07-04 21:43:46 -07:00
Daniel Mizyrycki
35e87ee571 Merge pull request #1132 from dotcloud/776-testing-commit
testing, issue #776: Ensure docker-ci test docker code as it was at commit time
2013-07-04 20:37:51 -07:00
Daniel Mizyrycki
ab3893ff4d testing, issue #776: Ensure docker-ci test docker code as it was at commit time 2013-07-04 20:28:54 -07:00
Caleb Spare
1277dca335 Style fixes for fmt + err usage.
fmt.Printf and friends will automatically format using the error
interface (.Error()) preferentially; no need to do err.Error().
2013-07-04 14:33:17 -07:00
Caleb Spare
ba9aef6f2c Typo fix
Error message grammar tweak
2013-07-04 12:40:14 -07:00
Karan Lyons
dd619d2bd6 Mount /dev/shm as a tmpfs.
Fixes #1122.
2013-07-04 09:58:50 -07:00
Marco Hennings
1e2ef274cd Pushing an Image causes the docker client to give an error message instead of
writing out streamed status.

This is caused by a Buffering message that is not in the correct json format:

[...]
{"status"
:"Pushing 6bba11a28f1ca247de9a47071355ce5923a45b8fea3182389f992f4
24b93edae"}Buffering to disk 244/? (n/a)..
{"status":"Pushing",[...]

The "Buffering to disk" message is originated in
srv.runtime.graph.TempLayerArchive

I am now using the StreamFormatter provided by the context from which the
method is called.
2013-07-04 10:50:37 +02:00
Guillaume J. Charmes
bcb5e36dd9 Merge pull request #1111 from cespare/style-fixes
Style fixes
2013-07-03 14:46:05 -07:00
Caleb Spare
19121c16d9 Implement several golint suggestions, including:
* Removing type declarations where they're inferred
* Changing Url -> URL, Id -> ID in names
* Fixing snake-case names
2013-07-03 14:36:04 -07:00
Caleb Spare
27ee261e60 Simplify the NopWriter code. 2013-07-03 14:35:18 -07:00
Caleb Spare
da3962266a Gofmt -s (simplify) 2013-07-03 14:35:18 -07:00
Caleb Spare
e93afcdd2b Use fmt.Errorf when appropriate. 2013-07-03 14:35:18 -07:00
Caleb Spare
dd1b9e38e9 Typo correction: Excepted -> Expected' 2013-07-03 14:35:18 -07:00
Guillaume J. Charmes
96bc9ea7c1 Merge pull request #1112 from cespare/mutex-style
Mutex style change.
2013-07-03 10:34:32 -07:00
Guillaume J. Charmes
16c8a10ef9 Merge pull request #1053 from dynport/do-not-copy-hostname-from-image
do not merge hostname from image
2013-07-03 10:34:15 -07:00
Joffrey F
5dcd11be16 Merge pull request #1109 from dynport/remote-lookup-fix
Fix remote lookup when pushing into registry
2013-07-03 06:29:19 -07:00
Ken Cochrane
dc91a7b641 Merge pull request #1113 from metalivedev/docs20130702
* Documentation: fix broken link on the documentation index page
2013-07-03 05:52:25 -07:00
Andy Rothfusz
11998ae7d6 Fix installation link from welcome page. 2013-07-02 16:48:57 -07:00
Caleb Spare
1cf9c80e97 Mutex style change.
For structs protected by a single mutex, embed the mutex for more
concise usage.

Also use a sync.Mutex directly, rather than a pointer, to avoid the
need for initialization (because a Mutex's zero-value is valid and
ready to be used).
2013-07-02 15:53:08 -07:00
Andy Rothfusz
6dbcdd3ed5 Merge pull request #1095 from metalivedev/docs20130627
Docs and images updates
2013-07-02 15:13:31 -07:00
Tobias Schwab
9632cf09bf fix two obvious bugs??? 2013-07-02 22:11:03 +00:00
Andy Rothfusz
96ab3c540d Merge branch 'docs20130627' of github.com:metalivedev/docker into docs20130627
Conflicts:
	docs/sources/concepts/manifesto.rst
	docs/sources/index.rst
2013-07-02 15:10:07 -07:00
Andy Rothfusz
ff964d327d Cleaning up the welcome page, terminology, and images. 2013-07-02 15:03:29 -07:00
Andy Rothfusz
4b8688f1e5 Fix broken quickstart link 2013-07-02 14:10:06 -07:00
Andy Rothfusz
55b5889a0f Merge branch 'docs20130627' of github.com:metalivedev/docker into docs20130627
Conflicts:
	docs/sources/concepts/manifesto.rst
2013-07-02 13:00:08 -07:00
Andy Rothfusz
dd4c6f6a09 Shortened lines to 80 columns 2013-07-02 12:09:57 -07:00
Andy Rothfusz
6058261a26 Clean up image text, minor updates to docs. 2013-07-02 12:09:57 -07:00
Andy Rothfusz
b461e4607d Adding files for terms 2013-07-02 12:09:57 -07:00
Andy Rothfusz
d399f72098 Cleaning up the welcome page, terminology. 2013-07-02 12:09:57 -07:00
Guillaume J. Charmes
c9e1c65c64 Merge pull request #1107 from eliasp/issue-1020
* Runtime: Don't remove the container= environment variable.
2013-07-02 11:42:44 -07:00
Victor Vieux
3042f11666 never remove the file and try to load it in start 2013-07-02 18:02:16 +00:00
Elias Probst
e5e47c9862 Don't remove the container= environment variable, as it is crucial for a lot of tools to detect, whether they're run inside an LXC container or not. 2013-07-02 19:13:37 +02:00
Victor Vieux
1c5083315d Merge pull request #1103 from shin-/1060-pull-only-tagged-images
*Registry: When no tag is specified in docker pull, skip images that are not tagged
2013-07-02 10:08:21 -07:00
Victor Vieux
27a137ccab change file location 2013-07-02 17:02:42 +00:00
shin-
7cc294e777 When no tag is specified in docker pull, skip images that are not tagged 2013-07-02 18:25:06 +02:00
Daniel Mizyrycki
a20dcfb049 Merge pull request #987 from dotcloud/601-packaging-ubuntu
Packaging|ubuntu, issue #601: Allow packaging prerm to do its job
2013-07-02 08:51:01 -07:00
Victor Vieux
06b53e3fc7 store hostConfig to /tmp while container is running 2013-07-02 12:19:25 +00:00
Victor Vieux
8f9dd86146 Merge pull request #1101 from dotcloud/fix-unit_tests
add sleep in tests and go fmt
2013-07-02 03:48:24 -07:00
Victor Vieux
ebba0a6024 add sleep in tests and go fmt 2013-07-02 10:47:37 +00:00
Victor Vieux
c9236d99d2 Merge pull request #1099 from lopter/master
*Test :  Fix
2013-07-02 02:42:21 -07:00
Louis Opter
f03c1b8eeb More unit test fixes
- Fix TestGetImagesJSON when there is more than one image in the test
  repository;
- Remove an hardcoded constant use in TestGetImagesByName;
- Wait in a loop in TestKillDifferentUser;
- Use env instead of /usr/bin/env in TestEnv;
- Create a daemon user in contrib/mkimage-unittest.sh.
2013-07-01 17:24:21 -07:00
Guillaume J. Charmes
6f23e39e6b Merge pull request #1097 from dotcloud/bump_0.4.8
Bump version to 0.4.8
2013-07-01 17:13:19 -07:00
Solomon Hykes
fe0378e9b3 Rephrase changelog 2013-07-01 17:05:49 -07:00
Guillaume J. Charmes
96a1d7c645 Bump version to 0.4.8 2013-07-01 16:58:25 -07:00
Guillaume J. Charmes
79ee8b46f4 Merge pull request #1046 from dotcloud/1043-output_id_non_attach-fix
- Runtime: Make sure the ID is displayed usgin run -d
2013-07-01 16:49:43 -07:00
Victor Vieux
55a7a8b8c9 Merge pull request #1092 from lopter/master
Fix TestGetInfo when there is more than one image in the test repository
2013-07-01 16:41:01 -07:00
Andy Rothfusz
b47873c5ac Clean up image text, minor updates to docs. 2013-07-01 16:37:13 -07:00
Andy Rothfusz
adf75d402a Adding files for terms 2013-07-01 16:37:13 -07:00
Andy Rothfusz
cb1fdb2f03 Cleaning up the welcome page, terminology. 2013-07-01 16:37:13 -07:00
Victor Vieux
d1d66b9c5f Merge pull request #1078 from kstaken/fix_json_error
* Remote API: Small fix in /start if empty host config
2013-07-01 16:36:58 -07:00
Louis Opter
6dacbb451f Fix TestGetInfo when there is more than one image in the test repository
See also #1089, #1072.
2013-07-01 15:06:08 -07:00
Guillaume J. Charmes
ead9cefadb Merge pull request #1089 from dotcloud/multiple_test_images-fix
- Tests: Fix unit tests when there is more than one tag within the test image
2013-07-01 13:58:04 -07:00
Guillaume J. Charmes
185a2fc55e Merge pull request #1086 from crosbymichael/1008-image-entrypoint
+ Builder: Add Entrypoint to builder and container config
2013-07-01 13:33:12 -07:00
Ken Cochrane
fb8fac6c60 Merge pull request #1088 from kpelykh/master
* Documentation: Update Docker Remote API client list to include Java library
2013-07-01 12:50:31 -07:00
Guillaume J. Charmes
b6f288a1ce Fix unit tests when there is more than one tag within the test image 2013-07-01 11:45:45 -07:00
zettaset-kpelykh
aa9bec96b1 Issue #1087 Docker Java API client -- added java to Docker Remote API Client document 2013-07-01 11:28:40 -07:00
Victor Vieux
11e28842ac change to top 2013-07-01 15:19:42 +00:00
Michael Crosby
b16ff9f859 Add Entrypoint to builder and container config
By setting an entrypoint in the Dockerfile this
allows one to run an image and only pass arguments.
2013-07-01 05:34:27 -09:00
Victor Vieux
348c5c4838 Merge pull request #1085 from dotcloud/1076-doc_delete-fix
fix status code in doc
2013-07-01 06:30:21 -07:00
Victor Vieux
8dcc6a0280 fix status code in doc 2013-07-01 13:28:58 +00:00
Victor Vieux
3b5ad44647 rebase master 2013-07-01 12:31:16 +00:00
Victor Vieux
5e029f7600 Merge pull request #1061 from proppy/fix-slices-ref
api,server: slice are already refs, no need to return ptr
2013-07-01 04:51:56 -07:00
Keli Hu
52cebe19e5 Keep debian package up-to-date 2013-07-01 16:15:56 +08:00
Kimbro Staken
d8d33e8b8b Adding check for content-type header 2013-06-30 10:46:09 -07:00
Solomon Hykes
b37f7d49d8 Documented release process for maintainers in hack/RELEASE.md 2013-06-29 22:08:25 -07:00
Solomon Hykes
d67d5dd963 Merge pull request #1065 from dotcloud/bump_0.4.7
Bump version to 0.4.7
2013-06-29 21:23:59 -07:00
Solomon Hykes
273e0d42b7 * Hack: change builder tests to always use the current unit test image, instead of hardcoding 'docker-ut' 2013-06-29 21:22:15 -07:00
Solomon Hykes
ca497a82ab Bump version to 0.4.7 2013-06-29 21:12:29 -07:00
Solomon Hykes
b7226316c7 * Hack: move unit tests to a different source image, to work around issues when docker-ut has more than 1 tag. 2013-06-28 19:43:55 -07:00
Guillaume J. Charmes
84f41954ae Merge pull request #1052 from lopter/master
Fix a couple critical bugs on the test suite
2013-06-28 17:00:51 -07:00
Johan Euphrosine
54da339b2c api,server: slice are already refs, no need to return ptr 2013-06-28 12:41:09 -07:00
Sam Alba
ac37fcf6f3 Fixed conflicts 2013-06-28 12:36:59 -07:00
Sam Alba
893c974b08 Resolve conflict 2013-06-28 12:32:41 -07:00
Joffrey F
30342efa37 Merge pull request #700 from dotcloud/615-pushbyid
Allow to push/pull on independent registries (by repository or image ID)
2013-06-28 10:29:10 -07:00
Daniel Mizyrycki
6165c246d4 Merge pull request #1057 from dotcloud/973-testing-stabilization
testing|stabilization, issue 973: Use docker-golang PPA and lts-raring kernel
2013-06-28 09:54:06 -07:00
shin-
72befeef24 Fixed issue in registry.GetRemoteTags 2013-06-28 18:42:37 +02:00
Victor Vieux
648c4f198b Add test 2013-06-28 16:27:00 +00:00
Daniel Mizyrycki
af2a92f22b testing|stabilization, issue 973: Use docker-golang PPA and lts-raring kernel 2013-06-28 09:23:25 -07:00
shin-
ad2f826a82 go fmt pass 2013-06-28 18:19:58 +02:00
shin-
e095a1572f Allow push by ID when using a custom registry 2013-06-28 18:19:58 +02:00
shin-
c3dd6e1926 Several fixes and updates to make this work with latest changes in master 2013-06-28 18:19:58 +02:00
Guillaume J. Charmes
67ecd2cb82 Reenable writeflusher for pull 2013-06-28 18:19:58 +02:00
Guillaume J. Charmes
57d751c377 Remove https prefix from registry 2013-06-28 18:19:58 +02:00
shin-
50075106b6 Rolled back of previous commit (skip cert verification) 2013-06-28 18:19:58 +02:00
shin-
2a1f8f6fda Ignore 'registry not found' when pushing on independent registries 2013-06-28 18:19:58 +02:00
shin-
1c817913ee Skip certificate check (don't error out on self-signed certs) 2013-06-28 18:19:58 +02:00
shin-
de0a48bd6f Tentative support for independent registries 2013-06-28 18:19:58 +02:00
Victor Vieux
8589fd6db8 Add doc 2013-06-28 18:05:41 +02:00
Victor Vieux
2e79719622 add /proc to list running processes inside a container 2013-06-28 15:51:58 +00:00
Tobias Schwab
9bfec5a538 do not merge hostname from image 2013-06-28 15:22:01 +02:00
Victor Vieux
a11fc9f067 Merge pull request #1032 from andrewsmedina/govet
following the 'go vet' suggestions for the docker package.
2013-06-28 05:27:53 -07:00
Thatcher
e12a204bcc Merge pull request #1028 from dhrp/bugfixes-on-docs
Bugfixes on documentation code
2013-06-27 19:01:13 -07:00
Louis Opter
fe014a8e6c Always return the correct test image.
And not a *random* one from its history.
2013-06-27 18:01:20 -07:00
Louis Opter
aa8ea84d11 Fix a nil dereference in buildfile_test.go
The test runtime object wasn't properly initialized.
2013-06-27 18:01:10 -07:00
Sam Alba
3175e56ad0 URL schemes of both Registry and Index are now consistent 2013-06-27 17:55:17 -07:00
Guillaume J. Charmes
800d900688 Ignore stderr while doing tests 2013-06-27 15:25:31 -07:00
Guillaume J. Charmes
1a201d2433 Merge pull request #1035 from dotcloud/fix_panic_cast
- Runtime: fix panic with unix socket
2013-06-27 15:22:18 -07:00
Guillaume J. Charmes
750c94efbb Merge pull request #1041 from unclejack/fix_minor_kernel_version_for_git_kernels
remove + from minor kernel version for kernels built from git
2013-06-27 15:21:34 -07:00
Guillaume J. Charmes
bd144a64f6 Make sure the ID is displayed usgin run -d 2013-06-27 12:48:25 -07:00
Guillaume J. Charmes
2a20e85203 Improve last log output 2013-06-27 11:10:19 -07:00
unclejack
5ed4386bbf remove + from minor kernel version 2013-06-27 17:51:17 +03:00
Victor Vieux
9d3ec7b39f fix panic with unix socket 2013-06-27 12:57:19 +00:00
Victor Vieux
e68a23bdc1 Merge pull request #1019 from dotcloud/1002-change_update_progress_bar_rate-feature
*Remote API: update progressbar every MIN(1%, 512kB)
2013-06-27 04:19:42 -07:00
Andrews Medina
6cf493bea7 following 'go vet' in utils pkg. 2013-06-27 01:40:13 -03:00
Andrews Medina
3d5633a0a0 following the 'go vet' suggestions. 2013-06-27 01:33:55 -03:00
Solomon Hykes
c4a44f6f0b Merge pull request #1029 from lopter/master
* Hack: add a script to create the docker-ut image (busybox + socat)
2013-06-26 16:28:58 -07:00
Solomon Hykes
3e29695c1f Merge pull request #602 from gabrtv/111-bind-mounts
+ Runtime: mount volumes from a host directory with 'docker run -b'
2013-06-26 15:59:35 -07:00
Solomon Hykes
46a9f29bae - Runtime: small bugfixes in external mount-bind integration 2013-06-26 15:26:47 -07:00
Gabriel Monroy
67239957c9 - Fix a few bugs in external mount-bind integration 2013-06-26 15:10:38 -07:00
Solomon Hykes
d4e62101ab * Runtime: better integration of external bind-mounts (run -b) into the volume subsystem (run -v) 2013-06-26 15:08:07 -07:00
Gabriel Monroy
4fdf11b2e6 + Runtime: mount volumes from a host directory with 'docker run -b' 2013-06-26 15:07:31 -07:00
Guillaume J. Charmes
cd0f22ef72 Merge pull request #1005 from dotcloud/1004-stdin_piping-fix
- Runtime: Fix issue when attaching stdin alone
2013-06-26 12:56:14 -07:00
Guillaume J. Charmes
27d6777376 Display containers logs in case of build failure 2013-06-26 12:50:20 -07:00
Louis Opter
e5c0b31107 Add a script to create the docker-ut image
It's a fork of the mkimage-busybox.sh script and it adds socat to the
image. (socat being needed to add udp support, see #33).

This script, like mkimage-busybox.sh, probably only works on
Debian/Ubuntu.
2013-06-26 12:35:14 -07:00
Guillaume J. Charmes
5cdbd2ed7a Merge pull request #1021 from errnoh/fix-test-filled-tmpfs
TestKill and TestMultipleContainers: run sleep instead of cat /bin/zero....
2013-06-26 11:59:06 -07:00
Guillaume J. Charmes
b44e2e71aa Merge pull request #1010 from dotcloud/1009-testing-hack
Testing|hack, issue #1009: Update make hack environment
2013-06-25 17:07:18 -07:00
Thatcher Peskens
73afc6311d Bugfixes on docs
* fixed canonical link from index
* added http redirect from builder/basics
* fixed url in redirect_home
2013-06-25 15:31:22 -07:00
Guillaume J. Charmes
6127d757a7 Add missing fprintf instead of printf 2013-06-25 10:39:11 -07:00
Erno Hopearuoho
fb86dcfb17 TestKill and TestMultipleContainers: run sleep instead of cat /bin/zero. fixes #737 2013-06-25 17:52:10 +03:00
Victor Vieux
bccf06c748 update progressbar every MIN(1%, 512kB) 2013-06-25 14:03:15 +00:00
Victor Vieux
862e223cec Merge branch 'add-daemon-storage-path-param' of https://github.com/heavenlyhash/docker into heavenlyhash-add-daemon-storage-path-param 2013-06-25 13:33:45 +00:00
Ken Cochrane
e1e2ff52fe Merge pull request #1018 from nahiluhmot/add-swipely-docker-gem
* Documentation: Added Swipely's `docker-api` gem to the table of Remote API Client Libraries.
2013-06-25 05:49:52 -07:00
Tom Hulihan
d03edf12e4 Added Swipely's docker-api gem to the table of Remote API Client
Libraries.
2013-06-25 08:26:41 -04:00
Guillaume J. Charmes
ec1dfc521c Merge pull request #992 from unclejack/use_numeric_owner_for_tar
* Runtime: use --numeric-owner for Tar and Untar
2013-06-24 18:40:43 -07:00
Guillaume J. Charmes
5190f7f33a Implement regression test for stdin attach 2013-06-24 18:36:04 -07:00
Guillaume J. Charmes
873a5aa8e7 Make NewDockerCli handle terminal 2013-06-24 18:29:08 -07:00
Guillaume J. Charmes
672d3a6c6c Make term function consistent with each other 2013-06-24 18:27:57 -07:00
Guillaume J. Charmes
a749fb2130 Make DockerCli use its own stdin/out/err instead of the os.Std* 2013-06-24 18:27:57 -07:00
Guillaume J. Charmes
25d1bc2c09 Fix issue when attaching stdin alone 2013-06-24 18:27:57 -07:00
Daniel Mizyrycki
cc63c1b584 Testing|hack, issue #1009: Update make kack environment 2013-06-24 15:01:51 -07:00
Solomon Hykes
145c622aba Merge pull request #990 from dotcloud/fix-tests-cgo
* Hack: remove dependency of unit tests on 'os/user', which cannot be used with CGO_ENABLED=0
2013-06-24 12:31:54 -07:00
Ken Cochrane
e2516c01b4 Merge pull request #932 from metalivedev/docs20130614
+ Documentation: Add terminology section
2013-06-24 10:39:25 -07:00
Victor Vieux
a3cb18d0f0 Merge pull request #1003 from dotcloud/fix_utils_tests
fix regression in utils tests introduced by #980
2013-06-24 09:14:13 -07:00
Victor Vieux
eca9f9c1a1 fix regrettion in utils tests introduced by #980 2013-06-24 16:12:39 +00:00
Daniel Mizyrycki
aee845682f Merge pull request #998 from dotcloud/861-hack-vagrant
Fixing hack/Vagrantfile to use uname for aufs linux extras
2013-06-22 20:10:00 -07:00
Anthony Bishopric
e3dbe2f2ba Fixing hack/Vagrantfile to use uname for aufs linux extras
Conflicts: hack/Vagrantfile
Resolved by: Daniel Mizyrycki <daniel@dotcloud.com>
2013-06-22 19:58:33 -07:00
Solomon Hykes
193888a2b4 Merge pull request #997 from dotcloud/bump_0.4.6
Bump version to 0.4.6
2013-06-22 13:41:48 -07:00
Solomon Hykes
9fe8bfb2bc Bump version to 0.4.6 2013-06-22 13:36:45 -07:00
Solomon Hykes
fc25973371 Merge pull request #996 from dotcloud/995-volumes-crash
- Runtime: fix a bug which caused creation of empty images (and volumes) to crash.
2013-06-22 13:35:26 -07:00
Solomon Hykes
f9acd605dc - Runtime: add regression test for issue #995 2013-06-22 13:33:43 -07:00
Solomon Hykes
290b1973a9 Fix a bug which caused creation of empty images (and volumes) to crash. FIxes #995. 2013-06-22 12:29:42 -07:00
unclejack
d7d42ff4fe use --numeric-owner for tar and untar 2013-06-22 18:53:10 +03:00
Solomon Hykes
ce9e50f4ee Remove dependency on 'os/user', which cannot be used with CGO_ENABLED=0. This allows running the tests without CGO. 2013-06-21 19:40:42 -07:00
Solomon Hykes
ecd1fff9b0 Fix formatting in Changelog 2013-06-21 20:22:14 -06:00
Daniel Mizyrycki
b0f12bd5e8 Merge pull request #988 from dotcloud/526-packaging-ubuntu
Packaging|Ubuntu, issue #526: Follow init permission pattern for docker.conf
2013-06-21 18:03:12 -07:00
Daniel Mizyrycki
5d61ec11e3 Packaging|Ubuntu, issue #526: Follow init permission pattern for docker.conf 2013-06-21 17:57:45 -07:00
Daniel Mizyrycki
41cdd9b27f Packaging|ubuntu, issue #601: Allow packaging prerm to do its job 2013-06-21 17:37:32 -07:00
Victor Vieux
ec6b35240e fix raw terminal 2013-06-22 00:37:02 +00:00
Guillaume J. Charmes
c792c0a6c9 Merge pull request #986 from dotcloud/bump_0.4.5
Bump version to 0.4.5
2013-06-21 17:13:38 -07:00
Solomon Hykes
d9d2540162 Small copy improvements in Changelog 2013-06-21 17:09:49 -07:00
Guillaume J. Charmes
f5d08fc49c Bump version to 0.4.5 2013-06-21 17:01:01 -07:00
Victor Vieux
b24759af1c Merge pull request #959 from dotcloud/947-add_ps_size_option
+ Client: Add docker ps -s option to display containers' sizes
2013-06-21 16:10:45 -07:00
Victor Vieux
4d1692726b merge master and add doc 2013-06-22 01:08:20 +02:00
Victor Vieux
1581ed52ba consistent codebase fix 2013-06-21 22:55:33 +00:00
Guillaume J. Charmes
de1a5a75cc Merge pull request #848 from dotcloud/builder_server-3
Improve Docker build
2013-06-21 14:55:08 -07:00
Guillaume J. Charmes
169ef21de7 Remove deprecated INSERT from documentation 2013-06-21 14:12:52 -07:00
Guillaume J. Charmes
d0fa6927f8 Update api docs 2013-06-21 13:51:48 -07:00
Victor Vieux
63e8a4ac74 Merge pull request #970 from titanous/go1.1-unreachable
Remove code unreachable using Go 1.1
2013-06-21 10:44:40 -07:00
Victor Vieux
459230d3f9 Merge pull request #980 from ToothlessGear/fix-progressbar
* Client: fix progressbar output
2013-06-21 10:42:14 -07:00
Guillaume J. Charmes
070e1aec7e Merge pull request #975 from unclejack/891-mark_command_as_optional
891 mark command as optional for docker run
2013-06-21 10:22:52 -07:00
Marcus Farkas
3ac68f1966 Client: better progressbar output 2013-06-21 17:19:27 +02:00
Victor Vieux
42bcfcc927 add options to docker login 2013-06-21 10:00:25 +00:00
Victor Vieux
5ccde4dffc Merge pull request #801 from dotcloud/build_docker_static
* Makefile: Add link flags in order to link statically and without debug symbols
2013-06-21 02:30:57 -07:00
Victor Vieux
dc847001a5 Merge pull request #888 from dotcloud/fix-auth
* Auth: fix auth small issue in case you change your password on index.io
2013-06-21 02:22:01 -07:00
Victor Vieux
639833aaf5 fix tests 2013-06-21 09:20:57 +00:00
Victor Vieux
8f2a80804c Merge branch 'master' into fix-auth 2013-06-21 09:18:03 +00:00
Victor Vieux
78842970cf Merge pull request #976 from dotcloud/fix_doc_post
fix typo in documentation
2013-06-21 02:14:39 -07:00
Victor Vieux
8e7d4cda07 fix doc post 2013-06-21 11:13:13 +02:00
Victor Vieux
5b3ad0023b inverse if 2013-06-21 09:06:09 +00:00
unclejack
6a1279fb90 docs: mark command as optional for docker run 2013-06-21 11:07:14 +03:00
unclejack
66910a7602 mark command as optional for docker run 2013-06-21 11:06:41 +03:00
Solomon Hykes
d9bce2defd - Builder: return an error when the build fails 2013-06-20 22:15:19 -07:00
Solomon Hykes
352991bdf4 Merge branch 'simpler-build-upload' (#900) into builder_server-3 (#848) 2013-06-20 22:02:36 -07:00
Solomon Hykes
4383d7b603 Merge pull request #953 from rhysh/952-swapaccount-docs
- Documentation: fix inconsistent/outdated instructions for setting up "swapaccount"
2013-06-20 20:49:44 -07:00
Solomon Hykes
89ae56820a Merge pull request #965 from freegenie/patch-1
* Documentation: use sudo for quick install script
2013-06-20 20:48:42 -07:00
Solomon Hykes
17489cac1a Merge pull request #972 from titanous/update-authors
Update AUTHORS
2013-06-20 20:42:52 -07:00
Solomon Hykes
c1a5318d8e Merge branch 'build-add-file' into simpler-build-upload
Conflicts:
	buildfile_test.go
2013-06-20 20:42:19 -07:00
Jonathan Rudenberg
b0b690cf23 Update AUTHORS 2013-06-20 23:29:20 -04:00
Solomon Hykes
86e83186b5 Merge branch 'master' into simpler-build-upload
Conflicts:
	commands.go
2013-06-20 20:25:59 -07:00
Solomon Hykes
36d610a388 - Builder: fixed a regression in ADD. Improved regression tests for build behavior. 2013-06-20 20:20:16 -07:00
Jonathan Rudenberg
50b70eeb68 Remove code unreachable using Go 1.1 2013-06-20 23:19:44 -04:00
Solomon Hykes
cc0f59742f * Builder: simplified unit tests. The tests are now embedded in the build itself. Yeah baby. 2013-06-20 20:16:39 -07:00
Solomon Hykes
09dd7f14de Merge pull request #969 from dotcloud/fix_logs
- Remote API: Fix a bug which caused 'docker logs' to fail
2013-06-20 18:33:33 -07:00
Guillaume J. Charmes
b419699ab8 Use hijack for logs instead of stream 2013-06-20 18:18:36 -07:00
Guillaume J. Charmes
08825fa611 remove unused files 2013-06-20 16:31:11 -07:00
Eric Myhre
e44f62a95c Add argument to allow setting base directory for docker daemon's storage to values other than "/var/lib/docker". 2013-06-20 16:29:54 -05:00
Solomon Hykes
e43323221b Merge branch 'master' into simpler-build-upload
Conflicts:
	api.go
	builder_client.go
	commands.go
2013-06-20 14:19:09 -07:00
Fabrizio Regini
da06349723 Update README.md 2013-06-20 23:15:38 +02:00
Solomon Hykes
cff2187a4c Fixed API version numbers in api docs 2013-06-20 12:30:02 -07:00
Solomon Hykes
db60337598 Makefile: added missing -a option 2013-06-20 10:39:09 -07:00
Guillaume J. Charmes
c5be64fec4 Add link flags in order to link statically and without debug symbols 2013-06-20 10:33:54 -07:00
Guillaume J. Charmes
659e846006 Merge branch 'master' into builder_server-3
Conflicts:
	docs/sources/use/builder.rst
2013-06-20 10:27:12 -07:00
Victor Vieux
da199846d2 use strconv.ParseBool in getBoolParam 2013-06-20 14:34:58 +00:00
Victor Vieux
bd04d7d475 add ps -s 2013-06-20 14:19:50 +00:00
Solomon Hykes
79efcb545d Merge branch 'master' into simpler-build-upload 2013-06-19 18:48:19 -07:00
Solomon Hykes
d6ab71f450 * Remote API: updated docs for 1.3 2013-06-19 15:03:33 -07:00
Solomon Hykes
55edbcd02f * Builder: remove duplicate unit test 2013-06-19 14:59:42 -07:00
Solomon Hykes
90dde9beab *Builder: warn pre-1.3 clients that they need to upgrade. This breaks semver, but our API should still be in 0.X versioning, in which case semver allows breaking changes. 2013-06-19 14:59:28 -07:00
Rhys Hiltner
5fc1329b2f the kernel needs "swapaccount=1" set - some docs are updated, this one seems to have slipped through
Fixes #952
2013-06-19 14:45:03 -07:00
Andy Rothfusz
5183399f50 Added multilayer example image. 2013-06-18 19:31:35 -07:00
Andy Rothfusz
a780b7c6b5 New Terminology section and illustrations. 2013-06-18 19:31:35 -07:00
Guillaume J. Charmes
34a434616a Merge branch 'master' into builder_server-3
Conflicts:
	buildfile.go
2013-06-18 16:12:30 -07:00
Guillaume J. Charmes
84ceeaa870 Update documentaiton 2013-06-18 14:36:35 -07:00
Guillaume J. Charmes
6e17cc45ea Fix merge issue 2013-06-18 12:33:06 -07:00
Solomon Hykes
0809f649d3 * Builder: upload progress bar
Fix progress bar
2013-06-17 18:49:16 -07:00
Guillaume J. Charmes
555552340d Merge branch 'master' into builder_server-3
Conflicts:
	buildfile_test.go
2013-06-17 14:01:32 -07:00
Guillaume J. Charmes
79b3265ef1 Merge branch 'master' into builder_server-3
Conflicts:
	buildfile.go
2013-06-17 11:09:53 -07:00
Solomon Hykes
fe88b5068d Merge branch 'master' into simpler-build-upload 2013-06-15 12:35:26 -07:00
Solomon Hykes
f50e40008f * Builder: added a regression test for #895 2013-06-15 11:35:56 -07:00
Solomon Hykes
061f8d12e0 * Builder: reorganized unit tests for better code reuse, and to test non-empty contexts 2013-06-15 11:07:49 -07:00
Solomon Hykes
38554fc2a7 * Builder: simplify the upload of the build context. Simply stream a tarball instead of multipart upload with 4 intermediary buffers. Simpler, less memory usage, less disk usage, and faster. 2013-06-15 09:38:18 -07:00
Solomon Hykes
cc7de8df75 Removed deprecated file builder_client.go 2013-06-15 09:30:52 -07:00
Guillaume J. Charmes
78f86ea502 Merge branch 'master' into builder_server-3
Conflicts:
	utils/utils.go
2013-06-14 17:08:39 -07:00
Victor Vieux
90f6bdd6e4 update docs, remove config file on 401 2013-06-14 13:38:51 +00:00
Victor Vieux
9cc72ff1a9 fix auth in case you change your password on index.io 2013-06-14 09:53:48 +00:00
Guillaume J. Charmes
2f14dae83f Add build UT 2013-06-13 18:52:41 -07:00
Guillaume J. Charmes
f03ebc20aa Fix issue with ADD 2013-06-13 18:42:27 -07:00
Guillaume J. Charmes
4b4918f2a7 Merge branch 'master' into builder_server-3
Conflicts:
	buildfile.go
	commands.go
	docs/sources/api/docker_remote_api.rst
2013-06-13 18:11:22 -07:00
Guillaume J. Charmes
f5fe3ce34e Remove run from non-running commmands 2013-06-13 15:08:53 -07:00
Guillaume J. Charmes
d0084ce5f2 Remove run from the ADD instruction 2013-06-13 14:57:50 -07:00
Guillaume J. Charmes
b38c6929be Updated build usage 2013-06-13 10:50:55 -07:00
Guillaume J. Charmes
8984aef899 Fix typo in docs 2013-06-10 09:32:31 -07:00
Guillaume J. Charmes
b103ac70bf Allow multiple tab/spaces between instructions and arguments 2013-06-10 09:31:59 -07:00
Guillaume J. Charmes
2cc22de696 Update documentation for docker build 2013-06-06 16:48:36 -07:00
Guillaume J. Charmes
12c9b9b3c9 Implement build from git 2013-06-06 16:41:41 -07:00
Guillaume J. Charmes
a11e61677c Move the docker build URL form client to server, prepare for GIT support 2013-06-06 16:09:46 -07:00
Guillaume J. Charmes
01f446e908 Allow to docker build URL 2013-06-06 15:56:09 -07:00
Guillaume J. Charmes
f4a4cfd2cc Move isUrl to utils.IsURL 2013-06-06 15:50:09 -07:00
Guillaume J. Charmes
eaa2183d77 Fix issue EXPOSE override CMD within builder 2013-06-06 15:48:12 -07:00
Guillaume J. Charmes
31d2b258c1 Allow remote url to be passed to the ADD instruction within the builder 2013-06-06 15:40:46 -07:00
100 changed files with 6852 additions and 2617 deletions

1
.gitignore vendored
View File

@@ -15,3 +15,4 @@ docs/_build
docs/_static
docs/_templates
.gopath/
.dotcloud

View File

@@ -19,3 +19,7 @@ Andy Smith <github@anarkystic.com>
<victor.vieux@dotcloud.com> <dev@vvieux.com>
<dominik@honnef.co> <dominikh@fork-bomb.org>
Thatcher Peskens <thatcher@dotcloud.com>
<ehanchrow@ine.com> <eric.hanchrow@gmail.com>
Walter Stanish <walter@pratyeka.org>
<daniel@gasienica.ch> <dgasienica@zynga.com>
Roberto Hashioka <roberto_hashioka@hotmail.com>

23
AUTHORS
View File

@@ -6,6 +6,8 @@
Al Tobey <al@ooyala.com>
Alexey Shamrin <shamrin@gmail.com>
Andrea Luzzardi <aluzzardi@gmail.com>
Andreas Tiefenthaler <at@an-ti.eu>
Andrew Munsell <andrew@wizardapps.net>
Andy Rothfusz <github@metaliveblog.com>
Andy Smith <github@anarkystic.com>
Antony Messerli <amesserl@rackspace.com>
@@ -14,7 +16,9 @@ Brandon Liu <bdon@bdon.org>
Brian McCallister <brianm@skife.org>
Bruno Bigras <bigras.bruno@gmail.com>
Caleb Spare <cespare@gmail.com>
Calen Pennington <cale@edx.org>
Charles Hooper <charles.hooper@dotcloud.com>
Christopher Currie <codemonkey+github@gmail.com>
Daniel Gasienica <daniel@gasienica.ch>
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
Daniel Robinson <gottagetmac@gmail.com>
@@ -22,11 +26,15 @@ Daniel Von Fange <daniel@leancoder.com>
Dominik Honnef <dominik@honnef.co>
Don Spaulding <donspauldingii@gmail.com>
Dr Nic Williams <drnicwilliams@gmail.com>
Elias Probst <mail@eliasprobst.eu>
Eric Hanchrow <ehanchrow@ine.com>
Evan Wies <evan@neomantra.net>
Eric Myhre <hash@exultant.us>
ezbercih <cem.ezberci@gmail.com>
Flavio Castelli <fcastelli@suse.com>
Francisco Souza <f@souza.cc>
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
Gareth Rushgrove <gareth@morethanseven.net>
Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
Harley Laue <losinggeneration@gmail.com>
Hunter Blanks <hunter@twilio.com>
@@ -34,15 +42,21 @@ Jeff Lindsay <progrium@gmail.com>
Jeremy Grosser <jeremy@synack.me>
Joffrey F <joffrey@dotcloud.com>
John Costa <john.costa@gmail.com>
Jon Wedaman <jweede@gmail.com>
Jonas Pfenniger <jonas@pfenniger.name>
Jonathan Rudenberg <jonathan@titanous.com>
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
Julien Barbier <write0@gmail.com>
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
Ken Cochrane <kencochrane@gmail.com>
Kevin J. Lynagh <kevin@keminglabs.com>
kim0 <email.ahmedkamal@googlemail.com>
Kiran Gangadharan <kiran.daredevil@gmail.com>
Louis Opter <kalessin@kalessin.fr>
Marcus Farkas <toothlessgear@finitebox.com>
Mark McGranaghan <mmcgrana@gmail.com>
Maxim Treskin <zerthurd@gmail.com>
meejah <meejah@meejah.ca>
Michael Crosby <crosby.michael@gmail.com>
Mikhail Sobolev <mss@mawhrin.net>
Nate Jones <nate@endot.org>
@@ -51,18 +65,25 @@ Niall O'Higgins <niallo@unworkable.org>
odk- <github@odkurzacz.org>
Paul Bowsher <pbowsher@globalpersonals.co.uk>
Paul Hammond <paul@paulhammond.org>
Phil Spitler <pspitler@gmail.com>
Piotr Bogdan <ppbogdan@gmail.com>
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
Robert Obryk <robryk@gmail.com>
Roberto Hashioka <roberto_hashioka@hotmail.com>
Sam Alba <sam.alba@gmail.com>
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
Shawn Siefkas <shawn.siefkas@meredith.com>
Silas Sewell <silas@sewell.org>
Solomon Hykes <solomon@dotcloud.com>
Sridhar Ratnakumar <sridharr@activestate.com>
Thatcher Peskens <thatcher@dotcloud.com>
Thomas Bikeev <thomas.bikeev@mac.com>
Thomas Hansen <thomas.hansen@gmail.com>
Tianon Gravi <admwiggin@gmail.com>
Tim Terhorst <mynamewastaken+git@gmail.com>
Troy Howard <thoward37@gmail.com>
Tobias Bieniek <Tobias.Bieniek@gmx.de>
unclejack <unclejacksons@gmail.com>
Victor Vieux <victor.vieux@dotcloud.com>
Vivek Agarwal <me@vivek.im>
Walter Stanish <walter@pratyeka.org>
Will Dietz <w@wdtz.org>

View File

@@ -1,5 +1,45 @@
# Changelog
## 0.5.0 (2013-07-17)
+ Runtime: List all processes running inside a container with 'docker top'
+ Runtime: Host directories can be mounted as volumes with 'docker run -v'
+ Runtime: Containers can expose public UDP ports (eg, '-p 123/udp')
+ Runtime: Optionally specify an exact public port (eg. '-p 80:4500')
+ Registry: New image naming scheme inspired by Go packaging convention allows arbitrary combinations of registries
+ Builder: ENTRYPOINT instruction sets a default binary entry point to a container
+ Builder: VOLUME instruction marks a part of the container as persistent data
* Builder: 'docker build' displays the full output of a build by default
* Runtime: 'docker login' supports additional options
- Runtime: Dont save a container's hostname when committing an image.
- Registry: Fix issues when uploading images to a private registry
## 0.4.8 (2013-07-01)
+ Builder: New build operation ENTRYPOINT adds an executable entry point to the container.
- Runtime: Fix a bug which caused 'docker run -d' to no longer print the container ID.
- Tests: Fix issues in the test suite
## 0.4.7 (2013-06-28)
* Registry: easier push/pull to a custom registry
* Remote API: the progress bar updates faster when downloading and uploading large files
- Remote API: fix a bug in the optional unix socket transport
* Runtime: improve detection of kernel version
+ Runtime: host directories can be mounted as volumes with 'docker run -b'
- Runtime: fix an issue when only attaching to stdin
* Runtime: use 'tar --numeric-owner' to avoid uid mismatch across multiple hosts
* Hack: improve test suite and dev environment
* Hack: remove dependency on unit tests on 'os/user'
+ Documentation: add terminology section
## 0.4.6 (2013-06-22)
- Runtime: fix a bug which caused creation of empty images (and volumes) to crash.
## 0.4.5 (2013-06-21)
+ Builder: 'docker build git://URL' fetches and builds a remote git repository
* Runtime: 'docker ps -s' optionally prints container size
* Tests: Improved and simplified
- Runtime: fix a regression introduced in 0.4.3 which caused the logs command to fail.
- Builder: fix a regression when using ADD with single regular file.
## 0.4.4 (2013-06-19)
- Builder: fix a regression introduced in 0.4.3 which caused builds to fail on new clients.

30
Dockerfile Normal file
View File

@@ -0,0 +1,30 @@
# This file describes the standard way to build Docker, using docker
docker-version 0.4.2
from ubuntu:12.04
maintainer Solomon Hykes <solomon@dotcloud.com>
# Build dependencies
run apt-get install -y -q curl
run apt-get install -y -q git
# Install Go
run curl -s https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz | tar -v -C /usr/local -xz
env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
env GOPATH /go
env CGO_ENABLED 0
run cd /tmp && echo 'package main' > t.go && go test -a -i -v
# Download dependencies
run PKG=github.com/kr/pty REV=27435c699; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
run PKG=github.com/gorilla/context/ REV=708054d61e5; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
run PKG=github.com/gorilla/mux/ REV=9b36453141c; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
# Run dependencies
run apt-get install -y iptables
# lxc requires updating ubuntu sources
run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
run apt-get update
run apt-get install -y lxc
run apt-get install -y aufs-tools
# Upload docker source
add . /go/src/github.com/dotcloud/docker
# Build the binary
run cd /go/src/github.com/dotcloud/docker/docker && go install -ldflags "-X main.GITCOMMIT '??' -d -w"
env PATH /usr/local/go/bin:/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
cmd ["docker"]

1
FIXME
View File

@@ -33,3 +33,4 @@ to put them - so we put them here :)
* Caching after an ADD
* entry point config
* bring back git revision info, looks like it was lost
* Clean up the ProgressReader api, it's a PITA to use

View File

@@ -2,6 +2,8 @@ DOCKER_PACKAGE := github.com/dotcloud/docker
RELEASE_VERSION := $(shell git tag | grep -E "v[0-9\.]+$$" | sort -nr | head -n 1)
SRCRELEASE := docker-$(RELEASE_VERSION)
BINRELEASE := docker-$(RELEASE_VERSION).tgz
BUILD_SRC := build_src
BUILD_PATH := ${BUILD_SRC}/src/${DOCKER_PACKAGE}
GIT_ROOT := $(shell git rev-parse --show-toplevel)
BUILD_DIR := $(CURDIR)/.gopath
@@ -17,7 +19,7 @@ endif
GIT_COMMIT = $(shell git rev-parse --short HEAD)
GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES")
BUILD_OPTIONS = -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS)"
BUILD_OPTIONS = -a -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS) -d -w"
SRC_DIR := $(GOPATH)/src
@@ -33,7 +35,7 @@ all: $(DOCKER_BIN)
$(DOCKER_BIN): $(DOCKER_DIR)
@mkdir -p $(dir $@)
@(cd $(DOCKER_MAIN); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
@(cd $(DOCKER_MAIN); CGO_ENABLED=0 go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
@echo $(DOCKER_BIN_RELATIVE) is created.
$(DOCKER_DIR):
@@ -47,6 +49,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
s3cmd -P put $(SRCRELEASE)/bin/docker s3://get.docker.io/builds/`uname -s`/`uname -m`/docker
srcrelease: $(SRCRELEASE)
deps: $(DOCKER_DIR)
@@ -71,8 +74,13 @@ else ifneq ($(DOCKER_DIR), $(realpath $(DOCKER_DIR)))
@rm -f $(DOCKER_DIR)
endif
test: all
@(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS))
test:
# Copy docker source and dependencies for testing
rm -rf ${BUILD_SRC}; mkdir -p ${BUILD_PATH}
tar --exclude=${BUILD_SRC} -cz . | tar -xz -C ${BUILD_PATH}
GOPATH=${CURDIR}/${BUILD_SRC} go get -d
# Do the test
sudo -E GOPATH=${CURDIR}/${BUILD_SRC} go test ${GO_OPTIONS}
testall: all
@(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS))

View File

@@ -23,15 +23,15 @@ happens, for a few reasons:
* *Size*: VMs are very large which makes them impractical to store and transfer.
* *Performance*: running VMs consumes significant CPU and memory, which makes them impractical in many scenarios, for example local development of multi-tier applications, and
large-scale deployment of cpu and memory-intensive applications on large numbers of machines.
large-scale deployment of cpu and memory-intensive applications on large numbers of machines.
* *Portability*: competing VM environments don't play well with each other. Although conversion tools do exist, they are limited and add even more overhead.
* *Hardware-centric*: VMs were designed with machine operators in mind, not software developers. As a result, they offer very limited tooling for what developers need most:
building, testing and running their software. For example, VMs offer no facilities for application versioning, monitoring, configuration, logging or service discovery.
building, testing and running their software. For example, VMs offer no facilities for application versioning, monitoring, configuration, logging or service discovery.
By contrast, Docker relies on a different sandboxing method known as *containerization*. Unlike traditional virtualization,
containerization takes place at the kernel level. Most modern operating system kernels now support the primitives necessary
for containerization, including Linux with [openvz](http://openvz.org), [vserver](http://linux-vserver.org) and more recently [lxc](http://lxc.sourceforge.net),
Solaris with [zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc) and FreeBSD with [Jails](http://www.freebsd.org/doc/handbook/jails.html).
Solaris with [zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc) and FreeBSD with [Jails](http://www.freebsd.org/doc/handbook/jails.html).
Docker builds on top of these low-level primitives to offer developers a portable format and runtime environment that solves
all 4 problems. Docker containers are small (and their transfer can be optimized with layers), they have basically zero memory and cpu overhead,
@@ -56,17 +56,17 @@ A common problem for developers is the difficulty of managing all their applicat
This is usually difficult for several reasons:
* *Cross-platform dependencies*. Modern applications often depend on a combination of system libraries and binaries, language-specific packages, framework-specific modules,
internal components developed for another project, etc. These dependencies live in different "worlds" and require different tools - these tools typically don't work
well with each other, requiring awkward custom integrations.
internal components developed for another project, etc. These dependencies live in different "worlds" and require different tools - these tools typically don't work
well with each other, requiring awkward custom integrations.
* Conflicting dependencies. Different applications may depend on different versions of the same dependency. Packaging tools handle these situations with various degrees of ease -
but they all handle them in different and incompatible ways, which again forces the developer to do extra work.
but they all handle them in different and incompatible ways, which again forces the developer to do extra work.
* Custom dependencies. A developer may need to prepare a custom version of his application's dependency. Some packaging systems can handle custom versions of a dependency,
others can't - and all of them handle it differently.
* Custom dependencies. A developer may need to prepare a custom version of their application's dependency. Some packaging systems can handle custom versions of a dependency,
others can't - and all of them handle it differently.
Docker solves dependency hell by giving the developer a simple way to express *all* his application's dependencies in one place,
Docker solves dependency hell by giving the developer a simple way to express *all* their application's dependencies in one place,
and streamline the process of assembling them. If this makes you think of [XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't
*replace* your favorite packaging systems. It simply orchestrates their use in a simple and repeatable way. How does it do that? With layers.
@@ -97,7 +97,7 @@ Quick install on Ubuntu 12.04 and 12.10
---------------------------------------
```bash
curl get.docker.io | sh -x
curl get.docker.io | sudo sh -x
```
Binary installs

173
api.go
View File

@@ -7,15 +7,17 @@ import (
"github.com/dotcloud/docker/utils"
"github.com/gorilla/mux"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
)
const APIVERSION = 1.2
const APIVERSION = 1.3
const DEFAULTHTTPHOST string = "127.0.0.1"
const DEFAULTHTTPPORT int = 4243
@@ -45,17 +47,22 @@ func parseMultipartForm(r *http.Request) error {
}
func httpError(w http.ResponseWriter, err error) {
statusCode := http.StatusInternalServerError
if strings.HasPrefix(err.Error(), "No such") {
http.Error(w, err.Error(), http.StatusNotFound)
statusCode = http.StatusNotFound
} else if strings.HasPrefix(err.Error(), "Bad parameter") {
http.Error(w, err.Error(), http.StatusBadRequest)
statusCode = http.StatusBadRequest
} else if strings.HasPrefix(err.Error(), "Conflict") {
http.Error(w, err.Error(), http.StatusConflict)
statusCode = http.StatusConflict
} else if strings.HasPrefix(err.Error(), "Impossible") {
http.Error(w, err.Error(), http.StatusNotAcceptable)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
statusCode = http.StatusNotAcceptable
} else if strings.HasPrefix(err.Error(), "Wrong login/password") {
statusCode = http.StatusUnauthorized
} else if strings.Contains(err.Error(), "hasn't been activated") {
statusCode = http.StatusForbidden
}
utils.Debugf("[error %d] %s", statusCode, err)
http.Error(w, err.Error(), statusCode)
}
func writeJSON(w http.ResponseWriter, b []byte) {
@@ -63,15 +70,15 @@ func writeJSON(w http.ResponseWriter, b []byte) {
w.Write(b)
}
// FIXME: Use stvconv.ParseBool() instead?
func getBoolParam(value string) (bool, error) {
if value == "1" || strings.ToLower(value) == "true" {
return true, nil
}
if value == "" || value == "0" || strings.ToLower(value) == "false" {
if value == "" {
return false, nil
}
return false, fmt.Errorf("Bad parameter")
ret, err := strconv.ParseBool(value)
if err != nil {
return false, fmt.Errorf("Bad parameter")
}
return ret, nil
}
func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@@ -164,7 +171,7 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r
name := vars["name"]
if err := srv.ContainerExport(name, w); err != nil {
utils.Debugf("%s", err.Error())
utils.Debugf("%s", err)
return err
}
return nil
@@ -244,6 +251,23 @@ func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r
return nil
}
func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
procsStr, err := srv.ContainerTop(name)
if err != nil {
return err
}
b, err := json.Marshal(procsStr)
if err != nil {
return err
}
writeJSON(w, b)
return nil
}
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
@@ -252,6 +276,10 @@ func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *h
if err != nil {
return err
}
size, err := getBoolParam(r.Form.Get("size"))
if err != nil {
return err
}
since := r.Form.Get("since")
before := r.Form.Get("before")
n, err := strconv.Atoi(r.Form.Get("limit"))
@@ -259,7 +287,7 @@ func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *h
n = -1
}
outs := srv.Containers(all, n, since, before)
outs := srv.Containers(all, size, n, since, before)
b, err := json.Marshal(outs)
if err != nil {
return err
@@ -296,7 +324,7 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req
}
config := &Config{}
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
utils.Debugf("%s", err.Error())
utils.Debugf("%s", err)
}
repo := r.Form.Get("repo")
tag := r.Form.Get("tag")
@@ -332,8 +360,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht
}
sf := utils.NewStreamFormatter(version > 1.0)
if image != "" { //pull
registry := r.Form.Get("registry")
if err := srv.ImagePull(image, tag, registry, w, sf, &auth.AuthConfig{}); err != nil {
if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}); err != nil {
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
@@ -416,7 +443,6 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
if err := parseForm(r); err != nil {
return err
}
registry := r.Form.Get("registry")
if vars == nil {
return fmt.Errorf("Missing parameter")
@@ -426,7 +452,7 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
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 err := srv.ImagePush(name, w, sf, authConfig); err != nil {
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
@@ -525,7 +551,7 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R
return err
}
if imgs != nil {
if len(*imgs) != 0 {
if len(imgs) != 0 {
b, err := json.Marshal(imgs)
if err != nil {
return err
@@ -541,11 +567,22 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R
}
func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
hostConfig := &HostConfig{}
// allow a nil body for backwards compatibility
if r.Body != nil {
if r.Header.Get("Content-Type") == "application/json" {
if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
return err
}
}
}
if vars == nil {
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
if err := srv.ContainerStart(name); err != nil {
if err := srv.ContainerStart(name, hostConfig); err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)
@@ -650,7 +687,20 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r
if err != nil {
return err
}
defer in.Close()
defer func() {
if tcpc, ok := in.(*net.TCPConn); ok {
tcpc.CloseWrite()
} else {
in.Close()
}
}()
defer func() {
if tcpc, ok := out.(*net.TCPConn); ok {
tcpc.CloseWrite()
} else if closer, ok := out.(io.Closer); ok {
closer.Close()
}
}()
fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil {
@@ -719,34 +769,72 @@ func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *
}
func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := r.ParseMultipartForm(4096); err != nil {
return err
if version < 1.3 {
return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.")
}
remote := r.FormValue("t")
remoteURL := r.FormValue("remote")
repoName := r.FormValue("t")
rawSuppressOutput := r.FormValue("q")
tag := ""
if strings.Contains(remote, ":") {
remoteParts := strings.Split(remote, ":")
if strings.Contains(repoName, ":") {
remoteParts := strings.Split(repoName, ":")
tag = remoteParts[1]
remote = remoteParts[0]
repoName = remoteParts[0]
}
dockerfile, _, err := r.FormFile("Dockerfile")
if err != nil {
return err
}
var context io.Reader
context, _, err := r.FormFile("Context")
if err != nil {
if err != http.ErrMissingFile {
if remoteURL == "" {
context = r.Body
} else if utils.IsGIT(remoteURL) {
if !strings.HasPrefix(remoteURL, "git://") {
remoteURL = "https://" + remoteURL
}
root, err := ioutil.TempDir("", "docker-build-git")
if err != nil {
return err
}
defer os.RemoveAll(root)
if output, err := exec.Command("git", "clone", remoteURL, root).CombinedOutput(); err != nil {
return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
}
c, err := Tar(root, Bzip2)
if err != nil {
return err
}
context = c
} else if utils.IsURL(remoteURL) {
f, err := utils.Download(remoteURL, ioutil.Discard)
if err != nil {
return err
}
defer f.Body.Close()
dockerFile, err := ioutil.ReadAll(f.Body)
if err != nil {
return err
}
c, err := mkBuildContext(string(dockerFile), nil)
if err != nil {
return err
}
context = c
}
b := NewBuildFile(srv, utils.NewWriteFlusher(w))
if id, err := b.Build(dockerfile, context); err != nil {
suppressOutput, err := getBoolParam(rawSuppressOutput)
if err != nil {
return err
}
b := NewBuildFile(srv, utils.NewWriteFlusher(w), !suppressOutput)
id, err := b.Build(context)
if err != nil {
fmt.Fprintf(w, "Error build: %s\n", err)
} else if remote != "" {
srv.runtime.repositories.Set(remote, tag, id, false)
return err
}
if repoName != "" {
srv.runtime.repositories.Set(repoName, tag, id, false)
}
return nil
}
@@ -779,6 +867,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/containers/{name:.*}/export": getContainersExport,
"/containers/{name:.*}/changes": getContainersChanges,
"/containers/{name:.*}/json": getContainersByName,
"/containers/{name:.*}/top": getContainersTop,
},
"POST": {
"/auth": postAuth,
@@ -815,7 +904,8 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
localMethod := method
localFct := fct
f := func(w http.ResponseWriter, r *http.Request) {
utils.Debugf("Calling %s %s", localMethod, localRoute)
utils.Debugf("Calling %s %s from %s", localMethod, localRoute, r.RemoteAddr)
if logging {
log.Println(r.Method, r.RequestURI)
}
@@ -836,6 +926,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
w.WriteHeader(http.StatusNotFound)
return
}
if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
httpError(w, err)
}

View File

@@ -26,6 +26,13 @@ type APIInfo struct {
SwapLimit bool `json:",omitempty"`
}
type APITop struct {
PID string
Tty string
Time string
Cmd string
}
type APIRmi struct {
Deleted string `json:",omitempty"`
Untagged string `json:",omitempty"`

View File

@@ -5,7 +5,6 @@ import (
"bufio"
"bytes"
"encoding/json"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/utils"
"io"
"net"
@@ -17,49 +16,33 @@ import (
"time"
)
func TestPostAuth(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
func TestGetBoolParam(t *testing.T) {
if ret, err := getBoolParam("true"); err != nil || !ret {
t.Fatalf("true -> true, nil | got %t %s", ret, err)
}
defer nuke(runtime)
srv := &Server{
runtime: runtime,
if ret, err := getBoolParam("True"); err != nil || !ret {
t.Fatalf("True -> true, nil | got %t %s", ret, err)
}
r := httptest.NewRecorder()
authConfig := &auth.AuthConfig{
Username: "utest",
Password: "utest",
Email: "utest@yopmail.com",
if ret, err := getBoolParam("1"); err != nil || !ret {
t.Fatalf("1 -> true, nil | got %t %s", ret, err)
}
authConfigJSON, err := json.Marshal(authConfig)
if err != nil {
t.Fatal(err)
if ret, err := getBoolParam(""); err != nil || ret {
t.Fatalf("\"\" -> false, nil | got %t %s", ret, err)
}
req, err := http.NewRequest("POST", "/auth", bytes.NewReader(authConfigJSON))
if err != nil {
t.Fatal(err)
if ret, err := getBoolParam("false"); err != nil || ret {
t.Fatalf("false -> false, nil | got %t %s", ret, err)
}
if err := postAuth(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
if ret, err := getBoolParam("0"); err != nil || ret {
t.Fatalf("0 -> false, nil | got %t %s", ret, err)
}
if r.Code != http.StatusOK && r.Code != 0 {
t.Fatalf("%d OK or 0 expected, received %d\n", http.StatusOK, r.Code)
if ret, err := getBoolParam("faux"); err == nil || ret {
t.Fatalf("faux -> false, err | got %t %s", ret, err)
}
}
func TestGetVersion(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
var err error
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -75,19 +58,21 @@ func TestGetVersion(t *testing.T) {
t.Fatal(err)
}
if v.Version != VERSION {
t.Errorf("Excepted version %s, %s found", VERSION, v.Version)
t.Errorf("Expected version %s, %s found", VERSION, v.Version)
}
}
func TestGetInfo(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
initialImages, err := srv.runtime.graph.All()
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := getInfo(srv, APIVERSION, r, nil, nil); err != nil {
@@ -99,21 +84,24 @@ func TestGetInfo(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if infos.Images != 1 {
t.Errorf("Excepted images: %d, %d found", 1, infos.Images)
if infos.Images != len(initialImages) {
t.Errorf("Expected images: %d, %d found", len(initialImages), infos.Images)
}
}
func TestGetImagesJSON(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
// all=0
initialImages, err := srv.Images(false, "")
if err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("GET", "/images/json?all=0", nil)
if err != nil {
t.Fatal(err)
@@ -130,17 +118,30 @@ func TestGetImagesJSON(t *testing.T) {
t.Fatal(err)
}
if len(images) != 1 {
t.Errorf("Excepted 1 image, %d found", len(images))
if len(images) != len(initialImages) {
t.Errorf("Expected %d image, %d found", len(initialImages), len(images))
}
if images[0].Repository != unitTestImageName {
t.Errorf("Excepted image %s, %s found", unitTestImageName, images[0].Repository)
found := false
for _, img := range images {
if img.Repository == unitTestImageName {
found = true
break
}
}
if !found {
t.Errorf("Expected image %s, %+v found", unitTestImageName, images)
}
r2 := httptest.NewRecorder()
// all=1
initialImages, err = srv.Images(true, "")
if err != nil {
t.Fatal(err)
}
req2, err := http.NewRequest("GET", "/images/json?all=true", nil)
if err != nil {
t.Fatal(err)
@@ -155,18 +156,25 @@ func TestGetImagesJSON(t *testing.T) {
t.Fatal(err)
}
if len(images2) != 1 {
t.Errorf("Excepted 1 image, %d found", len(images2))
if len(images2) != len(initialImages) {
t.Errorf("Expected %d image, %d found", len(initialImages), 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)
found = false
for _, img := range images2 {
if img.ID == GetTestImage(runtime).ID {
found = true
break
}
}
if !found {
t.Errorf("Retrieved image Id differs, expected %s, received %+v", GetTestImage(runtime).ID, images2)
}
r3 := httptest.NewRecorder()
// filter=a
req3, err := http.NewRequest("GET", "/images/json?filter=a", nil)
req3, err := http.NewRequest("GET", "/images/json?filter=aaaaaaaaaa", nil)
if err != nil {
t.Fatal(err)
}
@@ -181,7 +189,7 @@ func TestGetImagesJSON(t *testing.T) {
}
if len(images3) != 0 {
t.Errorf("Excepted 1 image, %d found", len(images3))
t.Errorf("Expected 0 image, %d found", len(images3))
}
r4 := httptest.NewRecorder()
@@ -204,10 +212,7 @@ func TestGetImagesJSON(t *testing.T) {
}
func TestGetImagesViz(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -227,46 +232,12 @@ func TestGetImagesViz(t *testing.T) {
t.Fatal(err)
}
if line != "digraph docker {\n" {
t.Errorf("Excepted digraph docker {\n, %s found", line)
}
}
func TestGetImagesSearch(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{
runtime: runtime,
}
r := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/images/search?term=redis", nil)
if err != nil {
t.Fatal(err)
}
if err := getImagesSearch(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
results := []APISearch{}
if err := json.Unmarshal(r.Body.Bytes(), &results); err != nil {
t.Fatal(err)
}
if len(results) < 2 {
t.Errorf("Excepted at least 2 lines, %d found", len(results))
t.Errorf("Expected digraph docker {\n, %s found", line)
}
}
func TestGetImagesHistory(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -282,15 +253,12 @@ func TestGetImagesHistory(t *testing.T) {
t.Fatal(err)
}
if len(history) != 1 {
t.Errorf("Excepted 1 line, %d found", len(history))
t.Errorf("Expected 1 line, %d found", len(history))
}
}
func TestGetImagesByName(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -304,16 +272,13 @@ 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 != unitTestImageID {
t.Errorf("Error inspecting image")
}
}
func TestGetContainersJSON(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -341,7 +306,7 @@ func TestGetContainersJSON(t *testing.T) {
t.Fatal(err)
}
if len(containers) != 1 {
t.Fatalf("Excepted %d container, %d found", 1, len(containers))
t.Fatalf("Expected %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)
@@ -349,10 +314,7 @@ func TestGetContainersJSON(t *testing.T) {
}
func TestGetContainersExport(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -404,10 +366,7 @@ func TestGetContainersExport(t *testing.T) {
}
func TestGetContainersChanges(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -451,7 +410,7 @@ func TestGetContainersChanges(t *testing.T) {
}
}
func TestGetContainersByName(t *testing.T) {
func TestGetContainersTop(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
@@ -462,6 +421,58 @@ func TestGetContainersByName(t *testing.T) {
builder := NewBuilder(runtime)
container, err := builder.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "sleep 2"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
// Give some time to the process to start
container.WaitTimeout(500 * time.Millisecond)
if !container.State.Running {
t.Errorf("Container should be running")
}
r := httptest.NewRecorder()
if err := getContainersTop(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
procs := []APITop{}
if err := json.Unmarshal(r.Body.Bytes(), &procs); err != nil {
t.Fatal(err)
}
if len(procs) != 2 {
t.Fatalf("Expected 2 processes, found %d.", len(procs))
}
if procs[0].Cmd != "sh" && procs[0].Cmd != "busybox" {
t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[0].Cmd)
}
if procs[1].Cmd != "sh" && procs[1].Cmd != "busybox" {
t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[1].Cmd)
}
}
func TestGetContainersByName(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
builder := NewBuilder(runtime)
// Create a container and remove a file
container, err := builder.Create(
&Config{
@@ -488,10 +499,7 @@ func TestGetContainersByName(t *testing.T) {
}
func TestPostCommit(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -536,249 +544,8 @@ func TestPostCommit(t *testing.T) {
}
}
func TestPostImagesCreate(t *testing.T) {
// FIXME: Use the staging in order to perform tests
// runtime, err := newTestRuntime()
// if err != nil {
// t.Fatal(err)
// }
// defer nuke(runtime)
// srv := &Server{runtime: runtime}
// stdin, stdinPipe := io.Pipe()
// stdout, stdoutPipe := io.Pipe()
// c1 := make(chan struct{})
// go func() {
// defer close(c1)
// r := &hijackTester{
// ResponseRecorder: httptest.NewRecorder(),
// in: stdin,
// out: stdoutPipe,
// }
// req, err := http.NewRequest("POST", "/images/create?fromImage="+unitTestImageName, bytes.NewReader([]byte{}))
// if err != nil {
// t.Fatal(err)
// }
// body, err := postImagesCreate(srv, r, req, nil)
// if err != nil {
// t.Fatal(err)
// }
// if body != nil {
// t.Fatalf("No body expected, received: %s\n", body)
// }
// }()
// // Acknowledge hijack
// setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() {
// stdout.Read([]byte{})
// stdout.Read(make([]byte, 4096))
// })
// setTimeout(t, "Waiting for imagesCreate output", 5*time.Second, func() {
// reader := bufio.NewReader(stdout)
// line, err := reader.ReadString('\n')
// if err != nil {
// t.Fatal(err)
// }
// if !strings.HasPrefix(line, "Pulling repository d from") {
// t.Fatalf("Expected Pulling repository docker-ut from..., found %s", line)
// }
// })
// // Close pipes (client disconnects)
// if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
// t.Fatal(err)
// }
// // Wait for imagesCreate to finish, the client disconnected, therefore, Create finished his job
// setTimeout(t, "Waiting for imagesCreate timed out", 10*time.Second, func() {
// <-c1
// })
}
func TestPostImagesInsert(t *testing.T) {
// runtime, err := newTestRuntime()
// if err != nil {
// t.Fatal(err)
// }
// defer nuke(runtime)
// srv := &Server{runtime: runtime}
// stdin, stdinPipe := io.Pipe()
// stdout, stdoutPipe := io.Pipe()
// // Attach to it
// c1 := make(chan struct{})
// go func() {
// defer close(c1)
// r := &hijackTester{
// ResponseRecorder: httptest.NewRecorder(),
// in: stdin,
// out: stdoutPipe,
// }
// req, err := http.NewRequest("POST", "/images/"+unitTestImageName+"/insert?path=%2Ftest&url=https%3A%2F%2Fraw.github.com%2Fdotcloud%2Fdocker%2Fmaster%2FREADME.md", bytes.NewReader([]byte{}))
// if err != nil {
// t.Fatal(err)
// }
// if err := postContainersCreate(srv, r, req, nil); err != nil {
// t.Fatal(err)
// }
// }()
// // Acknowledge hijack
// setTimeout(t, "hijack acknowledge timed out", 5*time.Second, func() {
// stdout.Read([]byte{})
// stdout.Read(make([]byte, 4096))
// })
// id := ""
// setTimeout(t, "Waiting for imagesInsert output", 10*time.Second, func() {
// for {
// reader := bufio.NewReader(stdout)
// id, err = reader.ReadString('\n')
// if err != nil {
// t.Fatal(err)
// }
// }
// })
// // Close pipes (client disconnects)
// if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
// t.Fatal(err)
// }
// // Wait for attach to finish, the client disconnected, therefore, Attach finished his job
// setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
// <-c1
// })
// img, err := srv.runtime.repositories.LookupImage(id)
// if err != nil {
// t.Fatalf("New image %s expected but not found", id)
// }
// layer, err := img.layer()
// if err != nil {
// t.Fatal(err)
// }
// if _, err := os.Stat(path.Join(layer, "test")); err != nil {
// t.Fatalf("The test file has not been found")
// }
// if err := srv.runtime.graph.Delete(img.ID); err != nil {
// t.Fatal(err)
// }
}
func TestPostImagesPush(t *testing.T) {
//FIXME: Use staging in order to perform tests
// runtime, err := newTestRuntime()
// if err != nil {
// t.Fatal(err)
// }
// defer nuke(runtime)
// srv := &Server{runtime: runtime}
// stdin, stdinPipe := io.Pipe()
// stdout, stdoutPipe := io.Pipe()
// c1 := make(chan struct{})
// go func() {
// r := &hijackTester{
// ResponseRecorder: httptest.NewRecorder(),
// in: stdin,
// out: stdoutPipe,
// }
// req, err := http.NewRequest("POST", "/images/docker-ut/push", bytes.NewReader([]byte{}))
// if err != nil {
// t.Fatal(err)
// }
// body, err := postImagesPush(srv, r, req, map[string]string{"name": "docker-ut"})
// close(c1)
// if err != nil {
// t.Fatal(err)
// }
// if body != nil {
// t.Fatalf("No body expected, received: %s\n", body)
// }
// }()
// // Acknowledge hijack
// setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() {
// stdout.Read([]byte{})
// stdout.Read(make([]byte, 4096))
// })
// setTimeout(t, "Waiting for imagesCreate output", 5*time.Second, func() {
// reader := bufio.NewReader(stdout)
// line, err := reader.ReadString('\n')
// if err != nil {
// t.Fatal(err)
// }
// if !strings.HasPrefix(line, "Processing checksum") {
// t.Fatalf("Processing checksum..., found %s", line)
// }
// })
// // Close pipes (client disconnects)
// if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
// t.Fatal(err)
// }
// // Wait for imagesPush to finish, the client disconnected, therefore, Push finished his job
// setTimeout(t, "Waiting for imagesPush timed out", 10*time.Second, func() {
// <-c1
// })
}
func TestPostImagesTag(t *testing.T) {
// FIXME: Use staging in order to perform tests
// runtime, err := newTestRuntime()
// if err != nil {
// t.Fatal(err)
// }
// defer nuke(runtime)
// srv := &Server{runtime: runtime}
// r := httptest.NewRecorder()
// req, err := http.NewRequest("POST", "/images/docker-ut/tag?repo=testrepo&tag=testtag", bytes.NewReader([]byte{}))
// if err != nil {
// t.Fatal(err)
// }
// body, err := postImagesTag(srv, r, req, map[string]string{"name": "docker-ut"})
// if err != nil {
// t.Fatal(err)
// }
// if body != nil {
// t.Fatalf("No body expected, received: %s\n", body)
// }
// if r.Code != http.StatusCreated {
// t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
// }
}
func TestPostContainersCreate(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -829,10 +596,7 @@ func TestPostContainersCreate(t *testing.T) {
}
func TestPostContainersKill(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -849,7 +613,8 @@ func TestPostContainersKill(t *testing.T) {
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -873,10 +638,7 @@ func TestPostContainersKill(t *testing.T) {
}
func TestPostContainersRestart(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -893,7 +655,8 @@ func TestPostContainersRestart(t *testing.T) {
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -929,10 +692,7 @@ func TestPostContainersRestart(t *testing.T) {
}
func TestPostContainersStart(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -949,8 +709,15 @@ func TestPostContainersStart(t *testing.T) {
}
defer runtime.Destroy(container)
hostConfigJSON, err := json.Marshal(&HostConfig{})
req, err := http.NewRequest("POST", "/containers/"+container.ID+"/start", bytes.NewReader(hostConfigJSON))
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusNoContent {
@@ -965,7 +732,7 @@ func TestPostContainersStart(t *testing.T) {
}
r = httptest.NewRecorder()
if err = postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err == nil {
if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil {
t.Fatalf("A running containter should be able to be started")
}
@@ -975,10 +742,7 @@ func TestPostContainersStart(t *testing.T) {
}
func TestPostContainersStop(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -995,7 +759,8 @@ func TestPostContainersStop(t *testing.T) {
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -1024,10 +789,7 @@ func TestPostContainersStop(t *testing.T) {
}
func TestPostContainersWait(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -1044,7 +806,8 @@ func TestPostContainersWait(t *testing.T) {
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -1068,10 +831,7 @@ func TestPostContainersWait(t *testing.T) {
}
func TestPostContainersAttach(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -1089,7 +849,8 @@ func TestPostContainersAttach(t *testing.T) {
defer runtime.Destroy(container)
// Start the process
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -1156,10 +917,7 @@ func TestPostContainersAttach(t *testing.T) {
// FIXME: Test deleting container with volume
// FIXME: Test deleting volume in use by other container
func TestDeleteContainers(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
@@ -1199,10 +957,7 @@ func TestDeleteContainers(t *testing.T) {
}
func TestOptionsRoute(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime, enableCors: true}
@@ -1225,10 +980,7 @@ func TestOptionsRoute(t *testing.T) {
}
func TestGetEnabledCors(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime, enableCors: true}
@@ -1266,14 +1018,16 @@ func TestGetEnabledCors(t *testing.T) {
}
func TestDeleteImages(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
initialImages, err := srv.Images(false, "")
if err != nil {
t.Fatal(err)
}
if err := srv.runtime.repositories.Set("test", "test", unitTestImageName, true); err != nil {
t.Fatal(err)
}
@@ -1283,25 +1037,35 @@ func TestDeleteImages(t *testing.T) {
t.Fatal(err)
}
if len(images) != 2 {
t.Errorf("Excepted 2 images, %d found", len(images))
if len(images) != len(initialImages)+1 {
t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
}
req, err := http.NewRequest("DELETE", "/images/test:test", nil)
req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, 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 {
if err := deleteImages(srv, APIVERSION, r, req, map[string]string{"name": unitTestImageID}); err == nil {
t.Fatalf("Expected conflict error, got none")
}
req2, err := http.NewRequest("DELETE", "/images/test:test", nil)
if err != nil {
t.Fatal(err)
}
if r.Code != http.StatusOK {
r2 := httptest.NewRecorder()
if err := deleteImages(srv, APIVERSION, r2, req2, map[string]string{"name": "test:test"}); err != nil {
t.Fatal(err)
}
if r2.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 {
if err := json.Unmarshal(r2.Body.Bytes(), &outs); err != nil {
t.Fatal(err)
}
if len(outs) != 1 {
@@ -1312,8 +1076,8 @@ func TestDeleteImages(t *testing.T) {
t.Fatal(err)
}
if len(images) != 1 {
t.Errorf("Excepted 1 image, %d found", len(images))
if len(images) != len(initialImages) {
t.Errorf("Expected %d image, %d found", len(initialImages), len(images))
}
/* if c := runtime.Get(container.Id); c != nil {

View File

@@ -1,8 +1,8 @@
package docker
import (
"bufio"
"errors"
"archive/tar"
"bytes"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
@@ -10,6 +10,7 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
)
type Archive io.Reader
@@ -24,10 +25,6 @@ const (
)
func DetectCompression(source []byte) Compression {
for _, c := range source[:10] {
utils.Debugf("%x", c)
}
sourceLen := len(source)
for compression, m := range map[Compression][]byte{
Bzip2: {0x42, 0x5A, 0x68},
@@ -89,7 +86,7 @@ func Tar(path string, compression Compression) (io.Reader, error) {
// Tar creates an archive from the directory at `path`, only including files whose relative
// paths are included in `filter`. If `filter` is nil, then all files are included.
func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) {
args := []string{"tar", "-f", "-", "-C", path}
args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path}
if filter == nil {
filter = []string{"."}
}
@@ -105,18 +102,29 @@ func TarFilter(path string, compression Compression, filter []string) (io.Reader
// identity (uncompressed), gzip, bzip2, xz.
// FIXME: specify behavior when target path exists vs. doesn't exist.
func Untar(archive io.Reader, path string) error {
if archive == nil {
return fmt.Errorf("Empty archive")
}
bufferedArchive := bufio.NewReaderSize(archive, 10)
buf, err := bufferedArchive.Peek(10)
if err != nil {
return err
buf := make([]byte, 10)
totalN := 0
for totalN < 10 {
if n, err := archive.Read(buf[totalN:]); err != nil {
if err == io.EOF {
return fmt.Errorf("Tarball too short")
}
return err
} else {
totalN += n
utils.Debugf("[tar autodetect] n: %d", n)
}
}
compression := DetectCompression(buf)
utils.Debugf("Archive compression detected: %s", compression.Extension())
cmd := exec.Command("tar", "-f", "-", "-C", path, "-x"+compression.Flag())
cmd.Stdin = bufferedArchive
cmd := exec.Command("tar", "--numeric-owner", "-f", "-", "-C", path, "-x"+compression.Flag())
cmd.Stdin = io.MultiReader(bytes.NewReader(buf), 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"}
@@ -160,51 +168,60 @@ func CopyWithTar(src, dst string) error {
if err != nil {
return err
}
var dstExists bool
dstSt, err := os.Stat(dst)
if err != nil {
if !os.IsNotExist(err) {
return err
}
} else {
dstExists = true
}
// Things that can go wrong if the source is a directory
if srcSt.IsDir() {
// The destination exists and is a regular file
if dstExists && !dstSt.IsDir() {
return fmt.Errorf("Can't copy a directory over a regular file")
}
// Things that can go wrong if the source is a regular file
} else {
utils.Debugf("The destination exists, it's a directory, and doesn't end in /")
// The destination exists, it's a directory, and doesn't end in /
if dstExists && dstSt.IsDir() && dst[len(dst)-1] != '/' {
return fmt.Errorf("Can't copy a regular file over a directory %s |%s|", dst, dst[len(dst)-1])
}
}
// Create the destination
var dstDir string
if srcSt.IsDir() || dst[len(dst)-1] == '/' {
// The destination ends in /, or the source is a directory
// --> dst is the holding directory and needs to be created for -C
dstDir = dst
} else {
// The destination doesn't end in /
// --> dst is the file
dstDir = path.Dir(dst)
}
if !dstExists {
// Create the holding directory if necessary
utils.Debugf("Creating the holding directory %s", dstDir)
if err := os.MkdirAll(dstDir, 0700); err != nil && !os.IsExist(err) {
return err
}
}
if !srcSt.IsDir() {
return TarUntar(path.Dir(src), []string{path.Base(src)}, dstDir)
return CopyFileWithTar(src, dst)
}
return TarUntar(src, nil, dstDir)
// Create dst, copy src's content into it
utils.Debugf("Creating dest directory: %s", dst)
if err := os.MkdirAll(dst, 0700); err != nil && !os.IsExist(err) {
return err
}
utils.Debugf("Calling TarUntar(%s, %s)", src, dst)
return TarUntar(src, nil, dst)
}
// CopyFileWithTar emulates the behavior of the 'cp' command-line
// for a single file. It copies a regular file from path `src` to
// path `dst`, and preserves all its metadata.
//
// If `dst` ends with a trailing slash '/', the final destination path
// will be `dst/base(src)`.
func CopyFileWithTar(src, dst string) error {
utils.Debugf("CopyFileWithTar(%s, %s)", src, dst)
srcSt, err := os.Stat(src)
if err != nil {
return err
}
if srcSt.IsDir() {
return fmt.Errorf("Can't copy a directory")
}
// Clean up the trailing /
if dst[len(dst)-1] == '/' {
dst = path.Join(dst, filepath.Base(src))
}
// Create the holding directory if necessary
if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
return err
}
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
hdr, err := tar.FileInfoHeader(srcSt, "")
if err != nil {
return err
}
hdr.Name = filepath.Base(dst)
if err := tw.WriteHeader(hdr); err != nil {
return err
}
srcF, err := os.Open(src)
if err != nil {
return err
}
if _, err := io.Copy(tw, srcF); err != nil {
return err
}
tw.Close()
return Untar(buf, filepath.Dir(dst))
}
// CmdStream executes a command, and returns its stdout as a stream.
@@ -237,7 +254,7 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
}
errText := <-errChan
if err := cmd.Wait(); err != nil {
pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText)))
pipeW.CloseWithError(fmt.Errorf("%s: %s", err, errText))
} else {
pipeW.Close()
}

View File

@@ -16,7 +16,7 @@ func TestCmdStreamLargeStderr(t *testing.T) {
cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
out, err := CmdStream(cmd)
if err != nil {
t.Fatalf("Failed to start command: " + err.Error())
t.Fatalf("Failed to start command: %s", err)
}
errCh := make(chan error)
go func() {
@@ -26,7 +26,7 @@ func TestCmdStreamLargeStderr(t *testing.T) {
select {
case err := <-errCh:
if err != nil {
t.Fatalf("Command should not have failed (err=%s...)", err.Error()[:100])
t.Fatalf("Command should not have failed (err=%.100s...)", err)
}
case <-time.After(5 * time.Second):
t.Fatalf("Command did not complete in 5 seconds; probable deadlock")
@@ -37,12 +37,12 @@ func TestCmdStreamBad(t *testing.T) {
badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
out, err := CmdStream(badCmd)
if err != nil {
t.Fatalf("Failed to start command: " + err.Error())
t.Fatalf("Failed to start command: %s", err)
}
if output, err := ioutil.ReadAll(out); err == nil {
t.Fatalf("Command should have failed")
} else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
t.Fatalf("Wrong error value (%s)", err.Error())
t.Fatalf("Wrong error value (%s)", err)
} else if s := string(output); s != "hello\n" {
t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
}

View File

@@ -15,8 +15,8 @@ import (
// Where we store the config file
const CONFIGFILE = ".dockercfg"
// the registry server we want to login against
const INDEXSERVER = "https://index.docker.io/v1"
// Only used for user auth + account creation
const INDEXSERVER = "https://index.docker.io/v1/"
//const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/"
@@ -41,9 +41,6 @@ func NewAuthConfig(username, password, email, rootPath string) *AuthConfig {
}
func IndexServerAddress() string {
if os.Getenv("DOCKER_INDEX_URL") != "" {
return os.Getenv("DOCKER_INDEX_URL") + "/v1"
}
return INDEXSERVER
}
@@ -74,7 +71,6 @@ func decodeAuth(authStr string) (*AuthConfig, error) {
}
password := strings.Trim(arr[1], "\x00")
return &AuthConfig{Username: arr[0], Password: password}, nil
}
// load up the auth config information and return values
@@ -133,7 +129,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
b := strings.NewReader(string(jsonBody))
req1, err := http.Post(IndexServerAddress()+"/users/", "application/json; charset=utf-8", b)
req1, err := http.Post(IndexServerAddress()+"users/", "application/json; charset=utf-8", b)
if err != nil {
return "", fmt.Errorf("Server Error: %s", err)
}
@@ -146,14 +142,14 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
if reqStatusCode == 201 {
status = "Account created. Please use the confirmation link we sent" +
" to your e-mail to activate it.\n"
" to your e-mail to activate it."
storeConfig = true
} else if reqStatusCode == 403 {
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
"Please check your e-mail for a confirmation link.")
} else if reqStatusCode == 400 {
if string(reqBody) == "\"Username or email already exists\"" {
req, err := http.NewRequest("GET", IndexServerAddress()+"/users/", nil)
req, err := http.NewRequest("GET", IndexServerAddress()+"users/", nil)
req.SetBasicAuth(authConfig.Username, authConfig.Password)
resp, err := client.Do(req)
if err != nil {
@@ -165,10 +161,11 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
return "", err
}
if resp.StatusCode == 200 {
status = "Login Succeeded\n"
status = "Login Succeeded"
storeConfig = true
} else if resp.StatusCode == 401 {
if store {
authConfig.Email = ""
if err := SaveConfig(authConfig); err != nil {
return "", err
}

View File

@@ -34,7 +34,7 @@ func TestLogin(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if status != "Login Succeeded\n" {
if status != "Login Succeeded" {
t.Fatalf("Expected status \"Login Succeeded\", found \"%s\" instead", status)
}
}
@@ -55,7 +55,7 @@ func TestCreateAccount(t *testing.T) {
t.Fatal(err)
}
expectedStatus := "Account created. Please use the confirmation link we sent" +
" to your e-mail to activate it.\n"
" to your e-mail to activate it."
if status != expectedStatus {
t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
}
@@ -68,6 +68,6 @@ func TestCreateAccount(t *testing.T) {
expectedError := "Login: Account is not Active"
if !strings.Contains(err.Error(), expectedError) {
t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err.Error())
t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err)
}
}

View File

@@ -50,12 +50,23 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
config.Hostname = id[:12]
}
var args []string
var entrypoint string
if len(config.Entrypoint) != 0 {
entrypoint = config.Entrypoint[0]
args = append(config.Entrypoint[1:], config.Cmd...)
} else {
entrypoint = config.Cmd[0]
args = config.Cmd[1:]
}
container := &Container{
// FIXME: we should generate the ID here instead of receiving it as an argument
ID: id,
Created: time.Now(),
Path: config.Cmd[0],
Args: config.Cmd[1:], //FIXME: de-duplicate from config
Path: entrypoint,
Args: args, //FIXME: de-duplicate from config
Config: config,
Image: img.ID, // Always use the resolved image id
NetworkSettings: &NetworkSettings{},

View File

@@ -1,314 +0,0 @@
package docker
import (
"bufio"
"encoding/json"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"net/url"
"os"
"reflect"
"strings"
)
type builderClient struct {
cli *DockerCli
image string
maintainer string
config *Config
tmpContainers map[string]struct{}
tmpImages map[string]struct{}
needCommit bool
}
func (b *builderClient) clearTmp(containers, images map[string]struct{}) {
for i := range images {
if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil {
utils.Debugf("%s", err)
}
utils.Debugf("Removing image %s", i)
}
}
func (b *builderClient) CmdFrom(name string) error {
obj, statusCode, err := b.cli.call("GET", "/images/"+name+"/json", nil)
if statusCode == 404 {
remote := name
var tag string
if strings.Contains(remote, ":") {
remoteParts := strings.Split(remote, ":")
tag = remoteParts[1]
remote = remoteParts[0]
}
var out io.Writer
if os.Getenv("DEBUG") != "" {
out = os.Stdout
} else {
out = &utils.NopWriter{}
}
if err := b.cli.stream("POST", "/images/create?fromImage="+remote+"&tag="+tag, nil, out); err != nil {
return err
}
obj, _, err = b.cli.call("GET", "/images/"+name+"/json", nil)
if err != nil {
return err
}
}
if err != nil {
return err
}
img := &APIID{}
if err := json.Unmarshal(obj, img); err != nil {
return err
}
b.image = img.ID
utils.Debugf("Using image %s", b.image)
return nil
}
func (b *builderClient) CmdMaintainer(name string) error {
b.needCommit = true
b.maintainer = name
return nil
}
func (b *builderClient) CmdRun(args string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to run")
}
config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
if err != nil {
return err
}
cmd, env := b.config.Cmd, b.config.Env
b.config.Cmd = nil
MergeConfig(b.config, 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 {
return err
}
utils.Debugf("Use cached version")
b.image = apiID.ID
return nil
}
cid, err := b.run()
if err != nil {
return err
}
b.config.Cmd, b.config.Env = cmd, env
return b.commit(cid)
}
func (b *builderClient) CmdEnv(args string) error {
b.needCommit = true
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ENV format")
}
key := strings.Trim(tmp[0], " ")
value := strings.Trim(tmp[1], " ")
for i, elem := range b.config.Env {
if strings.HasPrefix(elem, key+"=") {
b.config.Env[i] = key + "=" + value
return nil
}
}
b.config.Env = append(b.config.Env, key+"="+value)
return nil
}
func (b *builderClient) CmdCmd(args string) error {
b.needCommit = true
var cmd []string
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
b.config.Cmd = []string{"/bin/sh", "-c", args}
} else {
b.config.Cmd = cmd
}
return nil
}
func (b *builderClient) CmdExpose(args string) error {
ports := strings.Split(args, " ")
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
return nil
}
func (b *builderClient) CmdInsert(args string) error {
// tmp := strings.SplitN(args, "\t ", 2)
// sourceUrl, destPath := tmp[0], tmp[1]
// v := url.Values{}
// v.Set("url", sourceUrl)
// v.Set("path", destPath)
// body, _, err := b.cli.call("POST", "/images/insert?"+v.Encode(), nil)
// if err != nil {
// return err
// }
// apiId := &APIId{}
// if err := json.Unmarshal(body, apiId); err != nil {
// return err
// }
// FIXME: Reimplement this, we need to retrieve the resulting Id
return fmt.Errorf("INSERT not implemented")
}
func (b *builderClient) run() (string, error) {
if b.image == "" {
return "", fmt.Errorf("Please provide a source image with `from` prior to run")
}
b.config.Image = b.image
body, _, err := b.cli.call("POST", "/containers/create", b.config)
if err != nil {
return "", err
}
apiRun := &APIRun{}
if err := json.Unmarshal(body, apiRun); err != nil {
return "", err
}
for _, warning := range apiRun.Warnings {
fmt.Fprintln(os.Stderr, "WARNING: ", warning)
}
//start the container
_, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/start", nil)
if err != nil {
return "", err
}
b.tmpContainers[apiRun.ID] = struct{}{}
// Wait for it to finish
body, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/wait", nil)
if err != nil {
return "", err
}
apiWait := &APIWait{}
if err := json.Unmarshal(body, apiWait); err != nil {
return "", err
}
if apiWait.StatusCode != 0 {
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode)
}
return apiRun.ID, nil
}
func (b *builderClient) commit(id string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to run")
}
b.config.Image = b.image
if id == "" {
cmd := b.config.Cmd
b.config.Cmd = []string{"true"}
cid, err := b.run()
if err != nil {
return err
}
id = cid
b.config.Cmd = cmd
}
// Commit the container
v := url.Values{}
v.Set("container", id)
v.Set("author", b.maintainer)
body, _, err := b.cli.call("POST", "/commit?"+v.Encode(), b.config)
if err != nil {
return err
}
apiID := &APIID{}
if err := json.Unmarshal(body, apiID); err != nil {
return err
}
b.tmpImages[apiID.ID] = struct{}{}
b.image = apiID.ID
b.needCommit = false
return nil
}
func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) {
defer b.clearTmp(b.tmpContainers, b.tmpImages)
file := bufio.NewReader(dockerfile)
for {
line, err := file.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return "", err
}
line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
// Skip comments and empty line
if len(line) == 0 || line[0] == '#' {
continue
}
tmp := strings.SplitN(line, " ", 2)
if len(tmp) != 2 {
return "", fmt.Errorf("Invalid Dockerfile format")
}
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
arguments := strings.Trim(tmp[1], " ")
fmt.Fprintf(os.Stderr, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
if !exists {
fmt.Fprintf(os.Stderr, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
}
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
if ret != nil {
return "", ret.(error)
}
fmt.Fprintf(os.Stderr, "===> %v\n", b.image)
}
if b.needCommit {
if err := b.commit(""); err != nil {
return "", err
}
}
if b.image != "" {
// The build is successful, keep the temporary containers and images
for i := range b.tmpImages {
delete(b.tmpImages, i)
}
for i := range b.tmpContainers {
delete(b.tmpContainers, i)
}
fmt.Fprintf(os.Stderr, "Build finished. image id: %s\n", b.image)
return b.image, nil
}
return "", fmt.Errorf("An error occured during the build\n")
}
func NewBuilderClient(proto, addr string) BuildFile {
return &builderClient{
cli: NewDockerCli(proto, addr),
config: &Config{},
tmpContainers: make(map[string]struct{}),
tmpImages: make(map[string]struct{}),
}
}

View File

@@ -14,7 +14,7 @@ import (
)
type BuildFile interface {
Build(io.Reader, io.Reader) (string, error)
Build(io.Reader) (string, error)
CmdFrom(string) error
CmdRun(string) error
}
@@ -28,6 +28,7 @@ type buildFile struct {
maintainer string
config *Config
context string
verbose bool
tmpContainers map[string]struct{}
tmpImages map[string]struct{}
@@ -51,20 +52,10 @@ func (b *buildFile) CmdFrom(name string) error {
image, err := b.runtime.repositories.LookupImage(name)
if err != nil {
if b.runtime.graph.IsNotExist(err) {
var tag, remote string
if strings.Contains(name, ":") {
remoteParts := strings.Split(name, ":")
tag = remoteParts[1]
remote = remoteParts[0]
} else {
remote = name
}
if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false), nil); err != nil {
remote, tag := utils.ParseRepositoryTag(name)
if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil); err != nil {
return err
}
image, err = b.runtime.repositories.LookupImage(name)
if err != nil {
return err
@@ -87,7 +78,7 @@ func (b *buildFile) CmdRun(args string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to run")
}
config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
config, _, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
if err != nil {
return err
}
@@ -125,8 +116,8 @@ func (b *buildFile) CmdEnv(args string) error {
if len(tmp) != 2 {
return fmt.Errorf("Invalid ENV format")
}
key := strings.Trim(tmp[0], " ")
value := strings.Trim(tmp[1], " ")
key := strings.Trim(tmp[0], " \t")
value := strings.Trim(tmp[1], " \t")
for i, elem := range b.config.Env {
if strings.HasPrefix(elem, key+"=") {
@@ -141,7 +132,7 @@ func (b *buildFile) CmdEnv(args string) error {
func (b *buildFile) CmdCmd(args string) error {
var cmd []string
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
utils.Debugf("Error unmarshalling: %s, setting cmd to /bin/sh -c", err)
cmd = []string{"/bin/sh", "-c", args}
}
if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
@@ -165,34 +156,55 @@ func (b *buildFile) CmdCopy(args string) error {
return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
}
func (b *buildFile) CmdAdd(args string) error {
if b.context == "" {
return fmt.Errorf("No context given. Impossible to use ADD")
func (b *buildFile) CmdEntrypoint(args string) error {
if args == "" {
return fmt.Errorf("Entrypoint cannot be empty")
}
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ADD format")
var entrypoint []string
if err := json.Unmarshal([]byte(args), &entrypoint); err != nil {
b.config.Entrypoint = []string{"/bin/sh", "-c", args}
} else {
b.config.Entrypoint = entrypoint
}
orig := strings.Trim(tmp[0], " ")
dest := strings.Trim(tmp[1], " ")
if err := b.commit("", b.config.Cmd, fmt.Sprintf("ENTRYPOINT %s", args)); err != nil {
return err
}
return nil
}
cmd := b.config.Cmd
func (b *buildFile) CmdVolume(args string) error {
if args == "" {
return fmt.Errorf("Volume cannot be empty")
}
// Create the container and start it
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
b.config.Image = b.image
container, err := b.builder.Create(b.config)
var volume []string
if err := json.Unmarshal([]byte(args), &volume); err != nil {
volume = []string{args}
}
if b.config.Volumes == nil {
b.config.Volumes = NewPathOpts()
}
for _, v := range volume {
b.config.Volumes[v] = struct{}{}
}
if err := b.commit("", b.config.Cmd, fmt.Sprintf("VOLUME %s", args)); err != nil {
return err
}
return nil
}
func (b *buildFile) addRemote(container *Container, orig, dest string) error {
file, err := utils.Download(orig, ioutil.Discard)
if err != nil {
return err
}
b.tmpContainers[container.ID] = struct{}{}
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
defer file.Body.Close()
if err := container.EnsureMounted(); err != nil {
return err
}
defer container.Unmount()
return container.Inject(file.Body, dest)
}
func (b *buildFile) addContext(container *Container, orig, dest string) error {
origPath := path.Join(b.context, orig)
destPath := path.Join(container.RootfsPath(), dest)
// Preserve the trailing '/'
@@ -218,6 +230,46 @@ func (b *buildFile) CmdAdd(args string) error {
return err
}
}
return nil
}
func (b *buildFile) CmdAdd(args string) error {
if b.context == "" {
return fmt.Errorf("No context given. Impossible to use ADD")
}
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ADD format")
}
orig := strings.Trim(tmp[0], " \t")
dest := strings.Trim(tmp[1], " \t")
cmd := b.config.Cmd
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
b.config.Image = b.image
// Create the container and start it
container, err := b.builder.Create(b.config)
if err != nil {
return err
}
b.tmpContainers[container.ID] = struct{}{}
if err := container.EnsureMounted(); err != nil {
return err
}
defer container.Unmount()
if utils.IsURL(orig) {
if err := b.addRemote(container, orig, dest); err != nil {
return err
}
} else {
if err := b.addContext(container, orig, dest); err != nil {
return err
}
}
if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
return err
}
@@ -239,11 +291,23 @@ func (b *buildFile) run() (string, error) {
b.tmpContainers[c.ID] = struct{}{}
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID))
// override the entry point that may have been picked up from the base image
c.Path = b.config.Cmd[0]
c.Args = b.config.Cmd[1:]
//start the container
if err := c.Start(); err != nil {
hostConfig := &HostConfig{}
if err := c.Start(hostConfig); err != nil {
return "", err
}
if b.verbose {
err = <-c.Attach(nil, nil, b.out, b.out)
if err != nil {
return "", err
}
}
// Wait for it to finish
if ret := c.Wait(); ret != 0 {
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret)
@@ -259,7 +323,9 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
}
b.config.Image = b.image
if id == "" {
cmd := b.config.Cmd
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
return err
@@ -271,21 +337,17 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
} else {
utils.Debugf("[BUILDER] Cache miss")
}
// Create the container and start it
container, err := b.builder.Create(b.config)
if err != nil {
return err
}
b.tmpContainers[container.ID] = struct{}{}
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
id = container.ID
if err := container.EnsureMounted(); err != nil {
return err
}
defer container.Unmount()
id = container.ID
}
container := b.runtime.Get(id)
@@ -306,18 +368,23 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
return nil
}
func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
if context != nil {
name, err := ioutil.TempDir("/tmp", "docker-build")
if err != nil {
return "", err
}
if err := Untar(context, name); err != nil {
return "", err
}
defer os.RemoveAll(name)
b.context = name
func (b *buildFile) Build(context io.Reader) (string, error) {
// FIXME: @creack any reason for using /tmp instead of ""?
// FIXME: @creack "name" is a terrible variable name
name, err := ioutil.TempDir("/tmp", "docker-build")
if err != nil {
return "", err
}
if err := Untar(context, name); err != nil {
return "", err
}
defer os.RemoveAll(name)
b.context = name
dockerfile, err := os.Open(path.Join(name, "Dockerfile"))
if err != nil {
return "", fmt.Errorf("Can't build a directory with no Dockerfile")
}
// FIXME: "file" is also a terrible variable name ;)
file := bufio.NewReader(dockerfile)
stepN := 0
for {
@@ -329,7 +396,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
return "", err
}
}
line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n")
// Skip comments and empty line
if len(line) == 0 || line[0] == '#' {
continue
@@ -363,7 +430,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
return "", fmt.Errorf("An error occured during the build\n")
}
func NewBuildFile(srv *Server, out io.Writer) BuildFile {
func NewBuildFile(srv *Server, out io.Writer, verbose bool) BuildFile {
return &buildFile{
builder: NewBuilder(srv.runtime),
runtime: srv.runtime,
@@ -372,5 +439,6 @@ func NewBuildFile(srv *Server, out io.Writer) BuildFile {
out: out,
tmpContainers: make(map[string]struct{}),
tmpImages: make(map[string]struct{}),
verbose: verbose,
}
}

View File

@@ -1,89 +1,216 @@
package docker
import (
"github.com/dotcloud/docker/utils"
"strings"
"fmt"
"io/ioutil"
"testing"
)
const Dockerfile = `
# VERSION 0.1
# DOCKER-VERSION 0.2
// mkTestContext generates a build context from the contents of the provided dockerfile.
// This context is suitable for use as an argument to BuildFile.Build()
func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
context, err := mkBuildContext(fmt.Sprintf(dockerfile, unitTestImageID), files)
if err != nil {
t.Fatal(err)
}
return context
}
from ` + unitTestImageName + `
// A testContextTemplate describes a build context and how to test it
type testContextTemplate struct {
// Contents of the Dockerfile
dockerfile string
// Additional files in the context, eg [][2]string{"./passwd", "gordon"}
files [][2]string
}
// A table of all the contexts to build and test.
// A new docker runtime will be created and torn down for each context.
var testContexts = []testContextTemplate{
{
`
from %s
run sh -c 'echo root:testpass > /tmp/passwd'
run mkdir -p /var/run/sshd
`
run [ "$(cat /tmp/passwd)" = "root:testpass" ]
run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
`,
nil,
},
const DockerfileNoNewLine = `
# VERSION 0.1
# DOCKER-VERSION 0.2
{
`
from %s
add foo /usr/lib/bla/bar
run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ]
`,
[][2]string{{"foo", "hello world!"}},
},
from ` + unitTestImageName + `
run sh -c 'echo root:testpass > /tmp/passwd'
run mkdir -p /var/run/sshd`
{
`
from %s
add f /
run [ "$(cat /f)" = "hello" ]
add f /abc
run [ "$(cat /abc)" = "hello" ]
add f /x/y/z
run [ "$(cat /x/y/z)" = "hello" ]
add f /x/y/d/
run [ "$(cat /x/y/d/f)" = "hello" ]
add d /
run [ "$(cat /ga)" = "bu" ]
add d /somewhere
run [ "$(cat /somewhere/ga)" = "bu" ]
add d /anotherplace/
run [ "$(cat /anotherplace/ga)" = "bu" ]
add d /somewheeeere/over/the/rainbooow
run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
`,
[][2]string{
{"f", "hello"},
{"d/ga", "bu"},
},
},
// FIXME: test building with a context
{
`
from %s
env FOO BAR
run [ "$FOO" = "BAR" ]
`,
nil,
},
// FIXME: test building with a local ADD as first command
{
`
from %s
ENTRYPOINT /bin/echo
CMD Hello world
`,
nil,
},
{
`
from %s
VOLUME /test
CMD Hello world
`,
nil,
},
}
// FIXME: test building with 2 successive overlapping ADD commands
func TestBuild(t *testing.T) {
dockerfiles := []string{Dockerfile, DockerfileNoNewLine}
for _, Dockerfile := range dockerfiles {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
for _, ctx := range testContexts {
buildImage(ctx, t)
}
}
srv := &Server{runtime: runtime}
func buildImage(context testContextTemplate, t *testing.T) *Image {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
buildfile := NewBuildFile(srv, &utils.NopWriter{})
srv := &Server{
runtime: runtime,
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
}
buildfile := NewBuildFile(srv, ioutil.Discard, false)
imgID, err := buildfile.Build(strings.NewReader(Dockerfile), nil)
if err != nil {
t.Fatal(err)
}
id, err := buildfile.Build(mkTestContext(context.dockerfile, context.files, t))
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)
img, err := srv.ImageInspect(id)
if err != nil {
t.Fatal(err)
}
return img
}
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")
}
func TestVolume(t *testing.T) {
img := buildImage(testContextTemplate{`
from %s
volume /test
cmd Hello world
`, nil}, t)
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")
if len(img.Config.Volumes) == 0 {
t.Fail()
}
for key := range img.Config.Volumes {
if key != "/test" {
t.Fail()
}
}
}
func TestBuildMaintainer(t *testing.T) {
img := buildImage(testContextTemplate{`
from %s
maintainer dockerio
`, nil}, t)
if img.Author != "dockerio" {
t.Fail()
}
}
func TestBuildEnv(t *testing.T) {
img := buildImage(testContextTemplate{`
from %s
env port 4243
`,
nil}, t)
if img.Config.Env[0] != "port=4243" {
t.Fail()
}
}
func TestBuildCmd(t *testing.T) {
img := buildImage(testContextTemplate{`
from %s
cmd ["/bin/echo", "Hello World"]
`,
nil}, t)
if img.Config.Cmd[0] != "/bin/echo" {
t.Log(img.Config.Cmd[0])
t.Fail()
}
if img.Config.Cmd[1] != "Hello World" {
t.Log(img.Config.Cmd[1])
t.Fail()
}
}
func TestBuildExpose(t *testing.T) {
img := buildImage(testContextTemplate{`
from %s
expose 4243
`,
nil}, t)
if img.Config.PortSpecs[0] != "4243" {
t.Fail()
}
}
func TestBuildEntrypoint(t *testing.T) {
img := buildImage(testContextTemplate{`
from %s
entrypoint ["/bin/echo"]
`,
nil}, t)
if img.Config.Entrypoint[0] != "/bin/echo" {
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,9 @@ package docker
import (
"bufio"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
_ "io/ioutil"
"io/ioutil"
"strings"
"testing"
"time"
@@ -58,305 +59,58 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
return nil
}
/*TODO
func cmdWait(srv *Server, container *Container) error {
stdout, stdoutPipe := io.Pipe()
go func() {
srv.CmdWait(nil, stdoutPipe, container.Id)
}()
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
return err
}
// Cleanup pipes
return closeWrap(stdout, stdoutPipe)
}
func cmdImages(srv *Server, args ...string) (string, error) {
stdout, stdoutPipe := io.Pipe()
go func() {
if err := srv.CmdImages(nil, stdoutPipe, args...); err != nil {
return
}
// force the pipe closed, so that the code below gets an EOF
stdoutPipe.Close()
}()
output, err := ioutil.ReadAll(stdout)
if err != nil {
return "", err
}
// Cleanup pipes
return string(output), closeWrap(stdout, stdoutPipe)
}
// TestImages checks that 'docker images' displays information correctly
func TestImages(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
output, err := cmdImages(srv)
if !strings.Contains(output, "REPOSITORY") {
t.Fatal("'images' should have a header")
}
if !strings.Contains(output, "docker-ut") {
t.Fatal("'images' should show the docker-ut image")
}
if !strings.Contains(output, "e9aa60c60128") {
t.Fatal("'images' should show the docker-ut image id")
}
output, err = cmdImages(srv, "-q")
if strings.Contains(output, "REPOSITORY") {
t.Fatal("'images -q' should not have a header")
}
if strings.Contains(output, "docker-ut") {
t.Fatal("'images' should not show the docker-ut image name")
}
if !strings.Contains(output, "e9aa60c60128") {
t.Fatal("'images' should show the docker-ut image id")
}
output, err = cmdImages(srv, "-viz")
if !strings.HasPrefix(output, "digraph docker {") {
t.Fatal("'images -v' should start with the dot header")
}
if !strings.HasSuffix(output, "}\n") {
t.Fatal("'images -v' should end with a '}'")
}
if !strings.Contains(output, "base -> \"e9aa60c60128\" [style=invis]") {
t.Fatal("'images -v' should have the docker-ut image id node")
}
// todo: add checks for -a
}
// TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
func TestRunHostname(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
stdin, _ := io.Pipe()
stdout, stdoutPipe := io.Pipe()
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
c := make(chan struct{})
go func() {
if err := srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
defer close(c)
if err := cli.CmdRun("-h", "foobar", unitTestImageID, "hostname"); err != nil {
t.Fatal(err)
}
close(c)
}()
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if cmdOutput != "foobar\n" {
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
}
utils.Debugf("--")
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if cmdOutput != "foobar\n" {
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
}
})
setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c
cmdWait(srv, srv.runtime.List()[0])
})
}
func TestRunExit(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()
c1 := make(chan struct{})
go func() {
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
close(c1)
}()
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
t.Fatal(err)
}
})
container := runtime.List()[0]
// Closing /bin/cat stdin, expect it to exit
p, err := container.StdinPipe()
if err != nil {
t.Fatal(err)
}
if err := p.Close(); err != nil {
t.Fatal(err)
}
// as the process exited, CmdRun must finish and unblock. Wait for it
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
<-c1
cmdWait(srv, container)
})
// Make sure that the client has been disconnected
setTimeout(t, "The client should have been disconnected once the remote process exited.", 2*time.Second, func() {
// Expecting pipe i/o error, just check that read does not block
stdin.Read([]byte{})
})
// Cleanup pipes
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
}
// Expected behaviour: the process dies when the client disconnects
func TestRunDisconnect(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()
c1 := make(chan struct{})
go func() {
// We're simulating a disconnect so the return value doesn't matter. What matters is the
// fact that CmdRun returns.
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
close(c1)
}()
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
t.Fatal(err)
}
})
// Close pipes (simulate disconnect)
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
// as the pipes are close, we expect the process to die,
// therefore CmdRun to unblock. Wait for CmdRun
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
<-c1
})
// Client disconnect after run -i should cause stdin to be closed, which should
// cause /bin/cat to exit.
setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
container := runtime.List()[0]
container.Wait()
if container.State.Running {
t.Fatalf("/bin/cat is still running after closing stdin")
}
})
}
// Expected behaviour: the process dies when the client disconnects
func TestRunDisconnectTty(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()
c1 := make(chan struct{})
go func() {
// We're simulating a disconnect so the return value doesn't matter. What matters is the
// fact that CmdRun returns.
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-t", GetTestImage(runtime).Id, "/bin/cat")
close(c1)
}()
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
for {
// Client disconnect after run -i should keep stdin out in TTY mode
l := runtime.List()
if len(l) == 1 && l[0].State.Running {
break
}
time.Sleep(10 * time.Millisecond)
}
})
// Client disconnect after run -i should keep stdin out in TTY mode
container := runtime.List()[0]
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
t.Fatal(err)
}
})
// Close pipes (simulate disconnect)
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
// In tty mode, we expect the process to stay alive even after client's stdin closes.
// Do not wait for run to finish
// Give some time to monitor to do his thing
container.WaitTimeout(500 * time.Millisecond)
if !container.State.Running {
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
}
}
// TestAttachStdin checks attaching to stdin without stdout and stderr.
// 'docker run -i -a stdin' should sends the client's stdin to the command,
// then detach from it and print the container id.
func TestRunAttachStdin(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
ch := make(chan struct{})
go func() {
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
close(ch)
defer close(ch)
cli.CmdRun("-i", "-a", "stdin", unitTestImageID, "sh", "-c", "echo hello && cat")
}()
// Send input to the command, close stdin
setTimeout(t, "Write timed out", 2*time.Second, func() {
setTimeout(t, "Write timed out", 10*time.Second, func() {
if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
t.Fatal(err)
}
@@ -365,23 +119,27 @@ func TestRunAttachStdin(t *testing.T) {
}
})
container := runtime.List()[0]
container := globalRuntime.List()[0]
// Check output
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if cmdOutput != container.ShortId()+"\n" {
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput)
}
setTimeout(t, "Reading command output time out", 10*time.Second, func() {
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if cmdOutput != container.ShortID()+"\n" {
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortID()+"\n", cmdOutput)
}
})
// wait for CmdRun to return
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
setTimeout(t, "Waiting for CmdRun timed out", 5*time.Second, func() {
// Unblock hijack end
stdout.Read([]byte{})
<-ch
})
setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() {
setTimeout(t, "Waiting for command to exit timed out", 5*time.Second, func() {
container.Wait()
})
@@ -399,73 +157,3 @@ func TestRunAttachStdin(t *testing.T) {
}
}
}
// Expected behaviour, the process stays alive when the client disconnects
func TestAttachDisconnect(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
CpuShares: 1000,
Memory: 33554432,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
// Start the process
if err := container.Start(); err != nil {
t.Fatal(err)
}
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()
// Attach to it
c1 := make(chan struct{})
go func() {
// We're simulating a disconnect so the return value doesn't matter. What matters is the
// fact that CmdAttach returns.
srv.CmdAttach(stdin, rcli.NewDockerLocalConn(stdoutPipe), container.Id)
close(c1)
}()
setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
t.Fatal(err)
}
})
// Close pipes (client disconnects)
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
// Wait for attach to finish, the client disconnected, therefore, Attach finished his job
setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
<-c1
})
// We closed stdin, expect /bin/cat to still be running
// Wait a little bit to make sure container.monitor() did his thing
err = container.WaitTimeout(500 * time.Millisecond)
if err == nil || !container.State.Running {
t.Fatalf("/bin/cat is not running after closing stdin")
}
// Try to avoid the timeoout in destroy. Best effort, don't check error
cStdin, _ := container.StdinPipe()
cStdin.Close()
container.Wait()
}
*/

View File

@@ -52,6 +52,9 @@ type Container struct {
waitLock chan struct{}
Volumes map[string]string
// Store rw/ro in a separate structure to preserve reserve-compatibility on-disk.
// Easier than migrating older container configs :)
VolumesRW map[string]bool
}
type Config struct {
@@ -73,10 +76,21 @@ type Config struct {
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
Volumes map[string]struct{}
VolumesFrom string
Entrypoint []string
}
func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) {
cmd := Subcmd("run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
type HostConfig struct {
Binds []string
}
type BindMap struct {
SrcPath string
DstPath string
Mode string
}
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
if len(args) > 0 && args[0] != "--help" {
cmd.SetOutput(ioutil.Discard)
}
@@ -107,15 +121,16 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
cmd.Var(&flDns, "dns", "Set custom dns servers")
flVolumes := NewPathOpts()
cmd.Var(flVolumes, "v", "Attach a data volume")
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
if err := cmd.Parse(args); err != nil {
return nil, cmd, err
return nil, nil, cmd, err
}
if *flDetach && len(flAttach) > 0 {
return nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
return nil, nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
}
// If neither -d or -a are set, attach to everything by default
if len(flAttach) == 0 && !*flDetach {
@@ -127,8 +142,23 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
}
}
}
var binds []string
// add any bind targets to the list of container volumes
for bind := range flVolumes {
arr := strings.Split(bind, ":")
if len(arr) > 1 {
dstDir := arr[1]
flVolumes[dstDir] = struct{}{}
binds = append(binds, bind)
delete(flVolumes, bind)
}
}
parsedArgs := cmd.Args()
runCmd := []string{}
entrypoint := []string{}
image := ""
if len(parsedArgs) >= 1 {
image = cmd.Arg(0)
@@ -136,6 +166,10 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
if len(parsedArgs) > 1 {
runCmd = parsedArgs[1:]
}
if *flEntrypoint != "" {
entrypoint = []string{*flEntrypoint}
}
config := &Config{
Hostname: *flHostname,
PortSpecs: flPorts,
@@ -153,6 +187,10 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
Image: image,
Volumes: flVolumes,
VolumesFrom: *flVolumesFrom,
Entrypoint: entrypoint,
}
hostConfig := &HostConfig{
Binds: binds,
}
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
@@ -164,23 +202,28 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true
}
return config, cmd, nil
return config, hostConfig, cmd, nil
}
type PortMapping map[string]string
type NetworkSettings struct {
IPAddress string
IPPrefixLen int
Gateway string
Bridge string
PortMapping map[string]string
PortMapping map[string]PortMapping
}
// String returns a human-readable description of the port mapping defined in the settings
func (settings *NetworkSettings) PortMappingHuman() string {
var mapping []string
for private, public := range settings.PortMapping {
for private, public := range settings.PortMapping["Tcp"] {
mapping = append(mapping, fmt.Sprintf("%s->%s", public, private))
}
for private, public := range settings.PortMapping["Udp"] {
mapping = append(mapping, fmt.Sprintf("%s->%s/udp", public, private))
}
sort.Strings(mapping)
return strings.Join(mapping, ", ")
}
@@ -230,6 +273,26 @@ func (container *Container) ToDisk() (err error) {
return ioutil.WriteFile(container.jsonPath(), data, 0666)
}
func (container *Container) ReadHostConfig() (*HostConfig, error) {
data, err := ioutil.ReadFile(container.hostConfigPath())
if err != nil {
return &HostConfig{}, err
}
hostConfig := &HostConfig{}
if err := json.Unmarshal(data, hostConfig); err != nil {
return &HostConfig{}, err
}
return hostConfig, nil
}
func (container *Container) SaveHostConfig(hostConfig *HostConfig) (err error) {
data, err := json.Marshal(hostConfig)
if err != nil {
return
}
return ioutil.WriteFile(container.hostConfigPath(), data, 0666)
}
func (container *Container) generateLXCConfig() error {
fo, err := os.Create(container.lxcConfigPath())
if err != nil {
@@ -430,9 +493,13 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
})
}
func (container *Container) Start() error {
container.State.lock()
defer container.State.unlock()
func (container *Container) Start(hostConfig *HostConfig) error {
container.State.Lock()
defer container.State.Unlock()
if len(hostConfig.Binds) == 0 {
hostConfig, _ = container.ReadHostConfig()
}
if container.State.Running {
return fmt.Errorf("The container %s is already running.", container.ID)
@@ -454,17 +521,71 @@ func (container *Container) Start() error {
container.Config.MemorySwap = -1
}
container.Volumes = make(map[string]string)
container.VolumesRW = make(map[string]bool)
// Create the requested bind mounts
binds := make(map[string]BindMap)
// Define illegal container destinations
illegalDsts := []string{"/", "."}
for _, bind := range hostConfig.Binds {
// FIXME: factorize bind parsing in parseBind
var src, dst, mode string
arr := strings.Split(bind, ":")
if len(arr) == 2 {
src = arr[0]
dst = arr[1]
mode = "rw"
} else if len(arr) == 3 {
src = arr[0]
dst = arr[1]
mode = arr[2]
} else {
return fmt.Errorf("Invalid bind specification: %s", bind)
}
// Bail if trying to mount to an illegal destination
for _, illegal := range illegalDsts {
if dst == illegal {
return fmt.Errorf("Illegal bind destination: %s", dst)
}
}
bindMap := BindMap{
SrcPath: src,
DstPath: dst,
Mode: mode,
}
binds[path.Clean(dst)] = bindMap
}
// FIXME: evaluate volumes-from before individual volumes, so that the latter can override the former.
// Create the requested volumes volumes
for volPath := range container.Config.Volumes {
c, err := container.runtime.volumes.Create(nil, container, "", "", nil)
if err != nil {
return err
volPath = path.Clean(volPath)
// If an external bind is defined for this volume, use that as a source
if bindMap, exists := binds[volPath]; exists {
container.Volumes[volPath] = bindMap.SrcPath
if strings.ToLower(bindMap.Mode) == "rw" {
container.VolumesRW[volPath] = true
}
// Otherwise create an directory in $ROOT/volumes/ and use that
} else {
c, err := container.runtime.volumes.Create(nil, container, "", "", nil)
if err != nil {
return err
}
srcPath, err := c.layer()
if err != nil {
return err
}
container.Volumes[volPath] = srcPath
container.VolumesRW[volPath] = true // RW by default
}
// Create the mountpoint
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return nil
}
container.Volumes[volPath] = c.ID
}
if container.Config.VolumesFrom != "" {
@@ -480,6 +601,9 @@ func (container *Container) Start() error {
return nil
}
container.Volumes[volPath] = id
if isRW, exists := c.VolumesRW[volPath]; exists {
container.VolumesRW[volPath] = isRW
}
}
}
@@ -547,12 +671,14 @@ func (container *Container) Start() error {
container.waitLock = make(chan struct{})
container.ToDisk()
container.SaveHostConfig(hostConfig)
go container.monitor()
return nil
}
func (container *Container) Run() error {
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return err
}
container.Wait()
@@ -565,7 +691,8 @@ func (container *Container) Output() (output []byte, err error) {
return nil, err
}
defer pipe.Close()
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return nil, err
}
output, err = ioutil.ReadAll(pipe)
@@ -597,14 +724,18 @@ func (container *Container) allocateNetwork() error {
if err != nil {
return err
}
container.NetworkSettings.PortMapping = make(map[string]string)
container.NetworkSettings.PortMapping = make(map[string]PortMapping)
container.NetworkSettings.PortMapping["Tcp"] = make(PortMapping)
container.NetworkSettings.PortMapping["Udp"] = make(PortMapping)
for _, spec := range container.Config.PortSpecs {
nat, err := iface.AllocatePort(spec)
if err != nil {
iface.Release()
return err
}
container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend)
proto := strings.Title(nat.Proto)
backend, frontend := strconv.Itoa(nat.Backend), strconv.Itoa(nat.Frontend)
container.NetworkSettings.PortMapping[proto][backend] = frontend
}
container.network = iface
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
@@ -632,7 +763,6 @@ func (container *Container) waitLxc() error {
}
time.Sleep(500 * time.Millisecond)
}
panic("Unreachable")
}
func (container *Container) monitor() {
@@ -731,8 +861,8 @@ func (container *Container) kill() error {
}
func (container *Container) Kill() error {
container.State.lock()
defer container.State.unlock()
container.State.Lock()
defer container.State.Unlock()
if !container.State.Running {
return nil
}
@@ -740,8 +870,8 @@ func (container *Container) Kill() error {
}
func (container *Container) Stop(seconds int) error {
container.State.lock()
defer container.State.unlock()
container.State.Lock()
defer container.State.Unlock()
if !container.State.Running {
return nil
}
@@ -769,7 +899,8 @@ func (container *Container) Restart(seconds int) error {
if err := container.Stop(seconds); err != nil {
return err
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return err
}
return nil
@@ -821,8 +952,6 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
case <-done:
return nil
}
panic("Unreachable")
}
func (container *Container) EnsureMounted() error {
@@ -881,6 +1010,10 @@ func (container *Container) ReadLog(name string) (io.Reader, error) {
return os.Open(container.logPath(name))
}
func (container *Container) hostConfigPath() string {
return path.Join(container.root, "hostconfig.json")
}
func (container *Container) jsonPath() string {
return path.Join(container.root, "config.json")
}
@@ -894,22 +1027,6 @@ func (container *Container) RootfsPath() string {
return path.Join(container.root, "rootfs")
}
func (container *Container) GetVolumes() (map[string]string, error) {
ret := make(map[string]string)
for volPath, id := range container.Volumes {
volume, err := container.runtime.volumes.Get(id)
if err != nil {
return nil, err
}
root, err := volume.root()
if err != nil {
return nil, err
}
ret[volPath] = path.Join(root, "layer")
}
return ret, nil
}
func (container *Container) rwPath() string {
return path.Join(container.root, "rw")
}

View File

@@ -7,6 +7,7 @@ import (
"io/ioutil"
"math/rand"
"os"
"path"
"regexp"
"sort"
"strings"
@@ -15,10 +16,7 @@ import (
)
func TestIDFormat(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container1, err := NewBuilder(runtime).Create(
&Config{
@@ -39,10 +37,7 @@ func TestIDFormat(t *testing.T) {
}
func TestMultipleAttachRestart(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
@@ -70,7 +65,8 @@ func TestMultipleAttachRestart(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
l1, err := bufio.NewReader(stdout1).ReadString('\n')
@@ -111,7 +107,7 @@ func TestMultipleAttachRestart(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -142,10 +138,7 @@ func TestMultipleAttachRestart(t *testing.T) {
}
func TestDiff(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
builder := NewBuilder(runtime)
@@ -251,10 +244,7 @@ func TestDiff(t *testing.T) {
}
func TestCommitAutoRun(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
builder := NewBuilder(runtime)
@@ -306,7 +296,8 @@ func TestCommitAutoRun(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container2.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}
container2.Wait()
@@ -330,10 +321,7 @@ func TestCommitAutoRun(t *testing.T) {
}
func TestCommitRun(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
builder := NewBuilder(runtime)
@@ -388,7 +376,8 @@ func TestCommitRun(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container2.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}
container2.Wait()
@@ -412,10 +401,7 @@ func TestCommitRun(t *testing.T) {
}
func TestStart(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
@@ -436,7 +422,8 @@ func TestStart(t *testing.T) {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -446,7 +433,7 @@ func TestStart(t *testing.T) {
if !container.State.Running {
t.Errorf("Container should be running")
}
if err := container.Start(); err == nil {
if err := container.Start(hostConfig); err == nil {
t.Fatalf("A running containter should be able to be started")
}
@@ -456,10 +443,7 @@ func TestStart(t *testing.T) {
}
func TestRun(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
@@ -484,10 +468,7 @@ func TestRun(t *testing.T) {
}
func TestOutput(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
@@ -509,10 +490,7 @@ func TestOutput(t *testing.T) {
}
func TestKillDifferentUser(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
@@ -528,16 +506,19 @@ func TestKillDifferentUser(t *testing.T) {
if container.State.Running {
t.Errorf("Container shouldn't be running")
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
// Give some time to lxc to spawn the process (setuid might take some time)
container.WaitTimeout(500 * time.Millisecond)
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
for !container.State.Running {
time.Sleep(10 * time.Millisecond)
}
})
if !container.State.Running {
t.Errorf("Container should be running")
}
// Even if the state is running, lets give some time to lxc to spawn the process
container.WaitTimeout(500 * time.Millisecond)
if err := container.Kill(); err != nil {
t.Fatal(err)
@@ -556,15 +537,33 @@ func TestKillDifferentUser(t *testing.T) {
}
}
func TestKill(t *testing.T) {
runtime, err := newTestRuntime()
// Test that creating a container with a volume doesn't crash. Regression test for #995.
func TestCreateVolume(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
config, hc, _, err := ParseRun([]string{"-v", "/var/lib/data", GetTestImage(runtime).ID, "echo", "hello", "world"}, nil)
if err != nil {
t.Fatal(err)
}
c, err := NewBuilder(runtime).Create(config)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(c)
if err := c.Start(hc); err != nil {
t.Fatal(err)
}
c.WaitTimeout(500 * time.Millisecond)
c.Wait()
}
func TestKill(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/dev/zero"},
Cmd: []string{"sleep", "2"},
},
)
if err != nil {
@@ -575,7 +574,8 @@ func TestKill(t *testing.T) {
if container.State.Running {
t.Errorf("Container shouldn't be running")
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -602,10 +602,7 @@ func TestKill(t *testing.T) {
}
func TestExitCode(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
builder := NewBuilder(runtime)
@@ -642,10 +639,7 @@ func TestExitCode(t *testing.T) {
}
func TestRestart(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
@@ -675,10 +669,7 @@ func TestRestart(t *testing.T) {
}
func TestRestartStdin(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
@@ -700,7 +691,8 @@ func TestRestartStdin(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
if _, err := io.WriteString(stdin, "hello world"); err != nil {
@@ -730,7 +722,7 @@ func TestRestartStdin(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
if _, err := io.WriteString(stdin, "hello world #2"); err != nil {
@@ -753,10 +745,7 @@ func TestRestartStdin(t *testing.T) {
}
func TestUser(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
builder := NewBuilder(runtime)
@@ -860,20 +849,34 @@ func TestUser(t *testing.T) {
if !strings.Contains(string(output), "uid=1(daemon) gid=1(daemon)") {
t.Error(string(output))
}
}
func TestMultipleContainers(t *testing.T) {
runtime, err := newTestRuntime()
// Test an wrong username
container, err = builder.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
User: "unkownuser",
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
output, err = container.Output()
if container.State.ExitCode == 0 {
t.Fatal("Starting container with wrong uid should fail but it passed.")
}
}
func TestMultipleContainers(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
builder := NewBuilder(runtime)
container1, err := builder.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/dev/zero"},
Cmd: []string{"sleep", "2"},
},
)
if err != nil {
@@ -883,7 +886,7 @@ func TestMultipleContainers(t *testing.T) {
container2, err := builder.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/dev/zero"},
Cmd: []string{"sleep", "2"},
},
)
if err != nil {
@@ -892,10 +895,11 @@ func TestMultipleContainers(t *testing.T) {
defer runtime.Destroy(container2)
// Start both containers
if err := container1.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container1.Start(hostConfig); err != nil {
t.Fatal(err)
}
if err := container2.Start(); err != nil {
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}
@@ -922,10 +926,7 @@ func TestMultipleContainers(t *testing.T) {
}
func TestStdin(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
@@ -947,7 +948,8 @@ func TestStdin(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
defer stdin.Close()
@@ -969,10 +971,7 @@ func TestStdin(t *testing.T) {
}
func TestTty(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
@@ -994,7 +993,8 @@ func TestTty(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
defer stdin.Close()
@@ -1016,14 +1016,11 @@ func TestTty(t *testing.T) {
}
func TestEnv(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/usr/bin/env"},
Cmd: []string{"env"},
},
)
if err != nil {
@@ -1036,7 +1033,8 @@ func TestEnv(t *testing.T) {
t.Fatal(err)
}
defer stdout.Close()
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
container.Wait()
@@ -1064,6 +1062,29 @@ func TestEnv(t *testing.T) {
}
}
func TestEntrypoint(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).ID,
Entrypoint: []string{"/bin/echo"},
Cmd: []string{"-n", "foobar"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
output, err := container.Output()
if err != nil {
t.Fatal(err)
}
if string(output) != "foobar" {
t.Error(string(output))
}
}
func grepFile(t *testing.T, path string, pattern string) {
f, err := os.Open(path)
if err != nil {
@@ -1085,10 +1106,7 @@ func grepFile(t *testing.T, path string, pattern string) {
}
func TestLXCConfig(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
// Memory is allocated randomly for testing
rand.Seed(time.Now().UTC().UnixNano())
@@ -1121,10 +1139,7 @@ func TestLXCConfig(t *testing.T) {
}
func BenchmarkRunSequencial(b *testing.B) {
runtime, err := newTestRuntime()
if err != nil {
b.Fatal(err)
}
runtime := mkRuntime(b)
defer nuke(runtime)
for i := 0; i < b.N; i++ {
container, err := NewBuilder(runtime).Create(&Config{
@@ -1150,10 +1165,7 @@ func BenchmarkRunSequencial(b *testing.B) {
}
func BenchmarkRunParallel(b *testing.B) {
runtime, err := newTestRuntime()
if err != nil {
b.Fatal(err)
}
runtime := mkRuntime(b)
defer nuke(runtime)
var tasks []chan error
@@ -1172,7 +1184,8 @@ func BenchmarkRunParallel(b *testing.B) {
return
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
complete <- err
return
}
@@ -1201,3 +1214,88 @@ func BenchmarkRunParallel(b *testing.B) {
b.Fatal(errors)
}
}
func tempDir(t *testing.T) string {
tmpDir, err := ioutil.TempDir("", "docker-test")
if err != nil {
t.Fatal(err)
}
return tmpDir
}
func TestBindMounts(t *testing.T) {
r := mkRuntime(t)
defer nuke(r)
tmpDir := tempDir(t)
defer os.RemoveAll(tmpDir)
writeFile(path.Join(tmpDir, "touch-me"), "", t)
// Test reading from a read-only bind mount
stdout, _ := runContainer(r, []string{"-v", fmt.Sprintf("%s:/tmp:ro", tmpDir), "_", "ls", "/tmp"}, t)
if !strings.Contains(stdout, "touch-me") {
t.Fatal("Container failed to read from bind mount")
}
// test writing to bind mount
runContainer(r, []string{"-v", fmt.Sprintf("%s:/tmp:rw", tmpDir), "_", "touch", "/tmp/holla"}, t)
readFile(path.Join(tmpDir, "holla"), t) // Will fail if the file doesn't exist
// test mounting to an illegal destination directory
if _, err := runContainer(r, []string{"-v", fmt.Sprintf("%s:.", tmpDir), "ls", "."}, nil); err == nil {
t.Fatal("Container bind mounted illegal directory")
}
}
// Test that VolumesRW values are copied to the new container. Regression test for #1201
func TestVolumesFromReadonlyMount(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/echo", "-n", "foobar"},
Volumes: map[string]struct{}{"/test": {}},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
_, err = container.Output()
if err != nil {
t.Fatal(err)
}
if !container.VolumesRW["/test"] {
t.Fail()
}
container2, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/echo", "-n", "foobar"},
VolumesFrom: container.ID,
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container2)
_, err = container2.Output()
if err != nil {
t.Fatal(err)
}
if container.Volumes["/test"] != container2.Volumes["/test"] {
t.Fail()
}
actual, exists := container2.VolumesRW["/test"]
if !exists {
t.Fail()
}
if container.VolumesRW["/test"] != actual {
t.Fail()
}
}

View File

@@ -116,7 +116,6 @@ func crashTest() error {
return err
}
}
return nil
}
func main() {

49
contrib/mkimage-unittest.sh Executable file
View File

@@ -0,0 +1,49 @@
#!/bin/bash
# Generate a very minimal filesystem based on busybox-static,
# and load it into the local docker under the name "docker-ut".
missing_pkg() {
echo "Sorry, I could not locate $1"
echo "Try 'apt-get install ${2:-$1}'?"
exit 1
}
BUSYBOX=$(which busybox)
[ "$BUSYBOX" ] || missing_pkg busybox busybox-static
SOCAT=$(which socat)
[ "$SOCAT" ] || missing_pkg socat
shopt -s extglob
set -ex
ROOTFS=`mktemp -d /tmp/rootfs-busybox.XXXXXXXXXX`
trap "rm -rf $ROOTFS" INT QUIT TERM
cd $ROOTFS
mkdir bin etc dev dev/pts lib proc sys tmp
touch etc/resolv.conf
cp /etc/nsswitch.conf etc/nsswitch.conf
echo root:x:0:0:root:/:/bin/sh > etc/passwd
echo daemon:x:1:1:daemon:/usr/sbin:/bin/sh >> etc/passwd
echo root:x:0: > etc/group
echo daemon:x:1: >> etc/group
ln -s lib lib64
ln -s bin sbin
cp $BUSYBOX $SOCAT bin
for X in $(busybox --list)
do
ln -s busybox bin/$X
done
rm bin/init
ln bin/busybox bin/init
cp -P /lib/x86_64-linux-gnu/lib{pthread*,c*(-*),dl*(-*),nsl*(-*),nss_*,util*(-*),wrap,z}.so* lib
cp /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 lib
cp -P /usr/lib/x86_64-linux-gnu/lib{crypto,ssl}.so* lib
for X in console null ptmx random stdin stdout stderr tty urandom zero
do
cp -a /dev/$X dev
done
chmod 0755 $ROOTFS # See #486
tar -cf- . | docker import - docker-ut
docker run -i -u root docker-ut /bin/echo Success.
rm -rf $ROOTFS

View File

@@ -30,13 +30,14 @@ func main() {
flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.")
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
flDns := flag.String("dns", "", "Set custom dns servers")
flHosts := docker.ListOpts{fmt.Sprintf("tcp://%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT)}
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
flag.Parse()
if len(flHosts) > 1 {
flHosts = flHosts[1:len(flHosts)] //trick to display a nice defaul value in the usage
flHosts = flHosts[1:] //trick to display a nice defaul value in the usage
}
for i, flHost := range flHosts {
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
@@ -56,7 +57,7 @@ func main() {
flag.Usage()
return
}
if err := daemon(*pidfile, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
log.Fatal(err)
os.Exit(-1)
}
@@ -100,7 +101,7 @@ func removePidFile(pidfile string) {
}
}
func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
if err := createPidFile(pidfile); err != nil {
log.Fatal(err)
}
@@ -118,7 +119,7 @@ func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, f
if flDns != "" {
dns = []string{flDns}
}
server, err := docker.NewServer(autoRestart, enableCors, dns)
server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns)
if err != nil {
return err
}
@@ -126,7 +127,7 @@ func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, f
for _, protoAddr := range protoAddrs {
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
if protoAddrParts[0] == "unix" {
syscall.Unlink(protoAddrParts[1]);
syscall.Unlink(protoAddrParts[1])
} else if protoAddrParts[0] == "tcp" {
if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
@@ -139,7 +140,7 @@ func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, f
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
}()
}
for i :=0 ; i < len(protoAddrs); i+=1 {
for i := 0; i < len(protoAddrs); i += 1 {
err := <-chErrors
if err != nil {
return err
@@ -147,4 +148,3 @@ func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, f
}
return nil
}

View File

@@ -19,13 +19,43 @@ Docker Remote API
2. Versions
===========
The current verson of the API is 1.2
Calling /images/<name>/insert is the same as calling /v1.2/images/<name>/insert
The current verson of the API is 1.3
Calling /images/<name>/insert is the same as calling /v1.3/images/<name>/insert
You can still call an old version of the api using /v1.0/images/<name>/insert
:doc:`docker_remote_api_v1.3`
*****************************
What's new
----------
Listing processes (/top):
- List the processes inside a container
Builder (/build):
- Simplify the upload of the build context
- Simply stream a tarball instead of multipart upload with 4 intermediary buffers
- Simpler, less memory usage, less disk usage and faster
.. Note::
The /build improvements are not reverse-compatible. Pre 1.3 clients will break on /build.
List containers (/containers/json):
- You can use size=1 to get the size of the containers
Start containers (/containers/<id>/start):
- You can now pass host-specific configuration (e.g. bind mounts) in the POST body for start calls
:doc:`docker_remote_api_v1.2`
*****************************
docker v0.4.2 2e7649b_
What's new
----------
@@ -36,6 +66,7 @@ The client should send it's authConfig as POST on each call of /images/(name)/pu
.. http:post:: /auth only checks the configuration but doesn't store it on the server
Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any.
.. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged
@@ -64,6 +95,9 @@ Uses json stream instead of HTML hijack, it looks like this:
...
:doc:`docker_remote_api_v1.0`
*****************************
docker v0.3.4 8d73740_
What's new
@@ -74,6 +108,7 @@ Initial version
.. _a8ae398: https://github.com/dotcloud/docker/commit/a8ae398bf52e97148ee7bd0d5868de2e15bd297f
.. _8d73740: https://github.com/dotcloud/docker/commit/8d73740343778651c09160cde9661f5f387b36f4
.. _2e7649b: https://github.com/dotcloud/docker/commit/2e7649beda7c820793bd46766cbc2cfeace7b168
==================================
Docker Remote API Client Libraries
@@ -93,8 +128,13 @@ and we will add the libraries here.
+----------------------+----------------+--------------------------------------------+
| Ruby | docker-client | https://github.com/geku/docker-client |
+----------------------+----------------+--------------------------------------------+
| Ruby | docker-api | https://github.com/swipely/docker-api |
+----------------------+----------------+--------------------------------------------+
| Javascript | docker-js | https://github.com/dgoujard/docker-js |
+----------------------+----------------+--------------------------------------------+
| Javascript (Angular) | dockerui | https://github.com/crosbymichael/dockerui |
| **WebUI** | | |
+----------------------+----------------+--------------------------------------------+
| Java | docker-java | https://github.com/kpelykh/docker-java |
+----------------------+----------------+--------------------------------------------+

View File

@@ -847,7 +847,7 @@ Build an image from Dockerfile via stdin
.. http:post:: /build
Build an image from Dockerfile via stdin
Build an image from Dockerfile
**Example request**:
@@ -866,9 +866,12 @@ Build an image from Dockerfile via stdin
{{ STREAM }}
:query t: tag to be applied to the resulting image in case of success
:query remote: resource to fetch, as URI
:statuscode 200: no error
:statuscode 500: server error
{{ STREAM }} is the raw text output of the build command. It uses the HTTP Hijack method in order to stream.
Check auth configuration
************************
@@ -895,9 +898,16 @@ Check auth configuration
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
{
"Status": "Login Succeeded"
}
:statuscode 200: no error
:statuscode 204: no error
:statuscode 401: unauthorized
:statuscode 403: forbidden
:statuscode 500: server error

File diff suppressed because it is too large Load Diff

View File

@@ -52,5 +52,6 @@ Available Commands
command/start
command/stop
command/tag
command/top
command/version
command/wait

View File

@@ -8,9 +8,12 @@
::
Usage: docker build [OPTIONS] PATH | -
Usage: docker build [OPTIONS] PATH | URL | -
Build a new container image from the source code at PATH
-t="": Tag to be applied to the resulting image in case of success.
-q=false: Suppress verbose build output.
When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context
Examples
--------
@@ -27,7 +30,15 @@ Examples
.. code-block:: bash
docker build -
docker build - < Dockerfile
| 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.
.. code-block:: bash
docker build github.com/creack/docker-firefox
| This will clone the github repository and use it as context. The Dockerfile at the root of the repository is used as Dockerfile.
| Note that you can specify an arbitrary git repository by using the 'git://' schema.

View File

@@ -8,6 +8,10 @@
::
Usage: docker login
Usage: docker login [OPTIONS]
Register or Login to the docker registry server
-e="": email
-p="": password
-u="": username

View File

@@ -10,4 +10,4 @@
Usage: docker rm [OPTIONS] CONTAINER
Remove a container
Remove one or more containers

View File

@@ -8,6 +8,6 @@
::
Usage: docker rmimage [OPTIONS] IMAGE
Usage: docker rmi IMAGE [IMAGE...]
Remove an image
Remove one or more images

View File

@@ -8,7 +8,7 @@
::
Usage: docker run [OPTIONS] IMAGE COMMAND [ARG...]
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Run a command in a new container
@@ -23,5 +23,6 @@
-t=false: Allocate a pseudo-tty
-u="": Username or UID
-d=[]: Set custom dns servers for the container
-v=[]: Creates a new volume and mounts it at the specified path.
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume.
-volumes-from="": Mount all volumes from the given container.
-entrypoint="": Overwrite the default entrypoint set by the image.

View File

@@ -0,0 +1,13 @@
:title: Top Command
:description: Lookup the running processes of a container
:keywords: top, docker, container, documentation
=======================================================
``top`` -- Lookup the running processes of a container
=======================================================
::
Usage: docker top CONTAINER
Lookup the running processes of a container

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,10 +1,10 @@
:title: Concepts
:description: -- todo: change me
:title: Overview
:description: Docker documentation summary
:keywords: concepts, documentation, docker, containers
Concepts
Overview
========
Contents:
@@ -13,4 +13,4 @@ Contents:
:maxdepth: 1
../index
manifesto

View File

@@ -0,0 +1,190 @@
:title: Manifesto
:description: An overview of Docker and standard containers
:keywords: containers, lxc, concepts, explanation
.. _dockermanifesto:
*(This was our original Welcome page, but it is a bit forward-looking
for docs, and maybe not enough vision for a true manifesto. We'll
reveal more vision in the future to make it more Manifesto-y.)*
Docker Manifesto
----------------
Docker complements LXC with a high-level API which operates at the
process level. It runs unix processes with strong guarantees of
isolation and repeatability across servers.
Docker is a great building block for automating distributed systems:
large-scale web deployments, database clusters, continuous deployment
systems, private PaaS, service-oriented architectures, etc.
- **Heterogeneous payloads** Any combination of binaries, libraries,
configuration files, scripts, virtualenvs, jars, gems, tarballs, you
name it. No more juggling between domain-specific tools. Docker can
deploy and run them all.
- **Any server** Docker can run on any x64 machine with a modern linux
kernel - whether it's a laptop, a bare metal server or a VM. This
makes it perfect for multi-cloud deployments.
- **Isolation** docker isolates processes from each other and from the
underlying host, using lightweight containers.
- **Repeatability** Because containers are isolated in their own
filesystem, they behave the same regardless of where, when, and
alongside what they run.
.. image:: images/lego_docker.jpg
:target: http://bricks.argz.com/ins/7823-1/12
What is a Standard Container?
.............................
Docker defines a unit of software delivery called a Standard
Container. The goal of a Standard Container is to encapsulate a
software component and all its dependencies in a format that is
self-describing and portable, so that any compliant runtime can run it
without extra dependency, regardless of the underlying machine and the
contents of the container.
The spec for Standard Containers is currently work in progress, but it
is very straightforward. It mostly defines 1) an image format, 2) a
set of standard operations, and 3) an execution environment.
A great analogy for this is the shipping container. Just like Standard
Containers are a fundamental unit of software delivery, shipping
containers are a fundamental unit of physical delivery.
Standard operations
~~~~~~~~~~~~~~~~~~~
Just like shipping containers, Standard Containers define a set of
STANDARD OPERATIONS. Shipping containers can be lifted, stacked,
locked, loaded, unloaded and labelled. Similarly, standard containers
can be started, stopped, copied, snapshotted, downloaded, uploaded and
tagged.
Content-agnostic
~~~~~~~~~~~~~~~~~~~
Just like shipping containers, Standard Containers are
CONTENT-AGNOSTIC: all standard operations have the same effect
regardless of the contents. A shipping container will be stacked in
exactly the same way whether it contains Vietnamese powder coffee or
spare Maserati parts. Similarly, Standard Containers are started or
uploaded in the same way whether they contain a postgres database, a
php application with its dependencies and application server, or Java
build artifacts.
Infrastructure-agnostic
~~~~~~~~~~~~~~~~~~~~~~~~~~
Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be
transported to thousands of facilities around the world, and
manipulated by a wide variety of equipment. A shipping container can
be packed in a factory in Ukraine, transported by truck to the nearest
routing center, stacked onto a train, loaded into a German boat by an
Australian-built crane, stored in a warehouse at a US facility,
etc. Similarly, a standard container can be bundled on my laptop,
uploaded to S3, downloaded, run and snapshotted by a build server at
Equinix in Virginia, uploaded to 10 staging servers in a home-made
Openstack cluster, then sent to 30 production instances across 3 EC2
regions.
Designed for automation
~~~~~~~~~~~~~~~~~~~~~~~
Because they offer the same standard operations regardless of content
and infrastructure, Standard Containers, just like their physical
counterpart, are extremely well-suited for automation. In fact, you
could say automation is their secret weapon.
Many things that once required time-consuming and error-prone human
effort can now be programmed. Before shipping containers, a bag of
powder coffee was hauled, dragged, dropped, rolled and stacked by 10
different people in 10 different locations by the time it reached its
destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The
process was slow, inefficient and cost a fortune - and was entirely
different depending on the facility and the type of goods.
Similarly, before Standard Containers, by the time a software
component ran in production, it had been individually built,
configured, bundled, documented, patched, vendored, templated, tweaked
and instrumented by 10 different people on 10 different
computers. Builds failed, libraries conflicted, mirrors crashed,
post-it notes were lost, logs were misplaced, cluster updates were
half-broken. The process was slow, inefficient and cost a fortune -
and was entirely different depending on the language and
infrastructure provider.
Industrial-grade delivery
~~~~~~~~~~~~~~~~~~~~~~~~~
There are 17 million shipping containers in existence, packed with
every physical good imaginable. Every single one of them can be loaded
on the same boats, by the same cranes, in the same facilities, and
sent anywhere in the World with incredible efficiency. It is
embarrassing to think that a 30 ton shipment of coffee can safely
travel half-way across the World in *less time* than it takes a
software team to deliver its code from one datacenter to another
sitting 10 miles away.
With Standard Containers we can put an end to that embarrassment, by
making INDUSTRIAL-GRADE DELIVERY of software a reality.
Standard Container Specification
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(TODO)
Image format
~~~~~~~~~~~~
Standard operations
~~~~~~~~~~~~~~~~~~~
- Copy
- Run
- Stop
- Wait
- Commit
- Attach standard streams
- List filesystem changes
- ...
Execution environment
~~~~~~~~~~~~~~~~~~~~~
Root filesystem
^^^^^^^^^^^^^^^
Environment variables
^^^^^^^^^^^^^^^^^^^^^
Process arguments
^^^^^^^^^^^^^^^^^
Networking
^^^^^^^^^^
Process namespacing
^^^^^^^^^^^^^^^^^^^
Resource limits
^^^^^^^^^^^^^^^
Process monitoring
^^^^^^^^^^^^^^^^^^
Logging
^^^^^^^
Signals
^^^^^^^
Pseudo-terminal allocation
^^^^^^^^^^^^^^^^^^^^^^^^^^
Security
^^^^^^^^

View File

@@ -30,6 +30,7 @@ import sys, os
html_additional_pages = {
'concepts/containers': 'redirect_home.html',
'concepts/introduction': 'redirect_home.html',
'builder/basics': 'redirect_build.html',
}

View File

@@ -5,53 +5,52 @@
Setting Up a Dev Environment
============================
Instructions that have been verified to work on Ubuntu Precise 12.04 (LTS) (64-bit),
To make it easier to contribute to Docker, we provide a standard development environment. It is important that
the same environment be used for all tests, builds and releases. The standard development environment defines
all build dependencies: system libraries and binaries, go environment, go dependencies, etc.
Dependencies
------------
Step 1: install docker
----------------------
**Linux kernel 3.8**
Docker's build environment itself is a docker container, so the first step is to install docker on your system.
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.
You can follow the `install instructions most relevant to your system <https://docs.docker.io/en/latest/installation/>`.
Make sure you have a working, up-to-date docker installation, then continue to the next step.
.. code-block:: bash
Step 2: check out the source
----------------------------
# 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 xz-utils curl golang-stable git aufs-tools
export GOPATH=~/go/
export PATH=$GOPATH/bin:$PATH
mkdir -p $GOPATH/src/github.com/dotcloud
cd $GOPATH/src/github.com/dotcloud
git clone git://github.com/dotcloud/docker.git
git clone http://git@github.com/dotcloud/docker
cd docker
go get -v github.com/dotcloud/docker/...
go install -v github.com/dotcloud/docker/...
Step 3: build
-------------
When you are ready to build docker, run this command:
::
docker build -t docker .
This will build the revision currently checked out in the repository. Feel free to check out the version
of your choice.
If the build is successful, congratulations! You have produced a clean build of docker, neatly encapsulated
in a standard build environment.
You can run an interactive session in the newly built container:
::
docker run -i -t docker bash
Then run the docker daemon,
To extract the binaries from the container:
.. code-block:: bash
::
docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build
sudo $GOPATH/bin/docker -d
Run the ``go install`` command (above) to recompile docker.

View File

@@ -1,127 +1,38 @@
:title: Introduction
:description: An introduction to docker and standard containers?
:title: Welcome to the Docker Documentation
:description: An overview of the Docker Documentation
:keywords: containers, lxc, concepts, explanation
.. _introduction:
Introduction
============
Welcome
=======
Docker -- The Linux container runtime
-------------------------------------
.. image:: concepts/images/dockerlogo-h.png
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
``docker``, the Linux Container Runtime, runs Unix processes with
strong guarantees of isolation across servers. Your software runs
repeatably everywhere because its :ref:`container_def` includes any
dependencies.
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
``docker`` runs three ways:
* as a daemon to manage LXC containers on your :ref:`Linux host
<kernel>` (``sudo docker -d``)
* as a :ref:`CLI <cli>` which talks to the daemon's `REST API
<api/docker_remote_api>`_ (``docker run ...``)
* as a client of :ref:`Repositories <working_with_the_repository>`
that let you share what you've built (``docker pull, docker
commit``).
- **Heterogeneous payloads** Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
- **Any server** Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers.
- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
Each use of ``docker`` is documented here. The features of Docker are
currently in active development, so this documention will change
frequently.
.. image:: concepts/images/lego_docker.jpg
What is a Standard Container?
-----------------------------
Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in
a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container.
The spec for Standard Containers is currently work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment.
A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery.
Standard operations
~~~~~~~~~~~~~~~~~~~
Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged.
Content-agnostic
~~~~~~~~~~~~~~~~~~~
Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
Infrastructure-agnostic
~~~~~~~~~~~~~~~~~~~~~~~~~~
Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions.
Designed for automation
~~~~~~~~~~~~~~~~~~~~~~~~~~
Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon.
Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods.
Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider.
Industrial-grade delivery
~~~~~~~~~~~~~~~~~~~~~~~~~~
There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality.
Standard Container Specification
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(TODO)
Image format
~~~~~~~~~~~~
Standard operations
~~~~~~~~~~~~~~~~~~~
- Copy
- Run
- Stop
- Wait
- Commit
- Attach standard streams
- List filesystem changes
- ...
Execution environment
~~~~~~~~~~~~~~~~~~~~~
Root filesystem
^^^^^^^^^^^^^^^
Environment variables
^^^^^^^^^^^^^^^^^^^^^
Process arguments
^^^^^^^^^^^^^^^^^
Networking
^^^^^^^^^^
Process namespacing
^^^^^^^^^^^^^^^^^^^
Resource limits
^^^^^^^^^^^^^^^
Process monitoring
^^^^^^^^^^^^^^^^^^
Logging
^^^^^^^
Signals
^^^^^^^
Pseudo-terminal allocation
^^^^^^^^^^^^^^^^^^^^^^^^^^
Security
^^^^^^^^
For an overview of Docker, please see the `Introduction
<http://www.docker.io>`_. When you're ready to start working with
Docker, we have a `quick start <http://www.docker.io/gettingstarted>`_
and a more in-depth guide to :ref:`ubuntu_linux` and other
:ref:`installation_list` paths including prebuilt binaries,
Vagrant-created VMs, Rackspace and Amazon instances.
Enough reading! :ref:`Try it out! <running_examples>`

View File

@@ -1,12 +1,17 @@
:title: Documentation
:description: -- todo: change me
:keywords: todo, docker, documentation, installation, OS support
:title: Docker Installation
:description: many ways to install Docker
:keywords: docker, installation
.. _installation_list:
Installation
============
There are a number of ways to install Docker, depending on where you
want to run the daemon. The :ref:`ubuntu_linux` installation is the
officially-tested version, and the community adds more techniques for
installing Docker all the time.
Contents:
.. toctree::

View File

@@ -100,7 +100,7 @@ Memory and Swap Accounting on Debian/Ubuntu
If you use Debian or Ubuntu kernels, and want to enable memory and swap
accounting, you must add the following command-line parameters to your kernel::
cgroup_enable=memory swapaccount
cgroup_enable=memory swapaccount=1
On Debian or Ubuntu systems, if you use the default GRUB bootloader, you can
add those parameters by editing ``/etc/default/grub`` and extending
@@ -110,6 +110,6 @@ add those parameters by editing ``/etc/default/grub`` and extending
And replace it by the following one::
GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount"
GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"
Then run ``update-grub``, and reboot.

View File

@@ -0,0 +1,40 @@
:title: Container
:description: Definitions of a container
:keywords: containers, lxc, concepts, explanation, image, container
.. _container_def:
Container
=========
.. image:: images/docker-filesystems-busyboxrw.png
Once you start a process in Docker from an :ref:`image_def`, Docker
fetches the image and its :ref:`parent_image_def`, and repeats the
process until it reaches the :ref:`base_image_def`. Then the
:ref:`ufs_def` adds a read-write layer on top. That read-write layer,
plus the information about its :ref:`parent_image_def` and some
additional information like its unique id, networking configuration,
and resource limits is called a **container**.
.. _container_state_def:
Container State
...............
Containers can change, and so they have state. A container may be
**running** or **exited**.
When a container is running, the idea of a "container" also includes a
tree of processes running on the CPU, isolated from the other
processes running on the host.
When the container is exited, the state of the file system and
its exit value is preserved. You can start, stop, and restart a
container. The processes restart from scratch (their memory state is
**not** preserved in a container), but the file system is just as it
was when the container was stopped.
You can promote a container to an :ref:`image_def` with ``docker
commit``. Once a container is an image, you can use it as a parent for
new containers.

View File

@@ -0,0 +1,38 @@
:title: File Systems
:description: How Linux organizes its persistent storage
:keywords: containers, files, linux
.. _filesystem_def:
File System
===========
.. image:: images/docker-filesystems-generic.png
In order for a Linux system to run, it typically needs two `file
systems <http://en.wikipedia.org/wiki/Filesystem>`_:
1. boot file system (bootfs)
2. root file system (rootfs)
The **boot file system** contains the bootloader and the kernel. The
user never makes any changes to the boot file system. In fact, soon
after the boot process is complete, the entire kernel is in memory,
and the boot file system is unmounted to free up the RAM associated
with the initrd disk image.
The **root file system** includes the typical directory structure we
associate with Unix-like operating systems: ``/dev, /proc, /bin, /etc,
/lib, /usr,`` and ``/tmp`` plus all the configuration files, binaries
and libraries required to run user applications (like bash, ls, and so
forth).
While there can be important kernel differences between different
Linux distributions, the contents and organization of the root file
system are usually what make your software packages dependent on one
distribution versus another. Docker can help solve this problem by
running multiple distributions at the same time.
.. image:: images/docker-filesystems-multiroot.png

View File

@@ -0,0 +1,38 @@
:title: Images
:description: Definition of an image
:keywords: containers, lxc, concepts, explanation, image, container
.. _image_def:
Image
=====
.. image:: images/docker-filesystems-debian.png
In Docker terminology, a read-only :ref:`layer_def` is called an
**image**. An image never changes.
Since Docker uses a :ref:`ufs_def`, the processes think the whole file
system is mounted read-write. But all the changes go to the top-most
writeable layer, and underneath, the original file in the read-only
image is unchanged. Since images don't change, images do not have state.
.. image:: images/docker-filesystems-debianrw.png
.. _parent_image_def:
Parent Image
............
.. image:: images/docker-filesystems-multilayer.png
Each image may depend on one more image which forms the layer beneath
it. We sometimes say that the lower image is the **parent** of the
upper image.
.. _base_image_def:
Base Image
..........
An image that has no parent is a **base image**.

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 272 KiB

View File

@@ -0,0 +1,22 @@
:title: Glossary
:description: Definitions of terms used in Docker documentation
:keywords: concepts, documentation, docker, containers
Glossary
========
Definitions of terms used in Docker documentation.
Contents:
.. toctree::
:maxdepth: 1
filesystem
layer
image
container

View File

@@ -0,0 +1,40 @@
:title: Layers
:description: Organizing the Docker Root File System
:keywords: containers, lxc, concepts, explanation, image, container
Layers
======
In a traditional Linux boot, the kernel first mounts the root
:ref:`filesystem_def` as read-only, checks its integrity, and then
switches the whole rootfs volume to read-write mode.
.. _layer_def:
Layer
.....
When Docker mounts the rootfs, it starts read-only, as in a tradtional
Linux boot, but then, instead of changing the file system to
read-write mode, it takes advantage of a `union mount
<http://en.wikipedia.org/wiki/Union_mount>`_ to add a read-write file
system *over* the read-only file system. In fact there may be multiple
read-only file systems stacked on top of each other. We think of each
one of these file systems as a **layer**.
.. image:: images/docker-filesystems-multilayer.png
At first, the top read-write layer has nothing in it, but any time a
process creates a file, this happens in the top layer. And if
something needs to update an existing file in a lower layer, then the
file gets copied to the upper layer and changes go into the copy. The
version of the file on the lower layer cannot be seen by the
applications anymore, but it is there, unchanged.
.. _ufs_def:
Union File System
.................
We call the union of the read-write layer and all the read-only layers
a **union file system**.

View File

@@ -17,6 +17,8 @@ This documentation has the following resources:
commandline/index
contributing/index
api/index
terms/index
faq
.. image:: concepts/images/lego_docker.jpg

View File

@@ -1,25 +1,27 @@
:title: Docker Builder
:title: Dockerfile Builder
:description: Docker Builder specifes a simple DSL which allows you to automate the steps you would normally manually take to create an image.
:keywords: builder, docker, Docker Builder, automation, image creation
==============
Docker Builder
==============
==================
Dockerfile Builder
==================
**Docker can act as a builder** and read instructions from a text
Dockerfile to automate the steps you would otherwise make manually to
create an image. Executing ``docker build`` will run your steps and
commit them along the way, giving you a final image.
.. contents:: Table of Contents
Docker Builder specifes a simple DSL which allows you to automate the steps you
would normally manually take to create an image. Docker Build will run your
steps and commit them along the way, giving you a final image.
1. Usage
========
To build an image from a source repository, create a description file called `Dockerfile`
at the root of your repository. This file will describe the steps to assemble
the image.
To build an image from a source repository, create a description file
called ``Dockerfile`` at the root of your repository. This file will
describe the steps to assemble the image.
Then call `docker build` with the path of your source repository as argument:
Then call ``docker build`` with the path of your source repository as
argument:
``docker build .``
@@ -36,136 +38,164 @@ before finally outputting the ID of your new image.
The Dockerfile format is quite simple:
``instruction arguments``
::
# Comment
INSTRUCTION arguments
The Instruction is not case-sensitive, however convention is for them to be
UPPERCASE in order to distinguish them from arguments more easily.
Dockerfiles are evaluated in order, therefore the first instruction must be
`FROM` in order to specify the base image from which you are building.
Docker evaluates the instructions in a Dockerfile in order. **The first
instruction must be `FROM`** in order to specify the base image from
which you are building.
Docker will ignore lines in Dockerfiles prefixed with "`#`", so you may add
comment lines. A comment marker in the rest of the line will be treated as an
argument.
Docker will ignore **comment lines** *beginning* with ``#``. A comment
marker anywhere in the rest of the line will be treated as an argument.
2. Instructions
3. Instructions
===============
Docker builder comes with a set of instructions, described below.
Here is the set of instructions you can use in a ``Dockerfile`` for
building images.
2.1 FROM
3.1 FROM
--------
``FROM <image>``
The `FROM` instruction sets the base image for subsequent instructions. As such,
a valid Dockerfile must have it as its first instruction.
The ``FROM`` instruction sets the :ref:`base_image_def` for subsequent
instructions. As such, a valid Dockerfile must have ``FROM`` as its
first instruction.
`FROM` can be included multiple times within a single Dockerfile in order to
create multiple images. Simply make a note of the last image id output by the
commit before each new `FROM` command.
``FROM`` must be the first non-comment instruction in the
``Dockerfile``.
2.2 MAINTAINER
``FROM`` can appear multiple times within a single Dockerfile in order
to create multiple images. Simply make a note of the last image id
output by the commit before each new ``FROM`` command.
3.2 MAINTAINER
--------------
``MAINTAINER <name>``
The `MAINTAINER` instruction allows you to set the Author field of the generated
images.
The ``MAINTAINER`` instruction allows you to set the *Author* field of
the generated images.
2.3 RUN
3.3 RUN
-------
``RUN <command>``
The `RUN` instruction will execute any commands on the current image and commit
the results. The resulting committed image will be used for the next step in the
Dockerfile.
The ``RUN`` instruction will execute any commands on the current image
and commit the results. The resulting committed image will be used for
the next step in the Dockerfile.
Layering `RUN` instructions and generating commits conforms to the
core concepts of Docker where commits are cheap and containers can be created
from any point in an image's history, much like source control.
Layering ``RUN`` instructions and generating commits conforms to the
core concepts of Docker where commits are cheap and containers can be
created from any point in an image's history, much like source
control.
2.4 CMD
3.4 CMD
-------
``CMD <command>``
The `CMD` instruction sets the command to be executed when running the image.
This is functionally equivalent to running
`docker commit -run '{"Cmd": <command>}'` outside the builder.
The ``CMD`` instruction sets the command to be executed when running
the image. This is functionally equivalent to running ``docker commit
-run '{"Cmd": <command>}'`` outside the builder.
.. note::
Don't confuse `RUN` with `CMD`. `RUN` actually runs a command and commits
the result; `CMD` does not execute anything at build time, but specifies the
intended command for the image.
.. note::
Don't confuse `RUN` with `CMD`. `RUN` actually runs a
command and commits the result; `CMD` does not execute anything at
build time, but specifies the intended command for the image.
2.5 EXPOSE
3.5 EXPOSE
----------
``EXPOSE <port> [<port>...]``
The `EXPOSE` instruction sets ports to be publicly exposed when running the
image. This is functionally equivalent to running
`docker commit -run '{"PortSpecs": ["<port>", "<port2>"]}'` outside the builder.
The ``EXPOSE`` instruction sets ports to be publicly exposed when
running the image. This is functionally equivalent to running ``docker
commit -run '{"PortSpecs": ["<port>", "<port2>"]}'`` outside the
builder.
2.6 ENV
3.6 ENV
-------
``ENV <key> <value>``
The `ENV` instruction sets the environment variable `<key>` to the value
`<value>`. This value will be passed to all future ``RUN`` instructions. This is
functionally equivalent to prefixing the command with `<key>=<value>`
The ``ENV`` instruction sets the environment variable ``<key>`` to the
value ``<value>``. This value will be passed to all future ``RUN``
instructions. This is functionally equivalent to prefixing the command
with ``<key>=<value>``
.. note::
The environment variables will persist when a container is run from the resulting image.
.. note::
The environment variables will persist when a container is run
from the resulting image.
2.7 INSERT
----------
``INSERT <file url> <path>``
The `INSERT` instruction will download the file from the given url to the given
path within the image. It is similar to `RUN curl -o <path> <url>`, assuming
curl was installed within the image.
.. note::
The path must include the file name.
2.8 ADD
3.7 ADD
-------
``ADD <src> <dest>``
The `ADD` instruction will copy new files from <src> and add them to the container's filesystem at path `<dest>`.
The ``ADD`` instruction will copy new files from <src> and add them to
the container's filesystem at path ``<dest>``.
`<src>` must be the path to a file or directory relative to the source directory being built (also called the
context of the build).
``<src>`` must be the path to a file or directory relative to the
source directory being built (also called the *context* of the build) or
a remote file URL.
`<dest>` is the path at which the source will be copied in the destination container.
``<dest>`` is the path at which the source will be copied in the
destination container.
The copy obeys the following rules:
If `<src>` is a directory, the entire directory is copied, including filesystem metadata.
* If ``<src>`` is a directory, the entire directory is copied,
including filesystem metadata.
* If ``<src>``` is a tar archive in a recognized compression format
(identity, gzip, bzip2 or xz), it is unpacked as a directory.
If `<src>` is a tar archive in a recognized compression format (identity, gzip, bzip2 or xz), it
is unpacked as a directory.
When a directory is copied or unpacked, it has the same behavior as
``tar -x``: the result is the union of
When a directory is copied or unpacked, it has the same behavior as 'tar -x': the result is the union of
a) whatever existed at the destination path and b) the contents of the source tree, with conflicts resolved
in favor of b on a file-by-file basis.
1. whatever existed at the destination path and
2. the contents of the source tree,
If `<src>` is any other kind of file, it is copied individually along with its metadata. In this case,
if `<dst>` ends with a trailing slash '/', it will be considered a directory and the contents of `<src>`
will be written at `<dst>/base(<src>)`.
If `<dst>` does not end with a trailing slash, it will be considered a regular file and the contents
of `<src>` will be written at `<dst>`.
with conflicts resolved in favor of 2) on a file-by-file basis.
If `<dest>` doesn't exist, it is created along with all missing directories in its path. All new
files and directories are created with mode 0700, uid and gid 0.
* If ``<src>`` is any other kind of file, it is copied individually
along with its metadata. In this case, if ``<dst>`` ends with a
trailing slash ``/``, it will be considered a directory and the
contents of ``<src>`` will be written at ``<dst>/base(<src>)``.
* If ``<dst>`` does not end with a trailing slash, it will be
considered a regular file and the contents of ``<src>`` will be
written at ``<dst>``.
* If ``<dest>`` doesn't exist, it is created along with all missing
directories in its path. All new files and directories are created
with mode 0700, uid and gid 0.
3. Dockerfile Examples
3.8 ENTRYPOINT
-------------
``ENTRYPOINT /bin/echo``
The ``ENTRYPOINT`` instruction adds an entry command that will not be
overwritten when arguments are passed to docker run, unlike the
behavior of ``CMD``. This allows arguments to be passed to the
entrypoint. i.e. ``docker run <image> -d`` will pass the "-d" argument
to the entrypoint.
3.9 VOLUME
----------
``VOLUME ["/data"]``
The ``VOLUME`` instruction will add one or more new volumes to any
container created from the image.
4. Dockerfile Examples
======================
.. code-block:: bash
@@ -182,7 +212,6 @@ files and directories are created with mode 0700, uid and gid 0.
RUN apt-get update
RUN apt-get install -y inotify-tools nginx apache2 openssh-server
INSERT https://raw.github.com/creack/docker-vps/master/nginx-wrapper.sh /usr/sbin/nginx-wrapper
.. code-block:: bash

View File

@@ -1,27 +1,75 @@
:title: Working With Repositories
:description: Generally, there are two types of repositories: Top-level repositories which are controlled by the people behind Docker, and user repositories.
:description: Repositories allow users to share images.
:keywords: repo, repositiores, usage, pull image, push image, image, documentation
.. _working_with_the_repository:
Working with the Repository
===========================
Working with Repositories
=========================
A *repository* is a hosted collection of tagged :ref:`images
<image_def>` that together create the file system for a container. The
repository's name is a tag that indicates the provenance of the
repository, i.e. who created it and where the original copy is
located.
Top-level repositories and user repositories
--------------------------------------------
You can find one or more repositories hosted on a *registry*. There
can be an implicit or explicit host name as part of the repository
tag. The implicit registry is located at ``index.docker.io``, the home
of "top-level" repositories and the Central Index. This registry may
also include public "user" repositories.
Generally, there are two types of repositories: Top-level repositories which are controlled by the people behind
Docker, and user repositories.
So Docker is not only a tool for creating and managing your own
:ref:`containers <container_def>` -- **Docker is also a tool for
sharing**. The Docker project provides a Central Registry to host
public repositories, namespaced by user, and a Central Index which
provides user authentication and search over all the public
repositories. You can host your own Registry too! Docker acts as a
client for these services via ``docker search, pull, login`` and
``push``.
* 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 images are not checked, it is therefore up to you whether or not you trust the creator of this image.
Top-level, User, and Your Own Repositories
------------------------------------------
There are two types of public repositories: *top-level* repositories
which are controlled by the Docker team, and *user* repositories
created by individual contributors.
Find public images available on the index
-----------------------------------------
* 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 if you push to the public Central Registry.
* Only the authenticated user can push to their *username* namespace
on the Central Registry.
* User images are not checked, it is therefore up to you whether or
not you trust the creator of this image.
Right now (version 0.5), private repositories are only possible by
hosting `your own registry
<https://github.com/dotcloud/docker-registry>`_. To push or pull to a
repository on your own registry, you must prefix the tag with the
address of the registry's host, like this:
.. code-block:: bash
# Tag to create a repository with the full registry location.
# The location (e.g. localhost.localdomain:5000) becomes
# a permanent part of the repository name
docker tag 0u812deadbeef localhost.localdomain:5000/repo_name
# Push the new repository to its home location on localhost
docker push localhost.localdomain:5000/repo_name
Once a repository has your registry's host name as part of the tag,
you can push and pull it like any other repository, but it will
**not** be searchable (or indexed at all) in the Central Index, and
there will be no user name checking performed. Your registry will
function completely independently from the Central Index.
Find public images available on the Central Index
-------------------------------------------------
Seach by name, namespace or description
@@ -37,68 +85,48 @@ Download them simply by their name
docker pull <value>
Very similarly you can search for and browse the index online on https://index.docker.io
Very similarly you can search for and browse the index online on
https://index.docker.io
Connecting to the repository
----------------------------
Connecting to the Central Registry
----------------------------------
You can create a user on the central docker repository online, or by running
You can create a user on the central Docker Index online, or by running
.. code-block:: bash
docker login
This will prompt you for a username, which will become a public
namespace for your public repositories.
If your username does not exist it will prompt you to also enter a password and your e-mail address. It will then
automatically log you in.
If your username does not exist it will prompt you to also enter a
password and your e-mail address. It will then automatically log you
in.
Committing a container to a named image
---------------------------------------
In order to commit to the repository it is required to have committed your container to an image with your namespace.
In order to commit to the repository it is required to have committed
your container to an image within your username namespace.
.. code-block:: bash
# for example docker commit $CONTAINER_ID dhrp/kickassapp
docker commit <container_id> <your username>/<some_name>
docker commit <container_id> <username>/<repo_name>
Pushing a container to the repository
-----------------------------------------
Pushing a container to its repository
------------------------------------
In order to push an image to the repository you need to have committed your container to a named image (see above)
In order to push an image to its repository you need to have committed
your container to a named image (see above)
Now you can commit this image to the repository
.. code-block:: bash
# for example docker push dhrp/kickassapp
docker push <image-name>
Changing the server to connect to
----------------------------------
When you are running your own index and/or registry, You can change the server the docker client will connect to.
Variable
^^^^^^^^
.. code-block:: sh
DOCKER_INDEX_URL
Setting this environment variable on the docker server will change the URL docker index.
This address is used in commands such as ``docker login``, ``docker push`` and ``docker pull``.
The docker daemon doesn't need to be restarted for this parameter to take effect.
Example
^^^^^^^
.. code-block:: sh
docker -d &
export DOCKER_INDEX_URL="https://index.docker.io"
docker push <username>/<repo_name>

View File

@@ -40,8 +40,11 @@
{%- set script_files = script_files + ['_static/js/docs.js'] %}
{%- if pagename == 'index' %}
<link rel="canonical" href="http://docs.docker.io/en/latest/">
{% else %}
<link rel="canonical" href="http://docs.docker.io/en/latest/{{ pagename }}/">
{% endif %}
{%- for cssfile in css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{%- endfor %}

12
docs/theme/docker/redirect_build.html vendored Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Moved</title>
<meta http-equiv="refresh" content="0; url=http://docs.docker.io/en/latest/use/builder/">
</head>
<body>
This page has moved. Perhaps you should visit the <a href="http://docs.docker.io/en/latest/use/builder/" title="builder page">Builder page</a>
</body>
</html>

View File

@@ -2,7 +2,7 @@
<html>
<head>
<title>Page Moved</title>
<meta http-equiv="refresh" content="0; url=http://docks.docker.io/en/latest/">
<meta http-equiv="refresh" content="0; url=http://docs.docker.io/en/latest/">
</head>
<body>

View File

@@ -1,10 +0,0 @@
package docker
import (
"fmt"
"github.com/dotcloud/docker/utils"
)
func getKernelVersion() (*utils.KernelVersionInfo, error) {
return nil, fmt.Errorf("Kernel version detection is not available on darwin")
}

View File

@@ -1,71 +0,0 @@
package docker
import (
"bytes"
"github.com/dotcloud/docker/utils"
"strconv"
"strings"
"syscall"
)
// FIXME: Move this to utils package
func getKernelVersion() (*utils.KernelVersionInfo, error) {
var (
uts syscall.Utsname
flavor string
kernel, major, minor int
err error
)
if err := syscall.Uname(&uts); err != nil {
return nil, err
}
release := make([]byte, len(uts.Release))
i := 0
for _, c := range uts.Release {
release[i] = byte(c)
i++
}
// Remove the \x00 from the release for Atoi to parse correctly
release = release[:bytes.IndexByte(release, 0)]
tmp := strings.SplitN(string(release), "-", 2)
tmp2 := strings.SplitN(tmp[0], ".", 3)
if len(tmp2) > 0 {
kernel, err = strconv.Atoi(tmp2[0])
if err != nil {
return nil, err
}
}
if len(tmp2) > 1 {
major, err = strconv.Atoi(tmp2[1])
if err != nil {
return nil, err
}
}
if len(tmp2) > 2 {
minor, err = strconv.Atoi(tmp2[2])
if err != nil {
return nil, err
}
}
if len(tmp) == 2 {
flavor = tmp[1]
} else {
flavor = ""
}
return &utils.KernelVersionInfo{
Kernel: kernel,
Major: major,
Minor: minor,
Flavor: flavor,
}, nil
}

View File

@@ -162,7 +162,7 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
// The archive is stored on disk and will be automatically deleted as soon as has been read.
// If output is not nil, a human-readable progress bar will be written to it.
// FIXME: does this belong in Graph? How about MktempFile, let the caller use it for archives?
func (graph *Graph) TempLayerArchive(id string, compression Compression, output io.Writer) (*TempArchive, error) {
func (graph *Graph) TempLayerArchive(id string, compression Compression, sf *utils.StreamFormatter, output io.Writer) (*TempArchive, error) {
image, err := graph.Get(id)
if err != nil {
return nil, err
@@ -175,7 +175,6 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, output
if err != nil {
return nil, err
}
sf := utils.NewStreamFormatter(false)
return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)"), sf), tmp.Root)
}
@@ -189,7 +188,7 @@ func (graph *Graph) Mktemp(id string) (string, error) {
return "", fmt.Errorf("Couldn't create temp: %s", err)
}
if tmp.Exists(id) {
return "", fmt.Errorf("Image %d already exists", id)
return "", fmt.Errorf("Image %s already exists", id)
}
return tmp.imageRoot(id), nil
}

View File

@@ -3,8 +3,8 @@ This directory contains material helpful for hacking on docker.
make hack
=========
Set up an Ubuntu 13.04 virtual machine for developers including kernel 3.8
and buildbot. The environment is setup in a way that can be used through
Set up an Ubuntu 12.04 virtual machine for developers including kernel 3.8
go1.1 and buildbot. The environment is setup in a way that can be used through
the usual go workflow and/or the root Makefile. You can either edit on
your host, or inside the VM (using make ssh-dev) and run and test docker
inside the VM.
@@ -22,6 +22,7 @@ developers are inconvenienced by the failure.
When running 'make hack' at the docker root directory, it spawns a virtual
machine in the background running a buildbot instance and adds a git
post-commit hook that automatically run docker tests for you.
post-commit hook that automatically run docker tests for you each time you
commit in your local docker repository.
You can check your buildbot instance at http://192.168.33.21:8010/waterfall

133
hack/RELEASE.md Normal file
View File

@@ -0,0 +1,133 @@
## A maintainer's guide to releasing Docker
So you're in charge of a docker release? Cool. Here's what to do.
If your experience deviates from this document, please document the changes to keep it
up-to-date.
### 1. Pull from master and create a release branch
```bash
$ git checkout master
$ git pull
$ git checkout -b bump_$VERSION
```
### 2. Update CHANGELOG.md
You can run this command for reference:
```bash
LAST_VERSION=$(git tag | grep -E "v[0-9\.]+$" | sort -nr | head -n 1)
git log $LAST_VERSION..HEAD
```
Each change should be formatted as ```BULLET CATEGORY: DESCRIPTION```
* BULLET is either ```-```, ```+``` or ```*```, to indicate a bugfix,
new feature or upgrade, respectively.
* CATEGORY should describe which part of the project is affected.
Valid categories are:
* Builder
* Documentation
* Hack
* Packaging
* Remote API
* Runtime
* DESCRIPTION: a concise description of the change that is relevant to the end-user,
using the present tense.
Changes should be described in terms of how they affect the user, for example "new feature
X which allows Y", "fixed bug which caused X", "increased performance of Y".
EXAMPLES:
```
+ Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
* Runtime: improve detection of kernel version
- Remote API: fix a bug in the optional unix socket transport
```
### 3. Change VERSION in commands.go
### 4. Run all tests
```bash
$ make test
```
### 5. Commit and create a pull request
```bash
$ git add commands.go CHANGELOG.md
$ git commit -m "Bump version to $VERSION"
$ git push origin bump_$VERSION
```
### 6. Get 2 other maintainers to validate the pull request
### 7. Merge the pull request and apply tags
```bash
$ git checkout master
$ git merge bump_$VERSION
$ git tag -a v$VERSION # Don't forget the v!
$ git tag -f -a latest
$ git push
$ git push --tags
```
### 8. Publish binaries
To run this you will need access to the release credentials.
Get them from [the infrastructure maintainers](https://github.com/dotcloud/docker/blob/master/hack/infrastructure/MAINTAINERS).
```bash
$ RELEASE_IMAGE=image_provided_by_infrastructure_maintainers
$ BUILD=$(docker run -d -e RELEASE_PPA=0 $RELEASE_IMAGE)
```
This will do 2 things:
* It will build and upload the binaries on http://get.docker.io
* It will *test* the release on our Ubuntu PPA (a PPA is a community repository for ubuntu packages)
Wait for the build to complete.
```bash
$ docker wait $BUILD # This should print 0. If it doesn't, your build failed.
```
Check that the output looks OK. Here's an example of a correct output:
```bash
$ docker logs 2>&1 b4e7c8299d73 | grep -e 'Public URL' -e 'Successfully uploaded'
Public URL of the object is: http://get.docker.io.s3.amazonaws.com/builds/Linux/x86_64/docker-v0.4.7.tgz
Public URL of the object is: http://get.docker.io.s3.amazonaws.com/builds/Linux/x86_64/docker-latest.tgz
Successfully uploaded packages.
```
If you don't see 3 lines similar to this, something might be wrong. Check the full logs and try again.
### 9. Publish Ubuntu packages
If everything went well in the previous step, you can finalize the release by submitting the Ubuntu
packages.
```bash
$ RELEASE_IMAGE=image_provided_by_infrastructure_maintainers
$ docker run -e RELEASE_PPA=1 $RELEASE_IMAGE
```
If that goes well, Ubuntu Precise package is in its way. It will take anywhere from 0.5 to 30 hours
for the builders to complete their job depending on builder demand at this time. At this point, Quantal
and Raring packages need to be created using the Launchpad interface:
https://launchpad.net/~dotcloud/+archive/lxc-docker/+packages
Notify [the packager maintainers](https://github.com/dotcloud/docker/blob/master/packaging/MAINTAINERS)
who will ensure PPA is ready.
Congratulations! You're done

9
hack/Vagrantfile vendored
View File

@@ -2,7 +2,7 @@
# vi: set ft=ruby :
BOX_NAME = "ubuntu-dev"
BOX_URI = "http://cloud-images.ubuntu.com/raring/current/raring-server-cloudimg-vagrant-amd64-disk1.box"
BOX_URI = "http://files.vagrantup.com/precise64.box"
VM_IP = "192.168.33.21"
USER = "vagrant"
GOPATH = "/data/docker"
@@ -21,14 +21,15 @@ Vagrant::Config.run do |config|
# Touch for makefile
pkg_cmd = "touch #{DOCKER_PATH}; "
# Install docker dependencies
pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; " \
"apt-get install -q -y lxc git aufs-tools golang make linux-image-extra-3.8.0-19-generic; " \
pkg_cmd << "apt-get update -qq; apt-get install -y python-software-properties; " \
"add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu; apt-get update -qq; " \
"apt-get install -y linux-image-generic-lts-raring lxc git aufs-tools golang-stable make; " \
"chown -R #{USER}.#{USER} #{GOPATH}; " \
"install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile"
config.vm.provision :shell, :inline => pkg_cmd
# Deploy buildbot CI
pkg_cmd = "apt-get install -q -y python-dev python-pip supervisor; " \
"pip install -r #{CFG_PATH}/requirements.txt; " \
"pip install -q -r #{CFG_PATH}/requirements.txt; " \
"chown #{USER}.#{USER} /data; cd /data; " \
"#{CFG_PATH}/setup.sh #{USER} #{GOPATH} #{DOCKER_PATH} #{CFG_PATH} #{BUILDBOT_PATH}"
config.vm.provision :shell, :inline => pkg_cmd

View File

@@ -5,8 +5,11 @@
# 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
# RUN_CMD docker run -e AWS_ID="$AWS_ID" -e AWS_KEY="$AWS_KEY" -e GPG_KEY="$GPG_KEY" -e PUBLISH_PPA="$PUBLISH_PPA" dockerbuilder
#
# ENV_VARIABLES AWS_ID, AWS_KEY: S3 credentials for uploading Docker binary and tarball
# GPG_KEY: Signing key for docker package
# PUBLISH_PPA: 0 for staging release, 1 for production release
#
from ubuntu:12.04
maintainer Solomon Hykes <solomon@dotcloud.com>

View File

@@ -92,9 +92,14 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error {
defer file.Close()
layerData = file
}
if err := Untar(layerData, layer); err != nil {
return err
// If layerData is not nil, unpack it into the new layer
if layerData != nil {
start := time.Now()
utils.Debugf("Start untar layer")
if err := Untar(layerData, layer); err != nil {
return err
}
utils.Debugf("Untar time: %vs\n", time.Now().Sub(start).Seconds())
}
return StoreSize(img, root)

View File

@@ -84,8 +84,9 @@ lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/sbin/init none bind,ro 0 0
# In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
lxc.mount.entry = {{.ResolvConfPath}} {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0
{{if .Volumes}}
{{range $virtualPath, $realPath := .GetVolumes}}
lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,rw 0 0
{{ $rw := .VolumesRW }}
{{range $virtualPath, $realPath := .Volumes}}
lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,{{ if index $rw $virtualPath }}rw{{else}}ro{{end}} 0 0
{{end}}
{{end}}

View File

@@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"log"
"net"
"os/exec"
@@ -183,8 +182,10 @@ func getIfaceAddr(name string) (net.Addr, error) {
// up iptables rules.
// It keeps track of all mappings and is able to unmap at will
type PortMapper struct {
mapping map[int]net.TCPAddr
proxies map[int]net.Listener
tcpMapping map[int]*net.TCPAddr
tcpProxies map[int]Proxy
udpMapping map[int]*net.UDPAddr
udpProxies map[int]Proxy
}
func (mapper *PortMapper) cleanup() error {
@@ -197,8 +198,10 @@ func (mapper *PortMapper) cleanup() error {
iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")
iptables("-t", "nat", "-F", "DOCKER")
iptables("-t", "nat", "-X", "DOCKER")
mapper.mapping = make(map[int]net.TCPAddr)
mapper.proxies = make(map[int]net.Listener)
mapper.tcpMapping = make(map[int]*net.TCPAddr)
mapper.tcpProxies = make(map[int]Proxy)
mapper.udpMapping = make(map[int]*net.UDPAddr)
mapper.udpProxies = make(map[int]Proxy)
return nil
}
@@ -215,77 +218,72 @@ func (mapper *PortMapper) setup() error {
return nil
}
func (mapper *PortMapper) iptablesForward(rule string, port int, dest net.TCPAddr) error {
return iptables("-t", "nat", rule, "DOCKER", "-p", "tcp", "--dport", strconv.Itoa(port),
"-j", "DNAT", "--to-destination", net.JoinHostPort(dest.IP.String(), strconv.Itoa(dest.Port)))
func (mapper *PortMapper) iptablesForward(rule string, port int, proto string, dest_addr string, dest_port int) error {
return iptables("-t", "nat", rule, "DOCKER", "-p", proto, "--dport", strconv.Itoa(port),
"-j", "DNAT", "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port)))
}
func (mapper *PortMapper) Map(port int, dest net.TCPAddr) error {
if err := mapper.iptablesForward("-A", port, dest); err != nil {
return err
func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error {
if _, isTCP := backendAddr.(*net.TCPAddr); isTCP {
backendPort := backendAddr.(*net.TCPAddr).Port
backendIP := backendAddr.(*net.TCPAddr).IP
if err := mapper.iptablesForward("-A", port, "tcp", backendIP.String(), backendPort); err != nil {
return err
}
mapper.tcpMapping[port] = backendAddr.(*net.TCPAddr)
proxy, err := NewProxy(&net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port}, backendAddr)
if err != nil {
mapper.Unmap(port, "tcp")
return err
}
mapper.tcpProxies[port] = proxy
go proxy.Run()
} else {
backendPort := backendAddr.(*net.UDPAddr).Port
backendIP := backendAddr.(*net.UDPAddr).IP
if err := mapper.iptablesForward("-A", port, "udp", backendIP.String(), backendPort); err != nil {
return err
}
mapper.udpMapping[port] = backendAddr.(*net.UDPAddr)
proxy, err := NewProxy(&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port}, backendAddr)
if err != nil {
mapper.Unmap(port, "udp")
return err
}
mapper.udpProxies[port] = proxy
go proxy.Run()
}
mapper.mapping[port] = dest
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
mapper.Unmap(port)
return err
}
mapper.proxies[port] = listener
go proxy(listener, "tcp", dest.String())
return nil
}
// proxy listens for socket connections on `listener`, and forwards them unmodified
// to `proto:address`
func proxy(listener net.Listener, proto, address string) error {
utils.Debugf("proxying to %s:%s", proto, address)
defer utils.Debugf("Done proxying to %s:%s", proto, address)
for {
utils.Debugf("Listening on %s", listener)
src, err := listener.Accept()
if err != nil {
func (mapper *PortMapper) Unmap(port int, proto string) error {
if proto == "tcp" {
backendAddr, ok := mapper.tcpMapping[port]
if !ok {
return fmt.Errorf("Port tcp/%v is not mapped", port)
}
if proxy, exists := mapper.tcpProxies[port]; exists {
proxy.Close()
delete(mapper.tcpProxies, port)
}
if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
return err
}
utils.Debugf("Connecting to %s:%s", proto, address)
dst, err := net.Dial(proto, address)
if err != nil {
log.Printf("Error connecting to %s:%s: %s", proto, address, err)
src.Close()
continue
delete(mapper.tcpMapping, port)
} else {
backendAddr, ok := mapper.udpMapping[port]
if !ok {
return fmt.Errorf("Port udp/%v is not mapped", port)
}
utils.Debugf("Connected to backend, splicing")
splice(src, dst)
if proxy, exists := mapper.udpProxies[port]; exists {
proxy.Close()
delete(mapper.udpProxies, port)
}
if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
return err
}
delete(mapper.udpMapping, port)
}
panic("Unreachable")
}
func halfSplice(dst, src net.Conn) error {
_, err := io.Copy(dst, src)
// FIXME: on EOF from a tcp connection, pass WriteClose()
dst.Close()
src.Close()
return err
}
func splice(a, b net.Conn) {
go halfSplice(a, b)
go halfSplice(b, a)
}
func (mapper *PortMapper) Unmap(port int) error {
dest, ok := mapper.mapping[port]
if !ok {
return errors.New("Port is not mapped")
}
if proxy, exists := mapper.proxies[port]; exists {
proxy.Close()
delete(mapper.proxies, port)
}
if err := mapper.iptablesForward("-D", port, dest); err != nil {
return err
}
delete(mapper.mapping, port)
return nil
}
@@ -302,9 +300,9 @@ func newPortMapper() (*PortMapper, error) {
// Port allocator: Atomatically allocate and release networking ports
type PortAllocator struct {
sync.Mutex
inUse map[int]struct{}
fountain chan (int)
lock sync.Mutex
}
func (alloc *PortAllocator) runFountain() {
@@ -318,9 +316,9 @@ func (alloc *PortAllocator) runFountain() {
// FIXME: Release can no longer fail, change its prototype to reflect that.
func (alloc *PortAllocator) Release(port int) error {
utils.Debugf("Releasing %d", port)
alloc.lock.Lock()
alloc.Lock()
delete(alloc.inUse, port)
alloc.lock.Unlock()
alloc.Unlock()
return nil
}
@@ -335,8 +333,8 @@ func (alloc *PortAllocator) Acquire(port int) (int, error) {
}
return -1, fmt.Errorf("Port generator ended unexpectedly")
}
alloc.lock.Lock()
defer alloc.lock.Unlock()
alloc.Lock()
defer alloc.Unlock()
if _, inUse := alloc.inUse[port]; inUse {
return -1, fmt.Errorf("Port already in use: %d", port)
}
@@ -454,7 +452,7 @@ type NetworkInterface struct {
Gateway net.IP
manager *NetworkManager
extPorts []int
extPorts []*Nat
}
// Allocate an external TCP port and map it to the interface
@@ -463,17 +461,32 @@ func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
if err != nil {
return nil, err
}
// Allocate a random port if Frontend==0
extPort, err := iface.manager.portAllocator.Acquire(nat.Frontend)
if err != nil {
return nil, err
if nat.Proto == "tcp" {
extPort, err := iface.manager.tcpPortAllocator.Acquire(nat.Frontend)
if err != nil {
return nil, err
}
backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: nat.Backend}
if err := iface.manager.portMapper.Map(extPort, backend); err != nil {
iface.manager.tcpPortAllocator.Release(extPort)
return nil, err
}
nat.Frontend = extPort
} else {
extPort, err := iface.manager.udpPortAllocator.Acquire(nat.Frontend)
if err != nil {
return nil, err
}
backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: nat.Backend}
if err := iface.manager.portMapper.Map(extPort, backend); err != nil {
iface.manager.udpPortAllocator.Release(extPort)
return nil, err
}
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
}
iface.extPorts = append(iface.extPorts, nat.Frontend)
iface.extPorts = append(iface.extPorts, nat)
return nat, nil
}
@@ -486,6 +499,21 @@ type Nat struct {
func parseNat(spec string) (*Nat, error) {
var nat Nat
if strings.Contains(spec, "/") {
specParts := strings.Split(spec, "/")
if len(specParts) != 2 {
return nil, fmt.Errorf("Invalid port format.")
}
proto := specParts[1]
spec = specParts[0]
if proto != "tcp" && proto != "udp" {
return nil, fmt.Errorf("Invalid port format: unknown protocol %v.", proto)
}
nat.Proto = proto
} else {
nat.Proto = "tcp"
}
if strings.Contains(spec, ":") {
specParts := strings.Split(spec, ":")
if len(specParts) != 2 {
@@ -518,20 +546,24 @@ func parseNat(spec string) (*Nat, error) {
}
nat.Backend = int(port)
}
nat.Proto = "tcp"
return &nat, nil
}
// Release: Network cleanup - release all resources
func (iface *NetworkInterface) Release() {
for _, port := range iface.extPorts {
if err := iface.manager.portMapper.Unmap(port); err != nil {
log.Printf("Unable to unmap port %v: %v", port, err)
for _, nat := range iface.extPorts {
utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend)
if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil {
log.Printf("Unable to unmap port %v/%v: %v", nat.Proto, nat.Frontend, err)
}
if err := iface.manager.portAllocator.Release(port); err != nil {
log.Printf("Unable to release port %v: %v", port, err)
if nat.Proto == "tcp" {
if err := iface.manager.tcpPortAllocator.Release(nat.Frontend); err != nil {
log.Printf("Unable to release port tcp/%v: %v", nat.Frontend, err)
}
} else if err := iface.manager.udpPortAllocator.Release(nat.Frontend); err != nil {
log.Printf("Unable to release port udp/%v: %v", nat.Frontend, err)
}
}
iface.manager.ipAllocator.Release(iface.IPNet.IP)
@@ -543,9 +575,10 @@ type NetworkManager struct {
bridgeIface string
bridgeNetwork *net.IPNet
ipAllocator *IPAllocator
portAllocator *PortAllocator
portMapper *PortMapper
ipAllocator *IPAllocator
tcpPortAllocator *PortAllocator
udpPortAllocator *PortAllocator
portMapper *PortMapper
}
// Allocate a network interface
@@ -578,7 +611,11 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
ipAllocator := newIPAllocator(network)
portAllocator, err := newPortAllocator()
tcpPortAllocator, err := newPortAllocator()
if err != nil {
return nil, err
}
udpPortAllocator, err := newPortAllocator()
if err != nil {
return nil, err
}
@@ -589,11 +626,12 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
}
manager := &NetworkManager{
bridgeIface: bridgeIface,
bridgeNetwork: network,
ipAllocator: ipAllocator,
portAllocator: portAllocator,
portMapper: portMapper,
bridgeIface: bridgeIface,
bridgeNetwork: network,
ipAllocator: ipAllocator,
tcpPortAllocator: tcpPortAllocator,
udpPortAllocator: udpPortAllocator,
portMapper: portMapper,
}
return manager, nil
}

258
network_proxy.go Normal file
View File

@@ -0,0 +1,258 @@
package docker
import (
"encoding/binary"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"log"
"net"
"sync"
"syscall"
"time"
)
const (
UDPConnTrackTimeout = 90 * time.Second
UDPBufSize = 2048
)
type Proxy interface {
// Start forwarding traffic back and forth the front and back-end
// addresses.
Run()
// Stop forwarding traffic and close both ends of the Proxy.
Close()
// Return the address on which the proxy is listening.
FrontendAddr() net.Addr
// Return the proxied address.
BackendAddr() net.Addr
}
type TCPProxy struct {
listener *net.TCPListener
frontendAddr *net.TCPAddr
backendAddr *net.TCPAddr
}
func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) {
listener, err := net.ListenTCP("tcp", frontendAddr)
if err != nil {
return nil, err
}
// If the port in frontendAddr was 0 then ListenTCP will have a picked
// a port to listen on, hence the call to Addr to get that actual port:
return &TCPProxy{
listener: listener,
frontendAddr: listener.Addr().(*net.TCPAddr),
backendAddr: backendAddr,
}, nil
}
func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) {
backend, err := net.DialTCP("tcp", nil, proxy.backendAddr)
if err != nil {
log.Printf("Can't forward traffic to backend tcp/%v: %v\n", proxy.backendAddr, err.Error())
client.Close()
return
}
event := make(chan int64)
var broker = func(to, from *net.TCPConn) {
written, err := io.Copy(to, from)
if err != nil {
err, ok := err.(*net.OpError)
// If the socket we are writing to is shutdown with
// SHUT_WR, forward it to the other end of the pipe:
if ok && err.Err == syscall.EPIPE {
from.CloseWrite()
}
}
to.CloseRead()
event <- written
}
utils.Debugf("Forwarding traffic between tcp/%v and tcp/%v", client.RemoteAddr(), backend.RemoteAddr())
go broker(client, backend)
go broker(backend, client)
var transferred int64 = 0
for i := 0; i < 2; i++ {
select {
case written := <-event:
transferred += written
case <-quit:
// Interrupt the two brokers and "join" them.
client.Close()
backend.Close()
for ; i < 2; i++ {
transferred += <-event
}
goto done
}
}
client.Close()
backend.Close()
done:
utils.Debugf("%v bytes transferred between tcp/%v and tcp/%v", transferred, client.RemoteAddr(), backend.RemoteAddr())
}
func (proxy *TCPProxy) Run() {
quit := make(chan bool)
defer close(quit)
utils.Debugf("Starting proxy on tcp/%v for tcp/%v", proxy.frontendAddr, proxy.backendAddr)
for {
client, err := proxy.listener.Accept()
if err != nil {
utils.Debugf("Stopping proxy on tcp/%v for tcp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error())
return
}
go proxy.clientLoop(client.(*net.TCPConn), quit)
}
}
func (proxy *TCPProxy) Close() { proxy.listener.Close() }
func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
// A net.Addr where the IP is split into two fields so you can use it as a key
// in a map:
type connTrackKey struct {
IPHigh uint64
IPLow uint64
Port int
}
func newConnTrackKey(addr *net.UDPAddr) *connTrackKey {
if len(addr.IP) == net.IPv4len {
return &connTrackKey{
IPHigh: 0,
IPLow: uint64(binary.BigEndian.Uint32(addr.IP)),
Port: addr.Port,
}
}
return &connTrackKey{
IPHigh: binary.BigEndian.Uint64(addr.IP[:8]),
IPLow: binary.BigEndian.Uint64(addr.IP[8:]),
Port: addr.Port,
}
}
type connTrackMap map[connTrackKey]*net.UDPConn
type UDPProxy struct {
listener *net.UDPConn
frontendAddr *net.UDPAddr
backendAddr *net.UDPAddr
connTrackTable connTrackMap
connTrackLock sync.Mutex
}
func NewUDPProxy(frontendAddr, backendAddr *net.UDPAddr) (*UDPProxy, error) {
listener, err := net.ListenUDP("udp", frontendAddr)
if err != nil {
return nil, err
}
return &UDPProxy{
listener: listener,
frontendAddr: listener.LocalAddr().(*net.UDPAddr),
backendAddr: backendAddr,
connTrackTable: make(connTrackMap),
}, nil
}
func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr, clientKey *connTrackKey) {
defer func() {
proxy.connTrackLock.Lock()
delete(proxy.connTrackTable, *clientKey)
proxy.connTrackLock.Unlock()
utils.Debugf("Done proxying between udp/%v and udp/%v", clientAddr.String(), proxy.backendAddr.String())
proxyConn.Close()
}()
readBuf := make([]byte, UDPBufSize)
for {
proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout))
again:
read, err := proxyConn.Read(readBuf)
if err != nil {
if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED {
// This will happen if the last write failed
// (e.g: nothing is actually listening on the
// proxied port on the container), ignore it
// and continue until UDPConnTrackTimeout
// expires:
goto again
}
return
}
for i := 0; i != read; {
written, err := proxy.listener.WriteToUDP(readBuf[i:read], clientAddr)
if err != nil {
return
}
i += written
utils.Debugf("Forwarded %v/%v bytes to udp/%v", i, read, clientAddr.String())
}
}
}
func (proxy *UDPProxy) Run() {
readBuf := make([]byte, UDPBufSize)
utils.Debugf("Starting proxy on udp/%v for udp/%v", proxy.frontendAddr, proxy.backendAddr)
for {
read, from, err := proxy.listener.ReadFromUDP(readBuf)
if err != nil {
// NOTE: Apparently ReadFrom doesn't return
// ECONNREFUSED like Read do (see comment in
// UDPProxy.replyLoop)
utils.Debugf("Stopping proxy on udp/%v for udp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error())
break
}
fromKey := newConnTrackKey(from)
proxy.connTrackLock.Lock()
proxyConn, hit := proxy.connTrackTable[*fromKey]
if !hit {
proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr)
if err != nil {
log.Printf("Can't proxy a datagram to udp/%s: %v\n", proxy.backendAddr.String(), err)
continue
}
proxy.connTrackTable[*fromKey] = proxyConn
go proxy.replyLoop(proxyConn, from, fromKey)
}
proxy.connTrackLock.Unlock()
for i := 0; i != read; {
written, err := proxyConn.Write(readBuf[i:read])
if err != nil {
log.Printf("Can't proxy a datagram to udp/%s: %v\n", proxy.backendAddr.String(), err)
break
}
i += written
utils.Debugf("Forwarded %v/%v bytes to udp/%v", i, read, proxy.backendAddr.String())
}
}
}
func (proxy *UDPProxy) Close() {
proxy.listener.Close()
proxy.connTrackLock.Lock()
defer proxy.connTrackLock.Unlock()
for _, conn := range proxy.connTrackTable {
conn.Close()
}
}
func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
switch frontendAddr.(type) {
case *net.UDPAddr:
return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr))
case *net.TCPAddr:
return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr))
default:
panic(fmt.Errorf("Unsupported protocol"))
}
}

221
network_proxy_test.go Normal file
View File

@@ -0,0 +1,221 @@
package docker
import (
"bytes"
"fmt"
"io"
"net"
"strings"
"testing"
"time"
)
var testBuf = []byte("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo")
var testBufSize = len(testBuf)
type EchoServer interface {
Run()
Close()
LocalAddr() net.Addr
}
type TCPEchoServer struct {
listener net.Listener
testCtx *testing.T
}
type UDPEchoServer struct {
conn net.PacketConn
testCtx *testing.T
}
func NewEchoServer(t *testing.T, proto, address string) EchoServer {
var server EchoServer
if strings.HasPrefix(proto, "tcp") {
listener, err := net.Listen(proto, address)
if err != nil {
t.Fatal(err)
}
server = &TCPEchoServer{listener: listener, testCtx: t}
} else {
socket, err := net.ListenPacket(proto, address)
if err != nil {
t.Fatal(err)
}
server = &UDPEchoServer{conn: socket, testCtx: t}
}
t.Logf("EchoServer listening on %v/%v\n", proto, server.LocalAddr().String())
return server
}
func (server *TCPEchoServer) Run() {
go func() {
for {
client, err := server.listener.Accept()
if err != nil {
return
}
go func(client net.Conn) {
server.testCtx.Logf("TCP client accepted on the EchoServer\n")
written, err := io.Copy(client, client)
server.testCtx.Logf("%v bytes echoed back to the client\n", written)
if err != nil {
server.testCtx.Logf("can't echo to the client: %v\n", err.Error())
}
client.Close()
}(client)
}
}()
}
func (server *TCPEchoServer) LocalAddr() net.Addr { return server.listener.Addr() }
func (server *TCPEchoServer) Close() { server.listener.Addr() }
func (server *UDPEchoServer) Run() {
go func() {
readBuf := make([]byte, 1024)
for {
read, from, err := server.conn.ReadFrom(readBuf)
if err != nil {
return
}
server.testCtx.Logf("Writing UDP datagram back")
for i := 0; i != read; {
written, err := server.conn.WriteTo(readBuf[i:read], from)
if err != nil {
break
}
i += written
}
}
}()
}
func (server *UDPEchoServer) LocalAddr() net.Addr { return server.conn.LocalAddr() }
func (server *UDPEchoServer) Close() { server.conn.Close() }
func testProxyAt(t *testing.T, proto string, proxy Proxy, addr string) {
defer proxy.Close()
go proxy.Run()
client, err := net.Dial(proto, addr)
if err != nil {
t.Fatalf("Can't connect to the proxy: %v", err)
}
defer client.Close()
client.SetDeadline(time.Now().Add(10 * time.Second))
if _, err = client.Write(testBuf); err != nil {
t.Fatal(err)
}
recvBuf := make([]byte, testBufSize)
if _, err = client.Read(recvBuf); err != nil {
t.Fatal(err)
}
if !bytes.Equal(testBuf, recvBuf) {
t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf))
}
}
func testProxy(t *testing.T, proto string, proxy Proxy) {
testProxyAt(t, proto, proxy, proxy.FrontendAddr().String())
}
func TestTCP4Proxy(t *testing.T) {
backend := NewEchoServer(t, "tcp", "127.0.0.1:0")
defer backend.Close()
backend.Run()
frontendAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
if err != nil {
t.Fatal(err)
}
testProxy(t, "tcp", proxy)
}
func TestTCP6Proxy(t *testing.T) {
backend := NewEchoServer(t, "tcp", "[::1]:0")
defer backend.Close()
backend.Run()
frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
if err != nil {
t.Fatal(err)
}
testProxy(t, "tcp", proxy)
}
func TestTCPDualStackProxy(t *testing.T) {
// If I understand `godoc -src net favoriteAddrFamily` (used by the
// net.Listen* functions) correctly this should work, but it doesn't.
t.Skip("No support for dual stack yet")
backend := NewEchoServer(t, "tcp", "[::1]:0")
defer backend.Close()
backend.Run()
frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
if err != nil {
t.Fatal(err)
}
ipv4ProxyAddr := &net.TCPAddr{
IP: net.IPv4(127, 0, 0, 1),
Port: proxy.FrontendAddr().(*net.TCPAddr).Port,
}
testProxyAt(t, "tcp", proxy, ipv4ProxyAddr.String())
}
func TestUDP4Proxy(t *testing.T) {
backend := NewEchoServer(t, "udp", "127.0.0.1:0")
defer backend.Close()
backend.Run()
frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
if err != nil {
t.Fatal(err)
}
testProxy(t, "udp", proxy)
}
func TestUDP6Proxy(t *testing.T) {
backend := NewEchoServer(t, "udp", "[::1]:0")
defer backend.Close()
backend.Run()
frontendAddr := &net.UDPAddr{IP: net.IPv6loopback, Port: 0}
proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
if err != nil {
t.Fatal(err)
}
testProxy(t, "udp", proxy)
}
func TestUDPWriteError(t *testing.T) {
frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
// Hopefully, this port will be free: */
backendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 25587}
proxy, err := NewProxy(frontendAddr, backendAddr)
if err != nil {
t.Fatal(err)
}
defer proxy.Close()
go proxy.Run()
client, err := net.Dial("udp", "127.0.0.1:25587")
if err != nil {
t.Fatalf("Can't connect to the proxy: %v", err)
}
defer client.Close()
// Make sure the proxy doesn't stop when there is no actual backend:
client.Write(testBuf)
client.Write(testBuf)
backend := NewEchoServer(t, "udp", "127.0.0.1:25587")
defer backend.Close()
backend.Run()
client.SetDeadline(time.Now().Add(10 * time.Second))
if _, err = client.Write(testBuf); err != nil {
t.Fatal(err)
}
recvBuf := make([]byte, testBufSize)
if _, err = client.Read(recvBuf); err != nil {
t.Fatal(err)
}
if !bytes.Equal(testBuf, recvBuf) {
t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf))
}
}

View File

@@ -20,28 +20,97 @@ 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)
if nat.Frontend != 0 || nat.Backend != 4500 || nat.Proto != "tcp" {
t.Errorf("-p 4500 should produce 0->4500/tcp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} 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)
if nat.Frontend != 4501 || nat.Backend != 4501 || nat.Proto != "tcp" {
t.Errorf("-p :4501 should produce 4501->4501/tcp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} 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)
if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" {
t.Errorf("-p 4502:4503 should produce 4502->4503/tcp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat("4502:4503/tcp"); err == nil {
if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" {
t.Errorf("-p 4502:4503/tcp should produce 4502->4503/tcp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat("4502:4503/udp"); err == nil {
if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "udp" {
t.Errorf("-p 4502:4503/udp should produce 4502->4503/udp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat(":4503/udp"); err == nil {
if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "udp" {
t.Errorf("-p :4503/udp should produce 4503->4503/udp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat(":4503/tcp"); err == nil {
if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "tcp" {
t.Errorf("-p :4503/tcp should produce 4503->4503/tcp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat("4503/tcp"); err == nil {
if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "tcp" {
t.Errorf("-p 4503/tcp should produce 0->4503/tcp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if nat, err := parseNat("4503/udp"); err == nil {
if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "udp" {
t.Errorf("-p 4503/udp should produce 0->4503/udp, got %d->%d/%s",
nat.Frontend, nat.Backend, nat.Proto)
}
} else {
t.Fatal(err)
}
if _, err := parseNat("4503/tcpgarbage"); err == nil {
t.Fatal(err)
}
if _, err := parseNat("4503/tcp/udp"); err == nil {
t.Fatal(err)
}
if _, err := parseNat("4503/"); err == nil {
t.Fatal(err)
}
}
func TestPortAllocation(t *testing.T) {

View File

@@ -13,8 +13,8 @@ PKG_NAME=lxc-docker
ROOT_PATH=$(shell git rev-parse --show-toplevel)
GITHUB_PATH=github.com/dotcloud/docker
BUILD_SRC=build_src
VERSION_TAG?=v$(shell sed -E 's/.+\((.+)-.+\).+/\1/;q' changelog)
VERSION=$(shell echo ${VERSION_TAG} | cut -c2-)
VERSION=$(shell sed -En '0,/^\#\# /{s/^\#\# ([^ ]+).+/\1/p}' ../../CHANGELOG.md)
VERSION_TAG?=v${VERSION}
DOCKER_VERSION=${PKG_NAME}_${VERSION}
all:
@@ -28,7 +28,6 @@ install:
mkdir -p $(DESTDIR)/usr/share/doc/lxc-docker
install -m 0755 src/${GITHUB_PATH}/docker/docker $(DESTDIR)/usr/bin/lxc-docker
cp debian/lxc-docker.1 $(DESTDIR)/usr/share/man/man1
cp debian/CHANGELOG.md $(DESTDIR)/usr/share/doc/lxc-docker/changelog
debian:
# Prepare docker source from revision ${VERSION_TAG}
@@ -41,6 +40,7 @@ debian:
cp -r `ls | grep -v ${BUILD_SRC}` ${BUILD_SRC}/debian
cp ${ROOT_PATH}/README.md ${BUILD_SRC}
cp ${ROOT_PATH}/CHANGELOG.md ${BUILD_SRC}/debian
./parse_changelog.py < ../../CHANGELOG.md > ${BUILD_SRC}/debian/changelog
# Cleanup
rm -rf `find . -name '.git*'`
rm -f ${DOCKER_VERSION}*

View File

@@ -13,6 +13,9 @@ Vagrant::Config.run do |config|
# Install debian packaging dependencies and create debian packages
pkg_cmd = "apt-get -qq update; DEBIAN_FRONTEND=noninteractive apt-get install -qq -y #{PKG_DEP}; " \
"curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz; " \
"tar -C /usr/local -xzf /go.tar.gz; rm /usr/bin/go; " \
"ln -s /usr/local/go/bin/go /usr/bin; "\
"export GPG_KEY='#{ENV['GPG_KEY']}'; cd /data/docker/packaging/debian; make debian"
config.vm.provision :shell, :inline => pkg_cmd
end

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env python
'Parse main CHANGELOG.md from stdin outputing on stdout the debian changelog'
import sys,re, datetime
on_block=False
for line in sys.stdin.readlines():
line = line.strip()
if line.startswith('# ') or len(line) == 0:
continue
if line.startswith('## '):
if on_block:
print '\n -- dotCloud <ops@dotcloud.com> {0}\n'.format(date)
version, date = line[3:].split()
date = datetime.datetime.strptime(date, '(%Y-%m-%d)').strftime(
'%a, %d %b %Y 00:00:00 -0700')
on_block = True
print 'lxc-docker ({0}-1) precise; urgency=low'.format(version)
continue
if on_block:
print ' ' + line
print '\n -- dotCloud <ops@dotcloud.com> {0}'.format(date)

View File

@@ -15,7 +15,7 @@ VERSION=$(shell sed -En '0,/^\#\# /{s/^\#\# ([^ ]+).+/\1/p}' ../../CHANGELOG.md)
all:
# Compile docker. Used by dpkg-buildpackage.
cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} go build
cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} CGO_ENABLED=0 go build -a -ldflags '-d -w'
install:
# Used by dpkg-buildpackage
@@ -23,7 +23,7 @@ install:
mkdir -p ${DESTDIR}/etc/init
mkdir -p ${DESTDIR}/DEBIAN
install -m 0755 src/${GITHUB_PATH}/docker/docker ${DESTDIR}/usr/bin
install -o root -m 0755 debian/docker.upstart ${DESTDIR}/etc/init/docker.conf
install -o root -m 0644 debian/docker.upstart ${DESTDIR}/etc/init/docker.conf
install debian/lxc-docker.prerm ${DESTDIR}/DEBIAN/prerm
install debian/lxc-docker.postinst ${DESTDIR}/DEBIAN/postinst

View File

@@ -1,4 +1,4 @@
#!/bin/sh
# Stop docker
/sbin/stop docker
if [ "`pgrep -f '/usr/bin/docker -d'`" != "" ]; then /sbin/stop docker; fi

View File

@@ -12,11 +12,91 @@ import (
"net/http"
"net/http/cookiejar"
"net/url"
"regexp"
"strconv"
"strings"
)
var ErrAlreadyExists = errors.New("Image already exists")
var ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
func pingRegistryEndpoint(endpoint string) error {
if endpoint == auth.IndexServerAddress() {
// Skip the check, we now this one is valid
// (and we never want to fallback to http in case of error)
return nil
}
resp, err := http.Get(endpoint + "_ping")
if err != nil {
return err
}
if resp.Header.Get("X-Docker-Registry-Version") == "" {
return errors.New("This does not look like a Registry server (\"X-Docker-Registry-Version\" header not found in the response)")
}
return nil
}
func validateRepositoryName(repositoryName string) error {
var (
namespace string
name string
)
nameParts := strings.SplitN(repositoryName, "/", 2)
if len(nameParts) < 2 {
namespace = "library"
name = nameParts[0]
} else {
namespace = nameParts[0]
name = nameParts[1]
}
validNamespace := regexp.MustCompile(`^([a-z0-9_]{4,30})$`)
if !validNamespace.MatchString(namespace) {
return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", namespace)
}
validRepo := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)$`)
if !validRepo.MatchString(name) {
return fmt.Errorf("Invalid repository name (%s), only [a-zA-Z0-9-_.] are allowed", name)
}
return nil
}
// Resolves a repository name to a endpoint + name
func ResolveRepositoryName(reposName string) (string, string, error) {
if strings.Contains(reposName, "://") {
// It cannot contain a scheme!
return "", "", ErrInvalidRepositoryName
}
nameParts := strings.SplitN(reposName, "/", 2)
if !strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") {
// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
err := validateRepositoryName(reposName)
return auth.IndexServerAddress(), reposName, err
}
if len(nameParts) < 2 {
// There is a dot in repos name (and no registry address)
// Is it a Registry address without repos name?
return "", "", ErrInvalidRepositoryName
}
hostname := nameParts[0]
reposName = nameParts[1]
if strings.Contains(hostname, "index.docker.io") {
return "", "", fmt.Errorf("Invalid repository name, try \"%s\" instead", reposName)
}
if err := validateRepositoryName(reposName); err != nil {
return "", "", err
}
endpoint := fmt.Sprintf("https://%s/v1/", hostname)
if err := pingRegistryEndpoint(endpoint); err != nil {
utils.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err)
endpoint = fmt.Sprintf("http://%s/v1/", hostname)
if err = pingRegistryEndpoint(endpoint); err != nil {
//TODO: triggering highland build can be done there without "failing"
return "", "", errors.New("Invalid Registry endpoint: " + err.Error())
}
}
err := validateRepositoryName(reposName)
return endpoint, reposName, err
}
func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
for _, cookie := range c.Jar.Cookies(req.URL) {
@@ -27,8 +107,8 @@ func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
// Retrieve the history of a given image from the Registry.
// Return a list of the parent's json (requested image included)
func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]string, error) {
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/ancestry", nil)
func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]string, error) {
req, err := http.NewRequest("GET", registry+"images/"+imgID+"/ancestry", nil)
if err != nil {
return nil, err
}
@@ -36,7 +116,7 @@ func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]s
res, err := r.client.Do(req)
if err != nil || res.StatusCode != 200 {
if res != nil {
return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgId)
return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgID)
}
return nil, err
}
@@ -56,60 +136,25 @@ func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]s
}
// Check if an image exists in the Registry
func (r *Registry) LookupRemoteImage(imgId, registry string, authConfig *auth.AuthConfig) bool {
func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) bool {
rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
req, err := http.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
if err != nil {
return false
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
res, err := rt.RoundTrip(req)
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) {
u := auth.IndexServerAddress() + "/repositories/" + repository + "/images"
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
if authConfig != nil && len(authConfig.Username) > 0 {
req.SetBasicAuth(authConfig.Username, authConfig.Password)
}
res, err := r.client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
// Repository doesn't exist yet
if res.StatusCode == 404 {
return nil, nil
}
jsonData, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
imageList := []map[string]string{}
if err := json.Unmarshal(jsonData, &imageList); err != nil {
utils.Debugf("Body: %s (%s)\n", res.Body, u)
return nil, err
}
return imageList, nil
return res.StatusCode == 200
}
// Retrieve an image from the Registry.
func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, int, error) {
func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([]byte, int, error) {
// Get the JSON
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
req, err := http.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
if err != nil {
return nil, -1, fmt.Errorf("Failed to download json: %s", err)
}
@@ -135,8 +180,8 @@ func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([
return jsonString, imageSize, nil
}
func (r *Registry) GetRemoteImageLayer(imgId, registry string, token []string) (io.ReadCloser, error) {
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil)
func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) (io.ReadCloser, error) {
req, err := http.NewRequest("GET", registry+"images/"+imgID+"/layer", nil)
if err != nil {
return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
}
@@ -155,7 +200,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
repository = "library/" + repository
}
for _, host := range registries {
endpoint := fmt.Sprintf("https://%s/v1/repositories/%s/tags", host, repository)
endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository)
req, err := r.opaqueRequest("GET", endpoint, nil)
if err != nil {
return nil, err
@@ -165,6 +210,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
if err != nil {
return nil, err
}
utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
defer res.Body.Close()
@@ -187,8 +233,8 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
return nil, fmt.Errorf("Could not reach any registry endpoint")
}
func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
repositoryTarget := auth.IndexServerAddress() + "/repositories/" + remote + "/images"
func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, error) {
repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote)
req, err := r.opaqueRequest("GET", repositoryTarget, nil)
if err != nil {
@@ -219,8 +265,12 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
}
var endpoints []string
var urlScheme = indexEp[:strings.Index(indexEp, ":")]
if res.Header.Get("X-Docker-Endpoints") != "" {
endpoints = res.Header["X-Docker-Endpoints"]
// The Registry's URL scheme has to match the Index'
for _, ep := range res.Header["X-Docker-Endpoints"] {
endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, ep))
}
} else {
return nil, fmt.Errorf("Index response didn't contain any endpoints")
}
@@ -249,9 +299,8 @@ 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 {
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
}
@@ -284,9 +333,8 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis
return nil
}
func (r *Registry) PushImageLayerRegistry(imgId string, layer io.Reader, registry string, token []string) error {
registry = "https://" + registry + "/v1"
req, err := http.NewRequest("PUT", registry+"/images/"+imgId+"/layer", layer)
func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string) error {
req, err := http.NewRequest("PUT", registry+"images/"+imgID+"/layer", layer)
if err != nil {
return err
}
@@ -314,7 +362,7 @@ func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.R
if err != nil {
return nil, err
}
req.URL.Opaque = strings.Replace(urlStr, req.URL.Scheme + ":", "", 1)
req.URL.Opaque = strings.Replace(urlStr, req.URL.Scheme+":", "", 1)
return req, err
}
@@ -323,9 +371,8 @@ func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.R
func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
// "jsonify" the string
revision = "\"" + revision + "\""
registry = "https://" + registry + "/v1"
req, err := r.opaqueRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision))
req, err := r.opaqueRequest("PUT", registry+"repositories/"+remote+"/tags/"+tag, strings.NewReader(revision))
if err != nil {
return err
}
@@ -343,7 +390,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token
return nil
}
func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
imgListJSON, err := json.Marshal(imgList)
if err != nil {
return nil, err
@@ -353,9 +400,10 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
suffix = "images"
}
u := fmt.Sprintf("%srepositories/%s/%s", indexEp, remote, suffix)
utils.Debugf("PUT %s", u)
utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON)
req, err := r.opaqueRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJSON))
req, err := r.opaqueRequest("PUT", u, bytes.NewReader(imgListJSON))
if err != nil {
return nil, err
}
@@ -393,6 +441,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
}
var tokens, endpoints []string
var urlScheme = indexEp[:strings.Index(indexEp, ":")]
if !validate {
if res.StatusCode != 200 && res.StatusCode != 201 {
errBody, err := ioutil.ReadAll(res.Body)
@@ -409,7 +458,10 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
}
if res.Header.Get("X-Docker-Endpoints") != "" {
endpoints = res.Header["X-Docker-Endpoints"]
// The Registry's URL scheme has to match the Index'
for _, ep := range res.Header["X-Docker-Endpoints"] {
endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, ep))
}
} else {
return nil, fmt.Errorf("Index response didn't contain any endpoints")
}
@@ -431,7 +483,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
}
func (r *Registry) SearchRepositories(term string) (*SearchResults, error) {
u := auth.IndexServerAddress() + "/search?q=" + url.QueryEscape(term)
u := auth.IndexServerAddress() + "search?q=" + url.QueryEscape(term)
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return nil, err

View File

@@ -108,9 +108,6 @@ func (runtime *Runtime) Register(container *Container) error {
// init the wait lock
container.waitLock = make(chan struct{})
// Even if not running, we init the lock (prevents races in start/stop/kill)
container.State.initLock()
container.runtime = runtime
// Attach to stdout and stderr
@@ -144,7 +141,8 @@ func (runtime *Runtime) Register(container *Container) error {
utils.Debugf("Restarting")
container.State.Ghost = false
container.State.setStopped(0)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return err
}
nomonitor = true
@@ -246,8 +244,8 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) {
}
// FIXME: harmonize with NewGraph()
func NewRuntime(autoRestart bool, dns []string) (*Runtime, error) {
runtime, err := NewRuntimeFromDirectory("/var/lib/docker", autoRestart)
func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) {
runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart)
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,7 @@
package docker
import (
"bytes"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
@@ -8,17 +9,24 @@ import (
"log"
"net"
"os"
"os/user"
"strconv"
"strings"
"sync"
"syscall"
"testing"
"time"
)
const unitTestImageName string = "docker-ut"
const unitTestImageId string = "e9aa60c60128cad1"
const unitTestStoreBase string = "/var/lib/docker/unit-tests"
const (
unitTestImageName = "docker-test-image"
unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0
unitTestNetworkBridge = "testdockbr0"
unitTestStoreBase = "/var/lib/docker/unit-tests"
testDaemonAddr = "127.0.0.1:4270"
testDaemonProto = "tcp"
)
var globalRuntime *Runtime
func nuke(runtime *Runtime) error {
var wg sync.WaitGroup
@@ -33,6 +41,23 @@ func nuke(runtime *Runtime) error {
return os.RemoveAll(runtime.root)
}
func cleanup(runtime *Runtime) error {
for _, container := range runtime.List() {
container.Kill()
runtime.Destroy(container)
}
images, err := runtime.graph.All()
if err != nil {
return err
}
for _, image := range images {
if image.ID != unitTestImageID {
runtime.graph.Delete(image.ID)
}
}
return nil
}
func layerArchive(tarfile string) (io.Reader, error) {
// FIXME: need to close f somewhere
f, err := os.Open(tarfile)
@@ -49,71 +74,61 @@ func init() {
return
}
if usr, err := user.Current(); err != nil {
panic(err)
} else if usr.Uid != "0" {
panic("docker tests needs to be run as root")
if uid := syscall.Geteuid(); uid != 0 {
log.Fatal("docker tests need to be run as root")
}
NetworkBridgeIface = "testdockbr0"
NetworkBridgeIface = unitTestNetworkBridge
// Make it our Store root
runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false)
if err != nil {
panic(err)
}
globalRuntime = runtime
// Create the "Server"
srv := &Server{
runtime: runtime,
enableCors: false,
lock: &sync.Mutex{},
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
}
// Retrieve the Image
if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil {
panic(err)
// If the unit test is not found, try to download it.
if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID {
// Retrieve the Image
if err := srv.ImagePull(unitTestImageName, "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil {
panic(err)
}
}
// Spawn a Daemon
go func() {
if err := ListenAndServe(testDaemonProto, testDaemonAddr, srv, os.Getenv("DEBUG") != ""); err != nil {
panic(err)
}
}()
// Give some time to ListenAndServer to actually start
time.Sleep(time.Second)
}
// FIXME: test that ImagePull(json=true) send correct json output
func newTestRuntime() (*Runtime, error) {
root, err := ioutil.TempDir("", "docker-test")
if err != nil {
return nil, err
}
if err := os.Remove(root); err != nil {
return nil, err
}
if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil {
return nil, err
}
runtime, err := NewRuntimeFromDirectory(root, false)
if err != nil {
return nil, err
}
runtime.UpdateCapabilities(true)
return runtime, nil
}
func GetTestImage(runtime *Runtime) *Image {
imgs, err := runtime.graph.All()
if err != nil {
panic(err)
} else if len(imgs) < 1 {
panic("GASP")
}
return imgs[0]
for i := range imgs {
if imgs[i].ID == unitTestImageID {
return imgs[i]
}
}
panic(fmt.Errorf("Test image %v not found", unitTestImageID))
}
func TestRuntimeCreate(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
// Make sure we start we 0 containers
@@ -185,10 +200,7 @@ func TestRuntimeCreate(t *testing.T) {
}
func TestDestroy(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
@@ -232,10 +244,7 @@ func TestDestroy(t *testing.T) {
}
func TestGet(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
builder := NewBuilder(runtime)
@@ -284,81 +293,141 @@ 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,
Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p " + strPort},
PortSpecs: []string{strPort},
},
)
if err != nil {
return nil, err
}
if err := container.Start(); err != nil {
if strings.Contains(err.Error(), "address already in use") {
return nil, nil
}
return nil, err
}
return container, nil
}
// Run a container with a TCP port allocated, and test that it can receive connections on localhost
func TestAllocatePortLocalhost(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, string) {
var err error
runtime := mkRuntime(t)
port := 5554
var container *Container
var strPort string
for {
port += 1
log.Println("Trying port", port)
t.Log("Trying port", port)
container, err = findAvailalblePort(runtime, port)
strPort = strconv.Itoa(port)
var cmd string
if proto == "tcp" {
cmd = "socat TCP-LISTEN:" + strPort + ",reuseaddr,fork EXEC:/bin/cat"
} else if proto == "udp" {
cmd = "socat UDP-RECVFROM:" + strPort + ",fork EXEC:/bin/cat"
} else {
t.Fatal(fmt.Errorf("Unknown protocol %v", proto))
}
t.Log("Trying port", strPort)
container, err = NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", cmd},
PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)},
})
if container != nil {
break
}
if err != nil {
nuke(runtime)
t.Fatal(err)
}
log.Println("Port", port, "already in use")
t.Log("Port", port, "already in use")
t.Logf("Port %v already in use", strPort)
}
defer container.Kill()
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
nuke(runtime)
t.Fatal(err)
}
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
for {
if container.State.Running {
break
}
for !container.State.Running {
time.Sleep(10 * time.Millisecond)
}
})
conn, err := net.Dial("tcp",
fmt.Sprintf(
"localhost:%s", container.NetworkSettings.PortMapping[strconv.Itoa(port)],
),
)
// Even if the state is running, lets give some time to lxc to spawn the process
container.WaitTimeout(500 * time.Millisecond)
strPort = container.NetworkSettings.PortMapping[strings.Title(proto)][strPort]
return runtime, container, strPort
}
// Run a container with a TCP port allocated, and test that it can receive connections on localhost
func TestAllocateTCPPortLocalhost(t *testing.T) {
runtime, container, port := startEchoServerContainer(t, "tcp")
defer nuke(runtime)
defer container.Kill()
for i := 0; i != 10; i++ {
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%v", port))
if err != nil {
t.Fatal(err)
}
defer conn.Close()
input := bytes.NewBufferString("well hello there\n")
_, err = conn.Write(input.Bytes())
if err != nil {
t.Fatal(err)
}
buf := make([]byte, 16)
read := 0
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
read, err = conn.Read(buf)
if err != nil {
if err, ok := err.(*net.OpError); ok {
if err.Err == syscall.ECONNRESET {
t.Logf("Connection reset by the proxy, socat is probably not listening yet, trying again in a sec")
conn.Close()
time.Sleep(time.Second)
continue
}
if err.Timeout() {
t.Log("Timeout, trying again")
conn.Close()
continue
}
}
t.Fatal(err)
}
output := string(buf[:read])
if !strings.Contains(output, "well hello there") {
t.Fatal(fmt.Errorf("[%v] doesn't contain [well hello there]", output))
} else {
return
}
}
t.Fatal("No reply from the container")
}
// Run a container with an UDP port allocated, and test that it can receive connections on localhost
func TestAllocateUDPPortLocalhost(t *testing.T) {
runtime, container, port := startEchoServerContainer(t, "udp")
defer nuke(runtime)
defer container.Kill()
conn, err := net.Dial("udp", fmt.Sprintf("localhost:%v", port))
if err != nil {
t.Fatal(err)
}
defer conn.Close()
output, err := ioutil.ReadAll(conn)
if err != nil {
t.Fatal(err)
input := bytes.NewBufferString("well hello there\n")
buf := make([]byte, 16)
// Try for a minute, for some reason the select in socat may take ages
// to return even though everything on the path seems fine (i.e: the
// UDPProxy forwards the traffic correctly and you can see the packets
// on the interface from within the container).
for i := 0; i != 120; i++ {
_, err := conn.Write(input.Bytes())
if err != nil {
t.Fatal(err)
}
conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
read, err := conn.Read(buf)
if err == nil {
output := string(buf[:read])
if strings.Contains(output, "well hello there") {
return
}
}
}
if string(output) != "well hello there\n" {
t.Fatalf("Received wrong output from network connection: should be '%s', not '%s'",
"well hello there\n",
string(output),
)
}
container.Wait()
t.Fatal("No reply from the container")
}
func TestRestore(t *testing.T) {
@@ -405,7 +474,8 @@ func TestRestore(t *testing.T) {
defer runtime1.Destroy(container2)
// Start the container non blocking
if err := container2.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}

270
server.go
View File

@@ -1,6 +1,7 @@
package docker
import (
"bufio"
"errors"
"fmt"
"github.com/dotcloud/docker/auth"
@@ -12,6 +13,7 @@ import (
"net/http"
"net/url"
"os"
"os/exec"
"path"
"runtime"
"strings"
@@ -29,7 +31,7 @@ func (srv *Server) DockerVersion() APIVersion {
func (srv *Server) ContainerKill(name string) error {
if container := srv.runtime.Get(name); container != nil {
if err := container.Kill(); err != nil {
return fmt.Errorf("Error restarting container %s: %s", name, err.Error())
return fmt.Errorf("Error restarting container %s: %s", name, err)
}
} else {
return fmt.Errorf("No such container: %s", name)
@@ -87,7 +89,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.
}
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
}
@@ -98,7 +100,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.
return "", err
}
if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), path); err != nil {
if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), path); err != nil {
return "", err
}
// FIXME: Handle custom repo, tag comment, author
@@ -247,6 +249,40 @@ func (srv *Server) ImageHistory(name string) ([]APIHistory, error) {
}
func (srv *Server) ContainerTop(name string) ([]APITop, error) {
if container := srv.runtime.Get(name); container != nil {
output, err := exec.Command("lxc-ps", "--name", container.ID).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("Error trying to use lxc-ps: %s (%s)", err, output)
}
var procs []APITop
for i, line := range strings.Split(string(output), "\n") {
if i == 0 || len(line) == 0 {
continue
}
proc := APITop{}
scanner := bufio.NewScanner(strings.NewReader(line))
scanner.Split(bufio.ScanWords)
if !scanner.Scan() {
return nil, fmt.Errorf("Error trying to use lxc-ps")
}
// no scanner.Text because we skip container id
scanner.Scan()
proc.PID = scanner.Text()
scanner.Scan()
proc.Tty = scanner.Text()
scanner.Scan()
proc.Time = scanner.Text()
scanner.Scan()
proc.Cmd = scanner.Text()
procs = append(procs, proc)
}
return procs, nil
}
return nil, fmt.Errorf("No such container: %s", name)
}
func (srv *Server) ContainerChanges(name string) ([]Change, error) {
if container := srv.runtime.Get(name); container != nil {
return container.Changes()
@@ -254,7 +290,7 @@ 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, size bool, n int, since, before string) []APIContainers {
var foundBefore bool
var displayed int
retContainers := []APIContainers{}
@@ -288,8 +324,9 @@ func (srv *Server) Containers(all bool, n int, since, before string) []APIContai
c.Created = container.Created.Unix()
c.Status = container.State.String()
c.Ports = container.NetworkSettings.PortMappingHuman()
c.SizeRw, c.SizeRootFs = container.GetSize()
if size {
c.SizeRw, c.SizeRootFs = container.GetSize()
}
retContainers = append(retContainers, c)
}
return retContainers
@@ -314,8 +351,8 @@ 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, sf *utils.StreamFormatter) error {
history, err := r.GetRemoteHistory(imgId, endpoint, token)
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
}
@@ -342,7 +379,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
return err
}
defer layer.Close()
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil {
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), false, img); err != nil {
return err
}
}
@@ -350,9 +387,10 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
return nil
}
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)
func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName, remoteName, askedTag, indexEp string, sf *utils.StreamFormatter) error {
out.Write(sf.FormatStatus("Pulling repository %s", localName))
repoData, err := r.GetRepositoryData(indexEp, remoteName)
if err != nil {
return err
}
@@ -364,12 +402,22 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
}
utils.Debugf("Retrieving the tag list")
tagsList, err := r.GetRemoteTags(repoData.Endpoints, remote, repoData.Tokens)
tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens)
if err != nil {
utils.Debugf("%v", err)
return err
}
for tag, id := range tagsList {
repoData.ImgList[id] = &registry.ImgData{
ID: id,
Tag: tag,
Checksum: "",
}
}
utils.Debugf("Registering tags")
// If not specific tag have been asked, take all
// If no tag has been specified, pull them all
if askedTag == "" {
for tag, id := range tagsList {
repoData.ImgList[id].Tag = tag
@@ -378,7 +426,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
// Otherwise, check that the tag exists and use only that one
id, exists := tagsList[askedTag]
if !exists {
return fmt.Errorf("Tag %s not found in repositoy %s", askedTag, local)
return fmt.Errorf("Tag %s not found in repository %s", askedTag, localName)
}
repoData.ImgList[id].Tag = askedTag
}
@@ -388,10 +436,15 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.ID)
continue
}
out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, remote))
if img.Tag == "" {
utils.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID)
continue
}
out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, localName))
success := false
for _, ep := range repoData.Endpoints {
if err := srv.pullImage(r, out, img.ID, "https://"+ep+"/v1", repoData.Tokens, sf); err != nil {
if err := srv.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err))
continue
}
@@ -406,7 +459,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
if askedTag != "" && tag != askedTag {
continue
}
if err := srv.runtime.repositories.Set(local, tag, id, true); err != nil {
if err := srv.runtime.repositories.Set(localName, tag, id, true); err != nil {
return err
}
}
@@ -418,8 +471,8 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
}
func (srv *Server) poolAdd(kind, key string) error {
srv.lock.Lock()
defer srv.lock.Unlock()
srv.Lock()
defer srv.Unlock()
if _, exists := srv.pullingPool[key]; exists {
return fmt.Errorf("%s %s is already in progress", key, kind)
@@ -452,31 +505,36 @@ func (srv *Server) poolRemove(kind, key string) error {
return nil
}
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
r, err := registry.NewRegistry(srv.runtime.root, authConfig)
if err != nil {
return err
}
if err := srv.poolAdd("pull", name+":"+tag); err != nil {
if err := srv.poolAdd("pull", localName+":"+tag); err != nil {
return err
}
defer srv.poolRemove("pull", name+":"+tag)
defer srv.poolRemove("pull", localName+":"+tag)
// Resolve the Repository name from fqn to endpoint + name
endpoint, remoteName, err := registry.ResolveRepositoryName(localName)
if err != nil {
return err
}
if endpoint == auth.IndexServerAddress() {
// If pull "index.docker.io/foo/bar", it's stored locally under "foo/bar"
localName = remoteName
}
out = utils.NewWriteFlusher(out)
if endpoint != "" {
if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf)
if err != nil {
if err := srv.pullImage(r, out, remoteName, endpoint, nil, sf); err != nil {
return err
}
return 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
}
return nil
}
@@ -486,20 +544,20 @@ func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *util
// - Check if the archive exists, if it does not, ask the registry
// - If the archive does exists, process the checksum from it
// - If the archive does not exists and not found on registry, process checksum from layer
func (srv *Server) getChecksum(imageId string) (string, error) {
func (srv *Server) getChecksum(imageID string) (string, error) {
// FIXME: Use in-memory map instead of reading the file each time
if sums, err := srv.runtime.graph.getStoredChecksums(); err != nil {
return "", err
} else if checksum, exists := sums[imageId]; exists {
} else if checksum, exists := sums[imageID]; exists {
return checksum, nil
}
img, err := srv.runtime.graph.Get(imageId)
img, err := srv.runtime.graph.Get(imageID)
if err != nil {
return "", err
}
if _, err := os.Stat(layerArchivePath(srv.runtime.graph.imageRoot(imageId))); err != nil {
if _, err := os.Stat(layerArchivePath(srv.runtime.graph.imageRoot(imageID))); err != nil {
if os.IsNotExist(err) {
// TODO: Ask the registry for the checksum
// As the archive is not there, it is supposed to come from a pull.
@@ -546,7 +604,7 @@ 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, sf *utils.StreamFormatter) error {
func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error {
out = utils.NewWriteFlusher(out)
out.Write(sf.FormatStatus("Processing checksums"))
imgList, err := srv.getImageList(localRepo)
@@ -555,64 +613,63 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
}
out.Write(sf.FormatStatus("Sending image list"))
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)
var repoData *registry.RepositoryData
repoData, err = r.PushImageJSONIndex(indexEp, remoteName, imgList, false, nil)
if err != nil {
return err
}
for _, ep := range repoData.Endpoints {
out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo)))
out.Write(sf.FormatStatus("Pushing repository %s (%d tags)", localName, len(localRepo)))
// For each image within the repo, push them
for _, elem := range imgList {
if _, exists := repoData.ImgList[elem.ID]; exists {
out.Write(sf.FormatStatus("Image %s already on registry, skipping", name))
out.Write(sf.FormatStatus("Image %s already pushed, skipping", elem.ID))
continue
} else if r.LookupRemoteImage(elem.ID, ep, repoData.Tokens) {
out.Write(sf.FormatStatus("Image %s already pushed, skipping", elem.ID))
continue
}
if err := srv.pushImage(r, out, name, elem.ID, ep, repoData.Tokens, sf); err != nil {
if err := srv.pushImage(r, out, remoteName, elem.ID, ep, repoData.Tokens, sf); err != nil {
// FIXME: Continue on error?
return err
}
out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"/repositories/"+srvName+"/tags/"+elem.Tag))
if err := r.PushRegistryTag(srvName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil {
out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+remoteName+"/tags/"+elem.Tag))
if err := r.PushRegistryTag(remoteName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil {
return err
}
}
}
if _, err := r.PushImageJSONIndex(srvName, imgList, true, repoData.Endpoints); err != nil {
if _, err := r.PushImageJSONIndex(indexEp, remoteName, 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, sf *utils.StreamFormatter) 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"))
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)
return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgID, err)
}
out.Write(sf.FormatStatus("Pushing %s", imgId))
out.Write(sf.FormatStatus("Pushing %s", imgID))
// Make sure we have the image's checksum
checksum, err := srv.getChecksum(imgId)
checksum, err := srv.getChecksum(imgID)
if err != nil {
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 == registry.ErrAlreadyExists {
out.Write(sf.FormatStatus("Image %s already uploaded ; skipping", imgData.ID))
out.Write(sf.FormatStatus("Image %s already pushed, skipping", imgData.ID))
return nil
}
return err
@@ -621,11 +678,11 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
// Retrieve the tarball to be sent
var layerData *TempArchive
// If the archive exists, use it
file, err := os.Open(layerArchivePath(srv.runtime.graph.imageRoot(imgId)))
file, err := os.Open(layerArchivePath(srv.runtime.graph.imageRoot(imgID)))
if err != nil {
if os.IsNotExist(err) {
// If the archive does not exist, create one from the layer
layerData, err = srv.runtime.graph.TempLayerArchive(imgId, Xz, out)
layerData, err = srv.runtime.graph.TempLayerArchive(imgID, Xz, sf, out)
if err != nil {
return fmt.Errorf("Failed to generate layer archive: %s", err)
}
@@ -645,39 +702,48 @@ 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, sf.FormatProgress("Pushing", "%v/%v (%v)"), sf), ep, token); err != nil {
if err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token); err != nil {
return err
}
return nil
}
// FIXME: Allow to interupt current push when new push of same image is done.
func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
if err := srv.poolAdd("push", name); err != nil {
func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
if err := srv.poolAdd("push", localName); err != nil {
return err
}
defer srv.poolRemove("push", localName)
// Resolve the Repository name from fqn to endpoint + name
endpoint, remoteName, err := registry.ResolveRepositoryName(localName)
if err != nil {
return err
}
defer srv.poolRemove("push", name)
out = utils.NewWriteFlusher(out)
img, err := srv.runtime.graph.Get(name)
img, err := srv.runtime.graph.Get(localName)
r, err2 := registry.NewRegistry(srv.runtime.root, authConfig)
if err2 != nil {
return err2
}
if err != nil {
out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
reposLen := len(srv.runtime.repositories.Repositories[localName])
out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", localName, reposLen))
// 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, sf); err != nil {
if localRepo, exists := srv.runtime.repositories.Repositories[localName]; exists {
if err := srv.pushRepository(r, out, localName, remoteName, localRepo, endpoint, sf); err != nil {
return err
}
return nil
}
return err
}
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 {
var token []string
out.Write(sf.FormatStatus("The push refers to an image: [%s]", localName))
if err := srv.pushImage(r, out, remoteName, img.ID, endpoint, token, sf); err != nil {
return err
}
return nil
@@ -706,7 +772,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
if err != nil {
return err
}
archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("Importing", "%v/%v (%v)"), sf)
archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("Importing", "%8v/%v (%v)"), sf)
}
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
if err != nil {
@@ -749,7 +815,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
func (srv *Server) ContainerRestart(name string, t int) error {
if container := srv.runtime.Get(name); container != nil {
if err := container.Restart(t); err != nil {
return fmt.Errorf("Error restarting container %s: %s", name, err.Error())
return fmt.Errorf("Error restarting container %s: %s", name, err)
}
} else {
return fmt.Errorf("No such container: %s", name)
@@ -768,7 +834,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
volumes[volumeId] = struct{}{}
}
if err := srv.runtime.Destroy(container); err != nil {
return fmt.Errorf("Error destroying container %s: %s", name, err.Error())
return fmt.Errorf("Error destroying container %s: %s", name, err)
}
if removeVolume {
@@ -804,7 +870,6 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
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()
@@ -857,9 +922,23 @@ func (srv *Server) deleteImageParents(img *Image, imgs *[]APIRmi) error {
return nil
}
func (srv *Server) deleteImage(img *Image, repoName, tag string) (*[]APIRmi, error) {
func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, error) {
imgs := []APIRmi{}
//If delete by id, see if the id belong only to one repository
if strings.Contains(img.ID, repoName) && tag == "" {
for _, repoAndTag := range srv.runtime.repositories.ByID()[img.ID] {
parsedRepo := strings.Split(repoAndTag, ":")[0]
if strings.Contains(img.ID, repoName) {
repoName = parsedRepo
} else if repoName != parsedRepo {
// the id belongs to multiple repos, like base:latest and user:test,
// in that case return conflict
return imgs, nil
}
}
}
//Untag the current image
var imgs []APIRmi
tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag)
if err != nil {
return nil, err
@@ -870,25 +949,25 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) (*[]APIRmi, err
if len(srv.runtime.repositories.ByID()[img.ID]) == 0 {
if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil {
if err != ErrImageReferenced {
return &imgs, err
return imgs, err
}
} else if err := srv.deleteImageParents(img, &imgs); err != nil {
if err != ErrImageReferenced {
return &imgs, err
return imgs, err
}
}
}
return &imgs, nil
return imgs, nil
}
func (srv *Server) ImageDelete(name string, autoPrune bool) (*[]APIRmi, error) {
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, fmt.Errorf("Error deleting image %s: %s", name, err)
}
return nil, nil
}
@@ -903,7 +982,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) (*[]APIRmi, error) {
return srv.deleteImage(img, name, tag)
}
func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error) {
func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) {
// Retrieve all images
images, err := srv.runtime.graph.All()
@@ -921,7 +1000,7 @@ func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error)
}
// Loop on the children of the given image and check the config
for elem := range imageMap[imgId] {
for elem := range imageMap[imgID] {
img, err := srv.runtime.graph.Get(elem)
if err != nil {
return nil, err
@@ -933,10 +1012,10 @@ func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error)
return nil, nil
}
func (srv *Server) ContainerStart(name string) error {
func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
if container := srv.runtime.Get(name); container != nil {
if err := container.Start(); err != nil {
return fmt.Errorf("Error starting container %s: %s", name, err.Error())
if err := container.Start(hostConfig); err != nil {
return fmt.Errorf("Error starting container %s: %s", name, err)
}
} else {
return fmt.Errorf("No such container: %s", name)
@@ -947,7 +1026,7 @@ func (srv *Server) ContainerStart(name string) error {
func (srv *Server) ContainerStop(name string, t int) error {
if container := srv.runtime.Get(name); container != nil {
if err := container.Stop(t); err != nil {
return fmt.Errorf("Error stopping container %s: %s", name, err.Error())
return fmt.Errorf("Error stopping container %s: %s", name, err)
}
} else {
return fmt.Errorf("No such container: %s", name)
@@ -979,17 +1058,17 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
if stdout {
cLog, err := container.ReadLog("stdout")
if err != nil {
utils.Debugf(err.Error())
utils.Debugf("Error reading logs (stdout): %s", err)
} else if _, err := io.Copy(out, cLog); err != nil {
utils.Debugf(err.Error())
utils.Debugf("Error streaming logs (stdout): %s", err)
}
}
if stderr {
cLog, err := container.ReadLog("stderr")
if err != nil {
utils.Debugf(err.Error())
utils.Debugf("Error reading logs (stderr): %s", err)
} else if _, err := io.Copy(out, cLog); err != nil {
utils.Debugf(err.Error())
utils.Debugf("Error streaming logs (stderr): %s", err)
}
}
}
@@ -1048,18 +1127,17 @@ func (srv *Server) ImageInspect(name string) (*Image, error) {
return nil, fmt.Errorf("No such image: %s", name)
}
func NewServer(autoRestart, enableCors bool, dns ListOpts) (*Server, error) {
func NewServer(flGraphPath string, 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, dns)
runtime, err := NewRuntime(flGraphPath, autoRestart, dns)
if err != nil {
return nil, err
}
srv := &Server{
runtime: runtime,
enableCors: enableCors,
lock: &sync.Mutex{},
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
}
@@ -1068,9 +1146,9 @@ func NewServer(autoRestart, enableCors bool, dns ListOpts) (*Server, error) {
}
type Server struct {
sync.Mutex
runtime *Runtime
enableCors bool
lock *sync.Mutex
pullingPool map[string]struct{}
pushingPool map[string]struct{}
}

View File

@@ -5,14 +5,16 @@ import (
)
func TestContainerTagImageDelete(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
initialImages, err := srv.Images(false, "")
if err != nil {
t.Fatal(err)
}
if err := srv.runtime.repositories.Set("utest", "tag1", unitTestImageName, false); err != nil {
t.Fatal(err)
}
@@ -25,8 +27,8 @@ func TestContainerTagImageDelete(t *testing.T) {
t.Fatal(err)
}
if len(images) != 3 {
t.Errorf("Excepted 3 images, %d found", len(images))
if len(images) != len(initialImages)+2 {
t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images))
}
if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil {
@@ -38,8 +40,8 @@ func TestContainerTagImageDelete(t *testing.T) {
t.Fatal(err)
}
if len(images) != 2 {
t.Errorf("Excepted 2 images, %d found", len(images))
if len(images) != len(initialImages)+1 {
t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
}
if _, err := srv.ImageDelete("utest:tag1", true); err != nil {
@@ -51,21 +53,18 @@ func TestContainerTagImageDelete(t *testing.T) {
t.Fatal(err)
}
if len(images) != 1 {
t.Errorf("Excepted 1 image, %d found", len(images))
if len(images) != len(initialImages) {
t.Errorf("Expected %d image, %d found", len(initialImages), len(images))
}
}
func TestCreateRm(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
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)
}
@@ -90,15 +89,12 @@ func TestCreateRm(t *testing.T) {
}
func TestCreateStartRestartStopStartKillRm(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
config, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
if err != nil {
t.Fatal(err)
}
@@ -112,7 +108,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
t.Errorf("Expected 1 container, %v found", len(runtime.List()))
}
err = srv.ContainerStart(id)
err = srv.ContainerStart(id, hostConfig)
if err != nil {
t.Fatal(err)
}
@@ -127,7 +123,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
t.Fatal(err)
}
err = srv.ContainerStart(id)
err = srv.ContainerStart(id, hostConfig)
if err != nil {
t.Fatal(err)
}
@@ -149,11 +145,9 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
}
func TestRunWithTooLowMemoryLimit(t *testing.T) {
runtime, err := newTestRuntime()
var err error
runtime := mkRuntime(t)
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(

View File

@@ -8,11 +8,11 @@ import (
)
type State struct {
sync.Mutex
Running bool
Pid int
ExitCode int
StartedAt time.Time
l *sync.Mutex
Ghost bool
}
@@ -29,6 +29,7 @@ func (s *State) String() string {
func (s *State) setRunning(pid int) {
s.Running = true
s.Ghost = false
s.ExitCode = 0
s.Pid = pid
s.StartedAt = time.Now()
@@ -39,15 +40,3 @@ func (s *State) setStopped(exitCode int) {
s.Pid = 0
s.ExitCode = exitCode
}
func (s *State) initLock() {
s.l = &sync.Mutex{}
}
func (s *State) lock() {
s.l.Lock()
}
func (s *State) unlock() {
s.l.Unlock()
}

View File

@@ -3,10 +3,10 @@ package docker
import (
"flag"
"fmt"
"github.com/dotcloud/docker/utils"
"log"
"os"
"os/exec"
"os/user"
"strconv"
"strings"
"syscall"
@@ -27,10 +27,7 @@ func changeUser(u string) {
if u == "" {
return
}
userent, err := user.LookupId(u)
if err != nil {
userent, err = user.Lookup(u)
}
userent, err := utils.UserLookup(u)
if err != nil {
log.Fatalf("Unable to find user %v: %v", u, err)
}
@@ -60,9 +57,6 @@ func cleanupEnv(env ListOpts) {
if len(parts) == 1 {
parts = append(parts, "")
}
if parts[0] == "container" {
continue
}
os.Setenv(parts[0], parts[1])
}
}

23
tags.go
View File

@@ -70,11 +70,11 @@ func (store *TagStore) LookupImage(name string) (*Image, error) {
if err != nil {
// FIXME: standardize on returning nil when the image doesn't exist, and err for everything else
// (so we can pass all errors here)
repoAndTag := strings.SplitN(name, ":", 2)
if len(repoAndTag) == 1 {
repoAndTag = append(repoAndTag, DEFAULTTAG)
repos, tag := utils.ParseRepositoryTag(name)
if tag == "" {
tag = DEFAULTTAG
}
if i, err := store.GetImage(repoAndTag[0], repoAndTag[1]); err != nil {
if i, err := store.GetImage(repos, tag); err != nil {
return nil, err
} else if i == nil {
return nil, fmt.Errorf("Image does not exist: %s", name)
@@ -197,22 +197,22 @@ func (store *TagStore) Get(repoName string) (Repository, error) {
return nil, nil
}
func (store *TagStore) GetImage(repoName, tagOrId 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
}
//go through all the tags, to see if tag is in fact an ID
if revision, exists := repo[tagOrID]; exists {
return store.graph.Get(revision)
}
// If no matching tag is found, search through images for a matching image id
for _, revision := range repo {
if strings.HasPrefix(revision, tagOrId) {
if strings.HasPrefix(revision, tagOrID) {
return store.graph.Get(revision)
}
}
if revision, exists := repo[tagOrId]; exists {
return store.graph.Get(revision)
}
return nil, nil
}
@@ -221,9 +221,6 @@ func validateRepoName(name string) error {
if name == "" {
return fmt.Errorf("Repository name can't be empty")
}
if strings.Contains(name, ":") {
return fmt.Errorf("Illegal repository name: %s", name)
}
return nil
}

View File

@@ -5,10 +5,7 @@ import (
)
func TestLookupImage(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
runtime := mkRuntime(t)
defer nuke(runtime)
if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil {
@@ -35,13 +32,13 @@ func TestLookupImage(t *testing.T) {
t.Errorf("Expected 0 image, 1 found")
}
if img, err := runtime.repositories.LookupImage(unitTestImageId); err != nil {
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 {
if img, err := runtime.repositories.LookupImage(unitTestImageName + ":" + unitTestImageID); err != nil {
t.Fatal(err)
} else if img == nil {
t.Errorf("Expected 1 image, none found")

View File

@@ -12,8 +12,8 @@ type State struct {
}
type Winsize struct {
Width uint16
Height uint16
Width uint16
x uint16
y uint16
}
@@ -38,13 +38,13 @@ func IsTerminal(fd uintptr) bool {
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(fd uintptr, state *State) error {
func RestoreTerminal(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(os.Stdin.Fd())
func SetRawTerminal(fd uintptr) (*State, error) {
oldState, err := MakeRaw(fd)
if err != nil {
return nil, err
}
@@ -52,12 +52,8 @@ func SetRawTerminal() (*State, error) {
signal.Notify(c, os.Interrupt)
go func() {
_ = <-c
Restore(os.Stdin.Fd(), oldState)
RestoreTerminal(fd, oldState)
os.Exit(0)
}()
return oldState, err
}
func RestoreTerminal(state *State) {
Restore(os.Stdin.Fd(), state)
}

View File

@@ -30,6 +30,16 @@ Deployment
export AWS_KEYPAIR_NAME=xxxxxxxxxxxx
export AWS_SSH_PRIVKEY=xxxxxxxxxxxx
# Define email recipient and IRC channel
export EMAIL_RCP=xxxxxx@domain.com
export IRC_CHANNEL=docker
# Define buildbot credentials
export BUILDBOT_PWD=xxxxxxxxxxxx
export IRC_PWD=xxxxxxxxxxxx
export SMTP_USER=xxxxxxxxxxxx
export SMTP_PWD=xxxxxxxxxxxx
# Checkout docker
git clone git://github.com/dotcloud/docker.git

10
testing/Vagrantfile vendored
View File

@@ -19,17 +19,20 @@ Vagrant::Config.run do |config|
config.vm.share_folder "v-data", DOCKER_PATH, "#{File.dirname(__FILE__)}/.."
config.vm.network :hostonly, BUILDBOT_IP
# Deploy buildbot and its dependencies if it was not done
if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty?
pkg_cmd = "apt-get update -qq; apt-get install -q -y linux-image-3.8.0-19-generic; "
pkg_cmd = "apt-get update -qq; apt-get install -q -y linux-image-generic-lts-raring; "
# Deploy buildbot CI
pkg_cmd << "apt-get install -q -y python-dev python-pip supervisor; " \
"pip install -r #{CFG_PATH}/requirements.txt; " \
"chown #{USER}.#{USER} /data; cd /data; " \
"#{CFG_PATH}/setup.sh #{USER} #{CFG_PATH}; "
"#{CFG_PATH}/setup.sh #{USER} #{CFG_PATH} #{ENV['BUILDBOT_PWD']} " \
"#{ENV['IRC_PWD']} #{ENV['IRC_CHANNEL']} #{ENV['SMTP_USER']} " \
"#{ENV['SMTP_PWD']} #{ENV['EMAIL_RCP']}; "
# 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; " \
"add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu; apt-get update -qq; " \
"DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc git golang-stable aufs-tools make; "
# Activate new kernel
pkg_cmd << "shutdown -r +1; "
@@ -40,6 +43,7 @@ end
# Providers were added on Vagrant >= 1.1.0
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
config.vm.provider :aws do |aws, override|
aws.tags = { 'Name' => 'docker-ci' }
aws.access_key_id = ENV["AWS_ACCESS_KEY_ID"]
aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"]
aws.keypair_name = ENV["AWS_KEYPAIR_NAME"]

View File

@@ -5,9 +5,11 @@ from buildbot.schedulers.basic import SingleBranchScheduler
from buildbot.changes import filter
from buildbot.config import BuilderConfig
from buildbot.process.factory import BuildFactory
from buildbot.process.properties import Interpolate
from buildbot.steps.shell import ShellCommand
from buildbot.status import html
from buildbot.status import html, words
from buildbot.status.web import authz, auth
from buildbot.status.mail import MailNotifier
PORT_WEB = 80 # Buildbot webserver port
PORT_GITHUB = 8011 # Buildbot github hook port
@@ -15,20 +17,27 @@ PORT_MASTER = 9989 # Port where buildbot master listen buildworkers
TEST_USER = 'buildbot' # Credential to authenticate build triggers
TEST_PWD = 'docker' # Credential to authenticate build triggers
BUILDER_NAME = 'docker'
BUILDPASSWORD = 'pass-docker' # Credential to authenticate buildworkers
GITHUB_DOCKER = "github.com/dotcloud/docker"
DOCKER_PATH = "/data/docker"
BUILDER_PATH = "/data/buildbot/slave/{0}/build".format(BUILDER_NAME)
GITHUB_DOCKER = 'github.com/dotcloud/docker'
DOCKER_PATH = '/data/docker'
BUILDER_PATH = '/data/buildbot/slave/{0}/build'.format(BUILDER_NAME)
DOCKER_BUILD_PATH = BUILDER_PATH + '/src/github.com/dotcloud/docker'
# Credentials set by setup.sh and Vagrantfile
BUILDBOT_PWD = ''
IRC_PWD = ''
IRC_CHANNEL = ''
SMTP_USER = ''
SMTP_PWD = ''
EMAIL_RCP = ''
c = BuildmasterConfig = {}
c['title'] = "Docker"
c['titleURL'] = "waterfall"
c['buildbotURL'] = "http://0.0.0.0:{0}/".format(PORT_WEB)
c['buildbotURL'] = "http://docker-ci.dotcloud.com/"
c['db'] = {'db_url':"sqlite:///state.sqlite"}
c['slaves'] = [BuildSlave('buildworker', BUILDPASSWORD)]
c['slaves'] = [BuildSlave('buildworker', BUILDBOT_PWD)]
c['slavePortnum'] = PORT_MASTER
c['schedulers'] = [ForceScheduler(name='trigger',builderNames=[BUILDER_NAME])]
@@ -36,20 +45,25 @@ c['schedulers'].append(SingleBranchScheduler(name="all",
change_filter=filter.ChangeFilter(branch='master'),treeStableTimer=None,
builderNames=[BUILDER_NAME]))
# Docker test command
test_cmd = ("cd /tmp; rm -rf {0}; export GOPATH={0}; go get -d {1}; cd {2}; "
"go test").format(BUILDER_PATH,GITHUB_DOCKER,DOCKER_BUILD_PATH)
# Builder
factory = BuildFactory()
factory.addStep(ShellCommand(description='Docker',logEnviron=False,
usePTY=True,command=test_cmd))
factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True,
command=["sh", "-c", Interpolate("cd ..; rm -rf build; export GOPATH={0}; "
"go get -d {1}; cd {2}; git reset --hard %(src::revision:-unknown)s; "
"go test -v".format(BUILDER_PATH,GITHUB_DOCKER,DOCKER_BUILD_PATH))]))
c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'],
factory=factory)]
# Status
authz_cfg=authz.Authz(auth=auth.BasicAuth([(TEST_USER,TEST_PWD)]),
authz_cfg = authz.Authz(auth=auth.BasicAuth([(TEST_USER, TEST_PWD)]),
forceBuild='auth')
c['status'] = [html.WebStatus(http_port=PORT_WEB, authz=authz_cfg)]
c['status'].append(html.WebStatus(http_port=PORT_GITHUB,allowForce=True,
change_hook_dialects={ 'github' : True }))
c['status'].append(html.WebStatus(http_port=PORT_GITHUB, allowForce=True,
change_hook_dialects={ 'github': True }))
c['status'].append(MailNotifier(fromaddr='buildbot@docker.io',
sendToInterestedUsers=False, extraRecipients=[EMAIL_RCP],
mode='failing', relayhost='smtp.mailgun.org', smtpPort=587, useTls=True,
smtpUser=SMTP_USER, smtpPassword=SMTP_PWD))
c['status'].append(words.IRC("irc.freenode.net", "dockerqabot",
channels=[IRC_CHANNEL], password=IRC_PWD, allowForce=True,
notify_events={'exception':1, 'successToFailure':1, 'failureToSuccess':1}))

View File

@@ -6,11 +6,16 @@
USER=$1
CFG_PATH=$2
BUILDBOT_PWD=$3
IRC_PWD=$4
IRC_CHANNEL=$5
SMTP_USER=$6
SMTP_PWD=$7
EMAIL_RCP=$8
BUILDBOT_PATH="/data/buildbot"
DOCKER_PATH="/data/docker"
SLAVE_NAME="buildworker"
SLAVE_SOCKET="localhost:9989"
BUILDBOT_PWD="pass-docker"
export PATH="/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin"
function run { su $USER -c "$1"; }
@@ -23,7 +28,12 @@ run "mkdir -p $BUILDBOT_PATH"
cd $BUILDBOT_PATH
run "buildbot create-master master"
run "cp $CFG_PATH/master.cfg master"
run "sed -i -E 's#(DOCKER_PATH = ).+#\1\"$DOCKER_PATH\"#' master/master.cfg"
run "sed -i -E 's#(BUILDBOT_PWD = ).+#\1\"$BUILDBOT_PWD\"#' master/master.cfg"
run "sed -i -E 's#(IRC_PWD = ).+#\1\"$IRC_PWD\"#' master/master.cfg"
run "sed -i -E 's#(IRC_CHANNEL = ).+#\1\"$IRC_CHANNEL\"#' master/master.cfg"
run "sed -i -E 's#(SMTP_USER = ).+#\1\"$SMTP_USER\"#' master/master.cfg"
run "sed -i -E 's#(SMTP_PWD = ).+#\1\"$SMTP_PWD\"#' master/master.cfg"
run "sed -i -E 's#(EMAIL_RCP = ).+#\1\"$EMAIL_RCP\"#' master/master.cfg"
run "buildslave create-slave slave $SLAVE_SOCKET $SLAVE_NAME $BUILDBOT_PWD"
# Allow buildbot subprocesses (docker tests) to properly run in containers,

View File

@@ -1,5 +1,9 @@
package docker
import (
"strings"
)
// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields
// If OpenStdin is set, then it differs
func CompareConfig(a, b *Config) bool {
@@ -20,7 +24,8 @@ func CompareConfig(a, b *Config) bool {
if len(a.Cmd) != len(b.Cmd) ||
len(a.Dns) != len(b.Dns) ||
len(a.Env) != len(b.Env) ||
len(a.PortSpecs) != len(b.PortSpecs) {
len(a.PortSpecs) != len(b.PortSpecs) ||
len(a.Entrypoint) != len(b.Entrypoint) {
return false
}
@@ -44,14 +49,15 @@ func CompareConfig(a, b *Config) bool {
return false
}
}
for i := 0; i < len(a.Entrypoint); i++ {
if a.Entrypoint[i] != b.Entrypoint[i] {
return false
}
}
return true
}
func MergeConfig(userConf, imageConf *Config) {
if userConf.Hostname == "" {
userConf.Hostname = imageConf.Hostname
}
if userConf.User == "" {
userConf.User = imageConf.User
}
@@ -66,6 +72,20 @@ func MergeConfig(userConf, imageConf *Config) {
}
if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
userConf.PortSpecs = imageConf.PortSpecs
} else {
for _, imagePortSpec := range imageConf.PortSpecs {
found := false
imageNat, _ := parseNat(imagePortSpec)
for _, userPortSpec := range userConf.PortSpecs {
userNat, _ := parseNat(userPortSpec)
if imageNat.Proto == userNat.Proto && imageNat.Frontend == userNat.Frontend {
found = true
}
}
if !found {
userConf.PortSpecs = append(userConf.PortSpecs, imagePortSpec)
}
}
}
if !userConf.Tty {
userConf.Tty = imageConf.Tty
@@ -78,11 +98,38 @@ func MergeConfig(userConf, imageConf *Config) {
}
if userConf.Env == nil || len(userConf.Env) == 0 {
userConf.Env = imageConf.Env
} else {
for _, imageEnv := range imageConf.Env {
found := false
imageEnvKey := strings.Split(imageEnv, "=")[0]
for _, userEnv := range userConf.Env {
userEnvKey := strings.Split(userEnv, "=")[0]
if imageEnvKey == userEnvKey {
found = true
}
}
if !found {
userConf.Env = append(userConf.Env, imageEnv)
}
}
}
if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
userConf.Cmd = imageConf.Cmd
}
if userConf.Dns == nil || len(userConf.Dns) == 0 {
userConf.Dns = imageConf.Dns
} else {
//duplicates aren't an issue here
userConf.Dns = append(userConf.Dns, imageConf.Dns...)
}
if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 {
userConf.Entrypoint = imageConf.Entrypoint
}
if userConf.Volumes == nil || len(userConf.Volumes) == 0 {
userConf.Volumes = imageConf.Volumes
} else {
for k, v := range imageConf.Volumes {
userConf.Volumes[k] = v
}
}
}

View File

@@ -14,6 +14,7 @@ import (
"net/http"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
"strconv"
@@ -78,10 +79,10 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
read, err := io.ReadCloser(r.reader).Read(p)
r.readProgress += read
updateEvery := 4096
updateEvery := 1024 * 512 //512kB
if r.readTotal > 0 {
// Only update progress for every 1% read
if increment := int(0.01 * float64(r.readTotal)); increment > updateEvery {
// Update progress for every 1% read if 1% < 512kB
if increment := int(0.01 * float64(r.readTotal)); increment < updateEvery {
updateEvery = increment
}
}
@@ -106,7 +107,7 @@ func (r *progressReader) Close() error {
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)"))
tpl = string(sf.FormatProgress("", "%8v/%v (%v)"))
}
return &progressReader{r, NewWriteFlusher(output), size, 0, 0, tpl, sf}
}
@@ -133,7 +134,7 @@ func HumanDuration(d time.Duration) string {
} else if hours < 24*365*2 {
return fmt.Sprintf("%d months", hours/24/30)
}
return fmt.Sprintf("%d years", d.Hours()/24/365)
return fmt.Sprintf("%f years", d.Hours()/24/365)
}
// HumanSize returns a human-readable approximation of a size
@@ -170,10 +171,9 @@ func SelfPath() string {
return path
}
type NopWriter struct {
}
type NopWriter struct{}
func (w *NopWriter) Write(buf []byte) (int, error) {
func (*NopWriter) Write(buf []byte) (int, error) {
return len(buf), nil
}
@@ -188,10 +188,10 @@ func NopWriteCloser(w io.Writer) io.WriteCloser {
}
type bufReader struct {
sync.Mutex
buf *bytes.Buffer
reader io.Reader
err error
l sync.Mutex
wait sync.Cond
}
@@ -200,7 +200,7 @@ func NewBufReader(r io.Reader) *bufReader {
buf: &bytes.Buffer{},
reader: r,
}
reader.wait.L = &reader.l
reader.wait.L = &reader.Mutex
go reader.drain()
return reader
}
@@ -209,14 +209,14 @@ func (r *bufReader) drain() {
buf := make([]byte, 1024)
for {
n, err := r.reader.Read(buf)
r.l.Lock()
r.Lock()
if err != nil {
r.err = err
} else {
r.buf.Write(buf[0:n])
}
r.wait.Signal()
r.l.Unlock()
r.Unlock()
if err != nil {
break
}
@@ -224,8 +224,8 @@ func (r *bufReader) drain() {
}
func (r *bufReader) Read(p []byte) (n int, err error) {
r.l.Lock()
defer r.l.Unlock()
r.Lock()
defer r.Unlock()
for {
n, err = r.buf.Read(p)
if n > 0 {
@@ -236,7 +236,6 @@ func (r *bufReader) Read(p []byte) (n int, err error) {
}
r.wait.Wait()
}
panic("unreachable")
}
func (r *bufReader) Close() error {
@@ -248,27 +247,27 @@ func (r *bufReader) Close() error {
}
type WriteBroadcaster struct {
mu sync.Mutex
sync.Mutex
writers map[io.WriteCloser]struct{}
}
func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser) {
w.mu.Lock()
w.Lock()
w.writers[writer] = struct{}{}
w.mu.Unlock()
w.Unlock()
}
// FIXME: Is that function used?
// FIXME: This relies on the concrete writer type used having equality operator
func (w *WriteBroadcaster) RemoveWriter(writer io.WriteCloser) {
w.mu.Lock()
w.Lock()
delete(w.writers, writer)
w.mu.Unlock()
w.Unlock()
}
func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
w.mu.Lock()
defer w.mu.Unlock()
w.Lock()
defer w.Unlock()
for writer := range w.writers {
if n, err := writer.Write(p); err != nil || n != len(p) {
// On error, evict the writer
@@ -279,8 +278,8 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
}
func (w *WriteBroadcaster) CloseWriters() error {
w.mu.Lock()
defer w.mu.Unlock()
w.Lock()
defer w.Unlock()
for writer := range w.writers {
writer.Close()
}
@@ -529,7 +528,9 @@ func GetKernelVersion() (*KernelVersionInfo, error) {
}
if len(tmp2) > 2 {
minor, err = strconv.Atoi(tmp2[2])
// Removes "+" because git kernels might set it
minorUnparsed := strings.Trim(tmp2[2], "+")
minor, err = strconv.Atoi(minorUnparsed)
if err != nil {
return nil, err
}
@@ -637,6 +638,14 @@ func (sf *StreamFormatter) Used() bool {
return sf.used
}
func IsURL(str string) bool {
return strings.HasPrefix(str, "http://") || strings.HasPrefix(str, "https://")
}
func IsGIT(str string) bool {
return strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "github.com/")
}
func CheckLocalDns() bool {
resolv, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
@@ -679,4 +688,39 @@ func ParseHost(host string, port int, addr string) string {
return fmt.Sprintf("tcp://%s:%d", host, port)
}
// Get a repos name and returns the right reposName + tag
// The tag can be confusing because of a port in a repository name.
// Ex: localhost.localdomain:5000/samalba/hipache:latest
func ParseRepositoryTag(repos string) (string, string) {
n := strings.LastIndex(repos, ":")
if n < 0 {
return repos, ""
}
if tag := repos[n+1:]; !strings.Contains(tag, "/") {
return repos[:n], tag
}
return repos, ""
}
// UserLookup check if the given username or uid is present in /etc/passwd
// and returns the user struct.
// If the username is not found, an error is returned.
func UserLookup(uid string) (*user.User, error) {
file, err := ioutil.ReadFile("/etc/passwd")
if err != nil {
return nil, err
}
for _, line := range strings.Split(string(file), "\n") {
data := strings.Split(line, ":")
if len(data) > 5 && (data[0] == uid || data[2] == uid) {
return &user.User{
Uid: data[2],
Gid: data[3],
Username: data[0],
Name: data[4],
HomeDir: data[5],
}, nil
}
}
return nil, fmt.Errorf("User not found in /etc/passwd")
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"io"
"io/ioutil"
"strings"
"testing"
)
@@ -264,14 +265,16 @@ func TestCompareKernelVersion(t *testing.T) {
func TestHumanSize(t *testing.T) {
size1000 := HumanSize(1000)
if size1000 != "1 kB" {
t.Errorf("1000 -> expected 1 kB, got %s", size1000)
size := strings.Trim(HumanSize(1000), " \t")
expect := "1 kB"
if size != expect {
t.Errorf("1000 -> expected '%s', got '%s'", expect, size)
}
size1024 := HumanSize(1024)
if size1024 != "1.024 kB" {
t.Errorf("1024 -> expected 1.024 kB, got %s", size1024)
size = strings.Trim(HumanSize(1024), " \t")
expect = "1.024 kB"
if size != expect {
t.Errorf("1024 -> expected '%s', got '%s'", expect, size)
}
}

192
utils_test.go Normal file
View File

@@ -0,0 +1,192 @@
package docker
import (
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"os"
"path"
"strings"
"testing"
)
// This file contains utility functions for docker's unit test suite.
// It has to be named XXX_test.go, apparently, in other to access private functions
// from other XXX_test.go functions.
// Create a temporary runtime suitable for unit testing.
// Call t.Fatal() at the first error.
func mkRuntime(f Fataler) *Runtime {
runtime, err := newTestRuntime()
if err != nil {
f.Fatal(err)
}
return runtime
}
// A common interface to access the Fatal method of
// both testing.B and testing.T.
type Fataler interface {
Fatal(args ...interface{})
}
func newTestRuntime() (*Runtime, error) {
root, err := ioutil.TempDir("", "docker-test")
if err != nil {
return nil, err
}
if err := os.Remove(root); err != nil {
return nil, err
}
if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil {
return nil, err
}
runtime, err := NewRuntimeFromDirectory(root, false)
if err != nil {
return nil, err
}
runtime.UpdateCapabilities(true)
return runtime, nil
}
// Write `content` to the file at path `dst`, creating it if necessary,
// as well as any missing directories.
// The file is truncated if it already exists.
// Call t.Fatal() at the first error.
func writeFile(dst, content string, t *testing.T) {
// Create subdirectories if necessary
if err := os.MkdirAll(path.Dir(dst), 0700); err != nil && !os.IsExist(err) {
t.Fatal(err)
}
f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700)
if err != nil {
t.Fatal(err)
}
// Write content (truncate if it exists)
if _, err := io.Copy(f, strings.NewReader(content)); err != nil {
t.Fatal(err)
}
}
// Return the contents of file at path `src`.
// Call t.Fatal() at the first error (including if the file doesn't exist)
func readFile(src string, t *testing.T) (content string) {
f, err := os.Open(src)
if err != nil {
t.Fatal(err)
}
data, err := ioutil.ReadAll(f)
if err != nil {
t.Fatal(err)
}
return string(data)
}
// Create a test container from the given runtime `r` and run arguments `args`.
// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image.
// The caller is responsible for destroying the container.
// Call t.Fatal() at the first error.
func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig, error) {
config, hostConfig, _, err := ParseRun(args, nil)
if err != nil {
return nil, nil, err
}
config.Image = GetTestImage(r).ID
c, err := NewBuilder(r).Create(config)
if err != nil {
t.Fatal(err)
return nil, nil, err
}
return c, hostConfig, nil
}
// Create a test container, start it, wait for it to complete, destroy it,
// and return its standard output as a string.
// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image.
// If t is not nil, call t.Fatal() at the first error. Otherwise return errors normally.
func runContainer(r *Runtime, args []string, t *testing.T) (output string, err error) {
defer func() {
if err != nil && t != nil {
t.Fatal(err)
}
}()
container, hostConfig, err := mkContainer(r, args, t)
if err != nil {
return "", err
}
defer r.Destroy(container)
stdout, err := container.StdoutPipe()
if err != nil {
return "", err
}
defer stdout.Close()
if err := container.Start(hostConfig); err != nil {
return "", err
}
container.Wait()
data, err := ioutil.ReadAll(stdout)
if err != nil {
return "", err
}
output = string(data)
return
}
func TestMergeConfig(t *testing.T) {
volumesImage := make(map[string]struct{})
volumesImage["/test1"] = struct{}{}
volumesImage["/test2"] = struct{}{}
configImage := &Config{
Dns: []string{"1.1.1.1", "2.2.2.2"},
PortSpecs: []string{"1111:1111", "2222:2222"},
Env: []string{"VAR1=1", "VAR2=2"},
Volumes: volumesImage,
}
volumesUser := make(map[string]struct{})
volumesUser["/test3"] = struct{}{}
configUser := &Config{
Dns: []string{"3.3.3.3"},
PortSpecs: []string{"2222:3333", "3333:3333"},
Env: []string{"VAR2=3", "VAR3=3"},
Volumes: volumesUser,
}
MergeConfig(configUser, configImage)
if len(configUser.Dns) != 3 {
t.Fatalf("Expected 3 dns, 1.1.1.1, 2.2.2.2 and 3.3.3.3, found %d", len(configUser.Dns))
}
for _, dns := range configUser.Dns {
if dns != "1.1.1.1" && dns != "2.2.2.2" && dns != "3.3.3.3" {
t.Fatalf("Expected 1.1.1.1 or 2.2.2.2 or 3.3.3.3, found %s", dns)
}
}
if len(configUser.PortSpecs) != 3 {
t.Fatalf("Expected 3 portSpecs, 1111:1111, 2222:3333 and 3333:3333, found %d", len(configUser.PortSpecs))
}
for _, portSpecs := range configUser.PortSpecs {
if portSpecs != "1111:1111" && portSpecs != "2222:3333" && portSpecs != "3333:3333" {
t.Fatalf("Expected 1111:1111 or 2222:3333 or 3333:3333, found %s", portSpecs)
}
}
if len(configUser.Env) != 3 {
t.Fatalf("Expected 3 env var, VAR1=1, VAR2=3 and VAR3=3, found %d", len(configUser.Env))
}
for _, env := range configUser.Env {
if env != "VAR1=1" && env != "VAR2=3" && env != "VAR3=3" {
t.Fatalf("Expected VAR1=1 or VAR2=3 or VAR3=3, found %s", env)
}
}
if len(configUser.Volumes) != 3 {
t.Fatalf("Expected 3 volumes, /test1, /test2 and /test3, found %d", len(configUser.Volumes))
}
for v, _ := range configUser.Volumes {
if v != "/test1" && v != "/test2" && v != "/test3" {
t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v)
}
}
}

12
z_final_test.go Normal file
View File

@@ -0,0 +1,12 @@
package docker
import (
"github.com/dotcloud/docker/utils"
"runtime"
"testing"
)
func TestFinal(t *testing.T) {
cleanup(globalRuntime)
t.Logf("Fds: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine())
}