Compare commits

..

70 Commits

Author SHA1 Message Date
Antonio Murdaca 7946ee2696 Dockerfile: add kvmtool and linux-container for clr execdriver
This patch adds the needed pkgs to docker/docker development Dockerfile
in order to test the clr execdriver in the development container.

Signed-off-by: Antonio Murdaca <runcom@redhat.com>
2015-10-23 12:11:51 +01:00
James Hunt 77f05fa886 Change version tag.
Signed-off-by: James Hunt <james.o.hunt@intel.com>
2015-10-21 16:09:03 +01:00
James Hunt f554d104fb Merge tag 'v1.8.3' into github-clr-1.8.3-submit
v1.8.3

Signed-off-by: James Hunt <james.o.hunt@intel.com>

Conflicts:
	VERSION
2015-10-21 13:43:11 +01:00
Jessica Frazelle f4bf5c7026 bump version for v1.8.3
Signed-off-by: Jessica Frazelle <acidburn@docker.com>
2015-10-11 20:37:29 -07:00
Jessica Frazelle 1321794dc1 change flag name to better follow the other flags that start with disable;
Signed-off-by: Jessica Frazelle <acidburn@docker.com>
2015-10-07 10:50:50 -07:00
Richard Scothern 668a1758bd Prevent push and pull to v1 registries by filtering the available endpoints.
Add a daemon flag to control this behaviour.  Add a warning message when pulling
an image from a v1 registry.  The default order of pull is slightly altered
with this changset.

Previously it was:
https v2, https v1, http v2, http v1

now it is:
https v2, http v2, https v1, http v1

Prevent login to v1 registries by explicitly setting the version before ping to
prevent fallback to v1.

Add unit tests for v2 only mode.  Create a mock server that can register
handlers for various endpoints.  Assert no v1 endpoints are hit with legacy
registries disabled for the following commands:  pull, push, build, run and
login.  Assert the opposite when legacy registries are not disabled.

Signed-off-by: Richard Scothern <richard.scothern@gmail.com>
2015-10-06 18:26:17 -07:00
Richard Scothern 6321ac9182 Command line, manpage and deprecation documentation
Signed-off-by: Richard Scothern <richard.scothern@gmail.com>
2015-10-06 18:26:16 -07:00
Aaron Lehmann 40164ebcc1 Remove trust package
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2015-10-06 18:26:15 -07:00
Aaron Lehmann 56d463690f Unmarshal signed payload when pulling by digest
Add a unit test for validateManifest which ensures extra data can't be
injected by adding data to the JSON object outside the payload area.

This also removes validation of legacy signatures at pull time. This
starts the path of deprecating legacy signatures, whose presence in the
very JSON document they attempt to sign is problematic.  These
signatures were only checked for official images, and since they only
caused a weakly-worded message to be printed, removing the verification
should not cause impact.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2015-10-06 18:26:09 -07:00
Tonis Tiigi 9098628b29 Calculate hash based image IDs on pull
Generate a hash chain involving the image configuration, layer digests,
and parent image hashes. Use the digests to compute IDs for each image
in a manifest, instead of using the remotely specified IDs.

To avoid breaking users' caches, check for images already in the graph
under old IDs, and avoid repulling an image if the version on disk under
the legacy ID ends up with the same digest that was computed from the
manifest for that image.

When a calculated ID already exists in the graph but can't be verified,
continue trying SHA256(digest) until a suitable ID is found.

"save" and "load" are not changed to use a similar scheme. "load" will
preserve the IDs present in the tar file.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2015-10-06 18:25:17 -07:00
Dimitri John Ledkov a6e81cff5f Change version tag
Signed-off-by: Dimitri John Ledkov <dimitri.j.ledkov@intel.com>
2015-09-18 14:15:07 +01:00
Dimitri John Ledkov 023d4ed189 Fix ups.
Signed-off-by: Dimitri John Ledkov <dimitri.j.ledkov@intel.com>
2015-09-17 18:26:29 +01:00
Dimitri John Ledkov d7c5f31c5b Properly find the right Endpoint to steal networking information from.
Signed-off-by: Dimitri John Ledkov <dimitri.j.ledkov@intel.com>
2015-09-17 18:26:20 +01:00
David Calavera 0a8c2e3717 Bump to version 1.8.2.
Signed-off-by: David Calavera <david.calavera@gmail.com>
2015-09-10 14:43:43 -04:00
Tianon Gravi 57e9c4f7e5 Swap "ubuntu-debootstrap" for just "ubuntu"
See https://github.com/docker-library/official-images/pull/982#issuecomment-133207587.

Signed-off-by: Andrew "Tianon" Page <admwiggin@gmail.com>
2015-09-10 14:43:43 -04:00
Dimitri John Ledkov e51462ac68 Merge branch 'release/1.8' of git://github.com/docker/docker into new-merge
Signed-off-by: Dimitri John Ledkov <dimitri.j.ledkov@intel.com>
2015-09-09 14:42:44 +01:00
Dimitri John Ledkov e45d0eb532 Clear Containers for Docker Engine execution driver
Signed-off-by: Dimitri John Ledkov <dimitri.j.ledkov@intel.com>
Signed-off-by: James Hunt <james.o.hunt@intel.com>
Signed-off-by: Michael Doherty <michael.i.doherty@intel.com>
2015-09-09 14:31:36 +01:00
Jessica Frazelle 3e8da36017 use apt-ftparchive and reprepro to enable apt-pinning;
Signed-off-by: Jessica Frazelle <acidburn@docker.com>
(cherry picked from commit 12a71c8954)
2015-09-03 05:20:58 -04:00
David Calavera 1cce9a26a3 Bump libnetwork version to bc565c2d295067c1a43674a23a473ec6336d7fd4
Signed-off-by: David Calavera <david.calavera@gmail.com>
2015-09-02 17:24:37 -04:00
Tonis Tiigi d7f8b4d43e Fix goroutine leak on pull
Close the pipeWriter even if there was no error.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2015-09-01 09:31:43 -04:00
Derek McGowan 6f7bbc3171 Fix sanitize URL bug on layer upload
Update the distribution version to include sanitize URL fix

Fixes #15875

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
2015-08-31 03:37:50 -04:00
Harald Albers 947087fb24 Update bash completion for docker run
Also fixed sort order of options using `sort -d`

Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 644c158837)
2015-08-28 05:03:01 -04:00
Harald Albers ffe7e48ed6 Add options for the json-file logging driver to bash completion
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 5c1ad6f90c)
2015-08-28 05:02:55 -04:00
Harald Albers eeecd1cf59 Add storage options to bash completion
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit e4d8a8e1ca)

Conflicts:
	contrib/completion/bash/docker
2015-08-28 05:02:45 -04:00
Harald Albers 3f411db15b Add missing storage drivers to bash completion
Signed-off-by: Harald Albers <github@albersweb.de>
2015-08-28 04:59:30 -04:00
Harald Albers 789197f33d Remove -h flag from completion and daemon reference
All docker subcommands support `-h` as an alias for `--help`
unless they have `-h` aliased to something else like `docker run`,
which uses `-h` for `--hostname`.

`-h` is not included in the help messages of the commands, though.

It ist visible in
* reference: only in `docker daemon` reference,
  see output of `grep -Rse --help=false docs`
* man pages: only in `docker` man page
  see output of `grep -RF '**-h**' man`

For consistency reasons, this commit removes `-h` as an alias for
`--help` from the reference page, man page and the bash completion.

Signed-off-by: Harald Albers <github@albersweb.de>
2015-08-28 04:59:09 -04:00
Harald Albers 0c71d09921 Add docker ps --format to bash completion
Signed-off-by: Harald Albers <github@albersweb.de>
2015-08-28 04:57:54 -04:00
Tonis Tiigi cc8320cb58 Fix pull on client disconnect
Fixes #15589

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2015-08-28 04:26:22 -04:00
Vincent Batts 6bf1c41b77 devicemapper: fix zero-sized field access
Fixes: #15279

Due to
https://github.com/golang/go/commit/7904946eeb35faece61bbf6f5b3cc8be2f519c17
the devices field is dropped.

This solution works on go1.4 and go1.5

Signed-off-by: Vincent Batts <vbatts@redhat.com>
2015-08-26 13:27:04 +01:00
Arnaud Porterie c22b292719 Update vendoring
Signed-off-by: Arnaud Porterie <arnaud.porterie@docker.com>
2015-08-26 04:45:56 -04:00
Tonis Tiigi 14d2083f14 Remove nil contexts
Causes daemon panic because loggers can’t be found.

Fixes #15724

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit 0c08913d52)
2015-08-25 12:25:38 -04:00
Tonis Tiigi ea56c5e1ce Update volumes userguide
Fixes #15644

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2015-08-25 12:25:37 -04:00
Harald Albers 341ff018a2 Fix bash completion for log driver options
This option was incorrectly ported to the new `daemon` subcommand
structure.

Beside the obvious effect that completion of `docker daemon --log-opt`
did not work, this also caused completion of `docker` and `docker xxx`
to fail on macs with

> bash: words: bad array subscript

Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 18381faee6)

Conflicts:
	contrib/completion/bash/docker
2015-08-25 12:25:37 -04:00
Shijiang Wei e07819293a a quick fix to #15626
Signed-off-by: Shijiang Wei <mountkin@gmail.com>
2015-08-25 12:25:37 -04:00
Marius Sturm 6ec8d40ae7 Initialize LogConfig in daemon mode
Signed-off-by: Marius Sturm <marius@graylog.com>
(cherry picked from commit e904cbec03)

Conflicts:
	docker/daemon.go
2015-08-25 12:25:37 -04:00
Jana Radhakrishnan 16d64608f3 Vendoring in vishvananda/netlink
Updating netlink package to 4b5dce31de6d42af5bb9811c6d265472199e0fec
to fix certain wierd netlink issues seen.

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
(cherry picked from commit 7948b755c7)

Conflicts:
	hack/vendor.sh
2015-08-25 12:25:37 -04:00
David Calavera 00a27b6872 Fix ignore -q flag in docker ps when there is a default format.
Docker ps default format should not take precedence over cli flags.
This happens effectively for other flags except `-q`.
We need to let the cli to set the format as table to print the
expected output with `-q`.

Signed-off-by: David Calavera <david.calavera@gmail.com>
2015-08-25 04:32:20 -04:00
Vincent Batts fc12b9ddce vendor: update tar-split to v0.9.6
Fixes rare edge case of handling GNU LongLink and LongName entries.
Perf improvements. /dev/null writes were taking CPU time during docker
push. Thanks @LK4D4
Various cleanup too.

Signed-off-by: Vincent Batts <vbatts@redhat.com>
2015-08-25 04:32:07 -04:00
Jessie Frazelle b66e5ef208 Merge pull request #15544 from icecrime/bump_1.8.1
Bump 1.8.1
2015-08-12 20:19:06 -07:00
Arnaud Porterie d12ea79c9d Update VERSION and CHANGELOG.md
Signed-off-by: Arnaud Porterie <arnaud.porterie@docker.com>
2015-08-12 18:54:41 -07:00
Arnaud Porterie a9aaa66780 Update regression test
Signed-off-by: Arnaud Porterie <arnaud.porterie@docker.com>
2015-08-12 18:52:01 -07:00
Arnaud Porterie e19060dcea Revert #14884
This reverts commit 810d3b2642.

Signed-off-by: Arnaud Porterie <arnaud.porterie@docker.com>
2015-08-12 18:51:57 -07:00
David Calavera b0e0dbb33b Merge pull request #15091 from calavera/bump_v1.8.0
Bump v1.8.0
2015-08-11 13:16:04 -05:00
David Calavera 0d03096b65 Bump version to 1.8.0
Signed-off-by: David Calavera <david.calavera@gmail.com>
2015-08-11 11:35:27 -05:00
Jessica Frazelle 55e9551aaa minor cosmetic change to client output on pull
Signed-off-by: Jessica Frazelle <acidburn@docker.com>
(cherry picked from commit 8bd6322760)
2015-08-11 11:35:27 -05:00
Mary Anthony c65afe6ba8 Updating new vm and with migrate
Signed-off-by: Mary Anthony <mary@docker.com>
(cherry picked from commit e5c160e1d6)
2015-08-11 11:35:27 -05:00
Mary Anthony 5745aaed22 Initial work
Removing references to regsitry 1.0, pointing to distribution
Updating links and title
Adding in comments

Signed-off-by: Mary Anthony <mary@docker.com>
(cherry picked from commit d550d729e1)
2015-08-11 11:35:27 -05:00
Diogo Monica b6f0f93c94 Docker content trust documentation
- started from Diogo's work
- updated after discussions with team
- Updating with new key names
- fixing weight
- adding in sandbox
- adding in gliffy for images
- backing out to old names for now
- Copy edit pass
- Entering comments from the content trust team
- Update name of branch and image name
- Removing the last diogo reference
- Updating with Seb's comments

Signed-off-by: Mary Anthony <mary@docker.com>
(cherry picked from commit 753bf40f15)
2015-08-11 11:35:26 -05:00
Youcef YEKHLEF 33b16fef43 Add fedora 22 install docs warning about systemd-network
With systemd in version 219 IP forwarding should be forced in the interface configuration file.

Signed-off-by: yyekhlef <yyekhlef@gmail.com>
(cherry picked from commit 35aebdc803)
2015-08-10 12:11:42 -05:00
Youcef YEKHLEF 9705c349c5 Add fedora 22 install docs warning about systemd-network
Following @cles commit #6bd15a68c for Arch

Signed-off-by: yyekhlef <yyekhlef@gmail.com>
(cherry picked from commit f224a9f3d6)
2015-08-10 12:11:42 -05:00
Ankush Agarwal 3de34af5d1 Remove note from docker version 1.3 on Env variables
Fixes #14734

Signed-off-by: Ankush Agarwal <ankushagarwal11@gmail.com>
(cherry picked from commit 89ec5399af)
2015-08-10 12:11:42 -05:00
Ankush Agarwal b3f3abfc94 Make official install instructions use curl instead of wget
YOU GO CURL

Fixes #14650

Signed-off-by: Ankush Agarwal <ankushagarwal11@gmail.com>
(cherry picked from commit c76fef1791)
2015-08-10 12:10:42 -05:00
Mary Anthony 783baec49c Fixes #15349 too many apis
Signed-off-by: Mary Anthony <mary@docker.com>
(cherry picked from commit 1d5f479e01)
2015-08-10 12:10:42 -05:00
Vincent Bernat c1d9e7c6fb doc: add a notice about memory accounting overhead
The documentation for Debian and Ubuntu explains how to enable memory
and swap accounting but doesn't explain why it is disabled in the first
place. The problem with those subsystems is that they incurs a
performance hit even when not used at all. Add this explanation.

The provided figure are quite vague. The memory overhead is easily
verifiable. It is for example cited in [RedHat documentation][]. For the
performance hit, maybe the performance is better now, but a few years
ago, it was said to be [between 6 and 15%][].

The goal is that people don't just enable memory accounting if they
don't have a use for it.

[RedHat documentation]: https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Resource_Management_Guide/sec-memory.html
[between 6 and 15%]: https://lwn.net/Articles/517562/

Signed-off-by: Vincent Bernat <vincent@bernat.im>
(cherry picked from commit 56b33e9f27)
2015-08-10 12:10:42 -05:00
Derek McGowan 83f6dbe30a Skip notary tests which update system clock
Currently some notary tests change the system clock to check for expiration.
Skip these tests until the code can be refactored to not rely on updating the system clock.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
(cherry picked from commit bf3c1e6a3a)
2015-08-07 10:08:37 -07:00
Jessica Frazelle a97b89b585 remove docker-unconfined profile we were not using it and it breaks apparmor on wheezy
Signed-off-by: Jessica Frazelle <acidburn@docker.com>
(cherry picked from commit e542238f2a)
2015-08-06 20:08:25 -07:00
Tibor Vass 29ea36a880 registry: Do not push to mirrors
This patch splits LookupEndpoints into LookupPullEndpoints and
LookupPushEndpoints so that mirrors added with --registry-mirror are
skipped in the list returned by LookupPushEndpoints.

Fixes https://github.com/docker/distribution/issues/823

Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit b899977ee2)
2015-08-06 19:55:06 -07:00
Alessandro Boch ed672d1609 Vendoring libnetwork bd3eecc96f3c05a4acef1bedcf74397bc6850d22
Signed-off-by: Alessandro Boch <aboch@docker.com>
(cherry picked from commit e35a5ae463)
2015-08-06 19:55:05 -07:00
Jessica Frazelle 5916664220 revert apparmor changes back to how it was in 1.7.1, but keep tests
Signed-off-by: Jessica Frazelle <acidburn@docker.com>
(cherry picked from commit ed248207d7)
2015-08-06 19:55:05 -07:00
Lei da4b336233 Remove redundant ip_forward check
Signed-off-by: Lei Jitang <leijitang@huawei.com>
(cherry picked from commit 6a0050d0f0)
2015-08-06 09:55:02 -07:00
Josh Hawn 74df05ccaa [graph] Use a pipe for downloads to write progress
The process of pulling an image spawns a new goroutine for each layer in the
image manifest. If any of these downloads fail we would stop everything and
return the error, even though other goroutines would still be running and
writing output through a progress reader which is attached to an http response
writer. Since the request handler had already returned from the first error,
the http server panics when one of these download goroutines makes a write to
the response writer buffer.

This patch prevents this crash in the daemon http server by waiting for all of
the download goroutines to complete, even if one of them fails. Only then does
it return, terminating the request handler.

Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)

(cherry picked from commit d80c4244d3)
2015-08-06 09:14:16 -07:00
Dimitri John Ledkov 2c875215b1 systemd: set service type to notify.
Currently the service type is 'simple', the default, meaning that
docker.service is considered to be started straight after
spawning. This is incorrect as there is significant amount of time
between spawning and docker ready to accept connections on the passed
sockets. Docker does implement systemd socket activate and
notification protocol, and send the ready signal to systemd, once it
is ready. However for systemd to take those notifications into
account, the service file type should be set to notify.

Signed-off-by: Dimitri John Ledkov <dimitri.j.ledkov@intel.com>
(cherry picked from commit d3e5179c29)
2015-08-06 08:39:11 -07:00
Mary Anthony be40a48c12 - Remove references to sudo in basics.md; see sudo instructions top of file
- Removing references to Boot2Docker replacing with Docker Machine
- Removing sudo warnings in instances where appropriate (no sudo in file)
- Updating with comments

Signed-off-by: Mary Anthony <mary@docker.com>
(cherry picked from commit cc375a1e48)
2015-08-06 08:39:10 -07:00
Filipe Oliveira 85f7f7cfc7 Adding support to forked distributions in installer script.
Signed-off-by: Filipe Oliveira <contato@fmoliveira.com.br>
(cherry picked from commit f618de1543)
2015-08-06 08:35:41 -07:00
David Calavera e15f6fca3f Fail fail when the ps format template is invalid.
Fixes error continuing execution when the parsing fails.

Signed-off-by: David Calavera <david.calavera@gmail.com>
(cherry picked from commit 3d3db0d4af)
2015-08-05 16:21:30 -07:00
Vincent Demeester d3bbaa70cd Update some contributions documentations
- Add a golint entry to coding-style.md

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
(cherry picked from commit b304920021)
2015-08-05 10:54:39 -07:00
Harald Albers cc6f6cb2e2 Add --config to bash completion
The custom configuration will also be used in docker invocations made
by the completion script itself, just like `-H`.

Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit b898111d3a)
2015-08-05 09:52:08 -07:00
Jessica Frazelle c967dd289f update systemd article to reference dropin file
Signed-off-by: Jessica Frazelle <acidburn@docker.com>
(cherry picked from commit 35e7a7c3e2)
2015-08-04 14:11:36 -07:00
Sally O'Malley 7895ec25ea make man/docker.1.md consistent with docker --help
"Options:" listed when you run "docker --help" and "docker daemon
--help" do not match the options listed in "man/docker.1.md".  This PR
makes 'docker --help', 'docker daemon --help' and 'man docker' consistent.
Also 2 typo fixes.

Signed-off-by: Sally O'Malley <somalley@redhat.com>
(cherry picked from commit f3bea61c80)
2015-08-04 09:35:25 -07:00
Charles Chan 5b06c94701 Fix #15212: Add "Labels" key to output of /containers/json
Applied retroactively from API v1.18 - v1.21.

Signed-off-by: Charles Chan <charleswhchan@users.noreply.github.com>
(cherry picked from commit b245bcd458)
2015-08-04 09:35:25 -07:00
169 changed files with 5117 additions and 3689 deletions
+52 -6
View File
@@ -1,6 +1,47 @@
# Changelog
## 1.8.0 (2015-07-24)
## 1.8.3 (2015-10-12)
### Distribution
- Fix layer IDs lead to local graph poisoning (CVE-2014-8178)
- Fix manifest validation and parsing logic errors allow pull-by-digest validation bypass (CVE-2014-8179)
+ Add `--disable-legacy-registry` to prevent a daemon from using a v1 registry
## 1.8.2 (2015-09-10)
### Distribution
- Fixes rare edge case of handling GNU LongLink and LongName entries.
- Fix ^C on docker pull.
- Fix docker pull issues on client disconnection.
- Fix issue that caused the daemon to panic when loggers weren't configured properly.
- Fix goroutine leak pulling images from registry V2.
### Runtime
- Fix a bug mounting cgroups for docker daemons running inside docker containers.
- Initialize log configuration properly.
### Client:
- Handle `-q` flag in `docker ps` properly when there is a default format.
### Networking
- Fix several corner cases with netlink.
### Contrib
- Fix several issues with bash completion.
## 1.8.1 (2015-08-12)
### Distribution
- Fix a bug where pushing multiple tags would result in invalid images
## 1.8.0 (2015-08-11)
### Distribution
@@ -47,7 +88,7 @@
* Remove RC4 from the list of registry cipher suites
* Add syslog-facility option
* LXC execdriver compatibility with recent LXC versions
* Mark LXC execriver as deprecated (to be removed with the migration to runc)
### Plugins
@@ -85,6 +126,11 @@
- Fix error caused using default gateways outside of the allocated range
- Format times in inspect command with a template as RFC3339Nano
- Make registry client to accept 2xx and 3xx http status responses as successful
- Fix race issue that caused the daemon to crash with certain layer downloads failed in a specific order.
- Fix error when the docker ps format was not valid.
- Remove redundant ip forward check.
- Fix issue trying to push images to repository mirrors.
- Fix error cleaning up network entrypoints when there is an initialization issue.
## 1.7.1 (2015-07-14)
@@ -230,7 +276,7 @@
#### Notable Features since 1.3.0
+ Set key=value labels to the daemon (displayed in `docker info`), applied with
new `-label` daemon flag
+ Add support for `ENV` in Dockerfile of the form:
+ Add support for `ENV` in Dockerfile of the form:
`ENV name=value name2=value2...`
+ New Overlayfs Storage Driver
+ `docker info` now returns an `ID` and `Name` field
@@ -692,7 +738,7 @@
- Fix broken images API for version less than 1.7
- Use the right encoding for all API endpoints which return JSON
- Move remote api client to api/
- Queue calls to the API using generic socket wait
- Queue calls to the API using generic socket wait
#### Runtime
@@ -772,7 +818,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
- Do not add hostname when networking is disabled
* Return most recent image from the cache by date
- Return all errors from docker wait
* Add Content-Type Header "application/json" to GET /version and /info responses
* Add Content-Type Header "application/json" to GET /version and /info responses
#### Other
@@ -800,7 +846,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
#### Runtime
- Only get the image's rootfs when we need to calculate the image size
- Correctly handle unmapping UDP ports
- Correctly handle unmapping UDP ports
* Make CopyFileWithTar use a pipe instead of a buffer to save memory on docker build
- Fix login message to say pull instead of push
- Fix "docker load" help by removing "SOURCE" prompt and mentioning STDIN
+7 -1
View File
@@ -58,6 +58,12 @@ RUN apt-get update && apt-get install -y \
libzfs-dev \
--no-install-recommends
# clr
RUN echo deb http://download.opensuse.org/repositories/home:/clearlinux:/preview/xUbuntu_15.04/ / > /etc/apt/sources.list.d/clear-containers-docker.list
RUN apt-get --allow-unauthenticated update && apt-get install -y --force-yes \
kvmtool\
linux-container
# Get lvm2 source for compiling statically
RUN git clone -b v2_02_103 https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
@@ -127,7 +133,7 @@ RUN git clone https://github.com/golang/lint.git /go/src/github.com/golang/lint
RUN gem install --no-rdoc --no-ri fpm --version 1.3.2
# Install registry
ENV REGISTRY_COMMIT 2317f721a3d8428215a2b65da4ae85212ed473b4
ENV REGISTRY_COMMIT ec87e9b6971d831f0eff752ddb54fb64693e51cd
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
+1 -1
View File
@@ -1 +1 @@
1.8.0-rc2
1.8.3-clear-containers
+1 -1
View File
@@ -95,7 +95,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
f := *format
if len(f) == 0 {
if len(cli.PsFormat()) > 0 {
if len(cli.PsFormat()) > 0 && !*quiet {
f = cli.PsFormat()
} else {
f = "table"
+7 -4
View File
@@ -170,9 +170,11 @@ func customFormat(ctx Context, containers []types.Container) {
format += "\t{{.Size}}"
}
tmpl, err := template.New("ps template").Parse(format)
tmpl, err := template.New("").Parse(format)
if err != nil {
buffer.WriteString(fmt.Sprintf("Invalid `docker ps` format: %v\n", err))
buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
buffer.WriteTo(ctx.Output)
return
}
for _, container := range containers {
@@ -181,8 +183,9 @@ func customFormat(ctx Context, containers []types.Container) {
c: container,
}
if err := tmpl.Execute(buffer, containerCtx); err != nil {
buffer = bytes.NewBufferString(fmt.Sprintf("Invalid `docker ps` format: %v\n", err))
break
buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
buffer.WriteTo(ctx.Output)
return
}
if table && len(header) == 0 {
header = containerCtx.fullHeader()
+15 -1
View File
@@ -1,6 +1,7 @@
package ps
import (
"bytes"
"reflect"
"strings"
"testing"
@@ -10,7 +11,7 @@ import (
"github.com/docker/docker/pkg/stringid"
)
func TestContainerContextID(t *testing.T) {
func TestContainerPsContext(t *testing.T) {
containerId := stringid.GenerateRandomID()
unix := time.Now().Unix()
@@ -86,3 +87,16 @@ func TestContainerContextID(t *testing.T) {
}
}
func TestContainerPsFormatError(t *testing.T) {
out := bytes.NewBufferString("")
ctx := Context{
Format: "{{InvalidFunction}}",
Output: out,
}
customFormat(ctx, make([]types.Container, 0))
if out.String() != "Template parsing error: template: :1: function \"InvalidFunction\" not defined\n" {
t.Fatalf("Expected format error, got `%v`\n", out.String())
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
FROM debian:jessie
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
ENV GO_VERSION 1.4.2
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
@@ -4,7 +4,7 @@
FROM debian:stretch
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
ENV GO_VERSION 1.4.2
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
+1 -1
View File
@@ -5,7 +5,7 @@
FROM debian:wheezy
RUN echo deb http://http.debian.net/debian wheezy-backports main > /etc/apt/sources.list.d/wheezy-backports.list
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
ENV GO_VERSION 1.4.2
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
-1
View File
@@ -50,7 +50,6 @@ for version in "${versions[@]}"; do
build-essential # "essential for building Debian packages"
curl ca-certificates # for downloading Go
debhelper # for easy ".deb" building
dh-apparmor # for apparmor debhelper
dh-systemd # for systemd debhelper integration
git # for "git commit" info in "docker -v"
libapparmor-dev # for "sys/apparmor.h"
@@ -2,9 +2,9 @@
# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"!
#
FROM ubuntu-debootstrap:precise
FROM ubuntu:precise
RUN apt-get update && apt-get install -y bash-completion build-essential curl ca-certificates debhelper dh-apparmor git libapparmor-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y bash-completion build-essential curl ca-certificates debhelper git libapparmor-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
ENV GO_VERSION 1.4.2
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
@@ -2,9 +2,9 @@
# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"!
#
FROM ubuntu-debootstrap:wily
FROM ubuntu:trusty
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
ENV GO_VERSION 1.4.2
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
@@ -2,9 +2,9 @@
# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"!
#
FROM ubuntu-debootstrap:vivid
FROM ubuntu:vivid
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
ENV GO_VERSION 1.4.2
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
@@ -2,9 +2,9 @@
# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"!
#
FROM ubuntu-debootstrap:trusty
FROM ubuntu:wily
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
ENV GO_VERSION 1.4.2
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
+86 -16
View File
@@ -27,7 +27,7 @@
# This order should be applied to lists, alternatives and code blocks.
__docker_q() {
docker ${host:+-H "$host"} 2>/dev/null "$@"
docker ${host:+-H "$host"} ${config:+--config "$config"} 2>/dev/null "$@"
}
__docker_containers_all() {
@@ -139,7 +139,7 @@ __docker_value_of_option() {
local counter=$((command_pos + 1))
while [ $counter -lt $cword ]; do
case ${words[$counter]} in
$option_glob )
@($option_glob) )
echo ${words[$counter + 1]}
break
;;
@@ -229,11 +229,12 @@ __docker_log_driver_options() {
# see docs/reference/logging/index.md
local fluentd_options="fluentd-address fluentd-tag"
local gelf_options="gelf-address gelf-tag"
local json_file_options="max-file max-size"
local syslog_options="syslog-address syslog-facility syslog-tag"
case $(__docker_value_of_option --log-driver) in
'')
COMPREPLY=( $( compgen -W "$fluentd_options $gelf_options $syslog_options" -S = -- "$cur" ) )
COMPREPLY=( $( compgen -W "$fluentd_options $gelf_options $json_file_options $syslog_options" -S = -- "$cur" ) )
;;
fluentd)
COMPREPLY=( $( compgen -W "$fluentd_options" -S = -- "$cur" ) )
@@ -241,6 +242,9 @@ __docker_log_driver_options() {
gelf)
COMPREPLY=( $( compgen -W "$gelf_options" -S = -- "$cur" ) )
;;
json-file)
COMPREPLY=( $( compgen -W "$json_file_options" -S = -- "$cur" ) )
;;
syslog)
COMPREPLY=( $( compgen -W "$syslog_options" -S = -- "$cur" ) )
;;
@@ -320,11 +324,15 @@ __docker_signals() {
_docker_docker() {
local boolean_options="
$global_boolean_options
--help -h
--help
--version -v
"
case "$prev" in
--config)
_filedir -d
return
;;
--log-level|-l)
__docker_log_levels
return
@@ -334,8 +342,6 @@ _docker_docker() {
;;
esac
__docker_complete_log_driver_options && return
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "$boolean_options $global_options_with_args" -- "$cur" ) )
@@ -456,7 +462,8 @@ _docker_create() {
_docker_daemon() {
local boolean_options="
$global_boolean_options
--help -h
--disable-legacy-registry
--help
--icc=false
--ip-forward=false
--ip-masq=false
@@ -508,7 +515,39 @@ _docker_daemon() {
return
;;
--storage-driver|-s)
COMPREPLY=( $( compgen -W "aufs devicemapper btrfs overlay" -- "$(echo $cur | tr '[:upper:]' '[:lower:]')" ) )
COMPREPLY=( $( compgen -W "aufs btrfs devicemapper overlay vfs zfs" -- "$(echo $cur | tr '[:upper:]' '[:lower:]')" ) )
return
;;
--storage-opt)
local devicemapper_options="
dm.basesize
dm.blkdiscard
dm.blocksize
dm.fs
dm.loopdatasize
dm.loopmetadatasize
dm.mkfsarg
dm.mountopt
dm.override_udev_sync_check
dm.thinpooldev
"
local zfs_options="zfs.fsname"
case $(__docker_value_of_option '--storage-driver|-s') in
'')
COMPREPLY=( $( compgen -W "$devicemapper_options $zfs_options" -S = -- "$cur" ) )
;;
devicemapper)
COMPREPLY=( $( compgen -W "$devicemapper_options" -S = -- "$cur" ) )
;;
zfs)
COMPREPLY=( $( compgen -W "$zfs_options" -S = -- "$cur" ) )
;;
*)
return
;;
esac
compopt -o nospace
return
;;
--log-level|-l)
@@ -524,6 +563,27 @@ _docker_daemon() {
;;
esac
__docker_complete_log_driver_options && return
case "${words[$cword-2]}$prev=" in
*dm.blkdiscard=*)
COMPREPLY=( $( compgen -W "false true" -- "${cur#=}" ) )
return
;;
*dm.fs=*)
COMPREPLY=( $( compgen -W "ext4 xfs" -- "${cur#=}" ) )
return
;;
*dm.override_udev_sync_check=*)
COMPREPLY=( $( compgen -W "false true" -- "${cur#=}" ) )
return
;;
*dm.thinpooldev=*)
_filedir
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) )
@@ -865,7 +925,7 @@ _docker_ps() {
compopt -o nospace
return
;;
-n)
--format|-n)
return
;;
esac
@@ -889,7 +949,7 @@ _docker_ps() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--all -a --before --filter -f --help --latest -l -n --no-trunc --quiet -q --size -s --since" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--all -a --before --filter -f --format --help --latest -l -n --no-trunc --quiet -q --size -s --since" -- "$cur" ) )
;;
esac
}
@@ -994,15 +1054,16 @@ _docker_rmi() {
_docker_run() {
local options_with_args="
--add-host
--blkio-weight
--attach -a
--blkio-weight
--cap-add
--cap-drop
--cgroup-parent
--cidfile
--cpuset
--cpu-period
--cpu-quota
--cpuset-cpus
--cpuset-mems
--cpu-shares -c
--device
--dns
@@ -1014,8 +1075,8 @@ _docker_run() {
--group-add
--hostname -h
--ipc
--label -l
--label-file
--label -l
--link
--log-driver
--log-opt
@@ -1023,14 +1084,15 @@ _docker_run() {
--mac-address
--memory -m
--memory-swap
--memory-swappiness
--name
--net
--pid
--publish -p
--restart
--security-opt
--user -u
--ulimit
--user -u
--uts
--volumes-from
--volume -v
@@ -1038,8 +1100,10 @@ _docker_run() {
"
local all_options="$options_with_args
--disable-content-trust=false
--help
--interactive -i
--oom-kill-disable
--privileged
--publish-all -P
--read-only
@@ -1049,7 +1113,7 @@ _docker_run() {
[ "$command" = "run" ] && all_options="$all_options
--detach -d
--rm
--sig-proxy
--sig-proxy=false
"
local options_with_args_glob=$(__docker_to_extglob "$options_with_args")
@@ -1394,6 +1458,7 @@ _docker() {
--tlsverify
"
local global_options_with_args="
--config
--host -H
--log-level -l
--tlscacert
@@ -1401,7 +1466,7 @@ _docker() {
--tlskey
"
local host
local host config
COMPREPLY=()
local cur prev words cword
@@ -1416,6 +1481,11 @@ _docker() {
(( counter++ ))
host="${words[$counter]}"
;;
# save config so that completion can use custom configuration directories
--config)
(( counter++ ))
config="${words[$counter]}"
;;
$(__docker_to_extglob "$global_options_with_args") )
(( counter++ ))
;;
+1
View File
@@ -597,6 +597,7 @@ _docker() {
"($help -d --daeamon)"{-d,--daemon}"[Enable daemon mode]" \
"($help)--default-gateway[Container default gateway IPv4 address]:IPv4 address: " \
"($help)--default-gateway-v6[Container default gateway IPv6 address]:IPv6 address: " \
"($help)--disable-legacy-registry[Do not contact legacy registries]" \
"($help)*--dns=-[DNS server to use]:DNS: " \
"($help)*--dns-search=-[DNS search domains to use]" \
"($help)*--default-ulimit=-[Set default ulimit settings for containers]:ulimit: " \
+1
View File
@@ -5,6 +5,7 @@ After=network.target docker.socket
Requires=docker.socket
[Service]
Type=notify
ExecStart=/usr/bin/docker daemon -H fd://
MountFlags=slave
LimitNOFILE=1048576
+1 -1
View File
@@ -11,7 +11,7 @@ import (
var (
defaultPidFile = "/var/run/docker.pid"
defaultGraph = "/var/lib/docker"
defaultExec = "native"
defaultExec = "clr"
)
// Config defines the configuration of a docker daemon.
-3
View File
@@ -66,9 +66,6 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
if err := daemon.mergeAndVerifyConfig(config, img); err != nil {
return nil, nil, err
}
if !config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled {
warnings = append(warnings, "IPv4 forwarding is disabled.")
}
if hostConfig == nil {
hostConfig = &runconfig.HostConfig{}
}
+1 -7
View File
@@ -35,7 +35,6 @@ import (
"github.com/docker/docker/pkg/truncindex"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/trust"
"github.com/docker/libnetwork"
"github.com/opencontainers/runc/libcontainer/netlink"
)
@@ -670,10 +669,6 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
if err := system.MkdirAll(trustDir, 0700); err != nil && !os.IsExist(err) {
return nil, err
}
trustService, err := trust.NewTrustStore(trustDir)
if err != nil {
return nil, fmt.Errorf("could not create trust store: %s", err)
}
eventsService := events.New()
logrus.Debug("Creating repository list")
@@ -682,7 +677,6 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
Key: trustKey,
Registry: registryService,
Events: eventsService,
Trust: trustService,
}
repositories, err := graph.NewTagStore(filepath.Join(config.Root, "repositories-"+d.driver.String()), tagCfg)
if err != nil {
@@ -979,7 +973,7 @@ func getDefaultRouteMtu() (int, error) {
return 0, err
}
for _, r := range routes {
if r.Default {
if r.Default && r.Iface != nil {
return r.Iface.MTU, nil
}
}
+721
View File
@@ -0,0 +1,721 @@
// +build linux
package clr
import (
"fmt"
"io"
"io/ioutil"
"math"
"net/http"
"os"
"os/exec"
"net"
"path"
"strconv"
"strings"
"sync"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/pkg/mount"
sysinfo "github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/term"
"github.com/docker/libnetwork/netlabel"
"github.com/kr/pty"
"github.com/opencontainers/runc/libcontainer/configs"
)
const (
// Clear Linux for Intel(R) Architecture
driverName = "clr"
envVarPrefix = "CLR_"
// Command used for lkvm control
lkvmName = "lkvm"
// local "latest" information
clrFile = "latest"
// upstream base URL
clrURL = "https://download.clearlinux.org"
// upstream latest release file
latestFile = "https://download.clearlinux.org/latest"
// clr kernel (not bzimage)
clrKernel = "/usr/lib/kernel/vmlinux.container"
)
type driver struct {
root string // root path for the driver to use
libPath string
initPath string
version string
apparmor bool
sharedRoot bool
activeContainers map[string]*activeContainer
machineMemory int64
containerPid int
sync.Mutex
}
type activeContainer struct {
container *configs.Config
cmd *exec.Cmd
}
func getTapIf(c *execdriver.Command) string {
return fmt.Sprintf("tb-%s", c.ID[:12])
}
func getClrVersion(libPath string) string {
txt, err := ioutil.ReadFile(path.Join(libPath, clrFile))
if err != nil {
return ""
}
return strings.Split(string(txt), "\n")[0]
}
func fetchLatest(libPath string) error {
out, err := os.Create(path.Join(libPath, clrFile))
if err != nil {
return err
}
defer out.Close()
resp, err := http.Get(latestFile)
if err != nil {
return err
}
defer resp.Body.Close()
_, err = io.Copy(out, resp.Body)
return err
}
func fetchImage(version, libPath string) error {
// TODO: Add checksum validation
outfile := fmt.Sprintf("clear-%s-containers.img.xz", version)
url := fmt.Sprintf("%s/releases/%s/clear/%s", clrURL, version, outfile)
outpath := path.Join(libPath, outfile)
var output []byte
logrus.Debugf("Fetching clr version: %s, %s", version, outpath)
out, err := os.Create(outpath)
if err != nil {
return err
}
defer out.Close()
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
// Consider progress feedback ?
_, err = io.Copy(out, resp.Body)
if err != nil {
return err
}
// decompress the file
cmd := exec.Command("unxz", outpath)
cmd.Dir = libPath
if output, err = cmd.CombinedOutput(); err != nil {
logrus.Debugf("Unable to extract image %s: %s", version, output)
return err
}
return nil
}
// NewDriver creates a new clear linux execution driver.
func NewDriver(root, libPath, initPath string, apparmor bool) (*driver, error) {
if err := os.MkdirAll(root, 0700); err != nil {
return nil, err
}
meminfo, err := sysinfo.ReadMemInfo()
if err != nil {
return nil, err
}
version, err := prepareClr(libPath)
if err != nil {
return nil, err
}
return &driver{
apparmor: apparmor,
root: root,
libPath: libPath,
initPath: initPath,
version: version,
sharedRoot: false,
activeContainers: make(map[string]*activeContainer),
// FIXME:
machineMemory: meminfo.MemTotal,
}, nil
}
func prepareClr(libPath string) (string, error) {
var version = getClrVersion(libPath)
var nversion string
logrus.Debugf("%s preparing environment", driverName)
err := fetchLatest(libPath)
if err != nil {
return "", err
}
nversion = getClrVersion(libPath)
if nversion != version && version != "" {
logrus.Debugf("Updating to clr version: %s", nversion)
err = fetchImage(nversion, libPath)
} else if version == "" {
logrus.Debugf("Installing clr version: %s", nversion)
err = fetchImage(nversion, libPath)
} else {
logrus.Debugf("Using clr version: %s", nversion)
}
return nversion, nil
}
func (d *driver) Name() string {
return fmt.Sprintf("%s-%s", driverName, d.version)
}
func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
var (
term execdriver.Terminal
err error
)
container, err := d.createContainer(c)
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
memoryMiB := c.HostConfig.Memory
if memoryMiB == 0 {
memoryMiB = 1024
} else {
// docker passes the value as bytes
memoryMiB = memoryMiB / int64(math.Pow(2, 20))
}
workingDirVar := fmt.Sprintf("%s%s=%q", envVarPrefix, "WORKINGDIR", c.WorkingDir)
c.ProcessConfig.Cmd.Env = append(c.ProcessConfig.Cmd.Env, workingDirVar)
userVar := fmt.Sprintf("%s%s=%q", envVarPrefix, "USER", c.ProcessConfig.User)
c.ProcessConfig.Cmd.Env = append(c.ProcessConfig.Cmd.Env, userVar)
if err := d.setupNetwork(c); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
if c.ProcessConfig.Tty {
term, err = NewTtyConsole(&c.ProcessConfig, pipes)
} else {
term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes)
}
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
c.ProcessConfig.Terminal = term
d.Lock()
d.activeContainers[c.ID] = &activeContainer{
container: container,
cmd: &c.ProcessConfig.Cmd,
}
d.Unlock()
if err := d.generateEnvConfig(c); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
if err := d.generateDockerInit(c); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
for _, m := range c.Mounts {
dest := path.Join(c.Rootfs, m.Destination)
if m.Destination == "/etc/hostname" {
continue
}
if !pathExists(m.Source) {
continue
}
opts := "bind"
if m.Private {
opts = opts + ",rprivate"
}
if m.Slave {
opts = opts + ",rslave"
}
// This may look racy, but it isn't since the VM isn't
// running yet.
//
// The check is necessary to handle bind mounting of
// regular files correctly since without it we may be
// attempting to create a directory where there already
// exists a normal file.
if !pathExists(dest) {
if err := os.MkdirAll(dest, 0750); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
}
if err := mount.Mount(m.Source, dest, "", opts); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
if !m.Writable {
if err := mount.Mount("", dest, "", "bind,remount,ro"); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
}
defer mount.Unmount(dest)
}
var args []string
// various things for lkvm
ifname := getTapIf(c)
// FIXME: Should be real hostname from like process/container struct
hostname := c.ID[0:12]
img := fmt.Sprintf("%s/clear-%s-containers.img", d.libPath, d.version)
memory := fmt.Sprintf("%d", memoryMiB)
// FIXME: Locked cores to 6 ?
cores := fmt.Sprintf("%d", 6)
ipaddr := c.NetworkSettings.IPAddress
gateway := c.NetworkSettings.Gateway
macaddr := c.NetworkSettings.MacAddress
args = append(args, c.ProcessConfig.Entrypoint)
args = append(args, c.ProcessConfig.Arguments...)
rootParams := fmt.Sprintf("root=/dev/plkvm0p1 rootfstype=ext4 rootflags=dax,data=ordered "+
"init=/usr/lib/systemd/systemd systemd.unit=container.target rw tsc=reliable "+
"systemd.show_status=false "+
"no_timer_check rcupdate.rcu_expedited=1 console=hvc0 quiet ip=%s::%s::%s::off",
ipaddr, gateway, hostname)
params := []string{
lkvmName, "run", "-c", cores, "-m", memory,
"--name", c.ID, "--console", "virtio",
"--kernel", clrKernel,
"--params", rootParams,
"--shmem", fmt.Sprintf("0x200000000:0:file=%s:private", img),
"--network", fmt.Sprintf("mode=tap,script=none,tapif=%s,guest_mac=%s", ifname, macaddr),
"--9p", fmt.Sprintf("%s,rootfs", c.Rootfs),
}
logrus.Debugf("%s params %s", driverName, params)
var (
name = params[0]
arg = params[1:]
)
aname, err := exec.LookPath(name)
if err != nil {
aname = name
}
c.ProcessConfig.Path = aname
c.ProcessConfig.Args = append([]string{name}, arg...)
c.ProcessConfig.Env = []string{fmt.Sprintf("HOME=%s", d.root)}
// Start the container. Since it runs synchronously, we don't Wait()
// for it since we need to check the status to determine if it did
// actually start successfully.
if err := c.ProcessConfig.Start(); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
var (
waitErr error
waitLock = make(chan struct{})
)
go func() {
if err := c.ProcessConfig.Wait(); err != nil {
if _, ok := err.(*exec.ExitError); !ok { // Do not propagate the error if it's simply a status code != 0
waitErr = err
}
}
close(waitLock)
}()
// FIXME: need to create state.json for Stats() to work.
c.ContainerPid = c.ProcessConfig.Process.Pid
d.containerPid = c.ProcessConfig.Process.Pid
if startCallback != nil {
logrus.Debugf("Invoking startCallback")
startCallback(&c.ProcessConfig, c.ProcessConfig.Process.Pid)
}
// FIXME:
oomKill := false
// Wait for the VM to shutdown
<-waitLock
exitCode := getExitCode(c)
cExitStatus, cerr := d.cleanupVM(c)
if cerr != nil {
waitErr = cerr
exitCode = cExitStatus
}
// check oom error
if oomKill {
exitCode = 137
}
return execdriver.ExitStatus{ExitCode: exitCode, OOMKilled: false}, waitErr
}
func pathExists(path string) bool {
if _, err := os.Stat(path); err == nil {
return true
}
return false
}
func pathExecutable(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
mode := s.Mode()
if mode&0111 != 0 {
return true
}
return false
}
func (d *driver) cleanupVM(c *execdriver.Command) (exitStatus int, err error) {
cmd := exec.Command("ip", "tuntap", "del", "dev", getTapIf(c), "mode", "tap")
var output []byte
if output, err = cmd.CombinedOutput(); err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
waitStatus := exitError.Sys().(syscall.WaitStatus)
exitStatus = waitStatus.ExitStatus()
}
logrus.Debugf("teardown failed for vm %s: %s (%s)", c.ID, string(output), err.Error())
}
// doesn't matter if this fails
// lkvm could have removed it, and stale sockets are not fatal
_ = os.Remove(fmt.Sprintf("%s/.lkvm/%s.sock", d.root, c.ID))
return exitStatus, err
}
// createContainer populates and configures the container type with the
// data provided by the execdriver.Command
func (d *driver) createContainer(c *execdriver.Command) (*configs.Config, error) {
return execdriver.InitContainer(c), nil
}
/// Return the exit code of the process
// if the process has not exited -1 will be returned
func getExitCode(c *execdriver.Command) int {
if c.ProcessConfig.ProcessState == nil {
return -1
}
return c.ProcessConfig.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
}
func (d *driver) lkvmCommand(c *execdriver.Command, arg string) ([]byte, error) {
args := append([]string{lkvmName}, arg)
if c != nil {
args = append(args, "--name", c.ID)
}
cmd := exec.Command(lkvmName, args...)
cmd.Env = []string{fmt.Sprintf("HOME=%s", d.root)}
return cmd.Output()
}
// Kill sends a signal to workload
func (d *driver) Kill(c *execdriver.Command, sig int) error {
// Not supported
return nil
}
func (d *driver) Pause(c *execdriver.Command) error {
_, err := d.lkvmCommand(c, "pause")
return err
}
func (d *driver) Unpause(c *execdriver.Command) error {
_, err := d.lkvmCommand(c, "resume")
return err
}
// Terminate forcibly stops a container
func (d *driver) Terminate(c *execdriver.Command) error {
_, err := d.lkvmCommand(c, "stop")
return err
}
func (d *driver) containerDir(containerID string) string {
return path.Join(d.libPath, "containers", containerID)
}
// isDigit returns true if s can be represented as an integer
func isDigit(s string) bool {
if _, err := strconv.Atoi(s); err == nil {
return true
}
return false
}
func (d *driver) getInfo(id string) ([]byte, error) {
output, err := d.lkvmCommand(nil, "list")
if err != nil {
return nil, err
}
for i, line := range strings.Split(string(output), "\n") {
if i < 2 {
continue
}
fields := strings.Fields(strings.TrimSpace(line))
if len(fields) != 3 {
continue
}
if !isDigit(fields[0]) {
continue
}
if fields[1] != id {
continue
}
return []byte(line), nil
}
return []byte(fmt.Sprintf("-1 %s stopped", id)), nil
}
type info struct {
ID string
driver *driver
}
func (i *info) IsRunning() bool {
output, err := i.driver.getInfo(i.ID)
if err != nil {
logrus.Errorf("Error getting info for %s container %s: %s (%s)",
driverName, i.ID, err, output)
return false
}
clrInfo, err := parseClrInfo(i.ID, string(output))
if err != nil {
return false
}
return clrInfo.Running
}
func (d *driver) Info(id string) execdriver.Info {
return &info{
ID: id,
driver: d,
}
}
func (d *driver) GetPidsForContainer(id string) ([]int, error) {
// The VM doesn't expose the worload pid(s), so the only meaningful
// pid is that of the VM
return []int{d.containerPid}, nil
}
// TtyConsole is a type to represent a pseud-oterminal (see pty(7))
type TtyConsole struct {
MasterPty *os.File
SlavePty *os.File
}
// NewTtyConsole returns a new TtyConsole object.
func NewTtyConsole(processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes) (*TtyConsole, error) {
// lxc is special in that we cannot create the master outside of the container without
// opening the slave because we have nothing to provide to the cmd. We have to open both then do
// the crazy setup on command right now instead of passing the console path to lxc and telling it
// to open up that console. we save a couple of openfiles in the native driver because we can do
// this.
ptyMaster, ptySlave, err := pty.Open()
if err != nil {
return nil, err
}
tty := &TtyConsole{
MasterPty: ptyMaster,
SlavePty: ptySlave,
}
if err := tty.AttachPipes(&processConfig.Cmd, pipes); err != nil {
tty.Close()
return nil, err
}
processConfig.Console = tty.SlavePty.Name()
return tty, nil
}
// Master returns the master end of the pty
func (t *TtyConsole) Master() *os.File {
return t.MasterPty
}
// Resize modifies the size of the pty terminal being used.
func (t *TtyConsole) Resize(h, w int) error {
return term.SetWinsize(t.MasterPty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
}
// AttachPipes associates the specified pipes with the pty master.
func (t *TtyConsole) AttachPipes(command *exec.Cmd, pipes *execdriver.Pipes) error {
command.Stdout = t.SlavePty
command.Stderr = t.SlavePty
go func() {
if wb, ok := pipes.Stdout.(interface {
CloseWriters() error
}); ok {
defer wb.CloseWriters()
}
io.Copy(pipes.Stdout, t.MasterPty)
}()
if pipes.Stdin != nil {
command.Stdin = t.SlavePty
command.SysProcAttr.Setctty = true
go func() {
io.Copy(t.MasterPty, pipes.Stdin)
pipes.Stdin.Close()
}()
}
return nil
}
// Close closes both ends of the pty.
func (t *TtyConsole) Close() error {
t.SlavePty.Close()
return t.MasterPty.Close()
}
func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
return -1, fmt.Errorf("Unsupported: Exec is not supported by the %q driver", driverName)
}
// Clean up after an Exec
func (d *driver) Clean(id string) error {
return nil
}
func (d *driver) generateEnvConfig(c *execdriver.Command) error {
data := []byte(strings.Join(c.ProcessConfig.Env, "\n"))
p := path.Join(d.libPath, "containers", c.ID, "config.env")
c.Mounts = append(c.Mounts, execdriver.Mount{
Source: p,
Destination: "/.dockerenv",
Writable: false,
Private: true,
})
return ioutil.WriteFile(p, data, 0600)
}
func (d *driver) generateDockerInit(c *execdriver.Command) error {
p := fmt.Sprintf("%s/.containerexec", c.Rootfs)
var args []string
if pathExecutable(p) {
return nil
}
args = append(args, c.ProcessConfig.Entrypoint)
args = append(args, c.ProcessConfig.Arguments...)
data := []byte(fmt.Sprintf("#!/bin/sh\n%s\n", strings.Join(args, " ")))
return ioutil.WriteFile(p, data, 0755)
}
func (d *driver) setupNetwork(c *execdriver.Command) error {
ifname := getTapIf(c)
var bridgeName string
var bridgeLinkName string
var output []byte
var err error
for _, info := range c.EndpointInfo {
if mac, ok := info[netlabel.MacAddress].(net.HardwareAddr); ok {
if mac.String() == c.NetworkSettings.MacAddress {
bridgeName = info[netlabel.BridgeName].(string)
bridgeLinkName = info[netlabel.BridgeLinkName].(string)
}
}
}
// Strip existing veth
cmd := exec.Command("ip", "link", "del", bridgeLinkName)
if output, err = cmd.CombinedOutput(); err != nil {
logrus.Debugf("%s setupNetwork error: %v, %s", driverName, cmd.Args, output)
return err
}
cmd = exec.Command("ip", "tuntap", "add", "dev", ifname, "mode", "tap", "vnet_hdr")
if output, err = cmd.CombinedOutput(); err != nil {
logrus.Debugf("%s setupNetwork error: %v, %s", driverName, cmd.Args, output)
return err
}
cmd = exec.Command("ip", "link", "set", "dev", ifname, "master", bridgeName)
if output, err = cmd.CombinedOutput(); err != nil {
logrus.Debugf("%s setupNetwork error: %v, %s", driverName, cmd.Args, output)
return err
}
cmd = exec.Command("ip", "link", "set", "dev", ifname, "up")
if output, err = cmd.CombinedOutput(); err != nil {
logrus.Debugf("%s setupNetwork error: %v, %s", driverName, cmd.Args, output)
return err
}
return err
}
func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
if _, ok := d.activeContainers[id]; !ok {
return nil, fmt.Errorf("%s is not a key in active containers", id)
}
// FIXME:
return execdriver.Stats(d.containerDir(id), d.activeContainers[id].container.Cgroups.Memory, d.machineMemory)
}
+51
View File
@@ -0,0 +1,51 @@
// +build linux
package clr
import (
"errors"
"strconv"
"strings"
)
var (
ErrCannotParse = errors.New("cannot parse raw input")
)
type clrInfo struct {
Running bool
Pid int
}
func parseClrInfo(name, raw string) (*clrInfo, error) {
if raw == "" {
return nil, ErrCannotParse
}
var (
err error
info = &clrInfo{}
)
fields := strings.Fields(strings.TrimSpace(raw))
// The format is expected to be:
//
// <pid> <name> <state>
//
if len(fields) != 3 {
return nil, ErrCannotParse
}
info.Pid, err = strconv.Atoi(fields[0])
if err != nil {
return nil, ErrCannotParse
}
if fields[1] != name {
return nil, ErrCannotParse
}
info.Running = fields[2] == "running"
return info, nil
}
+97
View File
@@ -0,0 +1,97 @@
// +build linux
package clr
import (
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"syscall"
)
// Args provided to the init function for a driver
type InitArgs struct {
User string
Gateway string
Ip string
WorkDir string
Privileged bool
Env []string
Args []string
Mtu int
Console string
Pipe int
Root string
CapAdd string
CapDrop string
}
func getArgs() *InitArgs {
var (
// Get cmdline arguments
user = flag.String("u", "", "username or uid")
gateway = flag.String("g", "", "gateway address")
ip = flag.String("i", "", "ip address")
workDir = flag.String("w", "", "workdir")
privileged = flag.Bool("privileged", false, "privileged mode")
mtu = flag.Int("mtu", 1500, "interface mtu")
capAdd = flag.String("cap-add", "", "capabilities to add")
capDrop = flag.String("cap-drop", "", "capabilities to drop")
)
flag.Parse()
return &InitArgs{
User: *user,
Gateway: *gateway,
Ip: *ip,
WorkDir: *workDir,
Privileged: *privileged,
Args: flag.Args(),
Mtu: *mtu,
CapAdd: *capAdd,
CapDrop: *capDrop,
}
}
// Clear environment pollution introduced by lxc-start
func setupEnv(args *InitArgs) error {
// Get env
var env []string
dockerenv, err := os.Open(".dockerenv")
if err != nil {
return fmt.Errorf("Unable to load environment variables: %v", err)
}
defer dockerenv.Close()
if err := json.NewDecoder(dockerenv).Decode(&env); err != nil {
return fmt.Errorf("Unable to decode environment variables: %v", err)
}
// Propagate the plugin-specific container env variable
env = append(env, "container="+os.Getenv("container"))
args.Env = env
os.Clearenv()
for _, kv := range args.Env {
parts := strings.SplitN(kv, "=", 2)
if len(parts) == 1 {
parts = append(parts, "")
}
os.Setenv(parts[0], parts[1])
}
return nil
}
// Setup working directory
func setupWorkingDirectory(args *InitArgs) error {
if args.WorkDir == "" {
return nil
}
if err := syscall.Chdir(args.WorkDir); err != nil {
return fmt.Errorf("Unable to change dir to %v: %v", args.WorkDir, err)
}
return nil
}
+32 -27
View File
@@ -7,7 +7,9 @@ import (
"time"
// TODO Windows: Factor out ulimit
"github.com/docker/docker/daemon/network"
"github.com/docker/docker/pkg/ulimit"
"github.com/docker/docker/runconfig"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
)
@@ -150,31 +152,34 @@ type ProcessConfig struct {
//
// Process wrapps an os/exec.Cmd to add more metadata
type Command struct {
ID string `json:"id"`
Rootfs string `json:"rootfs"` // root fs of the container
ReadonlyRootfs bool `json:"readonly_rootfs"`
InitPath string `json:"initpath"` // dockerinit
WorkingDir string `json:"working_dir"`
ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver
Network *Network `json:"network"`
Ipc *Ipc `json:"ipc"`
Pid *Pid `json:"pid"`
UTS *UTS `json:"uts"`
Resources *Resources `json:"resources"`
Mounts []Mount `json:"mounts"`
AllowedDevices []*configs.Device `json:"allowed_devices"`
AutoCreatedDevices []*configs.Device `json:"autocreated_devices"`
CapAdd []string `json:"cap_add"`
CapDrop []string `json:"cap_drop"`
GroupAdd []string `json:"group_add"`
ContainerPid int `json:"container_pid"` // the pid for the process inside a container
ProcessConfig ProcessConfig `json:"process_config"` // Describes the init process of the container.
ProcessLabel string `json:"process_label"`
MountLabel string `json:"mount_label"`
LxcConfig []string `json:"lxc_config"`
AppArmorProfile string `json:"apparmor_profile"`
CgroupParent string `json:"cgroup_parent"` // The parent cgroup for this command.
FirstStart bool `json:"first_start"`
LayerPaths []string `json:"layer_paths"` // Windows needs to know the layer paths and folder for a command
LayerFolder string `json:"layer_folder"`
ID string `json:"id"`
Rootfs string `json:"rootfs"` // root fs of the container
ReadonlyRootfs bool `json:"readonly_rootfs"`
InitPath string `json:"initpath"` // dockerinit
WorkingDir string `json:"working_dir"`
ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver
Network *Network `json:"network"`
Ipc *Ipc `json:"ipc"`
Pid *Pid `json:"pid"`
UTS *UTS `json:"uts"`
Resources *Resources `json:"resources"`
Mounts []Mount `json:"mounts"`
AllowedDevices []*configs.Device `json:"allowed_devices"`
AutoCreatedDevices []*configs.Device `json:"autocreated_devices"`
CapAdd []string `json:"cap_add"`
CapDrop []string `json:"cap_drop"`
GroupAdd []string `json:"group_add"`
ContainerPid int `json:"container_pid"` // the pid for the process inside a container
ProcessConfig ProcessConfig `json:"process_config"` // Describes the init process of the container.
ProcessLabel string `json:"process_label"`
MountLabel string `json:"mount_label"`
LxcConfig []string `json:"lxc_config"`
AppArmorProfile string `json:"apparmor_profile"`
CgroupParent string `json:"cgroup_parent"` // The parent cgroup for this command.
FirstStart bool `json:"first_start"`
LayerPaths []string `json:"layer_paths"` // Windows needs to know the layer paths and folder for a command
LayerFolder string `json:"layer_folder"`
NetworkSettings *network.Settings `json:"network_settings"`
EndpointInfo []map[string]interface{} `json:"endpoint_info"`
HostConfig *runconfig.HostConfig
}
@@ -8,13 +8,17 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/execdriver/clr"
"github.com/docker/docker/daemon/execdriver/lxc"
"github.com/docker/docker/daemon/execdriver/native"
"github.com/docker/docker/pkg/sysinfo"
)
func NewDriver(name string, options []string, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
rootPath := path.Join(root, "execdriver", name)
switch name {
case "clr":
return clr.NewDriver(rootPath, libPath, initPath, sysInfo.AppArmor)
case "lxc":
// we want to give the lxc driver the full docker root because it needs
// to access and write config and template files in /var/lib/docker/containers/*
@@ -22,7 +26,7 @@ func NewDriver(name string, options []string, root, libPath, initPath string, sy
logrus.Warn("LXC built-in support is deprecated.")
return lxc.NewDriver(root, libPath, initPath, sysInfo.AppArmor)
case "native":
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath, options)
return native.NewDriver(rootPath, initPath, options)
}
return nil, fmt.Errorf("unknown exec driver %s", name)
}
+1 -20
View File
@@ -40,20 +40,16 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
file,
umount,
signal (receive) peer=/usr/bin/docker,
signal (receive) peer=docker-unconfined,
deny @{PROC}/sys/fs/** wklx,
deny @{PROC}/fs/** wklx,
deny @{PROC}/sysrq-trigger rwklx,
deny @{PROC}/mem rwklx,
deny @{PROC}/kmem rwklx,
deny @{PROC}/kore rwklx,
deny @{PROC}/kcore rwklx,
deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx,
deny @{PROC}/sys/kernel/*/** wklx,
deny mount,
deny ptrace (trace) peer=docker-default,
deny /sys/[^f]*/** wklx,
deny /sys/f[^s]*/** wklx,
@@ -63,21 +59,6 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
deny /sys/firmware/efi/efivars/** rwklx,
deny /sys/kernel/security/** rwklx,
}
profile docker-unconfined flags=(attach_disconnected,mediate_deleted,complain) {
#include <abstractions/base>
network,
capability,
file,
umount,
mount,
pivot_root,
change_profile -> *,
ptrace,
signal,
}
`
func generateProfile(out io.Writer) error {
+1 -2
View File
@@ -198,9 +198,8 @@ func (d *driver) setPrivileged(container *configs.Config) (err error) {
container.Devices = hostDevices
if apparmor.IsEnabled() {
container.AppArmorProfile = "docker-unconfined"
container.AppArmorProfile = "unconfined"
}
return nil
}
+2 -2
View File
@@ -54,11 +54,11 @@ func NewDriver(root, initPath string, options []string) (*driver, error) {
if apparmor.IsEnabled() {
if err := installAppArmorProfile(); err != nil {
apparmor_profiles := []string{"docker-default", "docker-unconfined"}
apparmorProfiles := []string{"docker-default"}
// Allow daemon to run if loading failed, but are active
// (possibly through another run, manually, or via system startup)
for _, policy := range apparmor_profiles {
for _, policy := range apparmorProfiles {
if err := hasAppArmorProfileLoaded(policy); err != nil {
return nil, fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy)
}
+9 -5
View File
@@ -1482,12 +1482,16 @@ func (devices *DeviceSet) deactivatePool() error {
if err != nil {
return err
}
if d, err := devicemapper.GetDeps(devname); err == nil {
// Access to more Debug output
logrus.Debugf("[devmapper] devicemapper.GetDeps() %s: %#v", devname, d)
if devinfo.Exists == 0 {
return nil
}
if devinfo.Exists != 0 {
return devicemapper.RemoveDevice(devname)
if err := devicemapper.RemoveDevice(devname); err != nil {
return err
}
if d, err := devicemapper.GetDeps(devname); err == nil {
logrus.Warnf("[devmapper] device %s still has %d active dependents", devname, d.Count)
}
return nil
+24
View File
@@ -138,6 +138,30 @@ func (m *containerMonitor) Start() error {
m.lastStartTime = time.Now()
// Make the network settings available to the execution
// driver to allow for integration with libnetwork networking.
m.container.command.NetworkSettings = m.container.NetworkSettings
// Allow the execution driver to query memory limits
m.container.command.HostConfig = m.container.hostConfig
// Make the network endpoint details available to the execution
// driver as well.
n, _err := m.container.daemon.netController.NetworkByID(m.container.NetworkSettings.NetworkID)
if _err == nil {
var eps []map[string]interface{}
for _, ep := range n.Endpoints() {
info, err := ep.DriverInfo()
if err != nil {
continue
}
eps = append(eps, info)
}
m.container.command.EndpointInfo = eps
}
if exitStatus, err = m.container.daemon.Run(m.container, pipes, m.callback); err != nil {
// if we receive an internal error from the initial start of a container then lets
// return it instead of entering the restart loop
+1 -4
View File
@@ -76,6 +76,7 @@ func NewDaemonCli() *DaemonCli {
// TODO(tiborvass): remove InstallFlags?
daemonConfig := new(daemon.Config)
daemonConfig.LogConfig.Config = make(map[string]string)
daemonConfig.InstallFlags(daemonFlags, presentInHelp)
daemonConfig.InstallFlags(flag.CommandLine, absentFromHelp)
registryOptions := new(registry.Options)
@@ -208,10 +209,6 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
}()
}
if cli.LogConfig.Config == nil {
cli.LogConfig.Config = make(map[string]string)
}
serverConfig := &apiserver.ServerConfig{
Logging: true,
EnableCors: cli.EnableCors,
+1 -1
View File
@@ -28,7 +28,7 @@ func main() {
flag.Merge(flag.CommandLine, clientFlags.FlagSet, commonFlags.FlagSet)
flag.Usage = func() {
fmt.Fprint(os.Stdout, "Usage: docker [OPTIONS] COMMAND [arg...]\n"+daemonUsage+" docker [ -h | --help | -v | --version ]\n\n")
fmt.Fprint(os.Stdout, "Usage: docker [OPTIONS] COMMAND [arg...]\n"+daemonUsage+" docker [ --help | -v | --version ]\n\n")
fmt.Fprint(os.Stdout, "A self-sufficient runtime for containers.\n\nOptions:\n")
flag.CommandLine.SetOutput(os.Stdout)
+8 -6
View File
@@ -87,8 +87,8 @@ own.
container with this image.
The container exposes port 8000 on the localhost so that you can connect and
see your changes. If you are running Boot2Docker, use the `boot2docker ip`
to get the address of your server.
see your changes. If you use Docker Machine, the `docker-machine ip
<machine-name>` command gives you the address of your server.
6. Check your writing for style and mechanical errors.
@@ -158,18 +158,20 @@ update the root docs pages by running
$ make AWS_S3_BUCKET=dowideit-docs BUILD_ROOT=yes docs-release
### Errors publishing using Boot2Docker
### Errors publishing using a Docker Machine VM
Sometimes, in a Boot2Docker environment, the publishing procedure returns this
Sometimes, in a Windows or Mac environment, the publishing procedure returns this
error:
Post http:///var/run/docker.sock/build?rm=1&t=docker-docs%3Apost-1.2.0-docs_update-2:
dial unix /var/run/docker.sock: no such file or directory.
If this happens, set the Docker host. Run the following command to set the
If this happens, set the Docker host. Run the following command to get the
variables in your shell:
$ eval "$(boot2docker shellinit)"
docker-machine env <machine-name>
Then, set your environment accordingly.
## Cherry-picking documentation changes to update an existing release.
-4
View File
@@ -47,10 +47,6 @@ image cache.
> characters of the full image ID - which can be found using
> `docker inspect` or `docker images --no-trunc=true`.
> **Note:** if you are using a remote Docker daemon, such as Boot2Docker,
> then _do not_ type the `sudo` before the `docker` commands shown in the
> documentation's examples.
## Running an interactive shell
To run an interactive shell in the Ubuntu image:
+4 -108
View File
@@ -11,111 +11,7 @@ weight = 7
# Using certificates for repository client verification
In [Running Docker with HTTPS](/articles/https), you learned that, by default,
Docker runs via a non-networked Unix socket and TLS must be enabled in order
to have the Docker client and the daemon communicate securely over HTTPS.
Now, you will see how to allow the Docker registry (i.e., *a server*) to
verify that the Docker daemon (i.e., *a client*) has the right to access the
images being hosted with *certificate-based client-server authentication*.
We will show you how to install a Certificate Authority (CA) root certificate
for the registry and how to set the client TLS certificate for verification.
## Understanding the configuration
A custom certificate is configured by creating a directory under
`/etc/docker/certs.d` using the same name as the registry's hostname (e.g.,
`localhost`). All `*.crt` files are added to this directory as CA roots.
> **Note:**
> In the absence of any root certificate authorities, Docker
> will use the system default (i.e., host's root CA set).
The presence of one or more `<filename>.key/cert` pairs indicates to Docker
that there are custom certificates required for access to the desired
repository.
> **Note:**
> If there are multiple certificates, each will be tried in alphabetical
> order. If there is an authentication error (e.g., 403, 404, 5xx, etc.), Docker
> will continue to try with the next certificate.
Our example is set up like this:
/etc/docker/certs.d/ <-- Certificate directory
└── localhost <-- Hostname
├── client.cert <-- Client certificate
├── client.key <-- Client key
└── localhost.crt <-- Registry certificate
## Creating the client certificates
You will use OpenSSL's `genrsa` and `req` commands to first generate an RSA
key and then use the key to create the certificate.
$ openssl genrsa -out client.key 4096
$ openssl req -new -x509 -text -key client.key -out client.cert
> **Warning:**:
> Using TLS and managing a CA is an advanced topic.
> You should be familiar with OpenSSL, x509, and TLS before
> attempting to use them in production.
> **Warning:**
> These TLS commands will only generate a working set of certificates on Linux.
> The version of OpenSSL in Mac OS X is incompatible with the type of
> certificate Docker requires.
## Testing the verification setup
You can test this setup by using Apache to host a Docker registry.
For this purpose, you can copy a registry tree (containing images) inside
the Apache root.
> **Note:**
> You can find such an example [here](
> http://people.gnome.org/~alexl/v1.tar.gz) - which contains the busybox image.
Once you set up the registry, you can use the following Apache configuration
to implement certificate-based protection.
# This must be in the root context, otherwise it causes a re-negotiation
# which is not supported by the TLS implementation in go
SSLVerifyClient optional_no_ca
<Location /v1>
Action cert-protected /cgi-bin/cert.cgi
SetHandler cert-protected
Header set x-docker-registry-version "0.6.2"
SetEnvIf Host (.*) custom_host=$1
Header set X-Docker-Endpoints "%{custom_host}e"
</Location>
Save the above content as `/etc/httpd/conf.d/registry.conf`, and
continue with creating a `cert.cgi` file under `/var/www/cgi-bin/`.
#!/bin/bash
if [ "$HTTPS" != "on" ]; then
echo "Status: 403 Not using SSL"
echo "x-docker-registry-version: 0.6.2"
echo
exit 0
fi
if [ "$SSL_CLIENT_VERIFY" == "NONE" ]; then
echo "Status: 403 Client certificate invalid"
echo "x-docker-registry-version: 0.6.2"
echo
exit 0
fi
echo "Content-length: $(stat --printf='%s' $PATH_TRANSLATED)"
echo "x-docker-registry-version: 0.6.2"
echo "X-Docker-Endpoints: $SERVER_NAME"
echo "X-Docker-Size: 0"
echo
cat $PATH_TRANSLATED
This CGI script will ensure that all requests to `/v1` *without* a valid
certificate will be returned with a `403` (i.e., HTTP forbidden) error.
The orginal content was deprecated. For information about configuring
cerficates, see [deploying a registry
server](http://docs.docker.com/registry/deploying/). To reach an older version
of this content, refer to an older version of the documentation.
+1 -1
View File
@@ -58,7 +58,7 @@ First generate CA private and public keys:
State or Province Name (full name) [Some-State]:Queensland
Locality Name (eg, city) []:Brisbane
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Docker Inc
Organizational Unit Name (eg, section) []:Boot2Docker
Organizational Unit Name (eg, section) []:Sales
Common Name (e.g. server FQDN or YOUR name) []:$HOST
Email Address []:Sven@home.org.au
+5 -78
View File
@@ -11,81 +11,8 @@ weight = 8
# Run a local registry mirror
## Why?
If you have multiple instances of Docker running in your environment
(e.g., multiple physical or virtual machines, all running the Docker
daemon), each time one of them requires an image that it doesn't have
it will go out to the internet and fetch it from the public Docker
registry. By running a local registry mirror, you can keep most of the
image fetch traffic on your local network.
## How does it work?
The first time you request an image from your local registry mirror,
it pulls the image from the public Docker registry and stores it locally
before handing it back to you. On subsequent requests, the local registry
mirror is able to serve the image from its own storage.
## How do I set up a local registry mirror?
There are two steps to set up and use a local registry mirror.
### Step 1: Configure your Docker daemons to use the local registry mirror
You will need to pass the `--registry-mirror` option to your Docker daemon on
startup:
docker daemon --registry-mirror=http://<my-docker-mirror-host>
For example, if your mirror is serving on `http://10.0.0.2:5000`, you would run:
docker daemon --registry-mirror=http://10.0.0.2:5000
**NOTE:**
Depending on your local host setup, you may be able to add the
`--registry-mirror` options to the `DOCKER_OPTS` variable in
`/etc/default/docker`.
### Step 2: Run the local registry mirror
You will need to start a local registry mirror service. The
[`registry` image](https://registry.hub.docker.com/_/registry/) provides this
functionality. For example, to run a local registry mirror that serves on
port `5000` and mirrors the content at `registry-1.docker.io`:
docker run -p 5000:5000 \
-e STANDALONE=false \
-e MIRROR_SOURCE=https://registry-1.docker.io \
-e MIRROR_SOURCE_INDEX=https://index.docker.io \
registry
## Test it out
With your mirror running, pull an image that you haven't pulled before (using
`time` to time it):
$ time docker pull node:latest
Pulling repository node
[...]
real 1m14.078s
user 0m0.176s
sys 0m0.120s
Now, remove the image from your local machine:
$ docker rmi node:latest
Finally, re-pull the image:
$ time docker pull node:latest
Pulling repository node
[...]
real 0m51.376s
user 0m0.120s
sys 0m0.116s
The second time around, the local registry mirror served the image from storage,
avoiding a trip out to the internet to refetch it.
The orginal content was deprecated. [An archived
version](https://docs.docker.com/v1.6/articles/registry_mirror) is available in
the 1.7 documentation. For information about configuring mirrors with the latest
Docker Registry version, please file a support request with [the Distribution
project](https://github.com/docker/distribution/issues).
+29 -11
View File
@@ -33,17 +33,33 @@ If you want Docker to start at boot, you should also:
There are a number of ways to configure the daemon flags and environment variables
for your Docker daemon.
If the `docker.service` file is set to use an `EnvironmentFile`
(often pointing to `/etc/sysconfig/docker`) then you can modify the
referenced file.
The recommended way is to use a systemd drop-in file. These are local files in
the `/etc/systemd/system/docker.service.d` directory. This could also be
`/etc/systemd/system/docker.service`, which also works for overriding the
defaults from `/lib/systemd/system/docker.service`.
Check if the `docker.service` uses an `EnvironmentFile`:
However, if you had previously used a package which had an `EnvironmentFile`
(often pointing to `/etc/sysconfig/docker`) then for backwards compatibility,
you drop a file in the `/etc/systemd/system/docker.service.d`
directory including the following:
[Service]
EnvironmentFile=-/etc/sysconfig/docker
EnvironmentFile=-/etc/sysconfig/docker-storage
EnvironmentFile=-/etc/sysconfig/docker-network
ExecStart=
ExecStart=/usr/bin/docker -d -H fd:// $OPTIONS \
$DOCKER_STORAGE_OPTIONS \
$DOCKER_NETWORK_OPTIONS \
$BLOCK_REGISTRY \
$INSECURE_REGISTRY
To check if the `docker.service` uses an `EnvironmentFile`:
$ sudo systemctl show docker | grep EnvironmentFile
EnvironmentFile=-/etc/sysconfig/docker (ignore_errors=yes)
Alternatively, find out where the service file is located, and look for the
property:
Alternatively, find out where the service file is located:
$ sudo systemctl status docker | grep Loaded
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled)
@@ -69,18 +85,20 @@ In this example, we'll assume that your `docker.service` file looks something li
[Service]
Type=notify
EnvironmentFile=-/etc/sysconfig/docker
ExecStart=/usr/bin/docker daemon -H fd:// $OPTIONS
ExecStart=/usr/bin/docker daemon -H fd://
LimitNOFILE=1048576
LimitNPROC=1048576
[Install]
Also=docker.socket
This will allow us to add extra flags to the `/etc/sysconfig/docker` file by
setting `OPTIONS`:
This will allow us to add extra flags via a drop-in file (mentioned above) by
placing a file containing the following in the `/etc/systemd/system/docker.service.d`
directory:
OPTIONS="--graph /mnt/docker-data --storage-driver btrfs"
[Service]
ExecStart=
ExecStart=/usr/bin/docker daemon -H fd:// --graph /mnt/docker-data --storage-driver btrfs
You can also set other environment variables in this file, for example, the
`HTTP_PROXY` environment variables described below.
+1 -1
View File
@@ -96,7 +96,7 @@ which is officially supported by Docker.
>command fails for the Docker repo during installation. To work around this,
>add the key directly using the following:
>
> $ wget -qO- https://get.docker.com/gpg | sudo apt-key add -
> $ curl -sSL https://get.docker.com/gpg | sudo apt-key add -
### Uninstallation
+18
View File
@@ -206,6 +206,24 @@ If you need to add an HTTP Proxy, set a different directory or partition for the
Docker runtime files, or make other customizations, read our Systemd article to
learn how to [customize your Systemd Docker daemon options](/articles/systemd/).
## Running Docker with a manually-defined network
If you manually configure your network using `systemd-network` with `systemd` version 219 or higher, containers you start with Docker may be unable to access your network.
Beginning with version 220, the forwarding setting for a given network (`net.ipv4.conf.<interface>.forwarding`) defaults to *off*. This setting prevents IP forwarding. It also conflicts with Docker which enables the `net.ipv4.conf.all.forwarding` setting within a container.
To work around this, edit the `<interface>.network` file in
`/usr/lib/systemd/network/` on your Docker host (ex: `/usr/lib/systemd/network/80-container-host0.network`) add the following block:
```
[Network]
...
IPForward=kernel
# OR
IPForward=true
...
```
This configuration allows IP forwarding from the container as expected.
## Uninstall
+240 -166
View File
@@ -10,37 +10,34 @@ parent = "smn_engine"
# Mac OS X
You can install Docker using Boot2Docker to run `docker` commands at your command-line.
Choose this installation if you are familiar with the command-line or plan to
contribute to the Docker project on GitHub.
> **Note**: This release of Docker deprecates the Boot2Docker command line in
> favor of Docker Machine. Use the Docker Toolbox to install Docker Machine as
> well as the other Docker tools.
[<img src="/installation/images/kitematic.png" alt="Download Kitematic"
style="float:right;">](https://kitematic.com/download)
You install Docker using Docker Toolbox. Docker Toolbox includes the following Docker tools:
Alternatively, you may want to try <a id="inlinelink" href="https://kitematic.com/"
target="_blank">Kitematic</a>, an application that lets you set up Docker and
run containers using a graphical user interface (GUI).
## Command-line Docker with Boot2Docker
* Docker Machine for running the `docker-machine` binary
* Docker Engine for running the `docker` binary
* Docker Compose for running the `docker-compose` binary
* Kitematic, the Docker GUI
* a shell preconfigured for a Docker command-line environment
* Oracle VM VirtualBox
Because the Docker daemon uses Linux-specific kernel features, you can't run
Docker natively in OS X. Instead, you must install the Boot2Docker application.
The application includes a VirtualBox Virtual Machine (VM), Docker itself, and the
Boot2Docker management tool.
The Boot2Docker management tool is a lightweight Linux virtual machine made
specifically to run the Docker daemon on Mac OS X. The VirtualBox VM runs
completely from RAM, is a small ~24MB download, and boots in approximately 5s.
Docker natively in OS X. Instead, you must use `docker-machine` to create and
attach to a virtual machine (VM). This machine is a Linux VM that hosts Docker
for you on your Mac.
**Requirements**
Your Mac must be running OS X 10.6 "Snow Leopard" or newer to run Boot2Docker.
Your Mac must be running OS X 10.8 "Mountain Lion" or newer to install the
Docker Toolbox.
### Learn the key concepts before installing
In a Docker installation on Linux, your machine is both the localhost and the
Docker host. In networking, localhost means your computer. The Docker host is
the machine on which the containers run.
In a Docker installation on Linux, your physical machine is both the localhost
and the Docker host. In networking, localhost means your computer. The Docker
host is the computer on which the containers run.
On a typical Linux installation, the Docker client, the Docker daemon, and any
containers run directly on your localhost. This means you can address ports on a
@@ -49,135 +46,243 @@ Docker container using standard localhost addressing such as `localhost:8000` or
![Linux Architecture Diagram](/installation/images/linux_docker_host.svg)
In an OS X installation, the `docker` daemon is running inside a Linux virtual
machine provided by Boot2Docker.
In an OS X installation, the `docker` daemon is running inside a Linux VM called
`default`. The `default` is a lightweight Linux VM made specifically to run
the Docker daemon on Mac OS X. The VM runs completely from RAM, is a small ~24MB
download, and boots in approximately 5s.
![OSX Architecture Diagram](/installation/images/mac_docker_host.svg)
In OS X, the Docker host address is the address of the Linux VM.
When you start the `boot2docker` process, the VM is assigned an IP address. Under
`boot2docker` ports on a container map to ports on the VM. To see this in
In OS X, the Docker host address is the address of the Linux VM. When you start
the VM with `docker-machine` it is assigned an IP address. When you start a
container, the ports on a container map to ports on the VM. To see this in
practice, work through the exercises on this page.
### Installation
1. Go to the [boot2docker/osx-installer ](
https://github.com/boot2docker/osx-installer/releases/latest) release page.
If you have VirtualBox running, you must shut it down before running the
installer.
4. Download Boot2Docker by clicking `Boot2Docker-x.x.x.pkg` in the "Downloads"
section.
1. Go to the [Docker Toolbox](https://www.docker.com/toolbox) page.
3. Install Boot2Docker by double-clicking the package.
2. Click the installer link to download.
The installer places Boot2Docker and VirtualBox in your "Applications" folder.
3. Install Docker Toolbox by double-clicking the package or by right-clicking
and choosing "Open" from the pop-up menu.
The installation places the `docker` and `boot2docker` binaries in your
`/usr/local/bin` directory.
The installer launches the "Install Docker Toolbox" dialog.
![Install Docker Toolbox](/installation/images/mac-welcome-page.png)
4. Press "Continue" to install the toolbox.
The installer presents you with options to customize the standard
installation.
![Standard install](/installation/images/mac-page-two.png)
By default, the standard Docker Toolbox installation:
* installs binaries for the Docker tools in `/usr/local/bin`
* makes these binaries available to all users
* updates any existing VirtualBox installation
Change these defaults by pressing "Customize" or "Change
Install Location."
5. Press "Install" to perform the standard installation.
The system prompts you for your password.
![Password prompt](/installation/images/mac-password-prompt.png)
6. Provide your password to continue with the installation.
When it completes, the installer provides you with some information you can
use to complete some common tasks.
![All finished](/installation/images/mac-page-finished.png)
7. Press "Close" to exit.
## Start the Boot2Docker Application
## Running a Docker Container
To run a Docker container, you first start the `boot2docker` VM and then issue
`docker` commands to create, load, and manage containers. You can launch
`boot2docker` from your Applications folder or from the command line.
To run a Docker container, you:
> **NOTE**: Boot2Docker is designed as a development tool. You should not use
> it in production environments.
* create a new (or start an existing) Docker virtual machine
* switch your environment to your new VM
* use the `docker` client to create, load, and manage containers
### From the Applications folder
Once you create a machine, you can reuse it as often as you like. Like any
VirtualBox VM, it maintains its configuration between uses.
When you launch the "Boot2Docker" application from your "Applications" folder, the
application:
There are two ways to use the installed tools, from the Docker Quickstart Terminal or
[from your shell](#from-your-shell).
* opens a terminal window
### From the Docker Quickstart Terminal
* creates a $HOME/.boot2docker directory
1. Open the "Applications" folder or the "Launchpad".
* creates a VirtualBox ISO and certs
2. Find the Docker Quickstart Terminal and double-click to launch it.
* starts a VirtualBox VM running the `docker` daemon
The application:
Once the launch completes, you can run `docker` commands. A good way to verify
your setup succeeded is to run the `hello-world` container.
* opens a terminal window
* creates a VM called `default` if it doesn't exists, starts the VM if it does
* points the terminal environment to this VM
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
511136ea3c5a: Pull complete
31cbccb51277: Pull complete
e45a5af57b00: Pull complete
hello-world:latest: The image you are pulling has been verified.
Important: image verification is a tech preview feature and should not be
relied on to provide security.
Status: Downloaded newer image for hello-world:latest
Hello from Docker.
This message shows that your installation appears to be working correctly.
Once the launch completes, the Docker Quickstart Terminal reports:
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(Assuming it was not already locally available.)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
![All finished](/installation/images/mac-success.png)
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Now, you can run `docker` commands.
For more examples and ideas, visit:
http://docs.docker.com/userguide/
3. Verify your setup succeeded by running the `hello-world` container.
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
511136ea3c5a: Pull complete
31cbccb51277: Pull complete
e45a5af57b00: Pull complete
hello-world:latest: The image you are pulling has been verified.
Important: image verification is a tech preview feature and should not be
relied on to provide security.
Status: Downloaded newer image for hello-world:latest
Hello from Docker.
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(Assuming it was not already locally available.)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
For more examples and ideas, visit:
http://docs.docker.com/userguide/
A more typical way to start and stop `boot2docker` is using the command line.
A more typical way to interact with the Docker tools is from your regular shell command line.
### From your command line
### From your shell
Initialize and run `boot2docker` from the command line, do the following:
This section assumes you are running a Bash shell. You may be running a
different shell such as C Shell but the commands are the same.
1. Create a new Boot2Docker VM.
1. Create a new Docker VM.
$ boot2docker init
$ docker-machine create --driver virtualbox default
Creating VirtualBox VM...
Creating SSH key...
Starting VirtualBox VM...
Starting VM...
To see how to connect Docker to this machine, run: docker-machine env default
This creates a new virtual machine. You only need to run this command once.
This creates a new `default` in VirtualBox.
2. Start the `boot2docker` VM.
![default](/installation/images/default.png)
$ boot2docker start
The command also creates a machine configuration in the
`~/.docker/machine/machines/default` directory. You only need to run the
`create` command once. Then, you can use `docker-machine` to start, stop,
query, and otherwise manage the VM from the command line.
3. Display the environment variables for the Docker client.
2. List your available machines.
$ boot2docker shellinit
Writing /Users/mary/.boot2docker/certs/boot2docker-vm/ca.pem
Writing /Users/mary/.boot2docker/certs/boot2docker-vm/cert.pem
Writing /Users/mary/.boot2docker/certs/boot2docker-vm/key.pem
export DOCKER_HOST=tcp://192.168.59.103:2376
export DOCKER_CERT_PATH=/Users/mary/.boot2docker/certs/boot2docker-vm
export DOCKER_TLS_VERIFY=1
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM
default * virtualbox Running tcp://192.168.99.101:2376
The specific paths and address on your machine will be different.
If you have previously installed the deprecated Boot2Docker application or
run the Docker Quickstart Terminal, you may have a `dev` VM as well. When you
created `default`, the `docker-machine` command provided instructions
for learning how to connect the VM.
4. To set the environment variables in your shell do the following:
3. Get the environment commands for your new VM.
$ eval "$(boot2docker shellinit)"
$ docker-machine env default
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.101:2376"
export DOCKER_CERT_PATH="/Users/mary/.docker/machine/machines/default"
export DOCKER_MACHINE_NAME="default"
# Run this command to configure your shell:
# eval "$(docker-machine env default)"
You can also set them manually by using the `export` commands `boot2docker`
returns.
4. Connect your shell to the `default` machine.
$ eval "$(docker-machine env default)"
5. Run the `hello-world` container to verify your setup.
$ docker run hello-world
## Basic Boot2Docker exercises
## Learn about your Toolbox installation
At this point, you should have `boot2docker` running and the `docker` client
environment initialized. To verify this, run the following commands:
Toolbox installs the Docker Engine binary, the Docker binary on your system. When you
use the Docker Quickstart Terminal or create a `default` manually, Docker
Machine updates the `~/.docker/machine/machines/default` folder to your
system. This folder contains the configuration for the VM.
$ boot2docker status
$ docker version
You can create multiple VMs on your system with Docker Machine. So, you may have
more than one VM folder if you have more than one VM. To remove a VM, use the
`docker-machine rm <machine-name>` command.
Work through this section to try some practical container tasks using `boot2docker` VM.
## Migrate from Boot2Docker
If you were using Boot2Docker previously, you have a pre-existing Docker
`boot2docker-vm` VM on your local system. To allow Docker Machine to manage
this older VM, you can migrate it.
1. Open a terminal or the Docker CLI on your system.
2. Type the following command.
$ docker-machine create -d virtualbox --virtualbox-import-boot2docker-vm boot2docker-vm docker-vm
3. Use the `docker-machine` command to interact with the migrated VM.
The `docker-machine` subcommands are slightly different than the `boot2docker`
subcommands. The table below lists the equivalent `docker-machine` subcommand
and what it does:
| `boot2docker` | `docker-machine` | `docker-machine` description |
|----------------|------------------|----------------------------------------------------------|
| init | create | Creates a new docker host. |
| up | start | Starts a stopped machine. |
| ssh | ssh | Runs a command or interactive ssh session on the machine.|
| save | - | Not applicable. |
| down | stop | Stops a running machine. |
| poweroff | stop | Stops a running machine. |
| reset | restart | Restarts a running machine. |
| config | inspect | Prints machine configuration details. |
| status | ls | Lists all machines and their status. |
| info | inspect | Displays a machine's details. |
| ip | ip | Displays the machine's ip address. |
| shellinit | env | Displays shell commands needed to configure your shell to interact with a machine |
| delete | rm | Removes a machine. |
| download | - | Not applicable. |
| upgrade | upgrade | Upgrades a machine's Docker client to the latest stable release. |
## Example of Docker on Mac OS X
Work through this section to try some practical container tasks on a VM. At this
point, you should have a VM running and be connected to it through your shell.
To verify this, run the following commands:
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM
dev * virtualbox Running tcp://192.168.99.100:2376
The `ACTIVE` machine, in this case `dev`, is the one your environment is pointing to.
### Access container ports
@@ -212,11 +317,11 @@ Work through this section to try some practical container tasks using `boot2dock
This didn't work. The reason it doesn't work is your `DOCKER_HOST` address is
not the localhost address (0.0.0.0) but is instead the address of the
`boot2docker` VM.
your Docker VM.
5. Get the address of the `boot2docker` VM.
5. Get the address of the `dev` VM.
$ boot2docker ip
$ docker-machine ip dev
192.168.59.103
6. Enter the `http://192.168.59.103:49157` address in your browser:
@@ -232,7 +337,7 @@ Work through this section to try some practical container tasks using `boot2dock
### Mount a volume on the container
When you start `boot2docker`, it automatically shares your `/Users` directory
When you start a container it automatically shares your `/Users/username` directory
with the VM. You can use this share point to mount directories onto your container.
The next exercise demonstrates how to do this.
@@ -254,7 +359,8 @@ The next exercise demonstrates how to do this.
5. Start a new `nginx` container and replace the `html` folder with your `site` directory.
$ docker run -d -P -v $HOME/site:/usr/share/nginx/html --name mysite nginx
$ docker run -d -P -v $HOME/site:/usr/share/nginx/html \
--name mysite nginx
6. Get the `mysite` container's port.
@@ -274,85 +380,53 @@ The next exercise demonstrates how to do this.
![Cool page](/installation/images/cool_view.png)
9. Stop and then remove your running `mysite` container.
10. Stop and then remove your running `mysite` container.
$ docker stop mysite
$ docker rm mysite
## Upgrade Boot2Docker
If you running Boot2Docker 1.4.1 or greater, you can upgrade Boot2Docker from
the command line. If you are running an older version, you should use the
package provided by the `boot2docker` repository.
## Upgrade Docker Toolbox
### From the command line
To upgrade from 1.4.1 or greater, you can do this:
1. Open a terminal on your local machine.
2. Stop the `boot2docker` application.
$ boot2docker stop
3. Run the upgrade command.
$ boot2docker upgrade
To upgrade Docker Toolbox, download an re-run [the Docker Toolbox
installer](https://docker.com/toolbox/).
### Use the installer
## Uninstall Docker Toolbox
To upgrade any version of Boot2Docker, do this:
To uninstall, do the following:
1. Open a terminal on your local machine.
1. List your machines.
2. Stop the `boot2docker` application.
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM
dev * virtualbox Running tcp://192.168.99.100:2376
my-docker-machine virtualbox Stopped
default virtualbox Stopped
$ boot2docker stop
2. Remove each machine.
3. Go to the [boot2docker/osx-installer ](
https://github.com/boot2docker/osx-installer/releases/latest) release page.
$ docker-machine rm dev
Successfully removed dev
4. Download Boot2Docker by clicking `Boot2Docker-x.x.x.pkg` in the "Downloads"
section.
Removing a machine deletes its VM from VirtualBox and from the
`~/.docker/machine/machines` directory.
2. Install Boot2Docker by double-clicking the package.
3. Remove the Docker Quickstart Terminal and Kitematic from your "Applications" folder.
The installer places Boot2Docker in your "Applications" folder.
4. Remove the `docker`, `docker-compose`, and `docker-machine` commands from the `/usr/local/bin` folder.
$ rm /usr/local/bin/docker
5. Delete the `~/.docker` folder from your system.
## Uninstallation
## Learning more
1. Go to the [boot2docker/osx-installer ](
https://github.com/boot2docker/osx-installer/releases/latest) release page.
Use `docker-machine help` to list the full command line reference for Docker Machine. For more
information about using SSH or SCP to access a VM, see [the Docker Machine
documentation](https://docs.docker.com/machine/).
2. Download the source code by clicking `Source code (zip)` or
`Source code (tar.gz)` in the "Downloads" section.
3. Extract the source code.
4. Open a terminal on your local machine.
5. Change to the directory where you extracted the source code:
$ cd <path to extracted source code>
6. Make sure the uninstall.sh script is executable:
$ chmod +x uninstall.sh
7. Run the uninstall.sh script:
$ ./uninstall.sh
## Learning more and acknowledgement
Use `boot2docker help` to list the full command line reference. For more
information about using SSH or SCP to access the Boot2Docker VM, see the README
at [Boot2Docker repository](https://github.com/boot2docker/boot2docker).
Thanks to Chris Jones whose [blog](http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide)
inspired me to redo this page.
Continue with the [Docker User Guide](/userguide).
You can continue with the [Docker User Guide](/userguide). If you are
interested in using the Kitematic GUI, see the [Kitermatic user
guide](/kitematic/userguide/).
+16 -11
View File
@@ -111,18 +111,18 @@ install Docker using the following:
1. Log into your Ubuntu installation as a user with `sudo` privileges.
2. Verify that you have `wget` installed.
2. Verify that you have `curl` installed.
$ which wget
$ which curl
If `wget` isn't installed, install it after updating your manager:
If `curl` isn't installed, install it after updating your manager:
$ sudo apt-get update
$ sudo apt-get install wget
$ sudo apt-get install curl
3. Get the latest Docker package.
$ wget -qO- https://get.docker.com/ | sh
$ curl -sSL https://get.docker.com/ | sh
The system prompts you for your `sudo` password. Then, it downloads and
installs Docker and its dependencies.
@@ -132,7 +132,7 @@ install Docker using the following:
>command fails for the Docker repo during installation. To work around this,
>add the key directly using the following:
>
> $ wget -qO- https://get.docker.com/gpg | sudo apt-key add -
> $ curl -sSL https://get.docker.com/gpg | sudo apt-key add -
4. Verify `docker` is installed correctly.
@@ -197,9 +197,14 @@ When users run Docker, they may see these messages when working with an image:
WARNING: Your kernel does not support cgroup swap limit. WARNING: Your
kernel does not support swap limit capabilities. Limitation discarded.
To prevent these messages, enable memory and swap accounting on your system. To
enable these on system using GNU GRUB (GNU GRand Unified Bootloader), do the
following.
To prevent these messages, enable memory and swap accounting on your
system. Enabling memory and swap accounting does induce both a memory
overhead and a performance degradation even when Docker is not in
use. The memory overhead is about 1% of the total available
memory. The performance degradation is roughly 10%.
To enable memory and swap on system using GNU GRUB (GNU GRand Unified
Bootloader), do the following:
1. Log into Ubuntu as a user with `sudo` privileges.
@@ -339,9 +344,9 @@ to start the docker daemon on boot
## Upgrade Docker
To install the latest version of Docker with `wget`:
To install the latest version of Docker with `curl`:
$ wget -qO- https://get.docker.com/ | sh
$ curl -sSL https://get.docker.com/ | sh
## Uninstallation
+296 -104
View File
@@ -9,165 +9,357 @@ parent = "smn_engine"
<![end-metadata]-->
# Windows
> **Note:**
> Docker has been tested on Windows 7 and 8.1; it may also run on older versions.
> Your processor needs to support hardware virtualization.
The Docker Engine uses Linux-specific kernel features, so to run it on Windows
we need to use a lightweight virtual machine (VM). You use the **Windows Docker
Client** to control the virtualized Docker Engine to build, run, and manage
Docker containers.
> **Note**: This release of Docker deprecates the Boot2Docker command line in
> favor of Docker Machine. Use the Docker Toolbox to install Docker Machine as
> well as the other Docker tools.
To make this process easier, we've designed a helper application called
[Boot2Docker](https://github.com/boot2docker/boot2docker) which creates a Linux virtual
machine on Windows to run Docker on a Linux operating system.
You install Docker using Docker Toolbox. Docker Toolbox includes the following Docker tools:
Although you will be using Windows Docker client, the docker engine hosting the
containers will still be running on Linux. Until the Docker engine for Windows
is developed, you can launch only Linux containers from your Windows machine.
* Docker Machine for running the `docker-machine` binary
* Docker Engine for running the `docker` binary
* Kitematic, the Docker GUI
* a shell preconfigured for a Docker command-line environment
* Oracle VM VirtualBox
Because the Docker daemon uses Linux-specific kernel features, you can't run
Docker natively in Windows. Instead, you must use `docker-machine` to create and attach to a Docker VM on your machine. This VM hosts Docker for you on your Windows system.
The Docker VM is lightweight Linux virtual machine made specifically to run the
Docker daemon on Windows. The VirtualBox VM runs completely from RAM, is a
small ~24MB download, and boots in approximately 5s.
## Requirements
Your machine must be running Windows 7.1, 8/8.1 or newer to run Docker. Windows 10 is not currently supported. To find out what version of Windows you have:
1. Right click the Windows message and choose **System**.
![Which version](/installation/images/win_ver.png)
If you aren't using a supported version, you could consider upgrading your
operating system.
2. Make sure your Windows system supports Hardware Virtualization Technology and that virtualization is enabled.
#### For Windows 8 or 8.1
Choose **Start > Task Manager** and navigate to the **Performance** tab.
Under **CPU** you should see the following:
![Release page](/installation/images/virtualization.png)
If virtualization is not enabled on your system, follow the manufacturer's instructions for enabling it.
### For Windows 7
Run the <a
href="http://www.microsoft.com/en-us/download/details.aspx?id=592"
target="_blank"> Microsoft® Hardware-Assisted Virtualization Detection
Tool</a> and follow the on-screen instructions.
> **Note**: If you have Docker hosts running and you don't wish to do a Docker Toolbox
installation, you can install the `docker.exe` using the *unofficial* Windows package
manager Chocolately. For information on how to do this, see [Docker package on
Chocolatey](http://chocolatey.org/packages/docker).
### Learn the key concepts before installing
In a Docker installation on Linux, your machine is both the localhost and the
Docker host. In networking, localhost means your computer. The Docker host is
the machine on which the containers run.
On a typical Linux installation, the Docker client, the Docker daemon, and any
containers run directly on your localhost. This means you can address ports on a
Docker container using standard localhost addressing such as `localhost:8000` or
`0.0.0.0:8376`.
![Linux Architecture Diagram](/installation/images/linux_docker_host.svg)
In an Windows installation, the `docker` daemon is running inside a Linux virtual
machine. You use the Windows Docker client to talk to the Docker host VM. Your
Docker containers run inside this host.
![Windows Architecture Diagram](/installation/images/win_docker_host.svg)
## Demonstration
In Windows, the Docker host address is the address of the Linux VM. When you
start the VM with `docker-machine` it is assigned an IP address. When you start
a container, the ports on a container map to ports on the VM. To see this in
practice, work through the exercises on this page.
<iframe width="640" height="480" src="//www.youtube.com/embed/TjMU3bDX4vo?rel=0" frameborder="0" allowfullscreen></iframe>
## Installation
### Installation
1. Download the latest release of the
[Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest).
2. Run the installer, which will install Docker Client for Windows, VirtualBox,
Git for Windows (MSYS-git), the boot2docker Linux ISO, and the Boot2Docker
management tool.
![](/installation/images/windows-installer.png)
3. Run the **Boot2Docker Start** shortcut from your Desktop or “Program Files →
Boot2Docker for Windows”.
The Start script will ask you to enter an ssh key passphrase - the simplest
(but least secure) is to just hit [Enter].
If you have VirtualBox running, you must shut it down before running the
installer.
4. The **Boot2Docker Start** will start a unix shell already configured to manage
Docker running inside the virtual machine. Run `docker version` to see
if it is working correctly:
1. Go to the [Docker Toolbox](https://www.docker.com/toolbox) page.
![](/installation/images/windows-boot2docker-start.png)
2. Click the installer link to download.
## Running Docker
3. Install Docker Toolbox by double-clicking the installer.
> **Note:** if you are using a remote Docker daemon, such as Boot2Docker,
> then _do not_ type the `sudo` before the `docker` commands shown in the
> documentation's examples.
The installer launches the "Setup - Docker Toolbox" dialog.
**Boot2Docker Start** will automatically start a shell with environment variables
correctly set so you can start using Docker right away:
![Install Docker Toolbox](/installation/images/win-welcome.png)
Let's try the `hello-world` example image. Run
4. Press "Next" to install the toolbox.
$ docker run hello-world
The installer presents you with options to customize the standard
installation. By default, the standard Docker Toolbox installation:
* installs executables for the Docker tools in `C:\Program Files\Docker Toolbox`
* updates any existing VirtualBox installation
* adds a Docker Inc. folder to your program shortcuts
* updates your `PATH` environment variable
* adds desktop icons for the Docker Quickstart Terminal and Kitematic
This installation assumes the defaults are acceptable.
5. Press "Next" until you reach the "Ready to Install" page.
The system prompts you for your password.
![Install](/installation/images/win-page-6.png)
6. Press "Install" to continue with the installation.
When it completes, the installer provides you with some information you can
use to complete some common tasks.
![All finished](/installation/images/windows-finish.png)
7. Press "Close" to exit.
## Running a Docker Container
To run a Docker container, you:
* create a new (or start an existing) Docker virtual machine
* switch your environment to your new VM
* use the `docker` client to create, load, and manage containers
Once you create a machine, you can reuse it as often as you like. Like any
VirtualBox VM, it maintains its configuration between uses.
There are several ways to use the installed tools, from the Docker Quickstart Terminal or
[from your shell](#from-your-shell).
### From the Docker Quickstart Terminal
1. Find the Docker Quickstart Terminal icon on your Desktop and double-click to launch it.
The application:
* opens a terminal window
* creates a `default` if it doesn't exist, starts the VM if it does
* points the terminal environment to this VM
Once the launch completes, you can run `docker` commands.
3. Verify your setup succeeded by running the `hello-world` container.
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
511136ea3c5a: Pull complete
31cbccb51277: Pull complete
e45a5af57b00: Pull complete
hello-world:latest: The image you are pulling has been verified.
Important: image verification is a tech preview feature and should not be
relied on to provide security.
Status: Downloaded newer image for hello-world:latest
Hello from Docker.
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(Assuming it was not already locally available.)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
For more examples and ideas, visit:
http://docs.docker.com/userguide/
This should download the very small `hello-world` image and print a
`Hello from Docker.` message.
## Using Docker from Windows Command Line Prompt (cmd.exe)
Launch a Windows Command Line Prompt (cmd.exe).
1. Launch a Windows Command Line Prompt (cmd.exe).
Boot2Docker command requires `ssh.exe` to be in the PATH, therefore we need to
include `bin` folder of the Git installation (which has ssh.exe) to the `%PATH%`
environment variable by running:
The `docker-machine` command requires `ssh.exe` in your `PATH` environment
variable. This `.exe` is in the MsysGit `bin` folder.
set PATH=%PATH%;"c:\Program Files (x86)\Git\bin"
2. Add this to the `%PATH%` environment variable by running:
and then we can run the `boot2docker start` command to start the Boot2Docker VM.
(Run `boot2docker init` command if you get an error saying machine does not
exist.) Then copy the instructions for cmd.exe to set the environment variables
to your console window and you are ready to run docker commands such as
`docker ps`:
set PATH=%PATH%;"c:\Program Files (x86)\Git\bin"
![](/installation/images/windows-boot2docker-cmd.png)
3. Create a new Docker VM.
docker-machine create --driver virtualbox my-default
Creating VirtualBox VM...
Creating SSH key...
Starting VirtualBox VM...
Starting VM...
To see how to connect Docker to this machine, run: docker-machine env my-default
The command also creates a machine configuration in the
`C:\USERS\USERNAME\.docker\machine\machines` directory. You only need to run the `create`
command once. Then, you can use `docker-machine` to start, stop, query, and
otherwise manage the VM from the command line.
4. List your available machines.
C:\Users\mary> docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM
my-default * virtualbox Running tcp://192.168.99.101:2376
If you have previously installed the deprecated Boot2Docker application or
run the Docker Quickstart Terminal, you may have a `dev` VM as well.
5. Get the environment commands for your new VM.
C:\Users\mary> docker-machine env --shell cmd my-default
6. Connect your shell to the `my-default` machine.
C:\Users\mary> eval "$(docker-machine env my-default)"
7. Run the `hello-world` container to verify your setup.
C:\Users\mary> docker run hello-world
## Using Docker from PowerShell
Launch a PowerShell window, then add `ssh.exe` to your PATH:
1. Launch a Windows PowerShell window.
$Env:Path = "${Env:Path};c:\Program Files (x86)\Git\bin"
2. Add `ssh.exe` to your PATH:
and after running the `boot2docker start` command it will print PowerShell
commands to set the environment variables to connect to the Docker daemon
running inside the VM. Run these commands and you are ready to run docker
commands such as `docker ps`:
PS C:\Users\mary> $Env:Path = "${Env:Path};c:\Program Files (x86)\Git\bin"
![](/installation/images/windows-boot2docker-powershell.png)
3. Create a new Docker VM.
> NOTE: You can alternatively run `boot2docker shellinit | Invoke-Expression`
> command to set the environment variables instead of copying and pasting on
> PowerShell.
PS C:\Users\mary> docker-machine create --driver virtualbox my-default
# Further Details
4. List your available machines.
The Boot2Docker management tool provides several commands:
C:\Users\mary> docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM
my-default * virtualbox Running tcp://192.168.99.101:2376
$ boot2docker
Usage: boot2docker.exe [<options>] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|shellinit|delete|download|upgrade|version} [<args>]
5. Get the environment commands for your new VM.
## Upgrading
C:\Users\mary> docker-machine env --shell powershell my-default
1. Download the latest release of the [Docker for Windows Installer](
https://github.com/boot2docker/windows-installer/releases/latest)
6. Connect your shell to the `my-default` machine.
2. Run the installer, which will update the Boot2Docker management tool.
C:\Users\mary> eval "$(docker-machine env my-default)"
3. To upgrade your existing virtual machine, open a terminal and run:
7. Run the `hello-world` container to verify your setup.
boot2docker stop
boot2docker download
boot2docker start
C:\Users\mary> docker run hello-world
## Learn about your Toolbox installation
Toolbox installs the Docker Engine binary in the `C:\Program Files\Docker
Toolbox` directory. When you use the Docker Quickstart Terminal or create a
`default` manually, Docker Machine updates the
`C:\USERS\USERNAME\.docker\machine\machines\default` folder to your
system. This folder contains the configuration for the VM.
You can create multiple VMs on your system with Docker Machine. So, you may have
more than one VM folder if you have more than one VM. To remove a VM, use the
`docker-machine rm <machine-name>` command.
## Migrate from Boot2Docker
If you were using Boot2Docker previously, you have a pre-existing Docker
`boot2docker-vm` VM on your local system. To allow Docker Machine to manage
this older VM, you can migrate it.
1. Open a terminal or the Docker CLI on your system.
2. Type the following command.
$ docker-machine create -d virtualbox --virtualbox-import-boot2docker-vm boot2docker-vm docker-vm
3. Use the `docker-machine` command to interact with the migrated VM.
The `docker-machine` subcommands are slightly different than the `boot2docker`
subcommands. The table below lists the equivalent `docker-machine` subcommand
and what it does:
| `boot2docker` | `docker-machine` | `docker-machine` description |
|----------------|------------------|----------------------------------------------------------|
| init | create | Creates a new docker host. |
| up | start | Starts a stopped machine. |
| ssh | ssh | Runs a command or interactive ssh session on the machine.|
| save | - | Not applicable. |
| down | stop | Stops a running machine. |
| poweroff | stop | Stops a running machine. |
| reset | restart | Restarts a running machine. |
| config | inspect | Prints machine configuration details. |
| status | ls | Lists all machines and their status. |
| info | inspect | Displays a machine's details. |
| ip | ip | Displays the machine's ip address. |
| shellinit | env | Displays shell commands needed to configure your shell to interact with a machine |
| delete | rm | Removes a machine. |
| download | - | Not applicable. |
| upgrade | upgrade | Upgrades a machine's Docker client to the latest stable release. |
## Upgrade Docker Toolbox
To upgrade Docker Toolbox, download an re-run [the Docker Toolbox
installer](https://www.docker.com/toolbox).
## Container port redirection
If you are curious, the username for the boot2docker default user is `docker`
and the password is `tcuser`.
If you are curious, the username for the Docker default user is `docker` and the
password is `tcuser`. The latest version of `docker-machine` sets up a host only
network adaptor which provides access to the container's ports.
The latest version of `boot2docker` sets up a host only network adaptor which
provides access to the container's ports.
If you run a container with a published port:
If you run a container with an exposed port:
$ docker run --rm -i -t -p 80:80 nginx
docker run --rm -i -t -p 80:80 nginx
Then you should be able to access that nginx server using the IP address
reported to you using:
Then you should be able to access that nginx server using the IP address reported
to you using:
$ docker-machine ip
boot2docker ip
Typically, it is 192.168.59.103, but it could get changed by VirtualBox's DHCP
implementation.
For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io)
Typically, the IP is 192.168.59.103, but it could get changed by VirtualBox's
DHCP implementation.
## Login with PUTTY instead of using the CMD
Boot2Docker generates and uses the public/private key pair in your `%USERPROFILE%\.ssh`
directory so to log in you need to use the private key from this same directory.
The private key needs to be converted into the format PuTTY uses.
You can do this with
Docker Machine generates and uses the public/private key pair in your
`%USERPROFILE%\.ssh` directory so to log in you need to use the private key from
this same directory. The private key needs to be converted into the format PuTTY
uses. You can do this with
[puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html):
- Open `puttygen.exe` and load ("File"->"Load" menu) the private key from
1. Open `puttygen.exe` and load ("File"->"Load" menu) the private key from
`%USERPROFILE%\.ssh\id_boot2docker`
- then click: "Save Private Key".
- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`.
2. Click "Save Private Key".
3. Use the saved file to login with PuTTY using `docker@127.0.0.1:2022`.
## Uninstallation
You can uninstall Boot2Docker using Window's standard process for removing programs.
This process does not remove the `docker-install.exe` file. You must delete that file
yourself.
You can uninstall Docker Toolbox using Window's standard process for removing
programs. This process does not remove the `docker-install.exe` file. You must
delete that file yourself.
## References
## Learn more
If you have Docker hosts running and if you don't wish to do a
Boot2Docker installation, you can install the docker.exe using
unofficial Windows package manager Chocolately. For information
on how to do this, see [Docker package on Chocolatey](http://chocolatey.org/packages/docker).
You can continue with the [Docker User Guide](/userguide). If you are
interested in using the Kitematic GUI, see the [Kitermatic user
guide](/kitematic/userguide/).
+8 -7
View File
@@ -116,11 +116,11 @@ images, or you can download Docker images that other people have already created
Docker images are the **build** component of Docker.
#### Docker registries
Docker registries hold images. These are public or private stores from which you upload
or download images. The public Docker registry is called
[Docker Hub](http://hub.docker.com). It provides a huge collection of existing
images for your use. These can be images you create yourself or you
can use images that others have previously created. Docker registries are the
Docker registries hold images. These are public or private stores from which you
upload or download images. The public Docker registry is provided with the
[Docker Hub](http://hub.docker.com). It serves a huge collection of existing
images for your use. These can be images you create yourself or you can use
images that others have previously created. Docker registries are the
**distribution** component of Docker.
#### Docker containers
@@ -179,8 +179,9 @@ returns a final image.
### How does a Docker registry work?
The Docker registry is the store for your Docker images. Once you build a Docker
image you can *push* it to a public registry [Docker Hub](https://hub.docker.com) or to
your own registry running behind your firewall.
image you can *push* it to a public registry such as the one provided by [Docker
Hub](https://hub.docker.com) or to your own registry running behind your
firewall.
Using the Docker client, you can search for already published images and then
pull them down to your Docker host to build containers from them.
+6 -2
View File
@@ -18,7 +18,7 @@ The following list of features are deprecated.
**Target For Removal In Release: v1.10**
The built-in LXC execution driver is deprecated for an external implementation.
The lxc-conf flag and API fields will also be removed.
The lxc-conf flag and API fields will also be removed.
### Old Command Line Options
**Deprecated In Release: [v1.8.0](/release-notes/#docker-engine-1-8-0)**
@@ -29,7 +29,7 @@ The flags `-d` and `--daemon` are deprecated in favor of the `daemon` subcommand
docker daemon -H ...
The following single-dash (`-opt`) variant of certain command line options
The following single-dash (`-opt`) variant of certain command line options
are deprecated and replaced with double-dash options (`--opt`):
docker attach -nostdin
@@ -68,3 +68,7 @@ The following double-dash options are deprecated and have no replacement:
docker ps --since-id
docker ps --before-id
docker search --trusted
### Interacting with V1 registries
Version 1.8.3 adds a flag (`--disable-legacy-registry=false`) which prevents the docker daemon from `pull`, `push`, and `login` operations against v1 registries. Though disabled by default, this signals the intent to deprecate the v1 protocol.
+2 -2
View File
@@ -33,7 +33,7 @@ Docker currently runs only on Linux, but you can use VirtualBox to run Docker in
a virtual machine on your box, and get the best of both worlds. Check out the
[*Mac OS X*](../installation/mac/#macosx) and [*Microsoft
Windows*](../installation/windows/#windows) installation guides. The small Linux
distribution boot2docker can be run inside virtual machines on these two
distribution Docker Machine can be run inside virtual machines on these two
operating systems.
> **Note:** if you are using a remote Docker daemon, such as Boot2Docker,
@@ -97,7 +97,7 @@ with several powerful functionalities:
applications. Your ideal Postgresql setup can be re-used for all your future
projects. And so on.
- *Sharing.* Docker has access to a [public registry](https://hub.docker.com)
- *Sharing.* Docker has access to a public registry [on Docker Hub](https://registry.hub.docker.com/)
where thousands of people have uploaded useful containers: anything from Redis,
CouchDB, Postgres to IRC bouncers to Rails app servers to Hadoop to base images
for various Linux distros. The
+3
View File
@@ -26,6 +26,9 @@ program code and documentation code.
* Run `gofmt -s -w file.go` on each changed file before
committing your changes. Most editors have plug-ins that do this automatically.
* Run `golint` on each changed file before
committing your changes.
* Update the documentation when creating or modifying features.
* Commits that fix or close an issue should reference them in the commit message
+1 -1
View File
@@ -29,7 +29,7 @@ you continue working with your fork on this branch.
## Clean your host of Docker artifacts
Docker developers run the latest stable release of the Docker software (with Boot2Docker if their machine is Mac OS X). They clean their local
Docker developers run the latest stable release of the Docker software (with Docker Machine if their machine is Mac OS X). They clean their local
hosts of unnecessary Docker artifacts such as stopped containers or unused
images. Cleaning unnecessary artifacts isn't strictly necessary, but it is
good practice, so it is included here.
+2 -2
View File
@@ -57,8 +57,8 @@ target="_blank">docker/docker repository</a>.
$ cd ~
In Windows, you'll work in your Boot2Docker window instead of Powershell or
a `cmd` window.
In Windows, you'll work in your Docker Quickstart Terminal window instead of
Powershell or a `cmd` window.
6. Create a `repos` directory.
+2 -2
View File
@@ -317,9 +317,9 @@ can browse the docs.
4. Enter the URL in your browser.
If you are running Boot2Docker, replace the default localhost address
If you are using Docker Machine, replace the default localhost address
(0.0.0.0) with your DOCKERHOST value. You can get this value at any time by
entering `boot2docker ip` at the command line.
entering `docker-machine ip <machine-name>` at the command line.
5. Once in the documentation, look for the red notice to verify you are seeing the correct build.
+1 -498
View File
@@ -10,502 +10,5 @@ parent = "smn_remoteapi"
# Docker Hub API
- This is the REST API for [Docker Hub](https://hub.docker.com).
- Authorization is done with basic auth over SSL
- Not all commands require authentication, only those noted as such.
This API is deprecated as of 1.7. To view the old version, see the [Docker Hub API](https://docs.docker.com/v1.7/reference/api/docker-io_api/) in the 1.7 documentation.
# Repositories
## User repository
### Create a user repository
`PUT /v1/repositories/(namespace)/(repo_name)/`
Create a user repository with the given `namespace` and `repo_name`.
**Example Request**:
PUT /v1/repositories/foo/bar/ HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
X-Docker-Token: true
[{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"}]
Parameters:
- **namespace** the namespace for the repo
- **repo_name** the name for the repo
**Example Response**:
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
WWW-Authenticate: Token signature=123abc,repository="foo/bar",access=write
X-Docker-Token: signature=123abc,repository="foo/bar",access=write
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
""
Status Codes:
- **200** Created
- **400** Errors (invalid json, missing or invalid fields, etc)
- **401** Unauthorized
- **403** Account is not Active
### Delete a user repository
`DELETE /v1/repositories/(namespace)/(repo_name)/`
Delete a user repository with the given `namespace` and `repo_name`.
**Example Request**:
DELETE /v1/repositories/foo/bar/ HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
X-Docker-Token: true
""
Parameters:
- **namespace** the namespace for the repo
- **repo_name** the name for the repo
**Example Response**:
HTTP/1.1 202
Vary: Accept
Content-Type: application/json
WWW-Authenticate: Token signature=123abc,repository="foo/bar",access=delete
X-Docker-Token: signature=123abc,repository="foo/bar",access=delete
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
""
Status Codes:
- **200** Deleted
- **202** Accepted
- **400** Errors (invalid json, missing or invalid fields, etc)
- **401** Unauthorized
- **403** Account is not Active
## Library repository
### Create a library repository
`PUT /v1/repositories/(repo_name)/`
Create a library repository with the given `repo_name`.
This is a restricted feature only available to docker admins.
> When namespace is missing, it is assumed to be `library`
**Example Request**:
PUT /v1/repositories/foobar/ HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
X-Docker-Token: true
[{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"}]
Parameters:
- **repo_name** the library name for the repo
**Example Response**:
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
WWW-Authenticate: Token signature=123abc,repository="library/foobar",access=write
X-Docker-Token: signature=123abc,repository="foo/bar",access=write
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
""
Status Codes:
- **200** Created
- **400** Errors (invalid json, missing or invalid fields, etc)
- **401** Unauthorized
- **403** Account is not Active
### Delete a library repository
`DELETE /v1/repositories/(repo_name)/`
Delete a library repository with the given `repo_name`.
This is a restricted feature only available to docker admins.
> When namespace is missing, it is assumed to be `library`
**Example Request**:
DELETE /v1/repositories/foobar/ HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
X-Docker-Token: true
""
Parameters:
- **repo_name** the library name for the repo
**Example Response**:
HTTP/1.1 202
Vary: Accept
Content-Type: application/json
WWW-Authenticate: Token signature=123abc,repository="library/foobar",access=delete
X-Docker-Token: signature=123abc,repository="foo/bar",access=delete
X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io]
""
Status Codes:
- **200** Deleted
- **202** Accepted
- **400** Errors (invalid json, missing or invalid fields, etc)
- **401** Unauthorized
- **403** Account is not Active
# Repository images
## User repository images
### Update user repository images
`PUT /v1/repositories/(namespace)/(repo_name)/images`
Update the images for a user repo.
**Example Request**:
PUT /v1/repositories/foo/bar/images HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
[{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
"checksum": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"}]
Parameters:
- **namespace** the namespace for the repo
- **repo_name** the name for the repo
**Example Response**:
HTTP/1.1 204
Vary: Accept
Content-Type: application/json
""
Status Codes:
- **204** Created
- **400** Errors (invalid json, missing or invalid fields, etc)
- **401** Unauthorized
- **403** Account is not Active or permission denied
### List user repository images
`GET /v1/repositories/(namespace)/(repo_name)/images`
Get the images for a user repo.
**Example Request**:
GET /v1/repositories/foo/bar/images HTTP/1.1
Host: index.docker.io
Accept: application/json
Parameters:
- **namespace** the namespace for the repo
- **repo_name** the name for the repo
**Example Response**:
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
[{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
"checksum": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"},
{"id": "ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds",
"checksum": "34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew"}]
Status Codes:
- **200** OK
- **404** Not found
## Library repository images
### Update library repository images
`PUT /v1/repositories/(repo_name)/images`
Update the images for a library repo.
**Example Request**:
PUT /v1/repositories/foobar/images HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
[{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
"checksum": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"}]
Parameters:
- **repo_name** the library name for the repo
**Example Response**:
HTTP/1.1 204
Vary: Accept
Content-Type: application/json
""
Status Codes:
- **204** Created
- **400** Errors (invalid json, missing or invalid fields, etc)
- **401** Unauthorized
- **403** Account is not Active or permission denied
### List library repository images
`GET /v1/repositories/(repo_name)/images`
Get the images for a library repo.
**Example Request**:
GET /v1/repositories/foobar/images HTTP/1.1
Host: index.docker.io
Accept: application/json
Parameters:
- **repo_name** the library name for the repo
**Example Response**:
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
[{"id": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
"checksum": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"},
{"id": "ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds",
"checksum": "34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew"}]
Status Codes:
- **200** OK
- **404** Not found
# Repository authorization
## Library repository
### Authorize a token for a library
`PUT /v1/repositories/(repo_name)/auth`
Authorize a token for a library repo
**Example Request**:
PUT /v1/repositories/foobar/auth HTTP/1.1
Host: index.docker.io
Accept: application/json
Authorization: Token signature=123abc,repository="library/foobar",access=write
Parameters:
- **repo_name** the library name for the repo
**Example Response**:
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
"OK"
Status Codes:
- **200** OK
- **403** Permission denied
- **404** Not found
## User repository
### Authorize a token for a user repository
`PUT /v1/repositories/(namespace)/(repo_name)/auth`
Authorize a token for a user repo
**Example Request**:
PUT /v1/repositories/foo/bar/auth HTTP/1.1
Host: index.docker.io
Accept: application/json
Authorization: Token signature=123abc,repository="foo/bar",access=write
Parameters:
- **namespace** the namespace for the repo
- **repo_name** the name for the repo
**Example Response**:
HTTP/1.1 200
Vary: Accept
Content-Type: application/json
"OK"
Status Codes:
- **200** OK
- **403** Permission denied
- **404** Not found
## Users
### User login
`GET /v1/users/`
If you want to check your login, you can try this endpoint
**Example Request**:
GET /v1/users/ HTTP/1.1
Host: index.docker.io
Accept: application/json
Authorization: Basic akmklmasadalkm==
**Example Response**:
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
OK
Status Codes:
- **200** no error
- **401** Unauthorized
- **403** Account is not Active
### User register
`POST /v1/users/`
Registering a new account.
**Example request**:
POST /v1/users/ HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
{"email": "sam@docker.com",
"password": "toto42",
"username": "foobar"}
Json Parameters:
- **email** valid email address, that needs to be confirmed
- **username** min 4 character, max 30 characters, must match
the regular expression [a-z0-9_].
- **password** min 5 characters
**Example Response**:
HTTP/1.1 201 OK
Vary: Accept
Content-Type: application/json
"User Created"
Status Codes:
- **201** User Created
- **400** Errors (invalid json, missing or invalid fields, etc)
### Update user
`PUT /v1/users/(username)/`
Change a password or email address for given user. If you pass in an
email, it will add it to your account, it will not remove the old
one. Passwords will be updated.
It is up to the client to verify that that password that is sent is
the one that they want. Common approach is to have them type it
twice.
**Example Request**:
PUT /v1/users/fakeuser/ HTTP/1.1
Host: index.docker.io
Accept: application/json
Content-Type: application/json
Authorization: Basic akmklmasadalkm==
{"email": "sam@docker.com",
"password": "toto42"}
Parameters:
- **username** username for the person you want to update
**Example Response**:
HTTP/1.1 204
Vary: Accept
Content-Type: application/json
""
Status Codes:
- **204** User Updated
- **400** Errors (invalid json, missing or invalid fields, etc)
- **401** Unauthorized
- **403** Account is not Active
- **404** User not found
-425
View File
@@ -298,429 +298,4 @@ The `fromImage` and `repo` parameters now supports the `repo:tag` format.
Consequently, the `tag` parameter is now obsolete. Using the new format and
the `tag` parameter at the same time will return an error.
## v1.13
### Full documentation
[*Docker Remote API v1.13*](/reference/api/docker_remote_api_v1.13/)
### What's new
`GET /containers/(name)/json`
**New!**
The `HostConfig.Links` field is now filled correctly
**New!**
`Sockets` parameter added to the `/info` endpoint listing all the sockets the
daemon is configured to listen on.
`POST /containers/(name)/start`
`POST /containers/(name)/stop`
**New!**
`start` and `stop` will now return 304 if the container's status is not modified
`POST /commit`
**New!**
Added a `pause` parameter (default `true`) to pause the container during commit
## v1.12
### Full documentation
[*Docker Remote API v1.12*](/reference/api/docker_remote_api_v1.12/)
### What's new
`POST /build`
**New!**
Build now has support for the `forcerm` parameter to always remove containers
`GET /containers/(name)/json`
`GET /images/(name)/json`
**New!**
All the JSON keys are now in CamelCase
**New!**
Trusted builds are now Automated Builds - `is_trusted` is now `is_automated`.
**Removed Insert Endpoint**
The `insert` endpoint has been removed.
## v1.11
### Full documentation
[*Docker Remote API v1.11*](/reference/api/docker_remote_api_v1.11/)
### What's new
`GET /_ping`
**New!**
You can now ping the server via the `_ping` endpoint.
`GET /events`
**New!**
You can now use the `-until` parameter to close connection
after timestamp.
`GET /containers/(id)/logs`
This url is preferred method for getting container logs now.
## v1.10
### Full documentation
[*Docker Remote API v1.10*](/reference/api/docker_remote_api_v1.10/)
### What's new
`DELETE /images/(name)`
**New!**
You can now use the force parameter to force delete of an
image, even if it's tagged in multiple repositories. **New!**
You
can now use the noprune parameter to prevent the deletion of parent
images
`DELETE /containers/(id)`
**New!**
You can now use the force parameter to force delete a
container, even if it is currently running
## v1.9
### Full documentation
[*Docker Remote API v1.9*](/reference/api/docker_remote_api_v1.9/)
### What's new
`POST /build`
**New!**
This endpoint now takes a serialized ConfigFile which it
uses to resolve the proper registry auth credentials for pulling the
base image. Clients which previously implemented the version
accepting an AuthConfig object must be updated.
## v1.8
### Full documentation
[*Docker Remote API v1.8*](/reference/api/docker_remote_api_v1.8/)
### What's new
`POST /build`
**New!**
This endpoint now returns build status as json stream. In
case of a build error, it returns the exit status of the failed
command.
`GET /containers/(id)/json`
**New!**
This endpoint now returns the host config for the
container.
`POST /images/create`
`POST /images/(name)/insert`
`POST /images/(name)/push`
**New!**
progressDetail object was added in the JSON. It's now
possible to get the current value and the total of the progress
without having to parse the string.
## v1.7
### Full documentation
[*Docker Remote API v1.7*](/reference/api/docker_remote_api_v1.7/)
### What's new
`GET /images/json`
The format of the json returned from this uri changed. Instead of an
entry for each repo/tag on an image, each image is only represented
once, with a nested attribute indicating the repo/tags that apply to
that image.
Instead of:
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"VirtualSize": 131506275,
"Size": 131506275,
"Created": 1365714795,
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
"Tag": "12.04",
"Repository": "ubuntu"
},
{
"VirtualSize": 131506275,
"Size": 131506275,
"Created": 1365714795,
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
"Tag": "latest",
"Repository": "ubuntu"
},
{
"VirtualSize": 131506275,
"Size": 131506275,
"Created": 1365714795,
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
"Tag": "precise",
"Repository": "ubuntu"
},
{
"VirtualSize": 180116135,
"Size": 24653,
"Created": 1364102658,
"Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
"Tag": "12.10",
"Repository": "ubuntu"
},
{
"VirtualSize": 180116135,
"Size": 24653,
"Created": 1364102658,
"Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
"Tag": "quantal",
"Repository": "ubuntu"
}
]
The returned json looks like this:
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"RepoTags": [
"ubuntu:12.04",
"ubuntu:precise",
"ubuntu:latest"
],
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
"Created": 1365714795,
"Size": 131506275,
"VirtualSize": 131506275
},
{
"RepoTags": [
"ubuntu:12.10",
"ubuntu:quantal"
],
"ParentId": "27cf784147099545",
"Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
"Created": 1364102658,
"Size": 24653,
"VirtualSize": 180116135
}
]
`GET /images/viz`
This URI no longer exists. The `images --viz`
output is now generated in the client, using the
`/images/json` data.
## v1.6
### Full documentation
[*Docker Remote API v1.6*](/reference/api/docker_remote_api_v1.6/)
### What's new
`POST /containers/(id)/attach`
**New!**
You can now split stderr from stdout. This is done by
prefixing a header to each transmission. See
[`POST /containers/(id)/attach`](
/reference/api/docker_remote_api_v1.9/#attach-to-a-container "POST /containers/(id)/attach").
The WebSocket attach is unchanged. Note that attach calls on the
previous API version didn't change. Stdout and stderr are merged.
## v1.5
### Full documentation
[*Docker Remote API v1.5*](/reference/api/docker_remote_api_v1.5/)
### What's new
`POST /images/create`
**New!**
You can now pass registry credentials (via an AuthConfig
object) through the X-Registry-Auth header
`POST /images/(name)/push`
**New!**
The AuthConfig object now needs to be passed through the
X-Registry-Auth header
`GET /containers/json`
**New!**
The format of the Ports entry has been changed to a list of
dicts each containing PublicPort, PrivatePort and Type describing a
port mapping.
## v1.4
### Full documentation
[*Docker Remote API v1.4*](/reference/api/docker_remote_api_v1.4/)
### What's new
`POST /images/create`
**New!**
When pulling a repo, all images are now downloaded in parallel.
`GET /containers/(id)/top`
**New!**
You can now use ps args with docker top, like docker top
<container_id> aux
`GET /events`
**New!**
Image's name added in the events
## v1.3
docker v0.5.0
[51f6c4a](https://github.com/docker/docker/commit/51f6c4a7372450d164c61e0054daf0223ddbd909)
### Full documentation
[*Docker Remote API v1.3*](/reference/api/docker_remote_api_v1.3/)
### What's new
`GET /containers/(id)/top`
List the processes running inside a container.
`GET /events`
**New!**
Monitor docker's events via streaming or via polling
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
> **Warning**:
> 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
## v1.2
docker v0.4.2
[2e7649b](https://github.com/docker/docker/commit/2e7649beda7c820793bd46766cbc2cfeace7b168)
### Full documentation
[*Docker Remote API v1.2*](/reference/api/docker_remote_api_v1.2/)
### What's new
The auth configuration is now handled by the client.
The client should send it's authConfig as POST on each call of
`/images/(name)/push`
`GET /auth`
**Deprecated.**
`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 children and remove all the untagged parents if has any.
`POST /images/<name>/delete`
Now returns a JSON structure with the list of images
deleted/untagged.
## v1.1
docker v0.4.0
[a8ae398](https://github.com/docker/docker/commit/a8ae398bf52e97148ee7bd0d5868de2e15bd297f)
### Full documentation
[*Docker Remote API v1.1*](/reference/api/docker_remote_api_v1.1/)
### What's new
`POST /images/create`
`POST /images/(name)/insert`
`POST /images/(name)/push`
Uses json stream instead of HTML hijack, it looks like this:
HTTP/1.1 200 OK
Content-Type: application/json
{"status":"Pushing..."}
{"status":"Pushing", "progress":"1/? (n/a)"}
{"error":"Invalid..."}
...
## v1.0
docker v0.3.4
[8d73740](https://github.com/docker/docker/commit/8d73740343778651c09160cde9661f5f387b36f4)
### Full documentation
[*Docker Remote API v1.0*](/reference/api/docker_remote_api_v1.0/)
### What's new
Initial version
@@ -49,6 +49,11 @@ List containers
"Created": 1367854155,
"Status": "Exit 0",
"Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
"Labels": {
"com.example.vendor": "Acme",
"com.example.license": "GPL",
"com.example.version": "1.0"
},
"SizeRw": 12288,
"SizeRootFs": 0
},
@@ -60,6 +65,7 @@ List containers
"Created": 1367854155,
"Status": "Exit 0",
"Ports": [],
"Labels": {},
"SizeRw": 12288,
"SizeRootFs": 0
},
@@ -71,6 +77,7 @@ List containers
"Created": 1367854154,
"Status": "Exit 0",
"Ports":[],
"Labels": {},
"SizeRw":12288,
"SizeRootFs":0
},
@@ -82,6 +89,7 @@ List containers
"Created": 1367854152,
"Status": "Exit 0",
"Ports": [],
"Labels": {},
"SizeRw": 12288,
"SizeRootFs": 0
}
@@ -51,6 +51,11 @@ List containers
"Created": 1367854155,
"Status": "Exit 0",
"Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
"Labels": {
"com.example.vendor": "Acme",
"com.example.license": "GPL",
"com.example.version": "1.0"
},
"SizeRw": 12288,
"SizeRootFs": 0
},
@@ -62,6 +67,7 @@ List containers
"Created": 1367854155,
"Status": "Exit 0",
"Ports": [],
"Labels": {},
"SizeRw": 12288,
"SizeRootFs": 0
},
@@ -73,6 +79,7 @@ List containers
"Created": 1367854154,
"Status": "Exit 0",
"Ports":[],
"Labels": {},
"SizeRw":12288,
"SizeRootFs":0
},
@@ -84,6 +91,7 @@ List containers
"Created": 1367854152,
"Status": "Exit 0",
"Ports": [],
"Labels": {},
"SizeRw": 12288,
"SizeRootFs": 0
}
@@ -51,6 +51,11 @@ List containers
"Created": 1367854155,
"Status": "Exit 0",
"Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
"Labels": {
"com.example.vendor": "Acme",
"com.example.license": "GPL",
"com.example.version": "1.0"
},
"SizeRw": 12288,
"SizeRootFs": 0
},
@@ -62,6 +67,7 @@ List containers
"Created": 1367854155,
"Status": "Exit 0",
"Ports": [],
"Labels": {},
"SizeRw": 12288,
"SizeRootFs": 0
},
@@ -73,6 +79,7 @@ List containers
"Created": 1367854154,
"Status": "Exit 0",
"Ports":[],
"Labels": {},
"SizeRw":12288,
"SizeRootFs":0
},
@@ -84,6 +91,7 @@ List containers
"Created": 1367854152,
"Status": "Exit 0",
"Ports": [],
"Labels": {},
"SizeRw": 12288,
"SizeRootFs": 0
}
-761
View File
@@ -1,761 +0,0 @@
<!--[metadata]>
+++
title = "The Docker Hub and the Registry v1"
description = "Documentation for docker Registry and Registry API"
keywords = ["docker, registry, api, hub"]
[menu.main]
parent="smn_hub_ref"
+++
<![end-metadata]-->
# The Docker Hub and the Registry v1
## The three roles
There are three major components playing a role in the Docker ecosystem.
### Docker Hub
The Docker Hub is responsible for centralizing information about:
- User accounts
- Checksums of the images
- Public namespaces
The Docker Hub has different components:
- Web UI
- Meta-data store (comments, stars, list public repositories)
- Authentication service
- Tokenization
The Docker Hub is authoritative for that information.
There is only one instance of the Docker Hub, run and
managed by Docker Inc.
### Docker Registry 1.0
The 1.0 registry has the following characteristics:
- It stores the images and the graph for a set of repositories
- It does not have user accounts data
- It has no notion of user accounts or authorization
- It delegates authentication and authorization to the Docker Hub Auth
service using tokens
- It supports different storage backends (S3, cloud files, local FS)
- It doesn't have a local database
- [Source Code](https://github.com/docker/docker-registry)
We expect that there will be multiple registries out there. To help you
grasp the context, here are some examples of registries:
- **sponsor registry**: such a registry is provided by a third-party
hosting infrastructure as a convenience for their customers and the
Docker community as a whole. Its costs are supported by the third
party, but the management and operation of the registry are
supported by Docker, Inc. It features read/write access, and delegates
authentication and authorization to the Docker Hub.
- **mirror registry**: such a registry is provided by a third-party
hosting infrastructure but is targeted at their customers only. Some
mechanism (unspecified to date) ensures that public images are
pulled from a sponsor registry to the mirror registry, to make sure
that the customers of the third-party provider can `docker pull`
those images locally.
- **vendor registry**: such a registry is provided by a software
vendor who wants to distribute docker images. It would be operated
and managed by the vendor. Only users authorized by the vendor would
be able to get write access. Some images would be public (accessible
for anyone), others private (accessible only for authorized users).
Authentication and authorization would be delegated to the Docker Hub.
The goal of vendor registries is to let someone do `docker pull
basho/riak1.3` and automatically push from the vendor registry
(instead of a sponsor registry); i.e., vendors get all the convenience of a
sponsor registry, while retaining control on the asset distribution.
- **private registry**: such a registry is located behind a firewall,
or protected by an additional security layer (HTTP authorization,
SSL client-side certificates, IP address authorization...). The
registry is operated by a private entity, outside of Docker's
control. It can optionally delegate additional authorization to the
Docker Hub, but it is not mandatory.
> **Note:** The latter implies that while HTTP is the protocol
> of choice for a registry, multiple schemes are possible (and
> in some cases, trivial):
>
> - HTTP with GET (and PUT for read-write registries);
> - local mount point;
> - remote docker addressed through SSH.
The latter would only require two new commands in Docker, e.g.,
`registryget` and `registryput`,
wrapping access to the local filesystem (and optionally doing
consistency checks). Authentication and authorization are then delegated
to SSH (e.g., with public keys).
### Docker
On top of being a runtime for LXC, Docker is the Registry client. It
supports:
- Push / Pull on the registry
- Client authentication on the Docker Hub
## Workflow
### Pull
![](/static_files/docker_pull_chart.png)
1. Contact the Docker Hub to know where I should download “samalba/busybox”
2. Docker Hub replies: a. `samalba/busybox` is on Registry A b. here are the
checksums for `samalba/busybox` (for all layers) c. token
3. Contact Registry A to receive the layers for `samalba/busybox` (all of
them to the base image). Registry A is authoritative for “samalba/busybox”
but keeps a copy of all inherited layers and serve them all from the same
location.
4. registry contacts Docker Hub to verify if token/user is allowed to download images
5. Docker Hub returns true/false lettings registry know if it should proceed or error
out
6. Get the payload for all layers
It's possible to run:
$ docker pull https://<registry>/repositories/samalba/busybox
In this case, Docker bypasses the Docker Hub. However the security is not
guaranteed (in case Registry A is corrupted) because there won't be any
checksum checks.
Currently registry redirects to s3 urls for downloads, going forward all
downloads need to be streamed through the registry. The Registry will
then abstract the calls to S3 by a top-level class which implements
sub-classes for S3 and local storage.
Token is only returned when the `X-Docker-Token`
header is sent with request.
Basic Auth is required to pull private repos. Basic auth isn't required
for pulling public repos, but if one is provided, it needs to be valid
and for an active account.
**API (pulling repository foo/bar):**
1. (Docker -> Docker Hub) GET /v1/repositories/foo/bar/images:
**Headers**:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
X-Docker-Token: true
**Action**:
(looking up the foo/bar in db and gets images and checksums
for that repo (all if no tag is specified, if tag, only
checksums for those tags) see part 4.4.1)
2. (Docker Hub -> Docker) HTTP 200 OK
**Headers**:
Authorization: Token
signature=123abc,repository=”foo/bar”,access=write
X-Docker-Endpoints: registry.docker.io [,registry2.docker.io]
**Body**:
Jsonified checksums (see part 4.4.1)
3. (Docker -> Registry) GET /v1/repositories/foo/bar/tags/latest
**Headers**:
Authorization: Token
signature=123abc,repository=”foo/bar”,access=write
4. (Registry -> Docker Hub) GET /v1/repositories/foo/bar/images
**Headers**:
Authorization: Token
signature=123abc,repository=”foo/bar”,access=read
**Body**:
<ids and checksums in payload>
**Action**:
(Lookup token see if they have access to pull.)
If good:
HTTP 200 OK Docker Hub will invalidate the token
If bad:
HTTP 401 Unauthorized
5. (Docker -> Registry) GET /v1/images/928374982374/ancestry
**Action**:
(for each image id returned in the registry, fetch /json + /layer)
> **Note**:
> If someone makes a second request, then we will always give a new token,
> never reuse tokens.
### Push
![](/static_files/docker_push_chart.png)
1. Contact the Docker Hub to allocate the repository name “samalba/busybox”
(authentication required with user credentials)
2. If authentication works and namespace available, “samalba/busybox”
is allocated and a temporary token is returned (namespace is marked
as initialized in Docker Hub)
3. Push the image on the registry (along with the token)
4. Registry A contacts the Docker Hub to verify the token (token must
corresponds to the repository name)
5. Docker Hub validates the token. Registry A starts reading the stream
pushed by docker and store the repository (with its images)
6. docker contacts the Docker Hub to give checksums for upload images
> **Note:**
> **It's possible not to use the Docker Hub at all!** In this case, a deployed
> version of the Registry is deployed to store and serve images. Those
> images are not authenticated and the security is not guaranteed.
> **Note:**
> **Docker Hub can be replaced!** For a private Registry deployed, a custom
> Docker Hub can be used to serve and validate token according to different
> policies.
Docker computes the checksums and submit them to the Docker Hub at the end of
the push. When a repository name does not have checksums on the Docker Hub,
it means that the push is in progress (since checksums are submitted at
the end).
**API (pushing repos foo/bar):**
1. (Docker -> Docker Hub) PUT /v1/repositories/foo/bar/
**Headers**:
Authorization: Basic sdkjfskdjfhsdkjfh== X-Docker-Token:
true
**Action**:
- in Docker Hub, we allocated a new repository, and set to
initialized
**Body**:
(The body contains the list of images that are going to be
pushed, with empty checksums. The checksums will be set at
the end of the push):
[{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}]
2. (Docker Hub -> Docker) 200 Created
**Headers**:
WWW-Authenticate: Token
signature=123abc,repository=”foo/bar”,access=write
X-Docker-Endpoints: registry.docker.io [, registry2.docker.io]
3. (Docker -> Registry) PUT /v1/images/98765432_parent/json
**Headers**:
Authorization: Token
signature=123abc,repository=”foo/bar”,access=write
4. (Registry->Docker Hub) GET /v1/repositories/foo/bar/images
**Headers**:
Authorization: Token
signature=123abc,repository=”foo/bar”,access=write
**Action**:
- Docker Hub:
will invalidate the token.
- Registry:
grants a session (if token is approved) and fetches
the images id
5. (Docker -> Registry) PUT /v1/images/98765432_parent/json
**Headers**:
Authorization: Token
signature=123abc,repository=”foo/bar”,access=write
Cookie: (Cookie provided by the Registry)
6. (Docker -> Registry) PUT /v1/images/98765432/json
**Headers**:
Cookie: (Cookie provided by the Registry)
7. (Docker -> Registry) PUT /v1/images/98765432_parent/layer
**Headers**:
Cookie: (Cookie provided by the Registry)
8. (Docker -> Registry) PUT /v1/images/98765432/layer
**Headers**:
X-Docker-Checksum: sha256:436745873465fdjkhdfjkgh
9. (Docker -> Registry) PUT /v1/repositories/foo/bar/tags/latest
**Headers**:
Cookie: (Cookie provided by the Registry)
**Body**:
“98765432”
10. (Docker -> Docker Hub) PUT /v1/repositories/foo/bar/images
**Headers**:
Authorization: Basic 123oislifjsldfj== X-Docker-Endpoints:
registry1.docker.io (no validation on this right now)
**Body**:
(The image, id`s, tags and checksums)
[{“id”:
“9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
“checksum”:
“b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}]
**Return**:
HTTP 204
> **Note:** If push fails and they need to start again, what happens in the Docker Hub,
> there will already be a record for the namespace/name, but it will be
> initialized. Should we allow it, or mark as name already used? One edge
> case could be if someone pushes the same thing at the same time with two
> different shells.
If it's a retry on the Registry, Docker has a cookie (provided by the
registry after token validation). So the Docker Hub won't have to provide a
new token.
### Delete
If you need to delete something from the Docker Hub or registry, we need a
nice clean way to do that. Here is the workflow.
1. Docker contacts the Docker Hub to request a delete of a repository
`samalba/busybox` (authentication required with user credentials)
2. If authentication works and repository is valid, `samalba/busybox`
is marked as deleted and a temporary token is returned
3. Send a delete request to the registry for the repository (along with
the token)
4. Registry A contacts the Docker Hub to verify the token (token must
corresponds to the repository name)
5. Docker Hub validates the token. Registry A deletes the repository and
everything associated to it.
6. docker contacts the Docker Hub to let it know it was removed from the
registry, the Docker Hub removes all records from the database.
> **Note**:
> The Docker client should present an "Are you sure?" prompt to confirm
> the deletion before starting the process. Once it starts it can't be
> undone.
**API (deleting repository foo/bar):**
1. (Docker -> Docker Hub) DELETE /v1/repositories/foo/bar/
**Headers**:
Authorization: Basic sdkjfskdjfhsdkjfh== X-Docker-Token:
true
**Action**:
- in Docker Hub, we make sure it is a valid repository, and set
to deleted (logically)
**Body**:
Empty
2. (Docker Hub -> Docker) 202 Accepted
**Headers**:
WWW-Authenticate: Token
signature=123abc,repository=”foo/bar”,access=delete
X-Docker-Endpoints: registry.docker.io [, registry2.docker.io]
# list of endpoints where this repo lives.
3. (Docker -> Registry) DELETE /v1/repositories/foo/bar/
**Headers**:
Authorization: Token
signature=123abc,repository=”foo/bar”,access=delete
4. (Registry->Docker Hub) PUT /v1/repositories/foo/bar/auth
**Headers**:
Authorization: Token
signature=123abc,repository=”foo/bar”,access=delete
**Action**:
- Docker Hub:
will invalidate the token.
- Registry:
deletes the repository (if token is approved)
5. (Registry -> Docker) 200 OK
200 If success 403 if forbidden 400 if bad request 404
if repository isn't found
6. (Docker -> Docker Hub) DELETE /v1/repositories/foo/bar/
**Headers**:
Authorization: Basic 123oislifjsldfj== X-Docker-Endpoints:
registry-1.docker.io (no validation on this right now)
**Body**:
Empty
**Return**:
HTTP 200
## How to use the Registry in standalone mode
The Docker Hub has two main purposes (along with its fancy social features):
- Resolve short names (to avoid passing absolute URLs all the time):
username/projectname ->
https://registry.docker.io/users/<username>/repositories/<projectname>/
team/projectname ->
https://registry.docker.io/team/<team>/repositories/<projectname>/
- Authenticate a user as a repos owner (for a central referenced
repository)
### Without a Docker Hub
Using the Registry without the Docker Hub can be useful to store the images
on a private network without having to rely on an external entity
controlled by Docker Inc.
In this case, the registry will be launched in a special mode
(-standalone? ne? -no-index?). In this mode, the only thing which changes is
that Registry will never contact the Docker Hub to verify a token. It will be
the Registry owner responsibility to authenticate the user who pushes
(or even pulls) an image using any mechanism (HTTP auth, IP based,
etc...).
In this scenario, the Registry is responsible for the security in case
of data corruption since the checksums are not delivered by a trusted
entity.
As hinted previously, a standalone registry can also be implemented by
any HTTP server handling GET/PUT requests (or even only GET requests if
no write access is necessary).
### With a Docker Hub
The Docker Hub data needed by the Registry are simple:
- Serve the checksums
- Provide and authorize a Token
In the scenario of a Registry running on a private network with the need
of centralizing and authorizing, it's easy to use a custom Docker Hub.
The only challenge will be to tell Docker to contact (and trust) this
custom Docker Hub. Docker will be configurable at some point to use a
specific Docker Hub, it'll be the private entity responsibility (basically
the organization who uses Docker in a private environment) to maintain
the Docker Hub and the Docker's configuration among its consumers.
## The API
The first version of the api is available here:
[https://github.com/jpetazzo/docker/blob/acd51ecea8f5d3c02b00a08176171c59442df8b3/docs/images-repositories-push-pull.md](https://github.com/jpetazzo/docker/blob/acd51ecea8f5d3c02b00a08176171c59442df8b3/docs/images-repositories-push-pull.md)
### Images
The format returned in the images is not defined here (for layer and
JSON), basically because Registry stores exactly the same kind of
information as Docker uses to manage them.
The format of ancestry is a line-separated list of image ids, in age
order, i.e. the image's parent is on the last line, the parent of the
parent on the next-to-last line, etc.; if the image has no parent, the
file is empty.
GET /v1/images/<image_id>/layer
PUT /v1/images/<image_id>/layer
GET /v1/images/<image_id>/json
PUT /v1/images/<image_id>/json
GET /v1/images/<image_id>/ancestry
PUT /v1/images/<image_id>/ancestry
### Users
### Create a user (Docker Hub)
POST /v1/users:
**Body**:
{"email": "[sam@docker.com](mailto:sam%40docker.com)",
"password": "toto42", "username": "foobar"`}
**Validation**:
- **username**: min 4 character, max 30 characters, must match the
regular expression [a-z0-9_].
- **password**: min 5 characters
**Valid**:
return HTTP 201
Errors: HTTP 400 (we should create error codes for possible errors) -
invalid json - missing field - wrong format (username, password, email,
etc) - forbidden name - name already exists
> **Note**:
> A user account will be valid only if the email has been validated (a
> validation link is sent to the email address).
### Update a user (Docker Hub)
PUT /v1/users/<username>
**Body**:
{"password": "toto"}
> **Note**:
> We can also update email address, if they do, they will need to reverify
> their new email address.
### Login (Docker Hub)
Does nothing else but asking for a user authentication. Can be used to
validate credentials. HTTP Basic Auth for now, maybe change in future.
GET /v1/users
**Return**:
- Valid: HTTP 200
- Invalid login: HTTP 401
- Account inactive: HTTP 403 Account is not Active
### Tags (Registry)
The Registry does not know anything about users. Even though
repositories are under usernames, it's just a namespace for the
registry. Allowing us to implement organizations or different namespaces
per user later, without modifying the Registry's API.
The following naming restrictions apply:
- Namespaces must match the same regular expression as usernames (See
4.2.1.)
- Repository names must match the regular expression [a-zA-Z0-9-_.]
### Get all tags:
GET /v1/repositories/<namespace>/<repository_name>/tags
**Return**: HTTP 200
[
{
"layer": "9e89cc6f",
"name": "latest"
},
{
"layer": "b486531f",
"name": "0.1.1",
}
]
**4.3.2 Read the content of a tag (resolve the image id):**
GET /v1/repositories/<namespace>/<repo_name>/tags/<tag>
**Return**:
"9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f"
**4.3.3 Delete a tag (registry):**
DELETE /v1/repositories/<namespace>/<repo_name>/tags/<tag>
### 4.4 Images (Docker Hub)
For the Docker Hub to “resolve” the repository name to a Registry location,
it uses the X-Docker-Endpoints header. In other terms, this requests
always add a `X-Docker-Endpoints` to indicate the
location of the registry which hosts this repository.
**4.4.1 Get the images:**
GET /v1/repositories/<namespace>/<repo_name>/images
**Return**: HTTP 200
[{“id”:
“9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
“checksum”:
“[md5:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087](md5:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087)”}]
### Add/update the images:
You always add images, you never remove them.
PUT /v1/repositories/<namespace>/<repo_name>/images
**Body**:
[ {“id”:
“9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”,
“checksum”:
“sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}
]
**Return**:
204
### Repositories
### Remove a Repository (Registry)
DELETE /v1/repositories/<namespace>/<repo_name>
Return 200 OK
### Remove a Repository (Docker Hub)
This starts the delete process. see 2.3 for more details.
DELETE /v1/repositories/<namespace>/<repo_name>
Return 202 OK
## Chaining Registries
It's possible to chain Registries server for several reasons:
- Load balancing
- Delegate the next request to another server
When a Registry is a reference for a repository, it should host the
entire images chain in order to avoid breaking the chain during the
download.
The Docker Hub and Registry use this mechanism to redirect on one or the
other.
Example with an image download:
On every request, a special header can be returned:
X-Docker-Endpoints: server1,server2
On the next request, the client will always pick a server from this
list.
## Authentication and authorization
### On the Docker Hub
The Docker Hub supports both “Basic” and “Token” challenges. Usually when
there is a `401 Unauthorized`, the Docker Hub replies
this:
401 Unauthorized
WWW-Authenticate: Basic realm="auth required",Token
You have 3 options:
1. Provide user credentials and ask for a token
**Header**:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
X-Docker-Token: true
In this case, along with the 200 response, you'll get a new token
(if user auth is ok): If authorization isn't correct you get a 401
response. If account isn't active you will get a 403 response.
**Response**:
200 OK
X-Docker-Token: Token
signature=123abc,repository=”foo/bar”,access=read
2. Provide user credentials only
**Header**:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
3. Provide Token
**Header**:
Authorization: Token
signature=123abc,repository=”foo/bar”,access=read
### 6.2 On the Registry
The Registry only supports the Token challenge:
401 Unauthorized
WWW-Authenticate: Token
The only way is to provide a token on `401 Unauthorized`
responses:
Authorization: Token signature=123abc,repository="foo/bar",access=read
Usually, the Registry provides a Cookie when a Token verification
succeeded. Every time the Registry passes a Cookie, you have to pass it
back the same cookie.:
200 OK
Set-Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=&timestamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="; Path=/; HttpOnly
Next request:
GET /(...)
Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=&timestamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="
## Document version
- 1.0 : May 6th 2013 : initial release
- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new
source namespace.
-6
View File
@@ -114,12 +114,6 @@ images.
### Environment replacement
> **Note**: prior to 1.3, `Dockerfile` environment variables were handled
> similarly, in that they would be replaced as described below. However, there
> was no formal definition on as to which instructions handled environment
> replacement at the time. After 1.3 this behavior will be preserved and
> canonical.
Environment variables (declared with [the `ENV` statement](#env)) can also be
used in certain instructions as variables to be interpreted by the
`Dockerfile`. Escapes are also handled for including variable-like syntax
+1 -5
View File
@@ -10,17 +10,13 @@ parent = "smn_cli"
# Using the command line
> **Note:** If you are using a remote Docker daemon, such as Boot2Docker,
> then _do not_ type the `sudo` before the `docker` commands shown in the
> documentation's examples.
To list available commands, either run `docker` with no parameters
or execute `docker help`:
$ docker
Usage: docker [OPTIONS] COMMAND [arg...]
docker daemon [ --help | ... ]
docker [ -h | --help | -v | --version ]
docker [ --help | -v | --version ]
-H, --host=[]: The socket(s) to bind to in daemon mode, specified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd.
+12 -4
View File
@@ -32,7 +32,7 @@ parent = "smn_cli"
-G, --group="docker" Group for the unix socket
-g, --graph="/var/lib/docker" Root of the Docker runtime
-H, --host=[] Daemon socket(s) to connect to
-h, --help=false Print usage
--help=false Print usage
--icc=true Enable inter-container communication
--insecure-registry=[] Enable insecure registry communication
--ip=0.0.0.0 Default IP when binding container ports
@@ -45,6 +45,7 @@ parent = "smn_cli"
--log-driver="json-file" Default driver for container logs
--log-opt=[] Log driver specific options
--mtu=0 Set the containers network MTU
--disable-legacy-registry=false Do not contact legacy registries
-p, --pidfile="/var/run/docker.pid" Path to use for daemon PID file
--registry-mirror=[] Preferred Docker registry mirror
-s, --storage-driver="" Storage driver to use
@@ -182,7 +183,7 @@ options for `zfs` start with `zfs`.
If using a block device for device mapper storage, it is best to use `lvm`
to create and manage the thin-pool volume. This volume is then handed to Docker
to exclusively create snapshot volumes needed for images and containers.
to exclusively create snapshot volumes needed for images and containers.
Managing the thin-pool outside of Docker makes for the most feature-rich
method of having Docker utilize device mapper thin provisioning as the
@@ -218,7 +219,7 @@ options for `zfs` start with `zfs`.
* `dm.loopdatasize`
>**Note**: This option configures devicemapper loopback, which should not be used in production.
Specifies the size to use when creating the loopback file for the
"data" device which is used for the thin pool. The default size is
100G. The file is sparse, so it will not initially take up this
@@ -444,6 +445,13 @@ Local registries, whose IP address falls in the 127.0.0.0/8 range, are
automatically marked as insecure as of Docker 1.3.2. It is not recommended to
rely on this, as it may change in the future.
## Legacy Registries
Enabling `--disable-legacy-registry` forces a docker daemon to only interact with
registries which support the V2 protocol. Specifically, the daemon will not
attempt `push`, `pull` and `login` to v1 registries. The exception to this
is `search` which can still be performed on v1 registries.
## Running a Docker daemon behind a HTTPS_PROXY
When running inside a LAN that uses a `HTTPS` proxy, the Docker Hub
@@ -466,7 +474,7 @@ use the proxy
`--default-ulimit` allows you to set the default `ulimit` options to use for
all containers. It takes the same options as `--ulimit` for `docker run`. If
these defaults are not set, `ulimit` settings will be inherited, if not set on
`docker run`, from the Docker daemon. Any `--ulimit` options passed to
`docker run`, from the Docker daemon. Any `--ulimit` options passed to
`docker run` will overwrite these defaults.
Be careful setting `nproc` with the `ulimit` flag as `nproc` is designed by Linux to
-4
View File
@@ -20,10 +20,6 @@ The `docker-default` profile the default for running
containers. It is moderately protective while
providing wide application compatability.
The `docker-unconfined` profile is intended for
privileged applications and is the default when runing
a container with the *--privileged* flag.
The system's standard `unconfined` profile inherits all
system-wide policies, applying path-based policies
intended for the host system inside of containers.
+291
View File
@@ -0,0 +1,291 @@
<!--[metadata]>
+++
title = "Content trust in Docker"
description = "Enabling content trust in Docker"
keywords = ["content, trust, security, docker, documentation"]
[menu.main]
parent= "smn_content_trust"
weight=-1
+++
<![end-metadata]-->
# Content trust in Docker
When transferring data among networked systems, *trust* is a central concern. In
particular, when communicating over an untrusted medium such as the internet, it
is critical to ensure the integrity and publisher of all the data a system
operates on. You use Docker to push and pull images (data) to a registry. Content trust
gives you the ability to both verify the integrity and the publisher of all the
data received from a registry over any channel.
Content trust is currently only available for users of the public Docker Hub. It
is currently not available for the Docker Trusted Registry or for private
registries.
## Understand trust in Docker
Content trust allows operations with a remote Docker registry to enforce
client-side signing and verification of image tags. Content trust provides the
ability to use digital signatures for data sent to and received from remote
Docker registries. These signatures allow client-side verification of the
integrity and publisher of specific image tags.
Currently, content trust is disabled by default. You must enabled it by setting
the `DOCKER_CONTENT_TRUST` environment variable.
Once content trust is enabled, image publishers can sign their images. Image consumers can
ensure that the images they use are signed. publishers and consumers can be
individuals alone or in organizations. Docker's content trust supports users and
automated processes such as builds.
### Image tags and content trust
An individual image record has the following identifier:
```
[REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]
```
A particular image `REPOSITORY` can have multiple tags. For example, `latest` and
`3.1.2` are both tags on the `mongo` image. An image publisher can build an image
and tag combination many times changing the image with each build.
Content trust is associated with the `TAG` portion of an image. Each image
repository has a set of keys that image publishers use to sign an image tag.
Image publishers have discretion on which tags they sign.
An image repository can contain an image with one tag that is signed and another
tag that is not. For example, consider [the Mongo image
repository](https://hub.docker.com/r/library/mongo/tags/). The `latest`
tag could be unsigned while the `3.1.6` tag could be signed. It is the
responsibility of the image publisher to decide if an image tag is signed or
not. In this representation, some image tags are signed, others are not:
![Signed tags](../images/tag_signing.png)
Publishers can choose to sign a specific tag or not. As a result, the content of
an unsigned tag and that of a signed tag with the same name may not match. For
example, a publisher can push a tagged image `someimage:latest` and sign it.
Later, the same publisher can push an unsigned `someimage:latest` image. This second
push replaces the last unsigned tag `latest` but does not affect the signed `latest` version.
The ability to choose which tags they can sign, allows publishers to iterate over
the unsigned version of an image before officially signing it.
Image consumers can enable content trust to ensure that images they use were
signed. If a consumer enables content trust, they can only pull, run, or build
with trusted images. Enabling content trust is like wearing a pair of
rose-colored glasses. Consumers "see" only signed images tags and the less
desirable, unsigned image tags are "invisible" to them.
![Trust view](../images/trust_view.png)
To the consumer who does not enabled content trust, nothing about how they
work with Docker images changes. Every image is visible regardless of whether it
is signed or not.
### Content trust operations and keys
When content trust is enabled, `docker` CLI commands that operate on tagged images must
either have content signatures or explicit content hashes. The commands that
operate with content trust are:
* `push`
* `build`
* `create`
* `pull`
* `run`
For example, with content trust enabled a `docker pull someimage:latest` only
succeeds if `someimage:latest` is signed. However, an operation with an explicit
content hash always succeeds as long as the hash exists:
```bash
$ docker pull someimage@sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a
```
Trust for an image tag is managed through the use of signing keys. Docker's content
trust makes use four different keys:
| Key | Description |
|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| offline key | Root of content trust for a image tag. When content trust is enabled, you create the offline key once. |
| target and snapshot | These two keys are known together as the "tagging" key. When content trust is enabled, you create this key when you add a new image repository. If you have the offline key, you can export the tagging key and allow other publishers to sign the image tags. |
| timestamp | This key applies to a repository. It allows Docker repositories to have freshness security guarantees without requiring periodic content refreshes on the client's side. |
With the exception of the timestamp, all the keys are generated and stored locally
client-side. The timestamp is safely generated and stored in a signing server that
is deployed alongside the Docker registry. All keys are generated in a backend
service that isn't directly exposed to the internet and are encrypted at rest.
The following image depicts the various signing keys and their relationships:
![Content trust components](../images/trust_components.png)
>**WARNING**: Loss of the offline key is **very difficult** to recover from.
>Correcting this loss requires intervention from [Docker
>Support](https://support.docker.com) to reset the repository state. This loss
>also requires **manual intervention** from every consumer that used a signed
>tag from this repository prior to the loss.
You should backup the offline key somewhere safe. Given that it is only required
to create new repositories, it is a good idea to store it offline. Make sure you
read [Manage keys for content trust](/security/trust/trust_key_mng) information
for details on creating, securing, and backing up your keys.
## Survey of typical content trust operations
This section surveys the typical trusted operations users perform with Docker
images.
### Enable content trust
Enable content trust by setting the `DOCKER_CONTENT_TRUST` environment variable.
Enabling per-shell is useful because you can have one shell configured for
trusted operations and another terminal shell for untrusted operations. You can
also add this declaration to your shell profile to have it turned on always by
default.
To enable content trust in a `bash` shell enter the following command:
```bash
export DOCKER_CONTENT_TRUST=1
```
Once set, each of the "tag" operations require key for trusted tag. All of these
commands also support the `--disable-content-trust` flag. This flag allows
publishers to run individual operations on tagged images without content trust on an
as-needed basis.
### Push trusted content
To create signed content for a specific image tag, simply enable content trust and push
a tagged image. If this is the first time you have pushed an image using content trust
on your system, the session looks like this:
```bash
$ docker push docker/trusttest:latest
The push refers to a repository [docker.io/docker/trusttest] (len: 1)
9a61b6b1315e: Image already exists
902b87aaaec9: Image already exists
latest: digest: sha256:d02adacee0ac7a5be140adb94fa1dae64f4e71a68696e7f8e7cbf9db8dd49418 size: 3220
Signing and pushing trust metadata
You are about to create a new offline signing key passphrase. This passphrase
will be used to protect the most sensitive key in your signing system. Please
choose a long, complex passphrase and be careful to keep the password and the
key file itself secure and backed up. It is highly recommended that you use a
password manager to generate the passphrase and keep it safe. There will be no
way to recover this key. You can find the key in your config directory.
Enter passphrase for new offline key with id a1d96fb:
Repeat passphrase for new offline key with id a1d96fb:
Enter passphrase for new tagging key with id docker.io/docker/trusttest (3a932f1):
Repeat passphrase for new tagging key with id docker.io/docker/trusttest (3a932f1):
Finished initializing "docker.io/docker/trusttest"
```
When you push your first tagged image with content trust enabled, the `docker` client
recognizes this is your first push and:
- alerts you that it will create a new offline key
- requests a passphrase for the key
- generates an offline key in the `~/.docker/trust` directory
- generates a tagging key for in the `~/.docker/trust` directory
The passphrase you chose for both the offline key and your content key-pair should
be randomly generated and stored in a *password manager*.
It is important to note, if you had left off the `latest` tag, content trust is skipped.
This is true even if content trust is enabled and even if this is your first push.
```bash
$ docker push docker/trusttest
The push refers to a repository [docker.io/docker/trusttest] (len: 1)
9a61b6b1315e: Image successfully pushed
902b87aaaec9: Image successfully pushed
latest: digest: sha256:a9a9c4402604b703bed1c847f6d85faac97686e48c579bd9c3b0fa6694a398fc size: 3220
No tag specified, skipping trust metadata push
```
It is skipped because as the message states, you did not supply an image `TAG`
value. In Docker content trust, signatures are associated with tags.
Once you have an offline key on your system, subsequent images repositories
you create can use that same offline key:
```bash
$ docker push docker.io/docker/seaside:latest
The push refers to a repository [docker.io/docker/seaside] (len: 1)
a9539b34a6ab: Image successfully pushed
b3dbab3810fc: Image successfully pushed
latest: digest: sha256:d2ba1e603661a59940bfad7072eba698b79a8b20ccbb4e3bfb6f9e367ea43939 size: 3346
Signing and pushing trust metadata
Enter key passphrase for offline key with id a1d96fb:
Enter passphrase for new tagging key with id docker.io/docker/seaside (bb045e3):
Repeat passphrase for new tagging key with id docker.io/docker/seaside (bb045e3):
Finished initializing "docker.io/docker/seaside"
```
The new image has its own tagging key and timestamp key. The `latest` tag is signed with both of
these.
### Pull image content
A common way to consume an image is to `pull` it. With content trust enabled, the Docker
client only allows `docker pull` to retrieve signed images.
```
$ docker pull docker/seaside
Using default tag: latest
Pull (1 of 1): docker/trusttest:latest@sha256:d149ab53f871
...
Tagging docker/trusttest@sha256:d149ab53f871 as docker/trusttest:latest
```
The `seaside:latest` image is signed. In the following example, the command does not specify a tag, so the system uses
the `latest` tag by default again and the `docker/cliffs:latest` tag is not signed.
```bash
$ docker pull docker/cliffs
Using default tag: latest
no trust data available
```
Because the tag `docker/cliffs:latest` is not trusted, the `pull` fails.
### Disable content trust for specific operations
A user that wants to disable content trust for a particular operation can use the
`--disable-content-trust` flag. **Warning: this flag disables content trust for
this operation**. With this flag, Docker will ignore content-trust and allow all
operations to be done without verifying any signatures. If we wanted the
previous untrusted build to succeed we could do:
```
$ cat Dockerfile
FROM docker/trusttest:notrust
RUN echo
$ docker build --disable-content-trust -t docker/trusttest:testing .
Sending build context to Docker daemon 42.84 MB
...
Successfully built f21b872447dc
```
The same is true for all the other commands, such as `pull` and `push`:
```
$ docker pull --disable-content-trust docker/trusttest:untrusted
...
$ docker push --disable-content-trust docker/trusttest:untrusted
...
```
## Related information
* [Manage keys for content trust](/security/trust/trust_key_mng)
* [Automation with content trust](/security/trust/trust_automation)
* [Play in a content trust sandbox](/security/trust/trust_sandbox)
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

+21
View File
@@ -0,0 +1,21 @@
<!--[metadata]>
+++
title = "Use trusted images"
description = "Use trusted images"
keywords = ["trust, security, docker, index"]
[menu.main]
identifier="smn_content_trust"
parent= "mn_docker_hub"
weight=4
+++
<![end-metadata]-->
# Use trusted images
The following topics are available:
* [Content trust in Docker](/security/trust/content_trust)
* [Manage keys for content trust](/security/trust/trust_key_mng)
* [Automation with content trust](/security/trust/trust_automation)
* [Play in a content trust sandbox](/security/trust/trust_sandbox)
+79
View File
@@ -0,0 +1,79 @@
<!--[metadata]>
+++
title = "Automation with content trust"
description = "Automating content push pulls with trust"
keywords = ["trust, security, docker, documentation, automation"]
[menu.main]
parent= "smn_content_trust"
+++
<![end-metadata]-->
# Automation with content trust
Your automation systems that pull or build images can also work with trust. Any automation environment must set `DOCKER_TRUST_ENABLED` either manually or in in a scripted fashion before processing images.
## Bypass requests for passphrases
To allow tools to wrap docker and push trusted content, there are two
environment variables that allow you to provide the passphrases without an
expect script, or typing them in:
- `DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE`
- `DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE`
Docker attempts to use the contents of these environment variables as passphrase
for the keys. For example, an image publisher can export the repository `target`
and `snapshot` passphrases:
```bash
$ export DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE="u7pEQcGoebUHm6LHe6"
$ export DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE="l7pEQcTKJjUHm6Lpe4"
```
Then, when pushing a new tag the Docker client does not request these values but signs automatically:
``bash
$ docker push docker/trusttest:latest
The push refers to a repository [docker.io/docker/trusttest] (len: 1)
a9539b34a6ab: Image already exists
b3dbab3810fc: Image already exists
latest: digest: sha256:d149ab53f871 size: 3355
Signing and pushing trust metadata
```
## Building with content trust
You can also build with content trust. Before running the `docker build` command, you should set the environment variable `DOCKER_CONTENT_TRUST` either manually or in in a scripted fashion. Consider the simple Dockerfile below.
```Dockerfilea
FROM docker/trusttest:latest
RUN echo
```
The `FROM` tag is pulling a signed image. You cannot build an image that has a
`FROM` that is not either present locally or signed. Given that content trust
data exists for the tag `latest`, the following build should succeed:
```bash
$ docker build -t docker/trusttest:testing .
Using default tag: latest
latest: Pulling from docker/trusttest
b3dbab3810fc: Pull complete
a9539b34a6ab: Pull complete
Digest: sha256:d149ab53f871
```
If content trust is enabled, building from a Dockerfile that relies on tag without trust data, causes the build command to fail:
```bash
$ docker build -t docker/trusttest:testing .
unable to process Dockerfile: No trust data for notrust
```
## Related information
* [Content trust in Docker](/security/trust/content_trust)
* [Manage keys for content trust](/security/trust/trust_key_mng)
* [Play in a content trust sandbox](/security/trust/trust_sandbox)
+74
View File
@@ -0,0 +1,74 @@
<!--[metadata]>
+++
title = "Manage keys for content trust"
description = "Manage keys for content trust"
keywords = ["trust, security, root, keys, repository"]
[menu.main]
parent= "smn_content_trust"
+++
<![end-metadata]-->
# Manage keys for content trust
Trust for an image tag is managed through the use of keys. Docker's content
trust makes use four different keys:
| Key | Description |
|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| offline key | Root of content trust for a image tag. When content trust is enabled, you create the offline key once. |
| target and snapshot | These two keys are known together as the "tagging" key. When content trust is enabled, you create this key when you add a new image repository. If you have the offline key, you can export the tagging key and allow other publishers to sign the image tags. |
| timestamp | This key applies to a repository. It allows Docker repositories to have freshness security guarantees without requiring periodic content refreshes on the client's side. |
With the exception of the timestamp, all the keys are generated and stored locally
client-side. The timestamp is safely generated and stored in a signing server that
is deployed alongside the Docker registry. All keys are generated in a backend
service that isn't directly exposed to the internet and are encrypted at rest.
## Choosing a passphrase
The passphrases you chose for both the offline key and your tagging key should
be randomly generated and stored in a password manager. Having the tagging key
allow users to sign image tags on a repository. Passphrases are used to encrypt
your keys at rest and ensures that a lost laptop or an unintended backup doesn't
put the private key material at risk.
## Back up your keys
All the Docker trust keys are stored encrypted using the passphrase you provide
on creation. Even so, you should still take care of the location where you back them up.
Good practice is to create two encrypted USB keys.
It is very important that you backup your keys to a safe, secure location. Loss
of the tagging key is recoverable; loss of the offline key is not.
The Docker client stores the keys in the `~/.docker/trust/private` directory.
Before backing them up, you should `tar` them into an archive:
```bash
$ tar -zcvf private_keys_backup.tar.gz ~/.docker/trust/private
$ chmod 600 private_keys_backup.tar.gz
```
## Lost keys
If a publisher loses keys it means losing the ability to sign trusted content for
your repositories. If you lose a key, contact [Docker
Support](https://support.docker.com) (support@docker.com) to reset the repository
state.
This loss also requires **manual intervention** from every consumer that pulled
the tagged image prior to the loss. Image consumers would get an error for
content that they already downloaded:
```
could not validate the path to a trusted root: failed to validate data with current trusted certificates
```
To correct this, they need to download a new image tag with that is signed with
the new key.
## Related information
* [Content trust in Docker](/security/trust/content_trust)
* [Automation with content trust](/security/trust/trust_automation)
* [Play in a content trust sandbox](/security/trust/trust_sandbox)
+331
View File
@@ -0,0 +1,331 @@
<!--[metadata]>
+++
title = "Play in a content trust sandbox"
description = "Play in a trust sandbox"
keywords = ["trust, security, root, keys, repository, sandbox"]
[menu.main]
parent= "smn_content_trust"
+++
<![end-metadata]-->
# Play in a content trust sandbox
This page explains how to set up and use a sandbox for experimenting with trust.
The sandbox allows you to configure and try trust operations locally without
impacting your production images.
Before working through this sandbox, you should have read through the [trust
overview](content_trust.md).
### Prerequisites
These instructions assume you are running in Linux or Mac OS X. You can run
this sandbox on a local machine or on a virtual machine. You will need to
have `sudo` privileges on your local machine or in the VM.
This sandbox requires you to install two Docker tools: Docker Engine and Docker
Compose. To install the Docker Engine, choose from the [list of supported
platforms]({{< relref "installation.md" >}}). To install Docker Compose, see the
[detailed instructions here]({{< relref "compose/install" >}}).
Finally, you'll need to have `git` installed on your local system or VM.
## What is in the sandbox?
If you are just using trust out-of-the-box you only need your Docker Engine
client and access to Docker's own public hub. The sandbox mimics a
production trust environment, and requires these additional components:
| Container | Description |
|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| nostarysandbox | A container with the latest version of Docker Engine and with some preconfigured certifications. This is your sandbox where you can use the `docker` client to test trust operations. |
| Registry server | A local registry service. |
| Notary server | The service that does all the heavy-lifting of managing trust |
| Notary signer | A service that ensures that your keys are secure. |
| MySQL | The database where all of the trust information will be stored |
The sandbox uses the Docker daemon on your local system. Within the `nostarysandbox`
you interact with a local registry rather than the public Docker Hub. This means
your everyday image repositories are not used. They are protected while you play.
When you play in the sandbox, you'll also create root and tagging keys. The
sandbox is configured to store all the keys and files inside the `notarysandbox`
container. Since the keys you create in the sandbox are for play only,
destroying the container destroys them as well.
## Build the sandbox
In this section, you build the Docker components for your trust sandbox. If you
work exclusively with the Docker Hub, you would not need with these components.
They are built into the Docker Hub for you. For the sandbox, however, you must
build your own entire, mock production environment and registry.
### Configure /etc/hosts
The sandbox' `notaryserver` and `sandboxregistry` run on your local server. The
client inside the `notarysandbox` container connects to them over your network.
So, you'll need an entry for both the servers in your local `/etc/hosts` file.
1. Add an entry for the `notaryserver` to `/etc/hosts`.
$ sudo sh -c 'echo "127.0.0.1 notaryserver" >> /etc/hosts'
2. Add an entry for the `sandboxregistry` to `/etc/hosts`.
$ sudo sh -c 'echo "127.0.0.1 sandboxregistry" >> /etc/hosts'
### Build the notarytest image
1. Create a `notarytest` directory on your system.
$ mkdir notarysandbox
2. Change into your `notarysandbox` directory.
$ cd notarysandbox
3. Create a `notarytest` directory then change into that.
$ mkdir notarytest
$ cd nostarytest
4. Create a filed called `Dockerfile` with your favorite editor.
5. Add the following to the new file.
FROM debian:jessie
ADD https://master.dockerproject.org/linux/amd64/docker /usr/bin/docker
RUN chmod +x /usr/bin/docker \
&& apt-get update \
&& apt-get install -y \
tree \
vim \
git \
ca-certificates \
--no-install-recommends
WORKDIR /root
RUN git clone -b trust-sandbox https://github.com/docker/notary.git
RUN cp /root/notary/fixtures/root-ca.crt /usr/local/share/ca-certificates/root-ca.crt
RUN update-ca-certificates
ENTRYPOINT ["bash"]
6. Save and close the file.
7. Build the testing container.
$ docker build -t nostarysandbox .
Sending build context to Docker daemon 2.048 kB
Step 0 : FROM debian:jessie
...
Successfully built 5683f17e9d72
### Build and start up the trust servers
In this step, you get the source code for your notary and registry services.
Then, you'll use Docker Compose to build and start them on your local system.
1. Change to back to the root of your `notarysandbox` directory.
$ cd notarysandbox
2. Clone the `notary` project.
$ git clone -b trust-sandbox https://github.com/docker/notary.git
3. Clone the `distribution` project.
$ git clone https://github.com/docker/distribution.git
4. Change to the `notary` project directory.
$ cd notary
The directory contains a `docker-compose` file that you'll use to run a
notary server together with a notary signer and the corresponding MySQL
databases. The databases store the trust information for an image.
5. Build the server images.
$ docker-compose build
The first time you run this, the build takes some time.
6. Run the server containers on your local system.
$ docker-compose up -d
Once the trust services are up, you'll setup a local version of the Docker
Registry v2.
7. Change to the `nostarysandbox/distribution` directory.
8. Build the `sandboxregistry` server.
$ docker build -t sandboxregistry .
9. Start the `sandboxregistry` server running.
$ docker run -p 5000:5000 --name sandboxregistry sandboxregistry &
## Playing in the sandbox
Now that everything is setup, you can go into your `nostarysandbox` container and
start testing Docker content trust.
### Start the notarysandbox container
In this procedure, you start the `notarysandbox` and link it to the running
`notary_notaryserver_1` and `sandboxregistry` containers. The links allow
communication among the containers.
```
$ docker run -it -v /var/run/docker.sock:/var/run/docker.sock --link notary_notaryserver_1:notaryserver --link sandboxregistry:sandboxregistry nostarysandbox
root@0710762bb59a:/#
```
Mounting the `docker.sock` gives the `nostarysandbox` access to the `docker`
deamon on your host, while storing all the keys and files inside the sandbox
container. When you destroy the container, you destroy the "play" keys.
### Test some trust operations
Now, you'll pull some images.
1. Download a `docker` image to test with.
# docker pull docker/trusttest
docker pull docker/trusttest
Using default tag: latest
latest: Pulling from docker/trusttest
b3dbab3810fc: Pull complete
a9539b34a6ab: Pull complete
Digest: sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a
Status: Downloaded newer image for docker/trusttest:latest
2. Tag it to be pushed to our sandbox registry:
# docker tag docker/trusttest sandboxregistry:5000/test/trusttest:latest
3. Enable content trust.
# export DOCKER_CONTENT_TRUST=1
4. Identify the trust server.
# export DOCKER_CONTENT_TRUST_SERVER=https://notaryserver:4443
This step is only necessary because the sandbox is using its own server.
Normally, if you are using the Docker Public Hub this step isn't necessary.
5. Pull the test image.
# docker pull sandboxregistry:5000/test/trusttest
Using default tag: latest
no trust data available
You see an error, because this content doesn't exist on the `sandboxregistry` yet.
6. Push the trusted image.
# docker push sandboxregistry:5000/test/trusttest:latest
The push refers to a repository [sandboxregistry:5000/test/trusttest] (len: 1)
a9539b34a6ab: Image successfully pushed
b3dbab3810fc: Image successfully pushed
latest: digest: sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c size: 3348
Signing and pushing trust metadata
You are about to create a new root signing key passphrase. This passphrase
will be used to protect the most sensitive key in your signing system. Please
choose a long, complex passphrase and be careful to keep the password and the
key file itself secure and backed up. It is highly recommended that you use a
password manager to generate the passphrase and keep it safe. There will be no
way to recover this key. You can find the key in your config directory.
Enter passphrase for new offline key with id 8c69e04:
Repeat passphrase for new offline key with id 8c69e04:
Enter passphrase for new tagging key with id sandboxregistry:5000/test/trusttest (93c362a):
Repeat passphrase for new tagging key with id sandboxregistry:5000/test/trusttest (93c362a):
Finished initializing "sandboxregistry:5000/test/trusttest"
latest: digest: sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a size: 3355
Signing and pushing trust metadata
7. Try pulling the image you just pushed:
# docker pull sandboxregistry:5000/test/trusttest
Using default tag: latest
Pull (1 of 1): sandboxregistry:5000/test/trusttest:latest@sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c
sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c: Pulling from test/trusttest
b3dbab3810fc: Already exists
a9539b34a6ab: Already exists
Digest: sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c
Status: Downloaded newer image for sandboxregistry:5000/test/trusttest@sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c
Tagging sandboxregistry:5000/test/trusttest@sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c as sandboxregistry:5000/test/trusttest:latest
### Test with malicious images
What happens when data is corrupted and you try to pull it when trust is
enabled? In this section, you go into the `sandboxregistry` and tamper with some
data. Then, you try and pull it.
1. Leave the sandbox container running.
2. Open a new bash terminal from your host into the `sandboxregistry`.
$ docker exec -it sandboxregistry bash
296db6068327#
3. Change into the registry storage.
You'll need to provide the `sha` you received when you pushed the image.
# cd /var/lib/registry/docker/registry/v2/blobs/sha256/aa/aac0c133338db2b18ff054943cee3267fe50c75cdee969aed88b1992539ed042
4. Add malicious data to one of the trusttest layers:
# echo "Malicious data" > data
5. Got back to your sandbox terminal.
6. List the trusttest image.
# docker images | grep trusttest
docker/trusttest latest a9539b34a6ab 7 weeks ago 5.025 MB
sandboxregistry:5000/test/trusttest latest a9539b34a6ab 7 weeks ago 5.025 MB
sandboxregistry:5000/test/trusttest <none> a9539b34a6ab 7 weeks ago 5.025 MB
7. Remove the `trusttest:latest` image.
# docker rmi -f a9539b34a6ab
Untagged: docker/trusttest:latest
Untagged: sandboxregistry:5000/test/trusttest:latest
Untagged: sandboxregistry:5000/test/trusttest@sha256:1d871dcb16805f0604f10d31260e79c22070b35abc71a3d1e7ee54f1042c8c7c
Deleted: a9539b34a6aba01d3942605dfe09ab821cd66abf3cf07755b0681f25ad81f675
Deleted: b3dbab3810fc299c21f0894d39a7952b363f14520c2f3d13443c669b63b6aa20
8. Pull the image again.
# docker pull sandboxregistry:5000/test/trusttest
Using default tag: latest
...
b3dbab3810fc: Verifying Checksum
a9539b34a6ab: Pulling fs layer
filesystem layer verification failed for digest sha256:aac0c133338db2b18ff054943cee3267fe50c75cdee969aed88b1992539ed042
You'll see the the pull did not complete because the trust system was
unable to verify the image.
## More play in the sandbox
Now, that you have a full Docker content trust sandbox on your local system,
feel free to play with it and see how it behaves. If you find any security
issues with Docker, feel free to send us an email at <security@docker.com>.
&nbsp;
+1 -1
View File
@@ -256,7 +256,7 @@ Let's create a directory and a `Dockerfile` first.
$ cd sinatra
$ touch Dockerfile
If you are using Boot2Docker on Windows, you may access your host
If you are using Docker Machine on Windows, you may access your host
directory by `cd` to `/c/Users/your_user_name`.
Each instruction creates a new layer of the image. Let's look at a simple
+4 -3
View File
@@ -15,9 +15,10 @@ parent = "smn_applied"
Docker allows you to run applications inside containers. Running an
application inside a container takes a single command: `docker run`.
> **Note:** if you are using a remote Docker daemon, such as Boot2Docker,
> then _do not_ type the `sudo` before the `docker` commands shown in the
> documentation's examples.
>**Note**: Depending on your Docker system configuration, you may be required to
>preface each `docker` command on this page with `sudo`. To avoid this behavior,
>your system administrator can create a Unix group called `docker` and add users
>to it.
## Hello world
+20 -15
View File
@@ -74,29 +74,34 @@ The output will provide details on the container configurations including the
volumes. The output should look something similar to the following:
...
"Volumes": {
"/webapp": "/var/lib/docker/volumes/fac362...80535"
},
"VolumesRW": {
"/webapp": true
}
Mounts": [
{
"Name": "fac362...80535",
"Source": "/var/lib/docker/volumes/fac362...80535/_data",
"Destination": "/webapp",
"Driver": "local",
"Mode": "",
"RW": true
}
]
...
You will notice in the above 'Volumes' is specifying the location on the host and
'VolumesRW' is specifying that the volume is read/write.
You will notice in the above 'Source' is specifying the location on the host and
'Destination' is specifying the volume location inside the container. `RW` shows
if the volume is read/write.
### Mount a host directory as a data volume
In addition to creating a volume using the `-v` flag you can also mount a
directory from your Docker daemon's host into a container.
> **Note:**
> If you are using Boot2Docker, your Docker daemon only has limited access to
> your OS X/Windows filesystem. Boot2Docker tries to auto-share your `/Users`
> (OS X) or `C:\Users` (Windows) directory - and so you can mount files or directories
> using `docker run -v /Users/<path>:/<container path> ...` (OS X) or
> `docker run -v /c/Users/<path>:/<container path ...` (Windows). All other paths
> come from the Boot2Docker virtual machine's filesystem.
>**Note**: If you are using Docker Machine on Mac or Windows, your Docker daemon
>only has limited access to your OS X/Windows filesystem. Docker Machine tries
>to auto-share your `/Users` (OS X) or `C:\Users` (Windows) directory - and so
>you can mount files or directories using `docker run -v
>/Users/<path>:/<container path> ...` (OS X) or `docker run -v
>/c/Users/<path>:/<container path ...` (Windows). All other paths come from your
>virtual machine's filesystem.
$ docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py
+53
View File
@@ -0,0 +1,53 @@
<!--[metadata]>
+++
alias = [ "/reference/api/hub_registry_spec/"]
title = "Image management"
description = "Documentation for docker Registry and Registry API"
keywords = ["docker, registry, api, hub"]
[menu.main]
parent="mn_docker_hub"
weight=-1
+++
<![end-metadata]-->
# Image management
The Docker Engine provides a client which you can use to create images on the command line or through a build process. You can run these images in a container or publish them for others to use. Storing the images you create, searching for images you might want, or publishing images others might use are all elements of image management.
This section provides an overview of the major features and products Docker provides for image management.
## Docker Hub
The [Docker Hub](https://docs.docker.com/docker-hub/) is responsible for centralizing information about user accounts, images, and public name spaces. It has different components:
- Web UI
- Meta-data store (comments, stars, list public repositories)
- Authentication service
- Tokenization
There is only one instance of the Docker Hub, run and managed by Docker Inc. This public Hub is useful for most individuals and smaller companies.
## Docker Registry and the Docker Trusted Registry
The Docker Registry is a component of Docker's ecosystem. A registry is a
storage and content delivery system, holding named Docker images, available in
different tagged versions. For example, the image `distribution/registry`, with
tags `2.0` and `latest`. Users interact with a registry by using docker push and
pull commands. For example, `docker pull myregistry.com/stevvooe/batman:voice`.
The Docker Hub has its own registry which, like the Hub itself, is run and managed by Docker. There are other ways to obtain a registry. You can purchase the [Docker Trusted Registry](https://docs.docker.com/dockter-trusted-registry) product to run on your company's network. Alternatively, you can use the Docker Registry component to build a private registry. For information about using a registry, see overview for the [Docker Registry](https://docs.docker.com/registry).
## Content Trust
When transferring data among networked systems, *trust* is a central concern. In
particular, when communicating over an untrusted medium such as the internet, it
is critical to ensure the integrity and publisher of the all the data a system
operates on. You use Docker to push and pull images (data) to a registry.
Content trust gives you the ability to both verify the integrity and the
publisher of all the data received from a registry over any channel.
[Content trust](/security/trust) is currently only available for users of the
public Docker Hub. It is currently not available for the Docker Trusted Registry
or for private registries.
+6 -6
View File
@@ -13,18 +13,18 @@ please feel free to provide any feedback on these features you wish.
Unlike the regular Docker binary, the experimental channels is built and updated nightly on TO.BE.ANNOUNCED. From one day to the next, new features may appear, while existing experimental features may be refined or entirely removed.
1. Verify that you have `wget` installed.
1. Verify that you have `curl` installed.
$ which wget
$ which curl
If `wget` isn't installed, install it after updating your manager:
If `curl` isn't installed, install it after updating your manager:
$ sudo apt-get update
$ sudo apt-get install wget
$ sudo apt-get install curl
2. Get the latest Docker package.
$ wget -qO- https://experimental.docker.com/ | sh
$ curl -sSL https://experimental.docker.com/ | sh
The system prompts you for your `sudo` password. Then, it downloads and
installs Docker and its dependencies.
@@ -34,7 +34,7 @@ Unlike the regular Docker binary, the experimental channels is built and updated
>command fails for the Docker repo during installation. To work around this,
>add the key directly using the following:
>
> $ wget -qO- https://experimental.docker.com/gpg | sudo apt-key add -
> $ curl -sSL https://experimental.docker.com/gpg | sudo apt-key add -
3. Verify `docker` is installed correctly.
+8 -9
View File
@@ -114,6 +114,8 @@ func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error {
// FIXME: this should be a top-level function, not a class method
func (s *TagStore) exportImage(name, tempdir string) error {
for n := name; n != ""; {
img, err := s.LookupImage(n)
// temporary directory
tmpImageDir := filepath.Join(tempdir, n)
if err := os.Mkdir(tmpImageDir, os.FileMode(0755)); err != nil {
@@ -130,15 +132,17 @@ func (s *TagStore) exportImage(name, tempdir string) error {
return err
}
imageInspectRaw, err := json.Marshal(img)
if err != nil {
return err
}
// serialize json
json, err := os.Create(filepath.Join(tmpImageDir, "json"))
if err != nil {
return err
}
imageInspectRaw, err := s.LookupRaw(n)
if err != nil {
return err
}
written, err := json.Write(imageInspectRaw)
if err != nil {
return err
@@ -156,11 +160,6 @@ func (s *TagStore) exportImage(name, tempdir string) error {
return err
}
// find parent
img, err := s.LookupImage(n)
if err != nil {
return err
}
n = img.Parent
}
return nil
@@ -0,0 +1,38 @@
{
"schemaVersion": 2,
"name": "library/hello-world",
"tag": "latest",
"architecture": "amd64",
"fsLayers": [
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
}
],
"history": [
{
"v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
},
{
"v1Compatibility": "{\"id\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.241352727Z\",\"container\":\"9aeb0006ffa72a8287564caaea87625896853701459261d3b569e320c0c9d5dc\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:4abd3bff60458ca3b079d7b131ce26b2719055a030dfa96ff827da2b7c7038a7 in /\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":960}\n"
}
],
"signatures": [
{
"header": {
"jwk": {
"crv": "P-256",
"kid": "OIH7:HQFS:44FK:45VB:3B53:OIAG:TPL4:ATF5:6PNE:MGHN:NHQX:2GE4",
"kty": "EC",
"x": "Cu_UyxwLgHzE9rvlYSmvVdqYCXY42E9eNhBb0xNv0SQ",
"y": "zUsjWJkeKQ5tv7S-hl1Tg71cd-CqnrtiiLxSi6N_yc8"
},
"alg": "ES256"
},
"signature": "Y6xaFz9Sy-OtcnKQS1Ilq3Dh8cu4h3nBTJCpOTF1XF7vKtcxxA_xMP8-SgDo869SJ3VsvgPL9-Xn-OoYG2rb1A",
"protected": "eyJmb3JtYXRMZW5ndGgiOjMxOTcsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wOS0xMVQwNDoxMzo0OFoifQ"
}
]
}
@@ -0,0 +1,46 @@
{
"schemaVersion": 1,
"name": "library/hello-world",
"tag": "latest",
"architecture": "amd64",
"fsLayers": [
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
}
],
"history": [
{
"v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
},
{
"v1Compatibility": "{\"id\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.241352727Z\",\"container\":\"9aeb0006ffa72a8287564caaea87625896853701459261d3b569e320c0c9d5dc\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:4abd3bff60458ca3b079d7b131ce26b2719055a030dfa96ff827da2b7c7038a7 in /\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":960}\n"
}
],
"fsLayers": [
{
"blobSum": "sha256:ffff95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:ffff658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
}
],
"signatures": [
{
"header": {
"jwk": {
"crv": "P-256",
"kid": "OIH7:HQFS:44FK:45VB:3B53:OIAG:TPL4:ATF5:6PNE:MGHN:NHQX:2GE4",
"kty": "EC",
"x": "Cu_UyxwLgHzE9rvlYSmvVdqYCXY42E9eNhBb0xNv0SQ",
"y": "zUsjWJkeKQ5tv7S-hl1Tg71cd-CqnrtiiLxSi6N_yc8"
},
"alg": "ES256"
},
"signature": "Y6xaFz9Sy-OtcnKQS1Ilq3Dh8cu4h3nBTJCpOTF1XF7vKtcxxA_xMP8-SgDo869SJ3VsvgPL9-Xn-OoYG2rb1A",
"protected": "eyJmb3JtYXRMZW5ndGgiOjMxOTcsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wOS0xMVQwNDoxMzo0OFoifQ"
}
]
}
@@ -0,0 +1,38 @@
{
"schemaVersion": 1,
"name": "library/hello-world",
"tag": "latest",
"architecture": "amd64",
"fsLayers": [
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
}
],
"history": [
{
"v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
},
{
"v1Compatibility": "{\"id\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.241352727Z\",\"container\":\"9aeb0006ffa72a8287564caaea87625896853701459261d3b569e320c0c9d5dc\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:4abd3bff60458ca3b079d7b131ce26b2719055a030dfa96ff827da2b7c7038a7 in /\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":960}\n"
}
],
"signatures": [
{
"header": {
"jwk": {
"crv": "P-256",
"kid": "OIH7:HQFS:44FK:45VB:3B53:OIAG:TPL4:ATF5:6PNE:MGHN:NHQX:2GE4",
"kty": "EC",
"x": "Cu_UyxwLgHzE9rvlYSmvVdqYCXY42E9eNhBb0xNv0SQ",
"y": "zUsjWJkeKQ5tv7S-hl1Tg71cd-CqnrtiiLxSi6N_yc8"
},
"alg": "ES256"
},
"signature": "Y6xaFz9Sy-OtcnKQS1Ilq3Dh8cu4h3nBTJCpOTF1XF7vKtcxxA_xMP8-SgDo869SJ3VsvgPL9-Xn-OoYG2rb1A",
"protected": "eyJmb3JtYXRMZW5ndGgiOjMxOTcsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wOS0xMVQwNDoxMzo0OFoifQ"
}
]
}
@@ -0,0 +1,22 @@
{
"schemaVersion": 1,
"name": "library/hello-world",
"tag": "latest",
"architecture": "amd64",
"fsLayers": [
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:03f4658f8b782e12230c1783426bd3bacce651ce582a4ffb6fbbfa2079428ecb"
}
],
"history": [
{
"v1Compatibility": "{\"id\":\"af340544ed62de0680f441c71fa1a80cb084678fed42bae393e543faea3a572c\",\"parent\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.608577814Z\",\"container\":\"c2b715156f640c7ac7d98472ea24335aba5432a1323a3bb722697e6d37ef794f\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/hello\\\"]\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/hello\"],\"Image\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
},
{
"v1Compatibility": "{\"id\":\"535020c3e8add9d6bb06e5ac15a261e73d9b213d62fb2c14d752b8e189b2b912\",\"created\":\"2015-08-06T23:53:22.241352727Z\",\"container\":\"9aeb0006ffa72a8287564caaea87625896853701459261d3b569e320c0c9d5dc\",\"container_config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) COPY file:4abd3bff60458ca3b079d7b131ce26b2719055a030dfa96ff827da2b7c7038a7 in /\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.7.1\",\"config\":{\"Hostname\":\"9aeb0006ffa7\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":960}\n"
}
]
}
+171 -30
View File
@@ -32,6 +32,26 @@ import (
"github.com/vbatts/tar-split/tar/storage"
)
// v1ImageDescriptor is a non-content-addressable image descriptor
type v1ImageDescriptor struct {
img *image.Image
}
// ID returns the image ID specified in the image structure.
func (img v1ImageDescriptor) ID() string {
return img.img.ID
}
// Parent returns the parent ID specified in the image structure.
func (img v1ImageDescriptor) Parent() string {
return img.img.Parent
}
// MarshalConfig renders the image structure into JSON.
func (img v1ImageDescriptor) MarshalConfig() ([]byte, error) {
return json.Marshal(img.img)
}
// The type is used to protect pulling or building related image
// layers from deleteing when filtered by dangling=true
// The key of layers is the images ID which is pulling or building
@@ -86,10 +106,12 @@ type Graph struct {
// file names for ./graph/<ID>/
const (
jsonFileName = "json"
layersizeFileName = "layersize"
digestFileName = "checksum"
tarDataFileName = "tar-data.json.gz"
jsonFileName = "json"
layersizeFileName = "layersize"
digestFileName = "checksum"
tarDataFileName = "tar-data.json.gz"
v1CompatibilityFileName = "v1Compatibility"
parentFileName = "parent"
)
var (
@@ -214,23 +236,36 @@ func (graph *Graph) Create(layerData archive.ArchiveReader, containerID, contain
img.ContainerConfig = *containerConfig
}
if err := graph.Register(img, layerData); err != nil {
if err := graph.Register(v1ImageDescriptor{img}, layerData); err != nil {
return nil, err
}
return img, nil
}
// Register imports a pre-existing image into the graph.
func (graph *Graph) Register(img *image.Image, layerData archive.ArchiveReader) (err error) {
// Returns nil if the image is already registered.
func (graph *Graph) Register(im image.ImageDescriptor, layerData archive.ArchiveReader) (err error) {
imgID := im.ID()
if err := image.ValidateID(img.ID); err != nil {
if err := image.ValidateID(imgID); err != nil {
return err
}
// We need this entire operation to be atomic within the engine. Note that
// this doesn't mean Register is fully safe yet.
graph.imageMutex.Lock(img.ID)
defer graph.imageMutex.Unlock(img.ID)
graph.imageMutex.Lock(imgID)
defer graph.imageMutex.Unlock(imgID)
return graph.register(im, layerData)
}
func (graph *Graph) register(im image.ImageDescriptor, layerData archive.ArchiveReader) (err error) {
imgID := im.ID()
// Skip register if image is already registered
if graph.Exists(imgID) {
return nil
}
// The returned `error` must be named in this function's signature so that
// `err` is not shadowed in this deferred cleanup.
@@ -238,19 +273,14 @@ func (graph *Graph) Register(img *image.Image, layerData archive.ArchiveReader)
// If any error occurs, remove the new dir from the driver.
// Don't check for errors since the dir might not have been created.
if err != nil {
graph.driver.Remove(img.ID)
graph.driver.Remove(imgID)
}
}()
// (This is a convenience to save time. Race conditions are taken care of by os.Rename)
if graph.Exists(img.ID) {
return fmt.Errorf("Image %s already exists", img.ID)
}
// Ensure that the image root does not exist on the filesystem
// when it is not registered in the graph.
// This is common when you switch from one graph driver to another
if err := os.RemoveAll(graph.imageRoot(img.ID)); err != nil && !os.IsNotExist(err) {
if err := os.RemoveAll(graph.imageRoot(imgID)); err != nil && !os.IsNotExist(err) {
return err
}
@@ -258,7 +288,7 @@ func (graph *Graph) Register(img *image.Image, layerData archive.ArchiveReader)
// (the graph is the source of truth).
// Ignore errors, since we don't know if the driver correctly returns ErrNotExist.
// (FIXME: make that mandatory for drivers).
graph.driver.Remove(img.ID)
graph.driver.Remove(imgID)
tmp, err := graph.mktemp("")
defer os.RemoveAll(tmp)
@@ -266,20 +296,33 @@ func (graph *Graph) Register(img *image.Image, layerData archive.ArchiveReader)
return fmt.Errorf("mktemp failed: %s", err)
}
parent := im.Parent()
// Create root filesystem in the driver
if err := createRootFilesystemInDriver(graph, img, layerData); err != nil {
if err := createRootFilesystemInDriver(graph, imgID, parent, layerData); err != nil {
return err
}
// Apply the diff/layer
if err := graph.storeImage(img, layerData, tmp); err != nil {
config, err := im.MarshalConfig()
if err != nil {
return err
}
if err := graph.storeImage(imgID, parent, config, layerData, tmp); err != nil {
return err
}
// Commit
if err := os.Rename(tmp, graph.imageRoot(img.ID)); err != nil {
if err := os.Rename(tmp, graph.imageRoot(imgID)); err != nil {
return err
}
graph.idIndex.Add(img.ID)
graph.idIndex.Add(imgID)
return nil
}
func createRootFilesystemInDriver(graph *Graph, id, parent string, layerData archive.ArchiveReader) error {
if err := graph.driver.Create(id, parent); err != nil {
return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, id, err)
}
return nil
}
@@ -462,6 +505,21 @@ func (graph *Graph) loadImage(id string) (*image.Image, error) {
if err := dec.Decode(img); err != nil {
return nil, err
}
if img.ID == "" {
img.ID = id
}
if img.Parent == "" && img.ParentID != "" && img.ParentID.Validate() == nil {
img.Parent = img.ParentID.Hex()
}
// compatibilityID for parent
parent, err := ioutil.ReadFile(filepath.Join(root, parentFileName))
if err == nil && len(parent) > 0 {
img.Parent = string(parent)
}
if err := image.ValidateID(img.ID); err != nil {
return nil, err
}
@@ -496,7 +554,13 @@ func (graph *Graph) saveSize(root string, size int) error {
}
// SetDigest sets the digest for the image layer to the provided value.
func (graph *Graph) SetDigest(id string, dgst digest.Digest) error {
func (graph *Graph) SetLayerDigest(id string, dgst digest.Digest) error {
graph.imageMutex.Lock(id)
defer graph.imageMutex.Unlock(id)
return graph.setLayerDigest(id, dgst)
}
func (graph *Graph) setLayerDigest(id string, dgst digest.Digest) error {
root := graph.imageRoot(id)
if err := ioutil.WriteFile(filepath.Join(root, digestFileName), []byte(dgst.String()), 0600); err != nil {
return fmt.Errorf("Error storing digest in %s/%s: %s", root, digestFileName, err)
@@ -505,7 +569,14 @@ func (graph *Graph) SetDigest(id string, dgst digest.Digest) error {
}
// GetDigest gets the digest for the provide image layer id.
func (graph *Graph) GetDigest(id string) (digest.Digest, error) {
func (graph *Graph) GetLayerDigest(id string) (digest.Digest, error) {
graph.imageMutex.Lock(id)
defer graph.imageMutex.Unlock(id)
return graph.getLayerDigest(id)
}
func (graph *Graph) getLayerDigest(id string) (digest.Digest, error) {
root := graph.imageRoot(id)
cs, err := ioutil.ReadFile(filepath.Join(root, digestFileName))
if err != nil {
@@ -517,6 +588,76 @@ func (graph *Graph) GetDigest(id string) (digest.Digest, error) {
return digest.ParseDigest(string(cs))
}
// SetV1CompatibilityConfig stores the v1Compatibility JSON data associated
// with the image in the manifest to the disk
func (graph *Graph) SetV1CompatibilityConfig(id string, data []byte) error {
graph.imageMutex.Lock(id)
defer graph.imageMutex.Unlock(id)
return graph.setV1CompatibilityConfig(id, data)
}
func (graph *Graph) setV1CompatibilityConfig(id string, data []byte) error {
root := graph.imageRoot(id)
return ioutil.WriteFile(filepath.Join(root, v1CompatibilityFileName), data, 0600)
}
// GetV1CompatibilityConfig reads the v1Compatibility JSON data for the image
// from the disk
func (graph *Graph) GetV1CompatibilityConfig(id string) ([]byte, error) {
graph.imageMutex.Lock(id)
defer graph.imageMutex.Unlock(id)
return graph.getV1CompatibilityConfig(id)
}
func (graph *Graph) getV1CompatibilityConfig(id string) ([]byte, error) {
root := graph.imageRoot(id)
return ioutil.ReadFile(filepath.Join(root, v1CompatibilityFileName))
}
// GenerateV1CompatibilityChain makes sure v1Compatibility JSON data exists
// for the image. If it doesn't it generates and stores it for the image and
// all of it's parents based on the image config JSON.
func (graph *Graph) GenerateV1CompatibilityChain(id string) ([]byte, error) {
graph.imageMutex.Lock(id)
defer graph.imageMutex.Unlock(id)
if v1config, err := graph.getV1CompatibilityConfig(id); err == nil {
return v1config, nil
}
// generate new, store it to disk
img, err := graph.Get(id)
if err != nil {
return nil, err
}
digestPrefix := string(digest.Canonical) + ":"
img.ID = strings.TrimPrefix(img.ID, digestPrefix)
if img.Parent != "" {
parentConfig, err := graph.GenerateV1CompatibilityChain(img.Parent)
if err != nil {
return nil, err
}
var parent struct{ ID string }
err = json.Unmarshal(parentConfig, &parent)
if err != nil {
return nil, err
}
img.Parent = parent.ID
}
json, err := json.Marshal(img)
if err != nil {
return nil, err
}
if err := graph.setV1CompatibilityConfig(id, json); err != nil {
return nil, err
}
return json, nil
}
// RawJSON returns the JSON representation for an image as a byte array.
func (graph *Graph) RawJSON(id string) ([]byte, error) {
root := graph.imageRoot(id)
@@ -533,11 +674,11 @@ func jsonPath(root string) string {
return filepath.Join(root, jsonFileName)
}
func (graph *Graph) disassembleAndApplyTarLayer(img *image.Image, layerData archive.ArchiveReader, root string) error {
func (graph *Graph) disassembleAndApplyTarLayer(id, parent string, layerData archive.ArchiveReader, root string) (size int64, err error) {
// this is saving the tar-split metadata
mf, err := os.OpenFile(filepath.Join(root, tarDataFileName), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
if err != nil {
return err
return 0, err
}
mfz := gzip.NewWriter(mf)
metaPacker := storage.NewJSONPacker(mfz)
@@ -546,21 +687,21 @@ func (graph *Graph) disassembleAndApplyTarLayer(img *image.Image, layerData arch
inflatedLayerData, err := archive.DecompressStream(layerData)
if err != nil {
return err
return 0, err
}
// we're passing nil here for the file putter, because the ApplyDiff will
// handle the extraction of the archive
rdr, err := asm.NewInputTarStream(inflatedLayerData, metaPacker, nil)
if err != nil {
return err
return 0, err
}
if img.Size, err = graph.driver.ApplyDiff(img.ID, img.Parent, archive.ArchiveReader(rdr)); err != nil {
return err
if size, err = graph.driver.ApplyDiff(id, parent, archive.ArchiveReader(rdr)); err != nil {
return 0, err
}
return nil
return
}
func (graph *Graph) assembleTarLayer(img *image.Image) (archive.Archive, error) {
+17 -7
View File
@@ -73,7 +73,7 @@ func TestInterruptedRegister(t *testing.T) {
Created: time.Now(),
}
w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling)
graph.Register(image, badArchive)
graph.Register(v1ImageDescriptor{image}, badArchive)
if _, err := graph.Get(image.ID); err == nil {
t.Fatal("Image should not exist after Register is interrupted")
}
@@ -82,7 +82,7 @@ func TestInterruptedRegister(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := graph.Register(image, goodArchive); err != nil {
if err := graph.Register(v1ImageDescriptor{image}, goodArchive); err != nil {
t.Fatal(err)
}
}
@@ -130,7 +130,7 @@ func TestRegister(t *testing.T) {
Comment: "testing",
Created: time.Now(),
}
err = graph.Register(image, archive)
err = graph.Register(v1ImageDescriptor{image}, archive)
if err != nil {
t.Fatal(err)
}
@@ -212,7 +212,7 @@ func TestDelete(t *testing.T) {
t.Fatal(err)
}
// Test delete twice (pull -> rm -> pull -> rm)
if err := graph.Register(img1, archive); err != nil {
if err := graph.Register(v1ImageDescriptor{img1}, archive); err != nil {
t.Fatal(err)
}
if err := graph.Delete(img1.ID); err != nil {
@@ -246,9 +246,19 @@ func TestByParent(t *testing.T) {
Created: time.Now(),
Parent: parentImage.ID,
}
_ = graph.Register(parentImage, archive1)
_ = graph.Register(childImage1, archive2)
_ = graph.Register(childImage2, archive3)
err := graph.Register(v1ImageDescriptor{parentImage}, archive1)
if err != nil {
t.Fatal(err)
}
err = graph.Register(v1ImageDescriptor{childImage1}, archive2)
if err != nil {
t.Fatal(err)
}
err = graph.Register(v1ImageDescriptor{childImage2}, archive3)
if err != nil {
t.Fatal(err)
}
byParent := graph.ByParent()
numChildren := len(byParent[parentImage.ID])
+17 -16
View File
@@ -3,8 +3,7 @@
package graph
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -74,13 +73,6 @@ func SetupInitLayer(initLayer string) error {
return nil
}
func createRootFilesystemInDriver(graph *Graph, img *image.Image, layerData archive.ArchiveReader) error {
if err := graph.driver.Create(img.ID, img.Parent); err != nil {
return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err)
}
return nil
}
func (graph *Graph) restoreBaseImages() ([]string, error) {
return nil, nil
}
@@ -88,26 +80,35 @@ func (graph *Graph) restoreBaseImages() ([]string, error) {
// storeImage stores file system layer data for the given image to the
// graph's storage driver. Image metadata is stored in a file
// at the specified root directory.
func (graph *Graph) storeImage(img *image.Image, layerData archive.ArchiveReader, root string) (err error) {
func (graph *Graph) storeImage(id, parent string, config []byte, layerData archive.ArchiveReader, root string) (err error) {
var size int64
// Store the layer. If layerData is not nil, unpack it into the new layer
if layerData != nil {
if err := graph.disassembleAndApplyTarLayer(img, layerData, root); err != nil {
if size, err = graph.disassembleAndApplyTarLayer(id, parent, layerData, root); err != nil {
return err
}
}
if err := graph.saveSize(root, int(img.Size)); err != nil {
if err := graph.saveSize(root, int(size)); err != nil {
return err
}
f, err := os.OpenFile(jsonPath(root), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
if err := ioutil.WriteFile(jsonPath(root), config, 0600); err != nil {
return err
}
// If image is pointing to a parent via CompatibilityID write the reference to disk
img, err := image.NewImgJSON(config)
if err != nil {
return err
}
defer f.Close()
return json.NewEncoder(f).Encode(img)
if img.ParentID.Validate() == nil && parent != img.ParentID.Hex() {
if err := ioutil.WriteFile(filepath.Join(root, parentFileName), []byte(parent), 0600); err != nil {
return err
}
}
return nil
}
// TarLayer returns a tar archive of the image's filesystem layer.
+28 -69
View File
@@ -3,9 +3,8 @@
package graph
import (
"encoding/json"
"fmt"
"os"
"io/ioutil"
"path/filepath"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/graphdriver/windows"
@@ -19,41 +18,6 @@ func SetupInitLayer(initLayer string) error {
return nil
}
func createRootFilesystemInDriver(graph *Graph, img *image.Image, layerData archive.ArchiveReader) error {
if wd, ok := graph.driver.(*windows.WindowsGraphDriver); ok {
if img.Container != "" && layerData == nil {
logrus.Debugf("Copying from container %s.", img.Container)
var ids []string
if img.Parent != "" {
parentImg, err := graph.Get(img.Parent)
if err != nil {
return err
}
ids, err = graph.ParentLayerIds(parentImg)
if err != nil {
return err
}
}
if err := wd.CopyDiff(img.Container, img.ID, wd.LayerIdsToPaths(ids)); err != nil {
return fmt.Errorf("Driver %s failed to copy image rootfs %s: %s", graph.driver, img.Container, err)
}
} else if img.Parent == "" {
if err := graph.driver.Create(img.ID, img.Parent); err != nil {
return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err)
}
}
} else {
// This fallback allows the use of VFS during daemon development.
if err := graph.driver.Create(img.ID, img.Parent); err != nil {
return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err)
}
}
return nil
}
func (graph *Graph) restoreBaseImages() ([]string, error) {
// TODO Windows. This needs implementing (@swernli)
return nil, nil
@@ -71,42 +35,28 @@ func (graph *Graph) ParentLayerIds(img *image.Image) (ids []string, err error) {
// storeImage stores file system layer data for the given image to the
// graph's storage driver. Image metadata is stored in a file
// at the specified root directory.
func (graph *Graph) storeImage(img *image.Image, layerData archive.ArchiveReader, root string) (err error) {
func (graph *Graph) storeImage(id, parent string, config []byte, layerData archive.ArchiveReader, root string) (err error) {
var size int64
if wd, ok := graph.driver.(*windows.WindowsGraphDriver); ok {
// Store the layer. If layerData is not nil and this isn't a base image,
// unpack it into the new layer
if layerData != nil && img.Parent != "" {
if layerData != nil && parent != "" {
var ids []string
if img.Parent != "" {
parentImg, err := graph.Get(img.Parent)
if err != nil {
return err
}
ids, err = graph.ParentLayerIds(parentImg)
if err != nil {
return err
}
parentImg, err := graph.Get(parent)
if err != nil {
return err
}
if img.Size, err = wd.Import(img.ID, layerData, wd.LayerIdsToPaths(ids)); err != nil {
ids, err = graph.ParentLayerIds(parentImg)
if err != nil {
return err
}
if size, err = wd.Import(id, layerData, wd.LayerIdsToPaths(ids)); err != nil {
return err
}
}
if err := graph.saveSize(root, int(img.Size)); err != nil {
return err
}
f, err := os.OpenFile(jsonPath(root), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
if err != nil {
return err
}
defer f.Close()
return json.NewEncoder(f).Encode(img)
} else {
// We keep this functionality here so that we can still work with the
// VFS driver during development. This will not be used for actual running
@@ -115,23 +65,32 @@ func (graph *Graph) storeImage(img *image.Image, layerData archive.ArchiveReader
// Store the layer. If layerData is not nil, unpack it into the new layer
if layerData != nil {
if err := graph.disassembleAndApplyTarLayer(img, layerData, root); err != nil {
if size, err = graph.disassembleAndApplyTarLayer(id, parent, layerData, root); err != nil {
return err
}
}
if err := graph.saveSize(root, int(img.Size)); err != nil {
if err := graph.saveSize(root, size); err != nil {
return err
}
f, err := os.OpenFile(jsonPath(root), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
if err := ioutil.WriteFile(jsonPath(root), config, 0600); err != nil {
return err
}
// If image is pointing to a parent via CompatibilityID write the reference to disk
img, err := image.NewImgJSON(config)
if err != nil {
return err
}
defer f.Close()
if img.ParentID.Validate() == nil && parent != img.ParentID.Hex() {
if err := ioutil.WriteFile(filepath.Join(root, parentFileName), []byte(parent), 0600); err != nil {
return err
}
}
return json.NewEncoder(f).Encode(img)
return nil
}
}
+1 -1
View File
@@ -125,7 +125,7 @@ func (s *TagStore) recursiveLoad(address, tmpImageDir string) error {
}
}
}
if err := s.graph.Register(img, layer); err != nil {
if err := s.graph.Register(v1ImageDescriptor{img}, layer); err != nil {
return err
}
}
+1 -7
View File
@@ -61,7 +61,7 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf
return err
}
endpoints, err := s.registryService.LookupEndpoints(repoInfo.CanonicalName)
endpoints, err := s.registryService.LookupPullEndpoints(repoInfo.CanonicalName)
if err != nil {
return err
}
@@ -86,12 +86,6 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf
for _, endpoint := range endpoints {
logrus.Debugf("Trying to pull %s from %s %s", repoInfo.LocalName, endpoint.URL, endpoint.Version)
if !endpoint.Mirror && (endpoint.Official || endpoint.Version == registry.APIVersion2) {
if repoInfo.Official {
s.trustService.UpdateBase()
}
}
puller, err := NewPuller(s, endpoint, repoInfo, imagePullConfig, sf)
if err != nil {
lastErr = err
+11 -3
View File
@@ -59,6 +59,9 @@ func (p *v1Puller) Pull(tag string) (fallback bool, err error) {
// TODO(dmcgowan): Check if should fallback
return false, err
}
out := p.config.OutStream
out.Write(p.sf.FormatStatus("", "%s: this image was pulled from a legacy registry. Important: This registry version will not be supported in future versions of docker.", p.repoInfo.CanonicalName))
return false, nil
}
@@ -123,7 +126,7 @@ func (p *v1Puller) pullRepository(askedTag string) error {
defer func() {
p.graph.Release(sessionID, imgIDs...)
}()
for _, image := range repoData.ImgList {
for _, imgData := range repoData.ImgList {
downloadImage := func(img *registry.ImgData) {
if askedTag != "" && img.Tag != askedTag {
errors <- nil
@@ -136,6 +139,11 @@ func (p *v1Puller) pullRepository(askedTag string) error {
return
}
if err := image.ValidateID(img.ID); err != nil {
errors <- err
return
}
// ensure no two downloads of the same image happen at the same time
if c, err := p.poolAdd("pull", "img:"+img.ID); err != nil {
if c != nil {
@@ -196,7 +204,7 @@ func (p *v1Puller) pullRepository(askedTag string) error {
errors <- nil
}
go downloadImage(image)
go downloadImage(imgData)
}
var lastError error
@@ -304,7 +312,7 @@ func (p *v1Puller) pullImage(imgID, endpoint string, token []string) (bool, erro
layersDownloaded = true
defer layer.Close()
err = p.graph.Register(img,
err = p.graph.Register(v1ImageDescriptor{img},
progressreader.New(progressreader.Config{
In: layer,
Out: out,
+422 -122
View File
@@ -1,10 +1,13 @@
package graph
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution"
@@ -15,9 +18,7 @@ import (
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/registry"
"github.com/docker/docker/trust"
"github.com/docker/docker/utils"
"github.com/docker/libtrust"
"golang.org/x/net/context"
)
@@ -72,17 +73,18 @@ func (p *v2Puller) pullV2Repository(tag string) (err error) {
}
c, err := p.poolAdd("pull", taggedName)
poolKey := "v2:" + taggedName
c, err := p.poolAdd("pull", poolKey)
if err != nil {
if c != nil {
// Another pull of the same repository is already taking place; just wait for it to finish
p.sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", p.repoInfo.CanonicalName)
p.config.OutStream.Write(p.sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", p.repoInfo.CanonicalName))
<-c
return nil
}
return err
}
defer p.poolRemove("pull", taggedName)
defer p.poolRemove("pull", poolKey)
var layersDownloaded bool
for _, tag := range tags {
@@ -102,12 +104,67 @@ func (p *v2Puller) pullV2Repository(tag string) (err error) {
// downloadInfo is used to pass information from download to extractor
type downloadInfo struct {
img *image.Image
img contentAddressableDescriptor
tmpFile *os.File
digest digest.Digest
layer distribution.ReadSeekCloser
size int64
err chan error
out io.Writer // Download progress is written here.
}
// contentAddressableDescriptor is used to pass image data from a manifest to the
// graph.
type contentAddressableDescriptor struct {
id string
parent string
strongID digest.Digest
compatibilityID string
config []byte
v1Compatibility []byte
}
func newContentAddressableImage(v1Compatibility []byte, blobSum digest.Digest, parent digest.Digest) (contentAddressableDescriptor, error) {
img := contentAddressableDescriptor{
v1Compatibility: v1Compatibility,
}
var err error
img.config, err = image.MakeImageConfig(v1Compatibility, blobSum, parent)
if err != nil {
return img, err
}
img.strongID, err = image.StrongID(img.config)
if err != nil {
return img, err
}
unmarshalledConfig, err := image.NewImgJSON(v1Compatibility)
if err != nil {
return img, err
}
img.compatibilityID = unmarshalledConfig.ID
img.id = img.strongID.Hex()
return img, nil
}
// ID returns the actual ID to be used for the downloaded image. This may be
// a computed ID.
func (img contentAddressableDescriptor) ID() string {
return img.id
}
// Parent returns the parent ID to be used for the image. This may be a
// computed ID.
func (img contentAddressableDescriptor) Parent() string {
return img.parent
}
// MarshalConfig renders the image structure into JSON.
func (img contentAddressableDescriptor) MarshalConfig() ([]byte, error) {
return img.config, nil
}
type errVerification struct{}
@@ -115,32 +172,33 @@ type errVerification struct{}
func (errVerification) Error() string { return "verification failed" }
func (p *v2Puller) download(di *downloadInfo) {
logrus.Debugf("pulling blob %q to %s", di.digest, di.img.ID)
logrus.Debugf("pulling blob %q to %s", di.digest, di.img.id)
out := p.config.OutStream
out := di.out
if c, err := p.poolAdd("pull", "img:"+di.img.ID); err != nil {
poolKey := "v2img:" + di.img.id
if c, err := p.poolAdd("pull", poolKey); err != nil {
if c != nil {
out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.ID), "Layer already being pulled by another client. Waiting.", nil))
out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.id), "Layer already being pulled by another client. Waiting.", nil))
<-c
out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.ID), "Download complete", nil))
out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.id), "Download complete", nil))
} else {
logrus.Debugf("Image (id: %s) pull is already running, skipping: %v", di.img.ID, err)
logrus.Debugf("Image (id: %s) pull is already running, skipping: %v", di.img.id, err)
}
di.err <- nil
return
}
defer p.poolRemove("pull", "img:"+di.img.ID)
defer p.poolRemove("pull", poolKey)
tmpFile, err := ioutil.TempFile("", "GetImageBlob")
if err != nil {
di.err <- err
return
}
blobs := p.repo.Blobs(nil)
blobs := p.repo.Blobs(context.Background())
desc, err := blobs.Stat(nil, di.digest)
desc, err := blobs.Stat(context.Background(), di.digest)
if err != nil {
logrus.Debugf("Error statting layer: %v", err)
di.err <- err
@@ -148,7 +206,7 @@ func (p *v2Puller) download(di *downloadInfo) {
}
di.size = desc.Size
layerDownload, err := blobs.Open(nil, di.digest)
layerDownload, err := blobs.Open(context.Background(), di.digest)
if err != nil {
logrus.Debugf("Error fetching layer: %v", err)
di.err <- err
@@ -168,12 +226,12 @@ func (p *v2Puller) download(di *downloadInfo) {
Formatter: p.sf,
Size: int(di.size),
NewLines: false,
ID: stringid.TruncateID(di.img.ID),
ID: stringid.TruncateID(di.img.id),
Action: "Downloading",
})
io.Copy(tmpFile, reader)
out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.ID), "Verifying Checksum", nil))
out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.id), "Verifying Checksum", nil))
if !verifier.Verified() {
err = fmt.Errorf("filesystem layer verification failed for digest %s", di.digest)
@@ -182,16 +240,16 @@ func (p *v2Puller) download(di *downloadInfo) {
return
}
out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.ID), "Download complete", nil))
out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.id), "Download complete", nil))
logrus.Debugf("Downloaded %s to tempfile %s", di.img.ID, tmpFile.Name())
logrus.Debugf("Downloaded %s to tempfile %s", di.img.id, tmpFile.Name())
di.tmpFile = tmpFile
di.layer = layerDownload
di.err <- nil
}
func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) {
func (p *v2Puller) pullV2Tag(tag, taggedName string) (tagUpdated bool, err error) {
logrus.Debugf("Pulling tag from V2 registry: %q", tag)
out := p.config.OutStream
@@ -200,52 +258,96 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) {
return false, err
}
manifest, err := manSvc.GetByTag(tag)
unverifiedManifest, err := manSvc.GetByTag(tag)
if err != nil {
return false, err
}
verified, err := p.validateManifest(manifest, tag)
if unverifiedManifest == nil {
return false, fmt.Errorf("image manifest does not exist for tag %q", tag)
}
var verifiedManifest *manifest.Manifest
verifiedManifest, err = verifyManifest(unverifiedManifest, tag)
if err != nil {
return false, err
}
if verified {
logrus.Printf("Image manifest for %s has been verified", taggedName)
// remove duplicate layers and check parent chain validity
err = fixManifestLayers(verifiedManifest)
if err != nil {
return false, err
}
imgs, err := p.getImageInfos(*verifiedManifest)
if err != nil {
return false, err
}
// By using a pipeWriter for each of the downloads to write their progress
// to, we can avoid an issue where this function returns an error but
// leaves behind running download goroutines. By splitting the writer
// with a pipe, we can close the pipe if there is any error, consequently
// causing each download to cancel due to an error writing to this pipe.
pipeReader, pipeWriter := io.Pipe()
go func() {
if _, err := io.Copy(out, pipeReader); err != nil {
logrus.Errorf("error copying from layer download progress reader: %s", err)
if err := pipeReader.CloseWithError(err); err != nil {
logrus.Errorf("error closing the progress reader: %s", err)
}
}
}()
defer func() {
if err != nil {
// All operations on the pipe are synchronous. This call will wait
// until all current readers/writers are done using the pipe then
// set the error. All successive reads/writes will return with this
// error.
pipeWriter.CloseWithError(fmt.Errorf("download canceled %+v", err))
} else {
// If no error then just close the pipe.
pipeWriter.Close()
}
}()
out.Write(p.sf.FormatStatus(tag, "Pulling from %s", p.repo.Name()))
downloads := make([]downloadInfo, len(manifest.FSLayers))
downloads := make([]downloadInfo, len(verifiedManifest.FSLayers))
layerIDs := []string{}
defer func() {
p.graph.Release(p.sessionID, layerIDs...)
}()
for i := len(manifest.FSLayers) - 1; i >= 0; i-- {
img, err := image.NewImgJSON([]byte(manifest.History[i].V1Compatibility))
if err != nil {
logrus.Debugf("error getting image v1 json: %v", err)
return false, err
}
downloads[i].img = img
downloads[i].digest = manifest.FSLayers[i].BlobSum
for i := len(verifiedManifest.FSLayers) - 1; i >= 0; i-- {
p.graph.Retain(p.sessionID, img.ID)
layerIDs = append(layerIDs, img.ID)
img := imgs[i]
downloads[i].img = img
downloads[i].digest = verifiedManifest.FSLayers[i].BlobSum
p.graph.Retain(p.sessionID, img.id)
layerIDs = append(layerIDs, img.id)
p.graph.imageMutex.Lock(img.id)
// Check if exists
if p.graph.Exists(img.ID) {
logrus.Debugf("Image already exists: %s", img.ID)
if p.graph.Exists(img.id) {
if err := p.validateImageInGraph(img.id, imgs, i); err != nil {
p.graph.imageMutex.Unlock(img.id)
return false, fmt.Errorf("image validation failed: %v", err)
}
logrus.Debugf("Image already exists: %s", img.id)
p.graph.imageMutex.Unlock(img.id)
continue
}
p.graph.imageMutex.Unlock(img.id)
out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pulling fs layer", nil))
out.Write(p.sf.FormatProgress(stringid.TruncateID(img.id), "Pulling fs layer", nil))
downloads[i].err = make(chan error)
downloads[i].err = make(chan error, 1)
downloads[i].out = pipeWriter
go p.download(&downloads[i])
}
var tagUpdated bool
for i := len(downloads) - 1; i >= 0; i-- {
d := &downloads[i]
if d.err != nil {
@@ -259,36 +361,58 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) {
defer d.tmpFile.Close()
d.tmpFile.Seek(0, 0)
if d.tmpFile != nil {
err := func() error {
reader := progressreader.New(progressreader.Config{
In: d.tmpFile,
Out: out,
Formatter: p.sf,
Size: int(d.size),
NewLines: false,
ID: stringid.TruncateID(d.img.id),
Action: "Extracting",
})
reader := progressreader.New(progressreader.Config{
In: d.tmpFile,
Out: out,
Formatter: p.sf,
Size: int(d.size),
NewLines: false,
ID: stringid.TruncateID(d.img.ID),
Action: "Extracting",
})
p.graph.imageMutex.Lock(d.img.id)
defer p.graph.imageMutex.Unlock(d.img.id)
err = p.graph.Register(d.img, reader)
// Must recheck the data on disk if any exists.
// This protects against races where something
// else is written to the graph under this ID
// after attemptIDReuse.
if p.graph.Exists(d.img.id) {
if err := p.validateImageInGraph(d.img.id, imgs, i); err != nil {
return fmt.Errorf("image validation failed: %v", err)
}
}
if err := p.graph.register(d.img, reader); err != nil {
return err
}
if err := p.graph.setLayerDigest(d.img.id, d.digest); err != nil {
return err
}
if err := p.graph.setV1CompatibilityConfig(d.img.id, d.img.v1Compatibility); err != nil {
return err
}
return nil
}()
if err != nil {
return false, err
}
if err := p.graph.SetDigest(d.img.ID, d.digest); err != nil {
return false, err
}
// FIXME: Pool release here for parallel tag pull (ensures any downloads block until fully extracted)
}
out.Write(p.sf.FormatProgress(stringid.TruncateID(d.img.ID), "Pull complete", nil))
out.Write(p.sf.FormatProgress(stringid.TruncateID(d.img.id), "Pull complete", nil))
tagUpdated = true
} else {
out.Write(p.sf.FormatProgress(stringid.TruncateID(d.img.ID), "Already exists", nil))
out.Write(p.sf.FormatProgress(stringid.TruncateID(d.img.id), "Already exists", nil))
}
}
manifestDigest, _, err := digestFromManifest(manifest, p.repoInfo.LocalName)
manifestDigest, _, err := digestFromManifest(unverifiedManifest, p.repoInfo.LocalName)
if err != nil {
return false, err
}
@@ -308,21 +432,17 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) {
}
}
if verified && tagUpdated {
out.Write(p.sf.FormatStatus(p.repo.Name()+":"+tag, "The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security."))
}
if utils.DigestReference(tag) {
// TODO(stevvooe): Ideally, we should always set the digest so we can
// use the digest whether we pull by it or not. Unfortunately, the tag
// store treats the digest as a separate tag, meaning there may be an
// untagged digest image that would seem to be dangling by a user.
if err = p.SetDigest(p.repoInfo.LocalName, tag, downloads[0].img.ID); err != nil {
if err = p.SetDigest(p.repoInfo.LocalName, tag, downloads[0].img.id); err != nil {
return false, err
}
} else {
// only set the repository/tag -> image ID mapping when pulling by tag (i.e. not by digest)
if err = p.Tag(p.repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil {
if err = p.Tag(p.repoInfo.LocalName, tag, downloads[0].img.id, true); err != nil {
return false, err
}
}
@@ -334,82 +454,262 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) {
return tagUpdated, nil
}
// verifyTrustedKeys checks the keys provided against the trust store,
// ensuring that the provided keys are trusted for the namespace. The keys
// provided from this method must come from the signatures provided as part of
// the manifest JWS package, obtained from unpackSignedManifest or libtrust.
func (p *v2Puller) verifyTrustedKeys(namespace string, keys []libtrust.PublicKey) (verified bool, err error) {
if namespace[0] != '/' {
namespace = "/" + namespace
}
for _, key := range keys {
b, err := key.MarshalJSON()
if err != nil {
return false, fmt.Errorf("error marshalling public key: %s", err)
}
// Check key has read/write permission (0x03)
v, err := p.trustService.CheckKey(namespace, b, 0x03)
if err != nil {
vErr, ok := err.(trust.NotVerifiedError)
if !ok {
return false, fmt.Errorf("error running key check: %s", err)
}
logrus.Debugf("Key check result: %v", vErr)
}
verified = v
}
if verified {
logrus.Debug("Key check result: verified")
}
return
}
func (p *v2Puller) validateManifest(m *manifest.SignedManifest, tag string) (verified bool, err error) {
func verifyManifest(signedManifest *manifest.SignedManifest, tag string) (m *manifest.Manifest, err error) {
// If pull by digest, then verify the manifest digest. NOTE: It is
// important to do this first, before any other content validation. If the
// digest cannot be verified, don't even bother with those other things.
if manifestDigest, err := digest.ParseDigest(tag); err == nil {
verifier, err := digest.NewDigestVerifier(manifestDigest)
if err != nil {
return false, err
return nil, err
}
payload, err := m.Payload()
payload, err := signedManifest.Payload()
if err != nil {
return false, err
// If this failed, the signatures section was corrupted
// or missing. Treat the entire manifest as the payload.
payload = signedManifest.Raw
}
if _, err := verifier.Write(payload); err != nil {
return false, err
return nil, err
}
if !verifier.Verified() {
err := fmt.Errorf("image verification failed for digest %s", manifestDigest)
logrus.Error(err)
return false, err
return nil, err
}
var verifiedManifest manifest.Manifest
if err = json.Unmarshal(payload, &verifiedManifest); err != nil {
return nil, err
}
m = &verifiedManifest
} else {
m = &signedManifest.Manifest
}
if m.SchemaVersion != 1 {
return nil, fmt.Errorf("unsupported schema version %d for tag %q", m.SchemaVersion, tag)
}
if len(m.FSLayers) != len(m.History) {
return nil, fmt.Errorf("length of history not equal to number of layers for tag %q", tag)
}
if len(m.FSLayers) == 0 {
return nil, fmt.Errorf("no FSLayers in manifest for tag %q", tag)
}
return m, nil
}
// fixManifestLayers removes repeated layers from the manifest and checks the
// correctness of the parent chain.
func fixManifestLayers(m *manifest.Manifest) error {
images := make([]*image.Image, len(m.FSLayers))
for i := range m.FSLayers {
img, err := image.NewImgJSON([]byte(m.History[i].V1Compatibility))
if err != nil {
return err
}
images[i] = img
if err := image.ValidateID(img.ID); err != nil {
return err
}
}
// TODO(tiborvass): what's the usecase for having manifest == nil and err == nil ? Shouldn't be the error be "DoesNotExist" ?
if m == nil {
return false, fmt.Errorf("image manifest does not exist for tag %q", tag)
if images[len(images)-1].Parent != "" {
return errors.New("Invalid parent ID in the base layer of the image.")
}
if m.SchemaVersion != 1 {
return false, fmt.Errorf("unsupported schema version %d for tag %q", m.SchemaVersion, tag)
// check general duplicates to error instead of a deadlock
idmap := make(map[string]struct{})
var lastID string
for _, img := range images {
// skip IDs that appear after each other, we handle those later
if _, exists := idmap[img.ID]; img.ID != lastID && exists {
return fmt.Errorf("ID %+v appears multiple times in manifest.", img.ID)
}
lastID = img.ID
idmap[lastID] = struct{}{}
}
if len(m.FSLayers) != len(m.History) {
return false, fmt.Errorf("length of history not equal to number of layers for tag %q", tag)
// backwards loop so that we keep the remaining indexes after removing items
for i := len(images) - 2; i >= 0; i-- {
if images[i].ID == images[i+1].ID { // repeated ID. remove and continue
m.FSLayers = append(m.FSLayers[:i], m.FSLayers[i+1:]...)
m.History = append(m.History[:i], m.History[i+1:]...)
} else if images[i].Parent != images[i+1].ID {
return fmt.Errorf("Invalid parent ID. Expected %v, got %v.", images[i+1].ID, images[i].Parent)
}
}
if len(m.FSLayers) == 0 {
return false, fmt.Errorf("no FSLayers in manifest for tag %q", tag)
}
keys, err := manifest.Verify(m)
if err != nil {
return false, fmt.Errorf("error verifying manifest for tag %q: %v", tag, err)
}
verified, err = p.verifyTrustedKeys(m.Name, keys)
if err != nil {
return false, fmt.Errorf("error verifying manifest keys: %v", err)
}
return verified, nil
return nil
}
// getImageInfos returns an imageinfo struct for every image in the manifest.
// These objects contain both calculated strongIDs and compatibilityIDs found
// in v1Compatibility object.
func (p *v2Puller) getImageInfos(m manifest.Manifest) ([]contentAddressableDescriptor, error) {
imgs := make([]contentAddressableDescriptor, len(m.FSLayers))
var parent digest.Digest
for i := len(imgs) - 1; i >= 0; i-- {
var err error
imgs[i], err = newContentAddressableImage([]byte(m.History[i].V1Compatibility), m.FSLayers[i].BlobSum, parent)
if err != nil {
return nil, err
}
parent = imgs[i].strongID
}
p.attemptIDReuse(imgs)
return imgs, nil
}
var idReuseLock sync.Mutex
// attemptIDReuse does a best attempt to match verified compatibilityIDs
// already in the graph with the computed strongIDs so we can keep using them.
// This process will never fail but may just return the strongIDs if none of
// the compatibilityIDs exists or can be verified. If the strongIDs themselves
// fail verification, we deterministically generate alternate IDs to use until
// we find one that's available or already exists with the correct data.
func (p *v2Puller) attemptIDReuse(imgs []contentAddressableDescriptor) {
// This function needs to be protected with a global lock, because it
// locks multiple IDs at once, and there's no good way to make sure
// the locking happens a deterministic order.
idReuseLock.Lock()
defer idReuseLock.Unlock()
idMap := make(map[string]struct{})
for _, img := range imgs {
idMap[img.id] = struct{}{}
idMap[img.compatibilityID] = struct{}{}
if p.graph.Exists(img.compatibilityID) {
if _, err := p.graph.GenerateV1CompatibilityChain(img.compatibilityID); err != nil {
logrus.Debugf("Migration v1Compatibility generation error: %v", err)
return
}
}
}
for id := range idMap {
p.graph.imageMutex.Lock(id)
defer p.graph.imageMutex.Unlock(id)
}
// continueReuse controls whether the function will try to find
// existing layers on disk under the old v1 IDs, to avoid repulling
// them. The hashes are checked to ensure these layers are okay to
// use. continueReuse starts out as true, but is set to false if
// the code encounters something that doesn't match the expected hash.
continueReuse := true
for i := len(imgs) - 1; i >= 0; i-- {
if p.graph.Exists(imgs[i].id) {
// Found an image in the graph under the strongID. Validate the
// image before using it.
if err := p.validateImageInGraph(imgs[i].id, imgs, i); err != nil {
continueReuse = false
logrus.Debugf("not using existing strongID: %v", err)
// The strong ID existed in the graph but didn't
// validate successfully. We can't use the strong ID
// because it didn't validate successfully. Treat the
// graph like a hash table with probing... compute
// SHA256(id) until we find an ID that either doesn't
// already exist in the graph, or has existing content
// that validates successfully.
for {
if err := p.tryNextID(imgs, i, idMap); err != nil {
logrus.Debug(err.Error())
} else {
break
}
}
}
continue
}
if continueReuse {
compatibilityID := imgs[i].compatibilityID
if err := p.validateImageInGraph(compatibilityID, imgs, i); err != nil {
logrus.Debugf("stopping ID reuse: %v", err)
continueReuse = false
} else {
// The compatibility ID exists in the graph and was
// validated. Use it.
imgs[i].id = compatibilityID
}
}
}
// fix up the parents of the images
for i := 0; i < len(imgs); i++ {
if i == len(imgs)-1 { // Base layer
imgs[i].parent = ""
} else {
imgs[i].parent = imgs[i+1].id
}
}
}
// validateImageInGraph checks that an image in the graph has the expected
// strongID. id is the entry in the graph to check, imgs is the slice of
// images being processed (for access to the parent), and i is the index
// into this slice which the graph entry should be checked against.
func (p *v2Puller) validateImageInGraph(id string, imgs []contentAddressableDescriptor, i int) error {
img, err := p.graph.Get(id)
if err != nil {
return fmt.Errorf("missing: %v", err)
}
layerID, err := p.graph.getLayerDigest(id)
if err != nil {
return fmt.Errorf("digest: %v", err)
}
var parentID digest.Digest
if i != len(imgs)-1 {
if img.Parent != imgs[i+1].id { // comparing that graph points to validated ID
return fmt.Errorf("parent: %v %v", img.Parent, imgs[i+1].id)
} else {
parentID = imgs[i+1].strongID
}
} else if img.Parent != "" {
return fmt.Errorf("unexpected parent: %v", img.Parent)
}
v1Config, err := p.graph.getV1CompatibilityConfig(img.ID)
if err != nil {
return fmt.Errorf("v1Compatibility: %v %v", img.ID, err)
}
json, err := image.MakeImageConfig(v1Config, layerID, parentID)
if err != nil {
return fmt.Errorf("make config: %v", err)
}
if dgst, err := image.StrongID(json); err == nil && dgst == imgs[i].strongID {
logrus.Debugf("Validated %v as %v", dgst, id)
} else {
return fmt.Errorf("digest mismatch: %v %v, error: %v", dgst, imgs[i].strongID, err)
}
// All clear
return nil
}
func (p *v2Puller) tryNextID(imgs []contentAddressableDescriptor, i int, idMap map[string]struct{}) error {
nextID, _ := digest.FromBytes([]byte(imgs[i].id))
imgs[i].id = nextID.Hex()
if _, exists := idMap[imgs[i].id]; !exists {
p.graph.imageMutex.Lock(imgs[i].id)
defer p.graph.imageMutex.Unlock(imgs[i].id)
}
if p.graph.Exists(imgs[i].id) {
if err := p.validateImageInGraph(imgs[i].id, imgs, i); err != nil {
return fmt.Errorf("not using existing strongID permutation %s: %v", imgs[i].id, err)
}
}
return nil
}
+194
View File
@@ -0,0 +1,194 @@
package graph
import (
"encoding/json"
"io/ioutil"
"reflect"
"strings"
"testing"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest"
)
// TestValidateManifest verifies the validateManifest function
func TestValidateManifest(t *testing.T) {
expectedDigest := "sha256:02fee8c3220ba806531f606525eceb83f4feb654f62b207191b1c9209188dedd"
expectedFSLayer0 := digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
// Good manifest
goodManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/good_manifest")
if err != nil {
t.Fatal("error reading fixture:", err)
}
var goodSignedManifest manifest.SignedManifest
err = json.Unmarshal(goodManifestBytes, &goodSignedManifest)
if err != nil {
t.Fatal("error unmarshaling manifest:", err)
}
verifiedManifest, err := verifyManifest(&goodSignedManifest, expectedDigest)
if err != nil {
t.Fatal("validateManifest failed:", err)
}
if verifiedManifest.FSLayers[0].BlobSum != expectedFSLayer0 {
t.Fatal("unexpected FSLayer in good manifest")
}
// "Extra data" manifest
extraDataManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/extra_data_manifest")
if err != nil {
t.Fatal("error reading fixture:", err)
}
var extraDataSignedManifest manifest.SignedManifest
err = json.Unmarshal(extraDataManifestBytes, &extraDataSignedManifest)
if err != nil {
t.Fatal("error unmarshaling manifest:", err)
}
verifiedManifest, err = verifyManifest(&extraDataSignedManifest, expectedDigest)
if err != nil {
t.Fatal("validateManifest failed:", err)
}
if verifiedManifest.FSLayers[0].BlobSum != expectedFSLayer0 {
t.Fatal("unexpected FSLayer in extra data manifest")
}
// Bad manifest
badManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/bad_manifest")
if err != nil {
t.Fatal("error reading fixture:", err)
}
var badSignedManifest manifest.SignedManifest
err = json.Unmarshal(badManifestBytes, &badSignedManifest)
if err != nil {
t.Fatal("error unmarshaling manifest:", err)
}
verifiedManifest, err = verifyManifest(&badSignedManifest, expectedDigest)
if err == nil || !strings.HasPrefix(err.Error(), "image verification failed for digest") {
t.Fatal("expected validateManifest to fail with digest error")
}
// Manifest with no signature
expectedWholeFileDigest := "7ec3615a120efcdfc270e9c7ea4183330775a3e52a09e2efb194b9a7c18e5ff7"
noSignatureManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/no_signature_manifest")
if err != nil {
t.Fatal("error reading fixture:", err)
}
var noSignatureSignedManifest manifest.SignedManifest
err = json.Unmarshal(noSignatureManifestBytes, &noSignatureSignedManifest)
if err != nil {
t.Fatal("error unmarshaling manifest:", err)
}
verifiedManifest, err = verifyManifest(&noSignatureSignedManifest, expectedWholeFileDigest)
if err != nil {
t.Fatal("validateManifest failed:", err)
}
if verifiedManifest.FSLayers[0].BlobSum != expectedFSLayer0 {
t.Fatal("unexpected FSLayer in no-signature manifest")
}
}
// TestFixManifestLayers checks that fixManifestLayers removes a duplicate
// layer, and that it makes no changes to the manifest when called a second
// time, after the duplicate is removed.
func TestFixManifestLayers(t *testing.T) {
duplicateLayerManifest := manifest.Manifest{
FSLayers: []manifest.FSLayer{
{BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
{BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
{BlobSum: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")},
},
History: []manifest.History{
{V1Compatibility: "{\"id\":\"3b38edc92eb7c074812e217b41a6ade66888531009d6286a6f5f36a06f9841b9\",\"parent\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"created\":\"2015-08-19T16:49:11.368300679Z\",\"container\":\"d91be3479d5b1e84b0c00d18eea9dc777ca0ad166d51174b24283e2e6f104253\",\"container_config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ENTRYPOINT [\\\"/go/bin/dnsdock\\\"]\"],\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"docker_version\":\"1.6.2\",\"config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":null,\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"},
{V1Compatibility: "{\"id\":\"3b38edc92eb7c074812e217b41a6ade66888531009d6286a6f5f36a06f9841b9\",\"parent\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"created\":\"2015-08-19T16:49:11.368300679Z\",\"container\":\"d91be3479d5b1e84b0c00d18eea9dc777ca0ad166d51174b24283e2e6f104253\",\"container_config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ENTRYPOINT [\\\"/go/bin/dnsdock\\\"]\"],\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"docker_version\":\"1.6.2\",\"config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":null,\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"},
{V1Compatibility: "{\"id\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"created\":\"2015-08-19T16:49:07.568027497Z\",\"container\":\"fe9e5a5264a843c9292d17b736c92dd19bdb49986a8782d7389964ddaff887cc\",\"container_config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"cd /go/src/github.com/tonistiigi/dnsdock \\u0026\\u0026 go get -v github.com/tools/godep \\u0026\\u0026 godep restore \\u0026\\u0026 go install -ldflags \\\"-X main.version `git describe --tags HEAD``if [[ -n $(command git status --porcelain --untracked-files=no 2\\u003e/dev/null) ]]; then echo \\\"-dirty\\\"; fi`\\\" ./...\"],\"Image\":\"e3b0ff09e647595dafee15c54cd632c900df9e82b1d4d313b1e20639a1461779\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"docker_version\":\"1.6.2\",\"config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/bash\"],\"Image\":\"e3b0ff09e647595dafee15c54cd632c900df9e82b1d4d313b1e20639a1461779\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":118430532}\n"},
},
}
duplicateLayerManifestExpectedOutput := manifest.Manifest{
FSLayers: []manifest.FSLayer{
{BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
{BlobSum: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")},
},
History: []manifest.History{
{V1Compatibility: "{\"id\":\"3b38edc92eb7c074812e217b41a6ade66888531009d6286a6f5f36a06f9841b9\",\"parent\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"created\":\"2015-08-19T16:49:11.368300679Z\",\"container\":\"d91be3479d5b1e84b0c00d18eea9dc777ca0ad166d51174b24283e2e6f104253\",\"container_config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ENTRYPOINT [\\\"/go/bin/dnsdock\\\"]\"],\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"docker_version\":\"1.6.2\",\"config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":null,\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"},
{V1Compatibility: "{\"id\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"created\":\"2015-08-19T16:49:07.568027497Z\",\"container\":\"fe9e5a5264a843c9292d17b736c92dd19bdb49986a8782d7389964ddaff887cc\",\"container_config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"cd /go/src/github.com/tonistiigi/dnsdock \\u0026\\u0026 go get -v github.com/tools/godep \\u0026\\u0026 godep restore \\u0026\\u0026 go install -ldflags \\\"-X main.version `git describe --tags HEAD``if [[ -n $(command git status --porcelain --untracked-files=no 2\\u003e/dev/null) ]]; then echo \\\"-dirty\\\"; fi`\\\" ./...\"],\"Image\":\"e3b0ff09e647595dafee15c54cd632c900df9e82b1d4d313b1e20639a1461779\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"docker_version\":\"1.6.2\",\"config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/bash\"],\"Image\":\"e3b0ff09e647595dafee15c54cd632c900df9e82b1d4d313b1e20639a1461779\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":118430532}\n"},
},
}
if err := fixManifestLayers(&duplicateLayerManifest); err != nil {
t.Fatalf("unexpected error from fixManifestLayers: %v", err)
}
if !reflect.DeepEqual(duplicateLayerManifest, duplicateLayerManifestExpectedOutput) {
t.Fatal("incorrect output from fixManifestLayers on duplicate layer manifest")
}
// Run fixManifestLayers again and confirm that it doesn't change the
// manifest (which no longer has duplicate layers).
if err := fixManifestLayers(&duplicateLayerManifest); err != nil {
t.Fatalf("unexpected error from fixManifestLayers: %v", err)
}
if !reflect.DeepEqual(duplicateLayerManifest, duplicateLayerManifestExpectedOutput) {
t.Fatal("incorrect output from fixManifestLayers on duplicate layer manifest (second pass)")
}
}
// TestFixManifestLayersBaseLayerParent makes sure that fixManifestLayers fails
// if the base layer configuration specifies a parent.
func TestFixManifestLayersBaseLayerParent(t *testing.T) {
duplicateLayerManifest := manifest.Manifest{
FSLayers: []manifest.FSLayer{
{BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
{BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
{BlobSum: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")},
},
History: []manifest.History{
{V1Compatibility: "{\"id\":\"3b38edc92eb7c074812e217b41a6ade66888531009d6286a6f5f36a06f9841b9\",\"parent\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"created\":\"2015-08-19T16:49:11.368300679Z\",\"container\":\"d91be3479d5b1e84b0c00d18eea9dc777ca0ad166d51174b24283e2e6f104253\",\"container_config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ENTRYPOINT [\\\"/go/bin/dnsdock\\\"]\"],\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"docker_version\":\"1.6.2\",\"config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":null,\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"},
{V1Compatibility: "{\"id\":\"3b38edc92eb7c074812e217b41a6ade66888531009d6286a6f5f36a06f9841b9\",\"parent\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"created\":\"2015-08-19T16:49:11.368300679Z\",\"container\":\"d91be3479d5b1e84b0c00d18eea9dc777ca0ad166d51174b24283e2e6f104253\",\"container_config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ENTRYPOINT [\\\"/go/bin/dnsdock\\\"]\"],\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"docker_version\":\"1.6.2\",\"config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":null,\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"},
{V1Compatibility: "{\"id\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"parent\":\"e3b0ff09e647595dafee15c54cd632c900df9e82b1d4d313b1e20639a1461779\",\"created\":\"2015-08-19T16:49:07.568027497Z\",\"container\":\"fe9e5a5264a843c9292d17b736c92dd19bdb49986a8782d7389964ddaff887cc\",\"container_config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"cd /go/src/github.com/tonistiigi/dnsdock \\u0026\\u0026 go get -v github.com/tools/godep \\u0026\\u0026 godep restore \\u0026\\u0026 go install -ldflags \\\"-X main.version `git describe --tags HEAD``if [[ -n $(command git status --porcelain --untracked-files=no 2\\u003e/dev/null) ]]; then echo \\\"-dirty\\\"; fi`\\\" ./...\"],\"Image\":\"e3b0ff09e647595dafee15c54cd632c900df9e82b1d4d313b1e20639a1461779\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"docker_version\":\"1.6.2\",\"config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/bash\"],\"Image\":\"e3b0ff09e647595dafee15c54cd632c900df9e82b1d4d313b1e20639a1461779\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":118430532}\n"},
},
}
if err := fixManifestLayers(&duplicateLayerManifest); err == nil || !strings.Contains(err.Error(), "Invalid parent ID in the base layer of the image.") {
t.Fatalf("expected an invalid parent ID error from fixManifestLayers")
}
}
// TestFixManifestLayersBadParent makes sure that fixManifestLayers fails
// if an image configuration specifies a parent that doesn't directly follow
// that (deduplicated) image in the image history.
func TestFixManifestLayersBadParent(t *testing.T) {
duplicateLayerManifest := manifest.Manifest{
FSLayers: []manifest.FSLayer{
{BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
{BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
{BlobSum: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")},
},
History: []manifest.History{
{V1Compatibility: "{\"id\":\"3b38edc92eb7c074812e217b41a6ade66888531009d6286a6f5f36a06f9841b9\",\"parent\":\"ac3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"created\":\"2015-08-19T16:49:11.368300679Z\",\"container\":\"d91be3479d5b1e84b0c00d18eea9dc777ca0ad166d51174b24283e2e6f104253\",\"container_config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ENTRYPOINT [\\\"/go/bin/dnsdock\\\"]\"],\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"docker_version\":\"1.6.2\",\"config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":null,\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"},
{V1Compatibility: "{\"id\":\"3b38edc92eb7c074812e217b41a6ade66888531009d6286a6f5f36a06f9841b9\",\"parent\":\"ac3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"created\":\"2015-08-19T16:49:11.368300679Z\",\"container\":\"d91be3479d5b1e84b0c00d18eea9dc777ca0ad166d51174b24283e2e6f104253\",\"container_config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ENTRYPOINT [\\\"/go/bin/dnsdock\\\"]\"],\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"docker_version\":\"1.6.2\",\"config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":null,\"Image\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":[\"/go/bin/dnsdock\"],\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"},
{V1Compatibility: "{\"id\":\"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02\",\"created\":\"2015-08-19T16:49:07.568027497Z\",\"container\":\"fe9e5a5264a843c9292d17b736c92dd19bdb49986a8782d7389964ddaff887cc\",\"container_config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"cd /go/src/github.com/tonistiigi/dnsdock \\u0026\\u0026 go get -v github.com/tools/godep \\u0026\\u0026 godep restore \\u0026\\u0026 go install -ldflags \\\"-X main.version `git describe --tags HEAD``if [[ -n $(command git status --porcelain --untracked-files=no 2\\u003e/dev/null) ]]; then echo \\\"-dirty\\\"; fi`\\\" ./...\"],\"Image\":\"e3b0ff09e647595dafee15c54cd632c900df9e82b1d4d313b1e20639a1461779\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"docker_version\":\"1.6.2\",\"config\":{\"Hostname\":\"03797203757d\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"GOLANG_VERSION=1.4.1\",\"GOPATH=/go\"],\"Cmd\":[\"/bin/bash\"],\"Image\":\"e3b0ff09e647595dafee15c54cd632c900df9e82b1d4d313b1e20639a1461779\",\"Volumes\":null,\"WorkingDir\":\"/go\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":118430532}\n"},
},
}
if err := fixManifestLayers(&duplicateLayerManifest); err == nil || !strings.Contains(err.Error(), "Invalid parent ID.") {
t.Fatalf("expected an invalid parent ID error from fixManifestLayers")
}
}
+7 -8
View File
@@ -29,13 +29,12 @@ func (s *TagStore) NewPusher(endpoint registry.APIEndpoint, localRepo Repository
switch endpoint.Version {
case registry.APIVersion2:
return &v2Pusher{
TagStore: s,
endpoint: endpoint,
localRepo: localRepo,
repoInfo: repoInfo,
config: imagePushConfig,
sf: sf,
layersSeen: make(map[string]bool),
TagStore: s,
endpoint: endpoint,
localRepo: localRepo,
repoInfo: repoInfo,
config: imagePushConfig,
sf: sf,
}, nil
case registry.APIVersion1:
return &v1Pusher{
@@ -60,7 +59,7 @@ func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) erro
return err
}
endpoints, err := s.registryService.LookupEndpoints(repoInfo.CanonicalName)
endpoints, err := s.registryService.LookupPushEndpoints(repoInfo.CanonicalName)
if err != nil {
return err
}
+63 -16
View File
@@ -8,6 +8,7 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/progressreader"
"github.com/docker/docker/pkg/streamformatter"
@@ -127,7 +128,7 @@ func (s *TagStore) createImageIndex(images []string, tags map[string][]string) [
continue
}
// If the image does not have a tag it still needs to be sent to the
// registry with an empty tag so that it is accociated with the repository
// registry with an empty tag so that it is associated with the repository
imageIndex = append(imageIndex, &registry.ImgData{
ID: id,
Tag: "",
@@ -137,9 +138,10 @@ func (s *TagStore) createImageIndex(images []string, tags map[string][]string) [
}
type imagePushData struct {
id string
endpoint string
tokens []string
id string
compatibilityID string
endpoint string
tokens []string
}
// lookupImageOnEndpoint checks the specified endpoint to see if an image exists
@@ -147,7 +149,7 @@ type imagePushData struct {
func (p *v1Pusher) lookupImageOnEndpoint(wg *sync.WaitGroup, images chan imagePushData, imagesToPush chan string) {
defer wg.Done()
for image := range images {
if err := p.session.LookupRemoteImage(image.id, image.endpoint); err != nil {
if err := p.session.LookupRemoteImage(image.compatibilityID, image.endpoint); err != nil {
logrus.Errorf("Error in LookupRemoteImage: %s", err)
imagesToPush <- image.id
continue
@@ -181,10 +183,15 @@ func (p *v1Pusher) pushImageToEndpoint(endpoint string, imageIDs []string, tags
pushes <- shouldPush
}()
for _, id := range imageIDs {
compatibilityID, err := p.getV1ID(id)
if err != nil {
return err
}
imageData <- imagePushData{
id: id,
endpoint: endpoint,
tokens: repo.Tokens,
id: id,
compatibilityID: compatibilityID,
endpoint: endpoint,
tokens: repo.Tokens,
}
}
// close the channel to notify the workers that there will be no more images to check.
@@ -204,7 +211,11 @@ func (p *v1Pusher) pushImageToEndpoint(endpoint string, imageIDs []string, tags
}
for _, tag := range tags[id] {
p.out.Write(p.sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", stringid.TruncateID(id), endpoint+"repositories/"+p.repoInfo.RemoteName+"/tags/"+tag))
if err := p.session.PushRegistryTag(p.repoInfo.RemoteName, id, tag, endpoint); err != nil {
compatibilityID, err := p.getV1ID(id)
if err != nil {
return err
}
if err := p.session.PushRegistryTag(p.repoInfo.RemoteName, compatibilityID, tag, endpoint); err != nil {
return err
}
}
@@ -214,7 +225,6 @@ func (p *v1Pusher) pushImageToEndpoint(endpoint string, imageIDs []string, tags
// pushRepository pushes layers that do not already exist on the registry.
func (p *v1Pusher) pushRepository(tag string) error {
logrus.Debugf("Local repo: %s", p.localRepo)
p.out = ioutils.NewWriteFlusher(p.config.OutStream)
imgList, tags, err := p.getImageList(tag)
@@ -227,6 +237,12 @@ func (p *v1Pusher) pushRepository(tag string) error {
logrus.Debugf("Preparing to push %s with the following images and tags", p.localRepo)
for _, data := range imageIndex {
logrus.Debugf("Pushing ID: %s with Tag: %s", data.ID, data.Tag)
// convert IDs to compatibilityIDs, imageIndex only used in registry calls
data.ID, err = p.getV1ID(data.ID)
if err != nil {
return err
}
}
if _, err := p.poolAdd("push", p.repoInfo.LocalName); err != nil {
@@ -256,20 +272,27 @@ func (p *v1Pusher) pushRepository(tag string) error {
}
func (p *v1Pusher) pushImage(imgID, ep string, token []string) (checksum string, err error) {
jsonRaw, err := p.graph.RawJSON(imgID)
jsonRaw, err := p.getV1Config(imgID)
if err != nil {
return "", fmt.Errorf("Cannot retrieve the path for {%s}: %s", imgID, err)
}
p.out.Write(p.sf.FormatProgress(stringid.TruncateID(imgID), "Pushing", nil))
compatibilityID, err := p.getV1ID(imgID)
if err != nil {
return "", err
}
// General rule is to use ID for graph accesses and compatibilityID for
// calls to session.registry()
imgData := &registry.ImgData{
ID: imgID,
ID: compatibilityID,
}
// Send the json
if err := p.session.PushImageJSONRegistry(imgData, jsonRaw, ep); err != nil {
if err == registry.ErrAlreadyExists {
p.out.Write(p.sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image already pushed, skipping", nil))
p.out.Write(p.sf.FormatProgress(stringid.TruncateID(imgID), "Image already pushed, skipping", nil))
return "", nil
}
return "", err
@@ -282,7 +305,7 @@ func (p *v1Pusher) pushImage(imgID, ep string, token []string) (checksum string,
defer os.RemoveAll(layerData.Name())
// Send the layer
logrus.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size)
logrus.Debugf("rendered layer for %s of [%d] size", imgID, layerData.Size)
checksum, checksumPayload, err := p.session.PushImageLayerRegistry(imgData.ID,
progressreader.New(progressreader.Config{
@@ -291,7 +314,7 @@ func (p *v1Pusher) pushImage(imgID, ep string, token []string) (checksum string,
Formatter: p.sf,
Size: int(layerData.Size),
NewLines: false,
ID: stringid.TruncateID(imgData.ID),
ID: stringid.TruncateID(imgID),
Action: "Pushing",
}), ep, jsonRaw)
if err != nil {
@@ -304,6 +327,30 @@ func (p *v1Pusher) pushImage(imgID, ep string, token []string) (checksum string,
return "", err
}
p.out.Write(p.sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image successfully pushed", nil))
p.out.Write(p.sf.FormatProgress(stringid.TruncateID(imgID), "Image successfully pushed", nil))
return imgData.Checksum, nil
}
// getV1ID returns the compatibilityID for the ID in the graph. compatibilityID
// is read from from the v1Compatibility config file in the disk.
func (p *v1Pusher) getV1ID(id string) (string, error) {
jsonData, err := p.getV1Config(id)
if err != nil {
return "", err
}
img, err := image.NewImgJSON(jsonData)
if err != nil {
return "", err
}
return img.ID, nil
}
// getV1Config returns v1Compatibility config for the image in the graph. If
// there is no v1Compatibility file on disk for the image
func (p *v1Pusher) getV1Config(id string) ([]byte, error) {
jsonData, err := p.graph.GenerateV1CompatibilityChain(id)
if err != nil {
return nil, err
}
return jsonData, nil
}
+16 -18
View File
@@ -27,11 +27,6 @@ type v2Pusher struct {
config *ImagePushConfig
sf *streamformatter.StreamFormatter
repo distribution.Repository
// layersSeen is the set of layers known to exist on the remote side.
// This avoids redundant queries when pushing multiple tags that
// involve the same layers.
layersSeen map[string]bool
}
func (p *v2Pusher) Push() (fallback bool, err error) {
@@ -92,6 +87,8 @@ func (p *v2Pusher) pushV2Tag(tag string) error {
return fmt.Errorf("tag does not exist: %s", tag)
}
layersSeen := make(map[string]bool)
layer, err := p.graph.Get(layerId)
if err != nil {
return err
@@ -120,7 +117,7 @@ func (p *v2Pusher) pushV2Tag(tag string) error {
return err
}
if p.layersSeen[layer.ID] {
if layersSeen[layer.ID] {
break
}
@@ -132,16 +129,11 @@ func (p *v2Pusher) pushV2Tag(tag string) error {
}
}
jsonData, err := p.graph.RawJSON(layer.ID)
if err != nil {
return fmt.Errorf("cannot retrieve the path for %s: %s", layer.ID, err)
}
var exists bool
dgst, err := p.graph.GetDigest(layer.ID)
dgst, err := p.graph.GetLayerDigest(layer.ID)
switch err {
case nil:
_, err := p.repo.Blobs(nil).Stat(nil, dgst)
_, err := p.repo.Blobs(context.Background()).Stat(context.Background(), dgst)
switch err {
case nil:
exists = true
@@ -161,21 +153,27 @@ func (p *v2Pusher) pushV2Tag(tag string) error {
// if digest was empty or not saved, or if blob does not exist on the remote repository,
// then fetch it.
if !exists {
if pushDigest, err := p.pushV2Image(p.repo.Blobs(nil), layer); err != nil {
if pushDigest, err := p.pushV2Image(p.repo.Blobs(context.Background()), layer); err != nil {
return err
} else if pushDigest != dgst {
// Cache new checksum
if err := p.graph.SetDigest(layer.ID, pushDigest); err != nil {
if err := p.graph.SetLayerDigest(layer.ID, pushDigest); err != nil {
return err
}
dgst = pushDigest
}
}
// read v1Compatibility config, generate new if needed
jsonData, err := p.graph.GenerateV1CompatibilityChain(layer.ID)
if err != nil {
return err
}
m.FSLayers = append(m.FSLayers, manifest.FSLayer{BlobSum: dgst})
m.History = append(m.History, manifest.History{V1Compatibility: string(jsonData)})
p.layersSeen[layer.ID] = true
layersSeen[layer.ID] = true
}
logrus.Infof("Signed manifest for %s:%s using daemon's key: %s", p.repo.Name(), tag, p.trustKey.KeyID())
@@ -229,7 +227,7 @@ func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (d
// Send the layer
logrus.Debugf("rendered layer for %s of [%d] size", img.ID, size)
layerUpload, err := bs.Create(nil)
layerUpload, err := bs.Create(context.Background())
if err != nil {
return "", err
}
@@ -253,7 +251,7 @@ func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (d
}
desc := distribution.Descriptor{Digest: dgst}
if _, err := layerUpload.Commit(nil, desc); err != nil {
if _, err := layerUpload.Commit(context.Background(), desc); err != nil {
return "", err
}
+3 -2
View File
@@ -100,8 +100,9 @@ func NewV2Repository(repoInfo *registry.RepositoryInfo, endpoint registry.APIEnd
func digestFromManifest(m *manifest.SignedManifest, localName string) (digest.Digest, int, error) {
payload, err := m.Payload()
if err != nil {
logrus.Debugf("could not retrieve manifest payload: %v", err)
return "", 0, err
// If this failed, the signatures section was corrupted
// or missing. Treat the entire manifest as the payload.
payload = m.Raw
}
manifestDigest, err := digest.FromBytes(payload)
if err != nil {
-14
View File
@@ -10,20 +10,6 @@ import (
"github.com/docker/docker/api/types"
)
func (s *TagStore) LookupRaw(name string) ([]byte, error) {
image, err := s.LookupImage(name)
if err != nil || image == nil {
return nil, fmt.Errorf("No such image %s", name)
}
imageInspectRaw, err := s.graph.RawJSON(image.ID)
if err != nil {
return nil, err
}
return imageInspectRaw, nil
}
// Lookup return an image encoded in JSON
func (s *TagStore) Lookup(name string) (*types.ImageInspect, error) {
image, err := s.LookupImage(name)
-4
View File
@@ -19,7 +19,6 @@ import (
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/registry"
"github.com/docker/docker/trust"
"github.com/docker/docker/utils"
"github.com/docker/libtrust"
)
@@ -38,7 +37,6 @@ type TagStore struct {
pushingPool map[string]chan struct{}
registryService *registry.Service
eventsService *events.Events
trustService *trust.TrustStore
}
type Repository map[string]string
@@ -66,7 +64,6 @@ type TagStoreConfig struct {
Key libtrust.PrivateKey
Registry *registry.Service
Events *events.Events
Trust *trust.TrustStore
}
func NewTagStore(path string, cfg *TagStoreConfig) (*TagStore, error) {
@@ -84,7 +81,6 @@ func NewTagStore(path string, cfg *TagStoreConfig) (*TagStore, error) {
pushingPool: make(map[string]chan struct{}),
registryService: cfg.Registry,
eventsService: cfg.Events,
trustService: cfg.Trust,
}
// Load the json file if it exists, otherwise create it.
if err := store.reload(); os.IsNotExist(err) {
+2 -9
View File
@@ -12,7 +12,6 @@ import (
"github.com/docker/docker/daemon/graphdriver"
_ "github.com/docker/docker/daemon/graphdriver/vfs" // import the vfs driver so it is used in the tests
"github.com/docker/docker/image"
"github.com/docker/docker/trust"
"github.com/docker/docker/utils"
)
@@ -62,15 +61,9 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
t.Fatal(err)
}
trust, err := trust.NewTrustStore(root + "/trust")
if err != nil {
t.Fatal(err)
}
tagCfg := &TagStoreConfig{
Graph: graph,
Events: events.New(),
Trust: trust,
}
store, err := NewTagStore(path.Join(root, "tags"), tagCfg)
if err != nil {
@@ -81,7 +74,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
t.Fatal(err)
}
img := &image.Image{ID: testOfficialImageID}
if err := graph.Register(img, officialArchive); err != nil {
if err := graph.Register(v1ImageDescriptor{img}, officialArchive); err != nil {
t.Fatal(err)
}
if err := store.Tag(testOfficialImageName, "", testOfficialImageID, false); err != nil {
@@ -92,7 +85,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
t.Fatal(err)
}
img = &image.Image{ID: testPrivateImageID}
if err := graph.Register(img, privateArchive); err != nil {
if err := graph.Register(v1ImageDescriptor{img}, privateArchive); err != nil {
t.Fatal(err)
}
if err := store.Tag(testPrivateImageName, "", testPrivateImageID, false); err != nil {
+30 -1
View File
@@ -51,6 +51,32 @@ echo_docker_as_nonroot() {
EOF
}
# Check if this is a forked Linux distro
check_forked() {
# Check for lsb_release command existence, it usually exists in forked distros
if command_exists lsb_release; then
# Check if the `-u` option is supported
lsb_release -a -u > /dev/null 2>&1
# Check if the command has exited successfully, it means we're in a forked distro
if [ "$?" = "0" ]; then
# Print info about current distro
cat <<-EOF
You're using '$lsb_dist' version '$dist_version'.
EOF
# Get the upstream release info
lsb_dist=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'id' | cut -d ':' -f 2 | tr -d '[[:space:]]')
dist_version=$(lsb_release -a -u 2>&1 | tr '[:upper:]' '[:lower:]' | grep -E 'codename' | cut -d ':' -f 2 | tr -d '[[:space:]]')
# Print info about upstream distro
cat <<-EOF
Upstream release is '$lsb_dist' version '$dist_version'.
EOF
fi
fi
}
do_install() {
case "$(uname -m)" in
*64)
@@ -187,8 +213,11 @@ do_install() {
esac
# Check if this is a forked Linux distro
check_forked
# Run setup for each distro accordingly
case "$lsb_dist" in
amzn)
(

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