Browse Source

Merge branch 'master' of github.com:docker/docker into volumeMan

Signed-off-by: Dan Walsh <dwalsh@redhat.com>
Dan Walsh 9 years ago
parent
commit
04e9087d5d
100 changed files with 2795 additions and 1524 deletions
  1. 51 0
      .github/ISSUE_TEMPLATE.md
  2. 23 0
      .github/PULL_REQUEST_TEMPLATE.md
  3. 68 1
      CHANGELOG.md
  4. 2 0
      CONTRIBUTING.md
  5. 12 7
      Dockerfile
  6. 8 18
      Dockerfile.aarch64
  7. 2 10
      Dockerfile.armhf
  8. 1 0
      Dockerfile.gccgo
  9. 26 20
      Dockerfile.ppc64le
  10. 1 9
      Dockerfile.s390x
  11. 55 47
      Dockerfile.windows
  12. 6 5
      MAINTAINERS
  13. 14 0
      Makefile
  14. 3 1
      README.md
  15. 72 115
      ROADMAP.md
  16. 15 6
      api/client/attach.go
  17. 9 253
      api/client/build.go
  18. 4 0
      api/client/cli.go
  19. 1 1
      api/client/create.go
  20. 29 0
      api/client/events.go
  21. 15 0
      api/client/formatter/custom.go
  22. 2 2
      api/client/formatter/custom_test.go
  23. 5 2
      api/client/info.go
  24. 56 38
      api/client/login.go
  25. 4 3
      api/client/logout.go
  26. 10 1
      api/client/network.go
  27. 1 1
      api/client/pull.go
  28. 1 1
      api/client/push.go
  29. 9 12
      api/client/run.go
  30. 1 1
      api/client/search.go
  31. 117 255
      api/client/stats.go
  32. 217 0
      api/client/stats_helpers.go
  33. 2 1
      api/client/stats_unit_test.go
  34. 54 36
      api/client/trust.go
  35. 12 1
      api/client/update.go
  36. 12 30
      api/client/utils.go
  37. 10 0
      api/client/volume.go
  38. 0 3
      api/common.go
  39. 1 1
      api/common_test.go
  40. 69 0
      api/server/httputils/errors.go
  41. 2 76
      api/server/httputils/httputils.go
  42. 16 170
      api/server/middleware.go
  43. 42 0
      api/server/middleware/authorization.go
  44. 33 0
      api/server/middleware/cors.go
  45. 56 0
      api/server/middleware/debug.go
  46. 7 0
      api/server/middleware/middleware.go
  47. 35 0
      api/server/middleware/user_agent.go
  48. 45 0
      api/server/middleware/version.go
  49. 16 9
      api/server/middleware/version_test.go
  50. 4 2
      api/server/profiler.go
  51. 6 6
      api/server/router/build/build_routes.go
  52. 1 2
      api/server/router/container/backend.go
  53. 13 9
      api/server/router/container/container_routes.go
  54. 2 3
      api/server/router/container/exec.go
  55. 0 1
      api/server/router/image/backend.go
  56. 4 4
      api/server/router/image/image.go
  57. 12 17
      api/server/router/image/image_routes.go
  58. 1 3
      api/server/router/network/backend.go
  59. 2 2
      api/server/router/network/filter.go
  60. 7 25
      api/server/router/network/network.go
  61. 3 1
      api/server/router/network/network_routes.go
  62. 1 1
      api/server/router_swapper.go
  63. 35 84
      api/server/server.go
  64. 25 0
      api/types/backend/backend.go
  65. 6 1
      builder/builder.go
  66. 260 0
      builder/context.go
  67. 1 1
      builder/context_unix.go
  68. 1 1
      builder/context_windows.go
  69. 37 26
      builder/dockerfile/dispatchers.go
  70. 12 13
      builder/dockerfile/internals.go
  71. 1 1
      builder/dockerfile/parser/line_parsers.go
  72. 1 1
      builder/dockerfile/parser/utils.go
  73. 1 2
      builder/remote.go
  74. 3 3
      cli/common.go
  75. 24 12
      cliconfig/config.go
  76. 102 25
      cliconfig/config_test.go
  77. 17 0
      cliconfig/credentials/credentials.go
  78. 22 0
      cliconfig/credentials/default_store.go
  79. 3 0
      cliconfig/credentials/default_store_darwin.go
  80. 3 0
      cliconfig/credentials/default_store_linux.go
  81. 5 0
      cliconfig/credentials/default_store_unsupported.go
  82. 3 0
      cliconfig/credentials/default_store_windows.go
  83. 67 0
      cliconfig/credentials/file_store.go
  84. 138 0
      cliconfig/credentials/file_store_test.go
  85. 180 0
      cliconfig/credentials/native_store.go
  86. 309 0
      cliconfig/credentials/native_store_test.go
  87. 28 0
      cliconfig/credentials/shell_command.go
  88. 29 26
      container/container.go
  89. 26 18
      container/container_unix.go
  90. 25 3
      container/container_windows.go
  91. 6 10
      container/monitor.go
  92. 7 7
      container/state.go
  93. 5 5
      container/state_test.go
  94. 0 0
      contrib/README.md
  95. 3 5
      contrib/apparmor/main.go
  96. 16 16
      contrib/apparmor/template.go
  97. 16 0
      contrib/check-config.sh
  98. 33 16
      contrib/completion/bash/docker
  99. 2 4
      contrib/completion/fish/docker.fish
  100. 35 32
      contrib/completion/zsh/_docker

+ 51 - 0
.github/ISSUE_TEMPLATE.md

@@ -0,0 +1,51 @@
+<!--
+If you are reporting a new issue, make sure that we do not have any duplicates
+already open. You can ensure this by searching the issue list for this
+repository. If there is a duplicate, please close your issue and add a comment
+to the existing issue instead.
+
+If you suspect your issue is a bug, please edit your issue description to
+include the BUG REPORT INFORMATION shown below. If you fail to provide this
+information within 7 days, we cannot debug your issue and will close it. We
+will, however, reopen it if you later provide the information.
+
+For more information about reporting issues, see
+https://github.com/docker/docker/blob/master/CONTRIBUTING.md#reporting-other-issues
+
+---------------------------------------------------
+BUG REPORT INFORMATION
+---------------------------------------------------
+Use the commands below to provide key information from your environment:
+You do NOT have to include this information if this is a FEATURE REQUEST
+-->
+
+Output of `docker version`:
+
+```
+(paste your output here)
+```
+
+
+Output of `docker info`:
+
+```
+(paste your output here)
+```
+
+Provide additional environment details (AWS, VirtualBox, physical, etc.):
+
+
+
+List the steps to reproduce the issue:
+1.
+2.
+3.
+
+
+Describe the results you received:
+
+
+Describe the results you expected:
+
+
+Provide additional info you think is important:

+ 23 - 0
.github/PULL_REQUEST_TEMPLATE.md

@@ -0,0 +1,23 @@
+<!--
+Please make sure you've read and understood our contributing guidelines;
+https://github.com/docker/docker/blob/master/CONTRIBUTING.md
+
+** Make sure all your commits include a signature generated with `git commit -s` **
+
+For additional information on our contributing process, read our contributing
+guide https://docs.docker.com/opensource/code/
+
+If this is a bug fix, make sure your description includes "fixes #xxxx", or
+"closes #xxxx"
+-->
+
+Please provide the following information:
+
+- What did you do?
+
+- How did you do it?
+
+- How do I see it or verify it?
+
+- A picture of a cute animal (not mandatory but encouraged)
+

+ 68 - 1
CHANGELOG.md

@@ -5,6 +5,73 @@ information on the list of deprecated flags and APIs please have a look at
 https://docs.docker.com/misc/deprecated/ where target removal dates can also
 https://docs.docker.com/misc/deprecated/ where target removal dates can also
 be found.
 be found.
 
 
+## 1.10.2 (2016-02-22)
+
+### Runtime
+
+- Prevent systemd from deleting containers' cgroups when its configuration is reloaded [#20518](https://github.com/docker/docker/pull/20518)
+- Fix SELinux issues by disregarding `--read-only` when mounting `/dev/mqueue` [#20333](https://github.com/docker/docker/pull/20333)
+- Fix chown permissions used during `docker cp` when userns is used [#20446](https://github.com/docker/docker/pull/20446)
+- Fix configuration loading issue with all booleans defaulting to `true` [#20471](https://github.com/docker/docker/pull/20471)
+- Fix occasional panic with `docker logs -f` [#20522](https://github.com/docker/docker/pull/20522)
+
+### Distribution
+
+- Keep layer reference if deletion failed to avoid a badly inconsistent state [#20513](https://github.com/docker/docker/pull/20513)
+- Handle gracefully a corner case when canceling migration [#20372](https://github.com/docker/docker/pull/20372)
+- Fix docker import on compressed data [#20367](https://github.com/docker/docker/pull/20367)
+- Fix tar-split files corruption during migration that later cause docker push and docker save to fail [#20458](https://github.com/docker/docker/pull/20458)
+
+### Networking
+
+- Fix daemon crash if embedded DNS is sent garbage [#20510](https://github.com/docker/docker/pull/20510)
+
+### Volumes
+
+- Fix issue with multiple volume references with same name [#20381](https://github.com/docker/docker/pull/20381)
+
+### Security
+
+- Fix potential cache corruption and delegation conflict issues [#20523](https://github.com/docker/docker/pull/20523)
+
+## 1.10.1 (2016-02-11)
+
+### Runtime
+
+* Do not stop daemon on migration hard failure [#20156](https://github.com/docker/docker/pull/20156)
+- Fix various issues with migration to content-addressable images [#20058](https://github.com/docker/docker/pull/20058)
+- Fix ZFS permission bug with user namespaces [#20045](https://github.com/docker/docker/pull/20045)
+- Do not leak /dev/mqueue from the host to all containers, keep it container-specific [#19876](https://github.com/docker/docker/pull/19876) [#20133](https://github.com/docker/docker/pull/20133)
+- Fix `docker ps --filter before=...` to not show stopped containers without providing `-a` flag [#20135](https://github.com/docker/docker/pull/20135)
+
+### Security
+
+- Fix issue preventing docker events to work properly with authorization plugin [#20002](https://github.com/docker/docker/pull/20002)
+
+### Distribution
+
+* Add additional verifications and prevent from uploading invalid data to registries [#20164](https://github.com/docker/docker/pull/20164)
+- Fix regression preventing uppercase characters in image reference hostname [#20175](https://github.com/docker/docker/pull/20175)
+
+### Networking
+
+- Fix embedded DNS for user-defined networks in the presence of firewalld [#20060](https://github.com/docker/docker/pull/20060)
+- Fix issue where removing a network during shutdown left Docker inoperable [#20181](https://github.com/docker/docker/issues/20181) [#20235](https://github.com/docker/docker/issues/20235)
+- Embedded DNS is now able to return compressed results [#20181](https://github.com/docker/docker/issues/20181)
+- Fix port-mapping issue with `userland-proxy=false` [#20181](https://github.com/docker/docker/issues/20181)
+
+### Logging
+
+- Fix bug where tcp+tls protocol would be rejected [#20109](https://github.com/docker/docker/pull/20109)
+
+### Volumes
+
+- Fix issue whereby older volume drivers would not receive volume options [#19983](https://github.com/docker/docker/pull/19983)
+
+### Misc
+
+- Remove TasksMax from Docker systemd service [#20167](https://github.com/docker/docker/pull/20167)
+
 ## 1.10.0 (2016-02-04)
 ## 1.10.0 (2016-02-04)
 
 
 **IMPORTANT**: Docker 1.10 uses a new content-addressable storage for images and layers.
 **IMPORTANT**: Docker 1.10 uses a new content-addressable storage for images and layers.
@@ -1771,7 +1838,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
 + Containers can expose public UDP ports (eg, '-p 123/udp')
 + Containers can expose public UDP ports (eg, '-p 123/udp')
 + Optionally specify an exact public port (eg. '-p 80:4500')
 + Optionally specify an exact public port (eg. '-p 80:4500')
 * 'docker login' supports additional options
 * 'docker login' supports additional options
-- Dont save a container`s hostname when committing an image.
+- Don't save a container`s hostname when committing an image.
 
 
 #### Registry
 #### Registry
 
 

+ 2 - 0
CONTRIBUTING.md

@@ -154,6 +154,8 @@ However, there might be a way to implement that feature *on top of* Docker.
       The <a href="https://groups.google.com/forum/#!forum/docker-dev" target="_blank">docker-dev</a>
       The <a href="https://groups.google.com/forum/#!forum/docker-dev" target="_blank">docker-dev</a>
       group is for contributors and other people contributing to the Docker
       group is for contributors and other people contributing to the Docker
       project.
       project.
+      You can join them without an google account by sending an email to e.g. "docker-user+subscribe@googlegroups.com".
+      After receiving the join-request message, you can simply reply to that to confirm the subscribtion.
     </td>
     </td>
   </tr>
   </tr>
   <tr>
   <tr>

+ 12 - 7
Dockerfile

@@ -23,14 +23,16 @@
 # the case. Therefore, you don't have to disable it anymore.
 # the case. Therefore, you don't have to disable it anymore.
 #
 #
 
 
-FROM ubuntu:trusty
+FROM debian:jessie
 
 
 # add zfs ppa
 # add zfs ppa
-RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys E871F18B51E0147C77796AC81196BA81F6B0FC61
+RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys E871F18B51E0147C77796AC81196BA81F6B0FC61 \
+	|| apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys E871F18B51E0147C77796AC81196BA81F6B0FC61
 RUN echo deb http://ppa.launchpad.net/zfs-native/stable/ubuntu trusty main > /etc/apt/sources.list.d/zfs.list
 RUN echo deb http://ppa.launchpad.net/zfs-native/stable/ubuntu trusty main > /etc/apt/sources.list.d/zfs.list
 
 
 # add llvm repo
 # add llvm repo
-RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 6084F3CF814B57C1CF12EFD515CF4D18AF4F7421
+RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 6084F3CF814B57C1CF12EFD515CF4D18AF4F7421 \
+	|| apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 6084F3CF814B57C1CF12EFD515CF4D18AF4F7421
 RUN echo deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty main > /etc/apt/sources.list.d/llvm.list
 RUN echo deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty main > /etc/apt/sources.list.d/llvm.list
 
 
 # Packaged dependencies
 # Packaged dependencies
@@ -56,12 +58,13 @@ RUN apt-get update && apt-get install -y \
 	libsystemd-journal-dev \
 	libsystemd-journal-dev \
 	libtool \
 	libtool \
 	mercurial \
 	mercurial \
+	net-tools \
 	pkg-config \
 	pkg-config \
 	python-dev \
 	python-dev \
 	python-mock \
 	python-mock \
 	python-pip \
 	python-pip \
 	python-websocket \
 	python-websocket \
-	s3cmd=1.1.0* \
+	s3cmd=1.5.0* \
 	ubuntu-zfs \
 	ubuntu-zfs \
 	xfsprogs \
 	xfsprogs \
 	libzfs-dev \
 	libzfs-dev \
@@ -88,9 +91,11 @@ RUN cd /usr/local/lvm2 \
 
 
 # Configure the container for OSX cross compilation
 # Configure the container for OSX cross compilation
 ENV OSX_SDK MacOSX10.11.sdk
 ENV OSX_SDK MacOSX10.11.sdk
+ENV OSX_CROSS_COMMIT 8aa9b71a394905e6c5f4b59e2b97b87a004658a4
 RUN set -x \
 RUN set -x \
 	&& export OSXCROSS_PATH="/osxcross" \
 	&& export OSXCROSS_PATH="/osxcross" \
-	&& git clone --depth 1 https://github.com/tpoechtrager/osxcross.git $OSXCROSS_PATH \
+	&& git clone https://github.com/tpoechtrager/osxcross.git $OSXCROSS_PATH \
+	&& ( cd $OSXCROSS_PATH && git checkout -q $OSX_CROSS_COMMIT) \
 	&& curl -sSL https://s3.dockerproject.org/darwin/${OSX_SDK}.tar.xz -o "${OSXCROSS_PATH}/tarballs/${OSX_SDK}.tar.xz" \
 	&& curl -sSL https://s3.dockerproject.org/darwin/${OSX_SDK}.tar.xz -o "${OSXCROSS_PATH}/tarballs/${OSX_SDK}.tar.xz" \
 	&& UNATTENDED=yes OSX_VERSION_MIN=10.6 ${OSXCROSS_PATH}/build.sh
 	&& UNATTENDED=yes OSX_VERSION_MIN=10.6 ${OSXCROSS_PATH}/build.sh
 ENV PATH /osxcross/target/bin:$PATH
 ENV PATH /osxcross/target/bin:$PATH
@@ -114,7 +119,7 @@ RUN set -x \
 # IMPORTANT: If the version of Go is updated, the Windows to Linux CI machines
 # IMPORTANT: If the version of Go is updated, the Windows to Linux CI machines
 #            will need updating, to avoid errors. Ping #docker-maintainers on IRC
 #            will need updating, to avoid errors. Ping #docker-maintainers on IRC
 #            with a heads-up.
 #            with a heads-up.
-ENV GO_VERSION 1.5.3
+ENV GO_VERSION 1.6
 RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" \
 RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" \
 	| tar -xzC /usr/local
 	| tar -xzC /usr/local
 ENV PATH /go/bin:/usr/local/go/bin:$PATH
 ENV PATH /go/bin:/usr/local/go/bin:$PATH
@@ -166,7 +171,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install notary server
 # Install notary server
-ENV NOTARY_VERSION docker-v1.10-5
+ENV NOTARY_VERSION v0.2.0
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

+ 8 - 18
Dockerfile.aarch64

@@ -11,19 +11,11 @@
 # # Run the test suite:
 # # Run the test suite:
 # docker run --privileged docker hack/make.sh test
 # docker run --privileged docker hack/make.sh test
 #
 #
-# # Publish a release:
-# docker run --privileged \
-#  -e AWS_S3_BUCKET=baz \
-#  -e AWS_ACCESS_KEY=foo \
-#  -e AWS_SECRET_KEY=bar \
-#  -e GPG_PASSPHRASE=gloubiboulga \
-#  docker hack/release.sh
-#
 # Note: AppArmor used to mess with privileged mode, but this is no longer
 # Note: AppArmor used to mess with privileged mode, but this is no longer
 # the case. Therefore, you don't have to disable it anymore.
 # the case. Therefore, you don't have to disable it anymore.
 #
 #
 
 
-FROM aarch64/ubuntu:trusty
+FROM aarch64/ubuntu:wily
 
 
 # Packaged dependencies
 # Packaged dependencies
 RUN apt-get update && apt-get install -y \
 RUN apt-get update && apt-get install -y \
@@ -45,15 +37,16 @@ RUN apt-get update && apt-get install -y \
 	libc6-dev \
 	libc6-dev \
 	libcap-dev \
 	libcap-dev \
 	libsqlite3-dev \
 	libsqlite3-dev \
-	libsystemd-journal-dev \
+	libsystemd-dev \
 	mercurial \
 	mercurial \
+	net-tools \
 	parallel \
 	parallel \
 	pkg-config \
 	pkg-config \
 	python-dev \
 	python-dev \
 	python-mock \
 	python-mock \
 	python-pip \
 	python-pip \
 	python-websocket \
 	python-websocket \
-	s3cmd=1.1.0* \
+	gccgo \
 	--no-install-recommends
 	--no-install-recommends
 
 
 # Install armhf loader to use armv6 binaries on armv8
 # Install armhf loader to use armv6 binaries on armv8
@@ -103,14 +96,11 @@ RUN set -x \
 # We don't have official binary tarballs for ARM64, eigher for Go or bootstrap,
 # We don't have official binary tarballs for ARM64, eigher for Go or bootstrap,
 # so we use the official armv6 released binaries as a GOROOT_BOOTSTRAP, and
 # so we use the official armv6 released binaries as a GOROOT_BOOTSTRAP, and
 # build Go from source code.
 # build Go from source code.
-ENV BOOT_STRAP_VERSION 1.6beta1
 ENV GO_VERSION 1.5.3
 ENV GO_VERSION 1.5.3
-RUN mkdir -p /usr/src/go-bootstrap \
-	&& curl -fsSL https://storage.googleapis.com/golang/go${BOOT_STRAP_VERSION}.linux-arm6.tar.gz | tar -v -C /usr/src/go-bootstrap -xz --strip-components=1 \
-	&& mkdir /usr/src/go \
-	&& curl -fsSL https://storage.googleapis.com/golang/go${GO_VERSION}.src.tar.gz | tar -v -C /usr/src/go -xz --strip-components=1 \
+RUN mkdir /usr/src/go && curl -fsSL https://storage.googleapis.com/golang/go${GO_VERSION}.src.tar.gz | tar -v -C /usr/src/go -xz --strip-components=1 \
 	&& cd /usr/src/go/src \
 	&& cd /usr/src/go/src \
-	&& GOOS=linux GOARCH=arm64 GOROOT_BOOTSTRAP=/usr/src/go-bootstrap ./make.bash
+	&& GOOS=linux GOARCH=arm64 GOROOT_BOOTSTRAP="$(go env GOROOT)" ./make.bash
+
 ENV PATH /usr/src/go/bin:$PATH
 ENV PATH /usr/src/go/bin:$PATH
 ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
 ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
 
 
@@ -127,7 +117,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install notary server
 # Install notary server
-ENV NOTARY_VERSION docker-v1.10-5
+ENV NOTARY_VERSION v0.2.0
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

+ 2 - 10
Dockerfile.armhf

@@ -11,19 +11,11 @@
 # # Run the test suite:
 # # Run the test suite:
 # docker run --privileged docker hack/make.sh test
 # docker run --privileged docker hack/make.sh test
 #
 #
-# # Publish a release:
-# docker run --privileged \
-#  -e AWS_S3_BUCKET=baz \
-#  -e AWS_ACCESS_KEY=foo \
-#  -e AWS_SECRET_KEY=bar \
-#  -e GPG_PASSPHRASE=gloubiboulga \
-#  docker hack/release.sh
-#
 # Note: AppArmor used to mess with privileged mode, but this is no longer
 # Note: AppArmor used to mess with privileged mode, but this is no longer
 # the case. Therefore, you don't have to disable it anymore.
 # the case. Therefore, you don't have to disable it anymore.
 #
 #
 
 
-FROM armhf/ubuntu:trusty
+FROM armhf/debian:jessie
 
 
 # Packaged dependencies
 # Packaged dependencies
 RUN apt-get update && apt-get install -y \
 RUN apt-get update && apt-get install -y \
@@ -143,7 +135,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install notary server
 # Install notary server
-ENV NOTARY_VERSION docker-v1.10-5
+ENV NOTARY_VERSION v0.2.0
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

+ 1 - 0
Dockerfile.gccgo

@@ -23,6 +23,7 @@ RUN apt-get update && apt-get install -y \
 	libcap-dev \
 	libcap-dev \
 	libsqlite3-dev \
 	libsqlite3-dev \
 	mercurial \
 	mercurial \
+	net-tools \
 	parallel \
 	parallel \
 	python-dev \
 	python-dev \
 	python-mock \
 	python-mock \

+ 26 - 20
Dockerfile.ppc64le

@@ -11,14 +11,6 @@
 # # Run the test suite:
 # # Run the test suite:
 # docker run --privileged docker hack/make.sh test
 # docker run --privileged docker hack/make.sh test
 #
 #
-# # Publish a release:
-# docker run --privileged \
-#  -e AWS_S3_BUCKET=baz \
-#  -e AWS_ACCESS_KEY=foo \
-#  -e AWS_SECRET_KEY=bar \
-#  -e GPG_PASSPHRASE=gloubiboulga \
-#  docker hack/release.sh
-#
 # Note: AppArmor used to mess with privileged mode, but this is no longer
 # Note: AppArmor used to mess with privileged mode, but this is no longer
 # the case. Therefore, you don't have to disable it anymore.
 # the case. Therefore, you don't have to disable it anymore.
 #
 #
@@ -82,7 +74,21 @@ RUN cd /usr/local/lvm2 \
 # TODO install Go, using gccgo as GOROOT_BOOTSTRAP (Go 1.5+ supports ppc64le properly)
 # TODO install Go, using gccgo as GOROOT_BOOTSTRAP (Go 1.5+ supports ppc64le properly)
 # possibly a ppc64le/golang image?
 # possibly a ppc64le/golang image?
 
 
-ENV PATH /go/bin:$PATH
+## BUILD GOLANG 1.6
+ENV GO_VERSION 1.6
+ENV GO_DOWNLOAD_URL https://golang.org/dl/go${GO_VERSION}.src.tar.gz
+ENV GO_DOWNLOAD_SHA256 a96cce8ce43a9bf9b2a4c7d470bc7ee0cb00410da815980681c8353218dcf146
+ENV GOROOT_BOOTSTRAP /usr/local
+
+RUN curl -fsSL "$GO_DOWNLOAD_URL" -o golang.tar.gz \
+    && echo "$GO_DOWNLOAD_SHA256  golang.tar.gz" | sha256sum -c - \
+    && tar -C /usr/src -xzf golang.tar.gz \
+    && rm golang.tar.gz \
+    && cd /usr/src/go/src && ./make.bash 2>&1
+
+ENV GOROOT_BOOTSTRAP /usr/src/
+
+ENV PATH /usr/src/go/bin/:/go/bin:$PATH
 ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
 ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
 
 
 # This has been commented out and kept as reference because we don't support compiling with older Go anymore.
 # This has been commented out and kept as reference because we don't support compiling with older Go anymore.
@@ -90,7 +96,7 @@ ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
 # RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt
 # RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt
 
 
 # TODO update this sha when we upgrade to Go 1.5+
 # TODO update this sha when we upgrade to Go 1.5+
-ENV GO_TOOLS_COMMIT 069d2f3bcb68257b627205f0486d6cc69a231ff9
+ENV GO_TOOLS_COMMIT d02228d1857b9f49cd0252788516ff5584266eb6
 # Grab Go's cover tool for dead-simple code coverage testing
 # Grab Go's cover tool for dead-simple code coverage testing
 # Grab Go's vet tool for examining go code to find suspicious constructs
 # Grab Go's vet tool for examining go code to find suspicious constructs
 # and help prevent errors that the compiler might not catch
 # and help prevent errors that the compiler might not catch
@@ -99,7 +105,7 @@ RUN git clone https://github.com/golang/tools.git /go/src/golang.org/x/tools \
 	&& go install -v golang.org/x/tools/cmd/cover \
 	&& go install -v golang.org/x/tools/cmd/cover \
 	&& go install -v golang.org/x/tools/cmd/vet
 	&& go install -v golang.org/x/tools/cmd/vet
 # Grab Go's lint tool
 # Grab Go's lint tool
-ENV GO_LINT_COMMIT f42f5c1c440621302702cb0741e9d2ca547ae80f
+ENV GO_LINT_COMMIT 32a87160691b3c96046c0c678fe57c5bef761456
 RUN git clone https://github.com/golang/lint.git /go/src/github.com/golang/lint \
 RUN git clone https://github.com/golang/lint.git /go/src/github.com/golang/lint \
 	&& (cd /go/src/github.com/golang/lint && git checkout -q $GO_LINT_COMMIT) \
 	&& (cd /go/src/github.com/golang/lint && git checkout -q $GO_LINT_COMMIT) \
 	&& go install -v github.com/golang/lint/golint
 	&& go install -v github.com/golang/lint/golint
@@ -121,16 +127,16 @@ RUN set -x \
 		go build -o /usr/local/bin/registry-v2-schema1 github.com/docker/distribution/cmd/registry \
 		go build -o /usr/local/bin/registry-v2-schema1 github.com/docker/distribution/cmd/registry \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
-# TODO update this when we upgrade to Go 1.5.1+
+
 # Install notary server
 # Install notary server
-#ENV NOTARY_VERSION docker-v1.10-5
-#RUN set -x \
-#	&& export GOPATH="$(mktemp -d)" \
-#	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
-#	&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
-#	&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
-#		go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
-#	&& rm -rf "$GOPATH"
+ENV NOTARY_VERSION v0.2.0
+RUN set -x \
+	&& export GOPATH="$(mktemp -d)" \
+	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
+	&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
+	&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
+		go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
+	&& rm -rf "$GOPATH"
 
 
 # Get the "docker-py" source so we can run their integration tests
 # Get the "docker-py" source so we can run their integration tests
 ENV DOCKER_PY_COMMIT e2878cbcc3a7eef99917adc1be252800b0e41ece
 ENV DOCKER_PY_COMMIT e2878cbcc3a7eef99917adc1be252800b0e41ece

+ 1 - 9
Dockerfile.s390x

@@ -11,14 +11,6 @@
 # # Run the test suite:
 # # Run the test suite:
 # docker run --privileged docker hack/make.sh test
 # docker run --privileged docker hack/make.sh test
 #
 #
-# # Publish a release:
-# docker run --privileged \
-#  -e AWS_S3_BUCKET=baz \
-#  -e AWS_ACCESS_KEY=foo \
-#  -e AWS_SECRET_KEY=bar \
-#  -e GPG_PASSPHRASE=gloubiboulga \
-#  docker hack/release.sh
-#
 # Note: AppArmor used to mess with privileged mode, but this is no longer
 # Note: AppArmor used to mess with privileged mode, but this is no longer
 # the case. Therefore, you don't have to disable it anymore.
 # the case. Therefore, you don't have to disable it anymore.
 #
 #
@@ -116,7 +108,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install notary server
 # Install notary server
-ENV NOTARY_VERSION docker-v1.10-5
+ENV NOTARY_VERSION v0.2.0
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

+ 55 - 47
Dockerfile.windows

@@ -19,14 +19,7 @@
 # Important notes:
 # Important notes:
 # ---------------
 # ---------------
 #
 #
-# Multiple commands in a single powershell RUN command are deliberately not done. This is
-# because PS doesn't have a concept quite like set -e in bash. It would be possible to use 
-# try-catch script blocks, but that would make this file unreadable. The problem is that
-# if there are two commands eg "RUN powershell -command fail; succeed", as far as docker
-# would be concerned, the return code from the overall RUN is succeed. This doesn't apply to
-# RUN which uses cmd as the command interpreter such as "RUN fail; succeed".
-#
-# 'sleep 5' is a deliberate workaround for a current problem on containers in Windows 
+# 'Start-Sleep' is a deliberate workaround for a current problem on containers in Windows 
 # Server 2016. It ensures that the network is up and available for when the command is
 # Server 2016. It ensures that the network is up and available for when the command is
 # network related. This bug is being tracked internally at Microsoft and exists in TP4.
 # network related. This bug is being tracked internally at Microsoft and exists in TP4.
 # Generally sleep 1 or 2 is probably enough, but making it 5 to make the build file
 # Generally sleep 1 or 2 is probably enough, but making it 5 to make the build file
@@ -39,55 +32,70 @@
 # Don't try to use a volume for passing the source through. The cygwin posix utilities will
 # Don't try to use a volume for passing the source through. The cygwin posix utilities will
 # balk at reparse points. Again, see the example at the top of this file on how use a volume
 # balk at reparse points. Again, see the example at the top of this file on how use a volume
 # to get the built binary out of the container.
 # to get the built binary out of the container.
+#
+# The steps are minimised dramatically to improve performance (TP4 is slow on commit)
 
 
 FROM windowsservercore
 FROM windowsservercore
 
 
 # Environment variable notes:
 # Environment variable notes:
-#  - GOLANG_VERSION should be updated to be consistent with the Linux dockerfile.
+#  - GOLANG_VERSION must consistent with 'Dockerfile' used by Linux'.
 #  - FROM_DOCKERFILE is used for detection of building within a container.
 #  - FROM_DOCKERFILE is used for detection of building within a container.
-ENV GOLANG_VERSION=1.5.3 \
-    GIT_VERSION=2.7.0 \
+ENV GOLANG_VERSION=1.6 \
+    GIT_LOCATION=https://github.com/git-for-windows/git/releases/download/v2.7.2.windows.1/Git-2.7.2-64-bit.exe \
     RSRC_COMMIT=ba14da1f827188454a4591717fff29999010887f \
     RSRC_COMMIT=ba14da1f827188454a4591717fff29999010887f \
     GOPATH=C:/go;C:/go/src/github.com/docker/docker/vendor \
     GOPATH=C:/go;C:/go/src/github.com/docker/docker/vendor \
     FROM_DOCKERFILE=1
     FROM_DOCKERFILE=1
 
 
-# Make sure we're in temp for the downloads
-WORKDIR c:/windows/temp
-
-# Download everything else we need to install
-# We want a 64-bit make.exe, not 16 or 32-bit. This was hard to find, so documenting the links
-#  - http://sourceforge.net/p/mingw-w64/wiki2/Make/ -->
-#  - http://sourceforge.net/projects/mingw-w64/files/External%20binary%20packages%20%28Win64%20hosted%29/ -->
-#  - http://sourceforge.net/projects/mingw-w64/files/External binary packages %28Win64 hosted%29/make/
-RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile make.zip http://downloads.sourceforge.net/project/mingw-w64/External%20binary%20packages%20%28Win64%20hosted%29/make/make-3.82.90-20111115.zip
-RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile gcc.zip http://downloads.sourceforge.net/project/tdm-gcc/TDM-GCC%205%20series/5.1.0-tdm64-1/gcc-5.1.0-tdm64-1-core.zip 
-RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile runtime.zip http://downloads.sourceforge.net/project/tdm-gcc/MinGW-w64%20runtime/GCC%205%20series/mingw64runtime-v4-git20150618-gcc5-tdm64-1.zip
-RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile binutils.zip http://downloads.sourceforge.net/project/tdm-gcc/GNU%20binutils/binutils-2.25-tdm64-1.zip
-RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile 7zsetup.exe http://www.7-zip.org/a/7z1514-x64.exe 
-RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile lzma.7z http://www.7-zip.org/a/lzma1514.7z
-RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile gitsetup.exe https://github.com/git-for-windows/git/releases/download/v%GIT_VERSION%.windows.1/Git-%GIT_VERSION%-64-bit.exe
-RUN powershell -command sleep 5; Invoke-WebRequest -UserAgent 'DockerCI' -outfile go.msi https://storage.googleapis.com/golang/go%GOLANG_VERSION%.windows-amd64.msi
-
-# Path
-RUN setx /M Path "c:\git\cmd;c:\git\bin;c:\git\usr\bin;%Path%;c:\gcc\bin;c:\7zip"
-
-# Install and expand the bits we downloaded. 
-# Note: The git, 7z and go.msi installers execute asynchronously. 
-RUN powershell -command start-process .\gitsetup.exe -ArgumentList '/VERYSILENT /SUPPRESSMSGBOXES /CLOSEAPPLICATIONS /DIR=c:\git' -Wait
-RUN powershell -command start-process .\7zsetup -ArgumentList '/S /D=c:/7zip' -Wait
-RUN powershell -command start-process .\go.msi -ArgumentList '/quiet' -Wait
-RUN powershell -command Expand-Archive gcc.zip \gcc -Force 
-RUN powershell -command Expand-Archive runtime.zip \gcc -Force 
-RUN powershell -command Expand-Archive binutils.zip \gcc -Force
-RUN powershell -command 7z e lzma.7z bin/lzma.exe
-RUN powershell -command 7z x make.zip  make-3.82.90-20111115/bin_amd64/make.exe 
-RUN powershell -command mv make-3.82.90-20111115/bin_amd64/make.exe /gcc/bin/ 
-
-# RSRC for manifest and icon	
-RUN powershell -command sleep 5 ; git clone https://github.com/akavel/rsrc.git c:\go\src\github.com\akavel\rsrc 
-RUN cd c:/go/src/github.com/akavel/rsrc && git checkout -q %RSRC_COMMIT% && go install -v
+WORKDIR c:/
 
 
+# Everything downloaded/installed in one go (better performance, esp on TP4)
+RUN \
+ setx /M Path "c:\git\cmd;c:\git\bin;c:\git\usr\bin;%Path%;c:\gcc\bin;c:\go\bin" && \
+ setx GOROOT "c:\go" && \
+ powershell -command \
+  $ErrorActionPreference = 'Stop'; \
+  Start-Sleep -Seconds 5; \
+  Function Download-File([string] $source, [string] $target) { \
+   $wc = New-Object net.webclient; $wc.Downloadfile($source, $target) \
+  } \
+  \
+  Write-Host INFO: Downloading git...; \		
+  Download-File %GIT_LOCATION% gitsetup.exe; \
+  \
+  Write-Host INFO: Downloading go...; \
+  Download-File https://storage.googleapis.com/golang/go%GOLANG_VERSION%.windows-amd64.msi go.msi; \
+  \
+  Write-Host INFO: Downloading compiler 1 of 3...; \
+  Download-File https://raw.githubusercontent.com/jhowardmsft/docker-tdmgcc/master/gcc.zip gcc.zip; \
+  \
+  Write-Host INFO: Downloading compiler 2 of 3...; \
+  Download-File https://raw.githubusercontent.com/jhowardmsft/docker-tdmgcc/master/runtime.zip runtime.zip; \
+  \
+  Write-Host INFO: Downloading compiler 3 of 3...; \
+  Download-File https://raw.githubusercontent.com/jhowardmsft/docker-tdmgcc/master/binutils.zip binutils.zip; \
+  \
+  Write-Host INFO: Installing git...; \
+  Start-Process gitsetup.exe -ArgumentList '/VERYSILENT /SUPPRESSMSGBOXES /CLOSEAPPLICATIONS /DIR=c:\git\' -Wait; \
+  \
+  Write-Host INFO: Installing go..."; \
+  Start-Process msiexec -ArgumentList '-i go.msi -quiet' -Wait; \
+  \
+  Write-Host INFO: Unzipping compiler...; \
+  c:\git\usr\bin\unzip.exe -q -o gcc.zip -d /c/gcc; \
+  c:\git\usr\bin\unzip.exe -q -o runtime.zip -d /c/gcc; \
+  c:\git\usr\bin\unzip.exe -q -o binutils.zip -d /c/gcc"; \
+  \
+  Write-Host INFO: Removing interim files; \
+  Remove-Item *.zip; \
+  Remove-Item go.msi; \
+  Remove-Item gitsetup.exe; \
+  \
+  Write-Host INFO: Cloning and installing RSRC; \
+  c:\git\bin\git.exe clone https://github.com/akavel/rsrc.git c:\go\src\github.com\akavel\rsrc; \
+  cd \go\src\github.com\akavel\rsrc; c:\git\bin\git.exe checkout -q %RSRC_COMMIT%; c:\go\bin\go.exe install -v; \
+  \
+  Write-Host INFO: Completed
+  
 # Prepare for building
 # Prepare for building
-WORKDIR c:/
 COPY . /go/src/github.com/docker/docker
 COPY . /go/src/github.com/docker/docker
 
 

+ 6 - 5
MAINTAINERS

@@ -26,6 +26,7 @@
 	# the release process is clear and up-to-date.
 	# the release process is clear and up-to-date.
 
 
 		people = [
 		people = [
+			"aaronlehmann",
 			"calavera",
 			"calavera",
 			"coolljt0725",
 			"coolljt0725",
 			"cpuguy83",
 			"cpuguy83",
@@ -111,6 +112,11 @@
 
 
 	# ADD YOURSELF HERE IN ALPHABETICAL ORDER
 	# ADD YOURSELF HERE IN ALPHABETICAL ORDER
 
 
+	[people.aaronlehmann]
+	Name = "Aaron Lehmann"
+	Email = "aaron.lehmann@docker.com"
+	GitHub = "aaronlehmann"
+
 	[people.calavera]
 	[people.calavera]
 	Name = "David Calavera"
 	Name = "David Calavera"
 	Email = "david.calavera@gmail.com"
 	Email = "david.calavera@gmail.com"
@@ -196,11 +202,6 @@
 	Email = "github@gone.nl"
 	Email = "github@gone.nl"
 	GitHub = "thaJeztah"
 	GitHub = "thaJeztah"
 
 
-	[people.theadactyl]
-	Name = "Thea Lamkin"
-	Email = "thea@docker.com"
-	GitHub = "theadactyl"
-
 	[people.tianon]
 	[people.tianon]
 	Name = "Tianon Gravi"
 	Name = "Tianon Gravi"
 	Email = "admwiggin@gmail.com"
 	Email = "admwiggin@gmail.com"

+ 14 - 0
Makefile

@@ -54,6 +54,10 @@ DOCKER_ENVS := \
 BIND_DIR := $(if $(BINDDIR),$(BINDDIR),$(if $(DOCKER_HOST),,bundles))
 BIND_DIR := $(if $(BINDDIR),$(BINDDIR),$(if $(DOCKER_HOST),,bundles))
 DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/docker/docker/$(BIND_DIR)")
 DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/docker/docker/$(BIND_DIR)")
 
 
+# This allows the test suite to be able to run without worrying about the underlying fs used by the container running the daemon (e.g. aufs-on-aufs), so long as the host running the container is running a supported fs.
+# The volume will be cleaned up when the container is removed due to `--rm`.
+# Note that `BIND_DIR` will already be set to `bundles` if `DOCKER_HOST` is not set (see above BIND_DIR line), in such case this will do nothing since `DOCKER_MOUNT` will already be set.
+DOCKER_MOUNT := $(if $(DOCKER_MOUNT),$(DOCKER_MOUNT),-v "/go/src/github.com/docker/docker/bundles")
 
 
 GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
 GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
 DOCKER_IMAGE := docker-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
 DOCKER_IMAGE := docker-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
@@ -80,6 +84,16 @@ binary: build
 	$(DOCKER_RUN_DOCKER) hack/make.sh binary
 	$(DOCKER_RUN_DOCKER) hack/make.sh binary
 
 
 build: bundles
 build: bundles
+ifeq ($(DOCKER_OSARCH), linux/arm)
+	# A few libnetwork integration tests require that the kernel be
+	# configured with "dummy" network interface and has the module
+	# loaded. However, the dummy module is not available by default
+	# on arm images. This ensures that it's built and loaded.
+	echo "Syncing kernel modules"
+	oc-sync-kernel-modules
+	depmod
+	modprobe dummy
+endif
 	docker build ${DOCKER_BUILD_ARGS} -t "$(DOCKER_IMAGE)" -f "$(DOCKERFILE)" .
 	docker build ${DOCKER_BUILD_ARGS} -t "$(DOCKER_IMAGE)" -f "$(DOCKERFILE)" .
 
 
 bundles:
 bundles:

+ 3 - 1
README.md

@@ -216,7 +216,7 @@ We are always open to suggestions on process improvements, and are always lookin
     <td>Internet&nbsp;Relay&nbsp;Chat&nbsp;(IRC)</td>
     <td>Internet&nbsp;Relay&nbsp;Chat&nbsp;(IRC)</td>
     <td>
     <td>
       <p>
       <p>
-        IRC a direct line to our most knowledgeable Docker users; we have
+        IRC is a direct line to our most knowledgeable Docker users; we have
         both the  <code>#docker</code> and <code>#docker-dev</code> group on
         both the  <code>#docker</code> and <code>#docker-dev</code> group on
         <strong>irc.freenode.net</strong>.
         <strong>irc.freenode.net</strong>.
         IRC is a rich chat protocol but it can overwhelm new users. You can search
         IRC is a rich chat protocol but it can overwhelm new users. You can search
@@ -234,6 +234,8 @@ We are always open to suggestions on process improvements, and are always lookin
       The <a href="https://groups.google.com/forum/#!forum/docker-dev" target="_blank">docker-dev</a>
       The <a href="https://groups.google.com/forum/#!forum/docker-dev" target="_blank">docker-dev</a>
       group is for contributors and other people contributing to the Docker
       group is for contributors and other people contributing to the Docker
       project.
       project.
+      You can join them without an google account by sending an email to e.g. "docker-user+subscribe@googlegroups.com".
+      After receiving the join-request message, you can simply reply to that to confirm the subscribtion.
     </td>
     </td>
   </tr>
   </tr>
   <tr>
   <tr>

+ 72 - 115
ROADMAP.md

@@ -33,97 +33,58 @@ won't be accepting pull requests adding or removing items from this file.
 
 
 # 1. Features and refactoring
 # 1. Features and refactoring
 
 
-## 1.1 Security
+## 1.1 Runtime improvements
 
 
-Security is a top objective for the Docker Engine. The most notable items we intend to provide in
-the near future are:
+We recently introduced [`runC`](https://runc.io) as a standalone low-level tool for container
+execution. The initial goal was to integrate runC as a replacement in the Engine for the traditional
+default libcontainer `execdriver`, but the Engine internals were not ready for this.
 
 
-- Trusted distribution of images: the effort is driven by the [distribution](https://github.com/docker/distribution)
-group but will have significant impact on the Engine
-- [User namespaces](https://github.com/docker/docker/pull/12648)
-- [Seccomp support](https://github.com/docker/libcontainer/pull/613)
+As runC continued evolving, and the OCI specification along with it, we created
+[`containerd`](https://containerd.tools/), a daemon to control and monitor multiple `runC`. This is
+the new target for Engine integration, as it can entirely replace the whole `execdriver`
+architecture, and container monitoring along with it.
 
 
-## 1.2 Plumbing project
+Docker Engine will rely on a long-running `containerd` companion daemon for all container execution
+related operations. This could open the door in the future for Engine restarts without interrupting
+running containers.
 
 
-We define a plumbing tool as a standalone piece of software usable and meaningful on its own. In
-the current state of the Docker Engine, most subsystems provide independent functionalities (such
-the builder, pushing and pulling images, running applications in a containerized environment, etc)
-but all are coupled in a single binary.  We want to offer the users to flexibility to use only the
-pieces they need, and we will also gain in maintainability by splitting the project among multiple
-repositories.
+## 1.2 Plugins improvements
 
 
-As it currently stands, the rough design outlines is to have:
-- Low level plumbing tools, each dealing with one responsibility (e.g., [runC](https://runc.io))
-- Docker subsystems services, each exposing an elementary concept over an API, and relying on one or
-multiple lower level plumbing tools for their implementation (e.g., network management)
-- Docker Engine to expose higher level actions (e.g., create a container with volume `V` and network
-`N`), while still providing pass-through access to the individual subsystems.
+Docker Engine 1.7.0 introduced plugin support, initially for the use cases of volumes and networks
+extensions. The plugin infrastructure was kept minimal as we were collecting use cases and real
+world feedback before optimizing for any particular workflow.
 
 
-The architectural details are still being worked on, but one thing we know for sure is that we need
-to technically decouple the pieces.
+In the future, we'd like plugins to become first class citizens, and encourage an ecosystem of
+plugins. This implies in particular making it trivially easy to distribute plugins as containers
+through any Registry instance, as well as solving the commonly heard pain points of plugins needing
+to be treated as somewhat special (being active at all time, started before any other user
+containers, and not as easily dismissed).
 
 
-### 1.2.1 Runtime
+## 1.3 Internal decoupling
 
 
-A Runtime tool already exists today in the form of [runC](https://github.com/opencontainers/runc).
-We intend to modify the Engine to directly call out to a binary implementing the Open Containers
-Specification such as runC rather than relying on libcontainer to set the container runtime up.
+A lot of work has been done in trying to decouple the Docker Engine's internals. In particular, the
+API implementation has been refactored and ongoing work is happening to move the code to a separate
+repository ([`docker/engine-api`](https://github.com/docker/engine-api)), and the Builder side of
+the daemon is now [fully independent](https://github.com/docker/docker/tree/master/builder) while
+still residing in the same repository.
 
 
-This plan will deprecate the existing [`execdriver`](https://github.com/docker/docker/tree/master/daemon/execdriver)
-as different runtime backends will be implemented as separated binaries instead of being compiled
-into the Engine.
+We are exploring ways to go further with that decoupling, capitalizing on the work introduced by the
+runtime renovation and plugins improvement efforts. Indeed, the combination of `containerd` support
+with the concept of "special" containers opens the door for bootstrapping more Engine internals
+using the same facilities.
 
 
-### 1.2.2 Builder
+## 1.4 Cluster capable Engine
 
 
-The Builder (i.e., the ability to build an image from a Dockerfile) is already nicely decoupled,
-but would benefit from being entirely separated from the Engine, and rely on the standard Engine
-API for its operations.
+The community has been pushing for a more cluster capable Docker Engine, and a huge effort was spent
+adding features such as multihost networking, and node discovery down at the Engine level. Yet, the
+Engine is currently incapable of taking scheduling decisions alone, and continues relying on Swarm
+for that.
 
 
-### 1.2.3 Distribution
-
-Distribution already has a [dedicated repository](https://github.com/docker/distribution) which
-holds the implementation for Registry v2 and client libraries. We could imagine going further by
-having the Engine call out to a binary providing image distribution related functionalities.
-
-There are two short term goals related to image distribution. The first is stabilize and simplify
-the push/pull code. Following that is the conversion to the more secure Registry V2 protocol.
-
-### 1.2.4 Networking
-
-Most of networking related code was already decoupled today in [libnetwork](https://github.com/docker/libnetwork).
-As with other ingredients, we might want to take it a step further and make it a meaningful utility
-that the Engine would call out to instead of a library.
-
-## 1.3 Plugins
-
-An initiative around plugins started with Docker 1.7.0, with the goal of allowing for out of
-process extensibility of some Docker functionalities, starting with volumes and networking. The
-approach is to provide specific extension points rather than generic hooking facilities. We also
-deliberately keep the extensions API the simplest possible, expanding as we discover valid use
-cases that cannot be implemented.
-
-At the time of writing:
-
-- Plugin support is merged as an experimental feature: real world use cases and user feedback will
-help us refine the UX to make the feature more user friendly.
-- There are no immediate plans to expand on the number of pluggable subsystems.
-- Golang 1.5 might add language support for [plugins](https://docs.google.com/document/d/1nr-TQHw_er6GOQRsF6T43GGhFDelrAP0NqSS_00RgZQ)
-which we consider supporting as an alternative to JSON/HTTP.
-
-## 1.4 Volume management
-
-Volumes are not a first class citizen in the Engine today: we would like better volume management,
-similar to the way network are managed in the new [CNM](https://github.com/docker/docker/issues/9983).
-
-## 1.5 Better API implementation
-
-The current Engine API is insufficiently typed, versioned, and ultimately hard to maintain. We
-also suffer from the lack of a common implementation with [Swarm](https://github.com/docker/swarm).
-
-## 1.6 Checkpoint/restore
-
-Support for checkpoint/restore was [merged](https://github.com/docker/libcontainer/pull/479) in
-[libcontainer](https://github.com/docker/libcontainer) and made available through [runC](https://runc.io):
-we intend to take advantage of it in the Engine.
+We plan to complete this effort and make Engine fully cluster capable. Multiple instances of the
+Docker Engine being already capable of discovering each other and establish overlay networking for
+their container to communicate, the next step is for a given Engine to gain ability to dispatch work
+to another node in the cluster. This will be introduced in a backward compatible way, such that a
+`docker run` invocation on a particular node remains fully deterministic.
 
 
 # 2 Frozen features
 # 2 Frozen features
 
 
@@ -139,45 +100,41 @@ The Dockerfile syntax as we know it is simple, and has proven successful in supp
 definitive move, we temporarily won't accept more patches to the Dockerfile syntax for several
 definitive move, we temporarily won't accept more patches to the Dockerfile syntax for several
 reasons:
 reasons:
 
 
-- Long term impact of syntax changes is a sensitive matter that require an amount of attention
-the volume of Engine codebase and activity today doesn't allow us to provide.
-- Allowing the Builder to be implemented as a separate utility consuming the Engine's API will
-open the door for many possibilities, such as offering alternate syntaxes or DSL for existing
-languages without cluttering the Engine's codebase.
-- A standalone Builder will also offer the opportunity for a better dedicated group of maintainers
-to own the Dockerfile syntax and decide collectively on the direction to give it.
-- Our experience with official images tend to show that no new instruction or syntax expansion is
-*strictly* necessary for the majority of use cases, and although we are aware many things are still
-lacking for many, we cannot make it a priority yet for the above reasons.
+  - Long term impact of syntax changes is a sensitive matter that require an amount of attention the
+    volume of Engine codebase and activity today doesn't allow us to provide.
+  - Allowing the Builder to be implemented as a separate utility consuming the Engine's API will
+    open the door for many possibilities, such as offering alternate syntaxes or DSL for existing
+    languages without cluttering the Engine's codebase.
+  - A standalone Builder will also offer the opportunity for a better dedicated group of maintainers
+    to own the Dockerfile syntax and decide collectively on the direction to give it.
+  - Our experience with official images tend to show that no new instruction or syntax expansion is
+    *strictly* necessary for the majority of use cases, and although we are aware many things are
+    still lacking for many, we cannot make it a priority yet for the above reasons.
 
 
 Again, this is not about saying that the Dockerfile syntax is done, it's about making choices about
 Again, this is not about saying that the Dockerfile syntax is done, it's about making choices about
 what we want to do first!
 what we want to do first!
 
 
 ## 2.3 Remote Registry Operations
 ## 2.3 Remote Registry Operations
 
 
-A large amount of work is ongoing in the area of image distribution and
-provenance. This includes moving to the V2 Registry API and heavily
-refactoring the code that powers these features. The desired result is more
-secure, reliable and easier to use image distribution.
-
-Part of the problem with this part of the code base is the lack of a stable
-and flexible interface. If new features are added that access the registry
-without solidifying these interfaces, achieving feature parity will continue
-to be elusive. While we get a handle on this situation, we are imposing a
-moratorium on new code that accesses the Registry API in commands that don't
-already make remote calls.
-
-Currently, only the following commands cause interaction with a remote
-registry:
-
-- push
-- pull
-- run
-- build
-- search
-- login
-
-In the interest of stabilizing the registry access model during this ongoing
-work, we are not accepting additions to other commands that will cause remote
-interaction with the Registry API. This moratorium will lift when the goals of
-the distribution project have been met.
+A large amount of work is ongoing in the area of image distribution and provenance. This includes
+moving to the V2 Registry API and heavily refactoring the code that powers these features. The
+desired result is more secure, reliable and easier to use image distribution.
+
+Part of the problem with this part of the code base is the lack of a stable and flexible interface.
+If new features are added that access the registry without solidifying these interfaces, achieving
+feature parity will continue to be elusive. While we get a handle on this situation, we are imposing
+a moratorium on new code that accesses the Registry API in commands that don't already make remote
+calls.
+
+Currently, only the following commands cause interaction with a remote registry:
+
+  - push
+  - pull
+  - run
+  - build
+  - search
+  - login
+
+In the interest of stabilizing the registry access model during this ongoing work, we are not
+accepting additions to other commands that will cause remote interaction with the Registry API. This
+moratorium will lift when the goals of the distribution project have been met.

+ 15 - 6
api/client/attach.go

@@ -41,12 +41,6 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 		return err
 		return err
 	}
 	}
 
 
-	if c.Config.Tty && cli.isTerminalOut {
-		if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
-			logrus.Debugf("Error monitoring TTY size: %s", err)
-		}
-	}
-
 	if *detachKeys != "" {
 	if *detachKeys != "" {
 		cli.configFile.DetachKeys = *detachKeys
 		cli.configFile.DetachKeys = *detachKeys
 	}
 	}
@@ -82,6 +76,21 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 		defer cli.restoreTerminal(in)
 		defer cli.restoreTerminal(in)
 	}
 	}
 
 
+	if c.Config.Tty && cli.isTerminalOut {
+		height, width := cli.getTtySize()
+		// To handle the case where a user repeatedly attaches/detaches without resizing their
+		// terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially
+		// resize it, then go back to normal. Without this, every attach after the first will
+		// require the user to manually resize or hit enter.
+		cli.resizeTtyTo(cmd.Arg(0), height+1, width+1, false)
+
+		// After the above resizing occurs, the call to monitorTtySize below will handle resetting back
+		// to the actual size.
+		if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
+			logrus.Debugf("Error monitoring TTY size: %s", err)
+		}
+	}
+
 	if err := cli.holdHijackedConnection(c.Config.Tty, in, cli.out, cli.err, resp); err != nil {
 	if err := cli.holdHijackedConnection(c.Config.Tty, in, cli.out, cli.err, resp); err != nil {
 		return err
 		return err
 	}
 	}

+ 9 - 253
api/client/build.go

@@ -6,25 +6,20 @@ import (
 	"bytes"
 	"bytes"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
-	"io/ioutil"
 	"os"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"path/filepath"
 	"regexp"
 	"regexp"
 	"runtime"
 	"runtime"
-	"strings"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api"
+	"github.com/docker/docker/builder"
 	"github.com/docker/docker/builder/dockerignore"
 	"github.com/docker/docker/builder/dockerignore"
 	Cli "github.com/docker/docker/cli"
 	Cli "github.com/docker/docker/cli"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/archive"
 	"github.com/docker/docker/pkg/archive"
 	"github.com/docker/docker/pkg/fileutils"
 	"github.com/docker/docker/pkg/fileutils"
-	"github.com/docker/docker/pkg/gitutils"
-	"github.com/docker/docker/pkg/httputils"
-	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/jsonmessage"
 	"github.com/docker/docker/pkg/jsonmessage"
 	flag "github.com/docker/docker/pkg/mflag"
 	flag "github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/pkg/progress"
 	"github.com/docker/docker/pkg/progress"
@@ -65,7 +60,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 	flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
 	flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
 	flBuildArg := opts.NewListOpts(runconfigopts.ValidateEnv)
 	flBuildArg := opts.NewListOpts(runconfigopts.ValidateEnv)
 	cmd.Var(&flBuildArg, []string{"-build-arg"}, "Set build-time variables")
 	cmd.Var(&flBuildArg, []string{"-build-arg"}, "Set build-time variables")
-	isolation := cmd.String([]string{"-isolation"}, "", "Container isolation level")
+	isolation := cmd.String([]string{"-isolation"}, "", "Container isolation technology")
 
 
 	ulimits := make(map[string]*units.Ulimit)
 	ulimits := make(map[string]*units.Ulimit)
 	flUlimits := runconfigopts.NewUlimitOpt(&ulimits)
 	flUlimits := runconfigopts.NewUlimitOpt(&ulimits)
@@ -102,13 +97,13 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 
 
 	switch {
 	switch {
 	case specifiedContext == "-":
 	case specifiedContext == "-":
-		ctx, relDockerfile, err = getContextFromReader(cli.in, *dockerfileName)
+		ctx, relDockerfile, err = builder.GetContextFromReader(cli.in, *dockerfileName)
 	case urlutil.IsGitURL(specifiedContext):
 	case urlutil.IsGitURL(specifiedContext):
-		tempDir, relDockerfile, err = getContextFromGitURL(specifiedContext, *dockerfileName)
+		tempDir, relDockerfile, err = builder.GetContextFromGitURL(specifiedContext, *dockerfileName)
 	case urlutil.IsURL(specifiedContext):
 	case urlutil.IsURL(specifiedContext):
-		ctx, relDockerfile, err = getContextFromURL(progBuff, specifiedContext, *dockerfileName)
+		ctx, relDockerfile, err = builder.GetContextFromURL(progBuff, specifiedContext, *dockerfileName)
 	default:
 	default:
-		contextDir, relDockerfile, err = getContextFromLocalDir(specifiedContext, *dockerfileName)
+		contextDir, relDockerfile, err = builder.GetContextFromLocalDir(specifiedContext, *dockerfileName)
 	}
 	}
 
 
 	if err != nil {
 	if err != nil {
@@ -143,7 +138,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 			}
 			}
 		}
 		}
 
 
-		if err := validateContextDirectory(contextDir, excludes); err != nil {
+		if err := builder.ValidateContextDirectory(contextDir, excludes); err != nil {
 			return fmt.Errorf("Error checking context: '%s'.", err)
 			return fmt.Errorf("Error checking context: '%s'.", err)
 		}
 		}
 
 
@@ -223,7 +218,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 		Remove:         *rm,
 		Remove:         *rm,
 		ForceRemove:    *forceRm,
 		ForceRemove:    *forceRm,
 		PullParent:     *pull,
 		PullParent:     *pull,
-		IsolationLevel: container.IsolationLevel(*isolation),
+		Isolation:      container.Isolation(*isolation),
 		CPUSetCPUs:     *flCPUSetCpus,
 		CPUSetCPUs:     *flCPUSetCpus,
 		CPUSetMems:     *flCPUSetMems,
 		CPUSetMems:     *flCPUSetMems,
 		CPUShares:      *flCPUShares,
 		CPUShares:      *flCPUShares,
@@ -234,7 +229,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 		ShmSize:        shmSize,
 		ShmSize:        shmSize,
 		Ulimits:        flUlimits.GetList(),
 		Ulimits:        flUlimits.GetList(),
 		BuildArgs:      runconfigopts.ConvertKVStringsToMap(flBuildArg.GetAll()),
 		BuildArgs:      runconfigopts.ConvertKVStringsToMap(flBuildArg.GetAll()),
-		AuthConfigs:    cli.configFile.AuthConfigs,
+		AuthConfigs:    cli.retrieveAuthConfigs(),
 	}
 	}
 
 
 	response, err := cli.client.ImageBuild(context.Background(), options)
 	response, err := cli.client.ImageBuild(context.Background(), options)
@@ -281,54 +276,6 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 	return nil
 	return nil
 }
 }
 
 
-// validateContextDirectory checks if all the contents of the directory
-// can be read and returns an error if some files can't be read
-// symlinks which point to non-existing files don't trigger an error
-func validateContextDirectory(srcPath string, excludes []string) error {
-	contextRoot, err := getContextRoot(srcPath)
-	if err != nil {
-		return err
-	}
-	return filepath.Walk(contextRoot, func(filePath string, f os.FileInfo, err error) error {
-		// skip this directory/file if it's not in the path, it won't get added to the context
-		if relFilePath, err := filepath.Rel(contextRoot, filePath); err != nil {
-			return err
-		} else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil {
-			return err
-		} else if skip {
-			if f.IsDir() {
-				return filepath.SkipDir
-			}
-			return nil
-		}
-
-		if err != nil {
-			if os.IsPermission(err) {
-				return fmt.Errorf("can't stat '%s'", filePath)
-			}
-			if os.IsNotExist(err) {
-				return nil
-			}
-			return err
-		}
-
-		// skip checking if symlinks point to non-existing files, such symlinks can be useful
-		// also skip named pipes, because they hanging on open
-		if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
-			return nil
-		}
-
-		if !f.IsDir() {
-			currentFile, err := os.Open(filePath)
-			if err != nil && os.IsPermission(err) {
-				return fmt.Errorf("no permission to read from '%s'", filePath)
-			}
-			currentFile.Close()
-		}
-		return nil
-	})
-}
-
 // validateTag checks if the given image name can be resolved.
 // validateTag checks if the given image name can be resolved.
 func validateTag(rawRepo string) (string, error) {
 func validateTag(rawRepo string) (string, error) {
 	_, err := reference.ParseNamed(rawRepo)
 	_, err := reference.ParseNamed(rawRepo)
@@ -339,96 +286,6 @@ func validateTag(rawRepo string) (string, error) {
 	return rawRepo, nil
 	return rawRepo, nil
 }
 }
 
 
-// isUNC returns true if the path is UNC (one starting \\). It always returns
-// false on Linux.
-func isUNC(path string) bool {
-	return runtime.GOOS == "windows" && strings.HasPrefix(path, `\\`)
-}
-
-// getDockerfileRelPath uses the given context directory for a `docker build`
-// and returns the absolute path to the context directory, the relative path of
-// the dockerfile in that context directory, and a non-nil error on success.
-func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDir, relDockerfile string, err error) {
-	if absContextDir, err = filepath.Abs(givenContextDir); err != nil {
-		return "", "", fmt.Errorf("unable to get absolute context directory: %v", err)
-	}
-
-	// The context dir might be a symbolic link, so follow it to the actual
-	// target directory.
-	//
-	// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
-	// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
-	// paths (those starting with \\). This hack means that when using links
-	// on UNC paths, they will not be followed.
-	if !isUNC(absContextDir) {
-		absContextDir, err = filepath.EvalSymlinks(absContextDir)
-		if err != nil {
-			return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
-		}
-	}
-
-	stat, err := os.Lstat(absContextDir)
-	if err != nil {
-		return "", "", fmt.Errorf("unable to stat context directory %q: %v", absContextDir, err)
-	}
-
-	if !stat.IsDir() {
-		return "", "", fmt.Errorf("context must be a directory: %s", absContextDir)
-	}
-
-	absDockerfile := givenDockerfile
-	if absDockerfile == "" {
-		// No -f/--file was specified so use the default relative to the
-		// context directory.
-		absDockerfile = filepath.Join(absContextDir, api.DefaultDockerfileName)
-
-		// Just to be nice ;-) look for 'dockerfile' too but only
-		// use it if we found it, otherwise ignore this check
-		if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) {
-			altPath := filepath.Join(absContextDir, strings.ToLower(api.DefaultDockerfileName))
-			if _, err = os.Lstat(altPath); err == nil {
-				absDockerfile = altPath
-			}
-		}
-	}
-
-	// If not already an absolute path, the Dockerfile path should be joined to
-	// the base directory.
-	if !filepath.IsAbs(absDockerfile) {
-		absDockerfile = filepath.Join(absContextDir, absDockerfile)
-	}
-
-	// Evaluate symlinks in the path to the Dockerfile too.
-	//
-	// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
-	// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
-	// paths (those starting with \\). This hack means that when using links
-	// on UNC paths, they will not be followed.
-	if !isUNC(absDockerfile) {
-		absDockerfile, err = filepath.EvalSymlinks(absDockerfile)
-		if err != nil {
-			return "", "", fmt.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err)
-		}
-	}
-
-	if _, err := os.Lstat(absDockerfile); err != nil {
-		if os.IsNotExist(err) {
-			return "", "", fmt.Errorf("Cannot locate Dockerfile: %q", absDockerfile)
-		}
-		return "", "", fmt.Errorf("unable to stat Dockerfile: %v", err)
-	}
-
-	if relDockerfile, err = filepath.Rel(absContextDir, absDockerfile); err != nil {
-		return "", "", fmt.Errorf("unable to get relative Dockerfile path: %v", err)
-	}
-
-	if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
-		return "", "", fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir)
-	}
-
-	return absContextDir, relDockerfile, nil
-}
-
 // writeToFile copies from the given reader and writes it to a file with the
 // writeToFile copies from the given reader and writes it to a file with the
 // given filename.
 // given filename.
 func writeToFile(r io.Reader, filename string) error {
 func writeToFile(r io.Reader, filename string) error {
@@ -445,107 +302,6 @@ func writeToFile(r io.Reader, filename string) error {
 	return nil
 	return nil
 }
 }
 
 
-// getContextFromReader will read the contents of the given reader as either a
-// Dockerfile or tar archive. Returns a tar archive used as a context and a
-// path to the Dockerfile inside the tar.
-func getContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
-	buf := bufio.NewReader(r)
-
-	magic, err := buf.Peek(archive.HeaderSize)
-	if err != nil && err != io.EOF {
-		return nil, "", fmt.Errorf("failed to peek context header from STDIN: %v", err)
-	}
-
-	if archive.IsArchive(magic) {
-		return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil
-	}
-
-	// Input should be read as a Dockerfile.
-	tmpDir, err := ioutil.TempDir("", "docker-build-context-")
-	if err != nil {
-		return nil, "", fmt.Errorf("unbale to create temporary context directory: %v", err)
-	}
-
-	f, err := os.Create(filepath.Join(tmpDir, api.DefaultDockerfileName))
-	if err != nil {
-		return nil, "", err
-	}
-	_, err = io.Copy(f, buf)
-	if err != nil {
-		f.Close()
-		return nil, "", err
-	}
-
-	if err := f.Close(); err != nil {
-		return nil, "", err
-	}
-	if err := r.Close(); err != nil {
-		return nil, "", err
-	}
-
-	tar, err := archive.Tar(tmpDir, archive.Uncompressed)
-	if err != nil {
-		return nil, "", err
-	}
-
-	return ioutils.NewReadCloserWrapper(tar, func() error {
-		err := tar.Close()
-		os.RemoveAll(tmpDir)
-		return err
-	}), api.DefaultDockerfileName, nil
-
-}
-
-// getContextFromGitURL uses a Git URL as context for a `docker build`. The
-// git repo is cloned into a temporary directory used as the context directory.
-// Returns the absolute path to the temporary context directory, the relative
-// path of the dockerfile in that context directory, and a non-nil error on
-// success.
-func getContextFromGitURL(gitURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
-	if _, err := exec.LookPath("git"); err != nil {
-		return "", "", fmt.Errorf("unable to find 'git': %v", err)
-	}
-	if absContextDir, err = gitutils.Clone(gitURL); err != nil {
-		return "", "", fmt.Errorf("unable to 'git clone' to temporary context directory: %v", err)
-	}
-
-	return getDockerfileRelPath(absContextDir, dockerfileName)
-}
-
-// getContextFromURL uses a remote URL as context for a `docker build`. The
-// remote resource is downloaded as either a Dockerfile or a tar archive.
-// Returns the tar archive used for the context and a path of the
-// dockerfile inside the tar.
-func getContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.ReadCloser, string, error) {
-	response, err := httputils.Download(remoteURL)
-	if err != nil {
-		return nil, "", fmt.Errorf("unable to download remote context %s: %v", remoteURL, err)
-	}
-	progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(out, true)
-
-	// Pass the response body through a progress reader.
-	progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL))
-
-	return getContextFromReader(ioutils.NewReadCloserWrapper(progReader, func() error { return response.Body.Close() }), dockerfileName)
-}
-
-// getContextFromLocalDir uses the given local directory as context for a
-// `docker build`. Returns the absolute path to the local context directory,
-// the relative path of the dockerfile in that context directory, and a non-nil
-// error on success.
-func getContextFromLocalDir(localDir, dockerfileName string) (absContextDir, relDockerfile string, err error) {
-	// When using a local context directory, when the Dockerfile is specified
-	// with the `-f/--file` option then it is considered relative to the
-	// current directory and not the context directory.
-	if dockerfileName != "" {
-		if dockerfileName, err = filepath.Abs(dockerfileName); err != nil {
-			return "", "", fmt.Errorf("unable to get absolute path to Dockerfile: %v", err)
-		}
-	}
-
-	return getDockerfileRelPath(localDir, dockerfileName)
-}
-
 var dockerfileFromLinePattern = regexp.MustCompile(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)
 var dockerfileFromLinePattern = regexp.MustCompile(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)
 
 
 // resolvedTag records the repository, tag, and resolved digest reference
 // resolvedTag records the repository, tag, and resolved digest reference

+ 4 - 0
api/client/cli.go

@@ -11,6 +11,7 @@ import (
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/cliconfig"
+	"github.com/docker/docker/cliconfig/credentials"
 	"github.com/docker/docker/dockerversion"
 	"github.com/docker/docker/dockerversion"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/term"
 	"github.com/docker/docker/pkg/term"
@@ -125,6 +126,9 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientF
 		if e != nil {
 		if e != nil {
 			fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
 			fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
 		}
 		}
+		if !configFile.ContainsAuth() {
+			credentials.DetectDefaultStore(configFile)
+		}
 		cli.configFile = configFile
 		cli.configFile = configFile
 
 
 		host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions)
 		host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions)

+ 1 - 1
api/client/create.go

@@ -42,7 +42,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
 		return err
 		return err
 	}
 	}
 
 
-	authConfig := cli.resolveAuthConfig(cli.configFile.AuthConfigs, repoInfo.Index)
+	authConfig := cli.resolveAuthConfig(repoInfo.Index)
 	encodedAuth, err := encodeAuthToBase64(authConfig)
 	encodedAuth, err := encodeAuthToBase64(authConfig)
 	if err != nil {
 	if err != nil {
 		return err
 		return err

+ 29 - 0
api/client/events.go

@@ -6,10 +6,12 @@ import (
 	"io"
 	"io"
 	"sort"
 	"sort"
 	"strings"
 	"strings"
+	"sync"
 	"time"
 	"time"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
+	"github.com/Sirupsen/logrus"
 	Cli "github.com/docker/docker/cli"
 	Cli "github.com/docker/docker/cli"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/jsonlog"
 	"github.com/docker/docker/pkg/jsonlog"
@@ -115,3 +117,30 @@ func printOutput(event eventtypes.Message, output io.Writer) {
 	}
 	}
 	fmt.Fprint(output, "\n")
 	fmt.Fprint(output, "\n")
 }
 }
+
+type eventHandler struct {
+	handlers map[string]func(eventtypes.Message)
+	mu       sync.Mutex
+}
+
+func (w *eventHandler) Handle(action string, h func(eventtypes.Message)) {
+	w.mu.Lock()
+	w.handlers[action] = h
+	w.mu.Unlock()
+}
+
+// Watch ranges over the passed in event chan and processes the events based on the
+// handlers created for a given action.
+// To stop watching, close the event chan.
+func (w *eventHandler) Watch(c <-chan eventtypes.Message) {
+	for e := range c {
+		w.mu.Lock()
+		h, exists := w.handlers[e.Action]
+		w.mu.Unlock()
+		if !exists {
+			continue
+		}
+		logrus.Debugf("event handler: received event: %v", e)
+		go h(e)
+	}
+}

+ 15 - 0
api/client/formatter/custom.go

@@ -31,6 +31,7 @@ const (
 	repositoryHeader   = "REPOSITORY"
 	repositoryHeader   = "REPOSITORY"
 	tagHeader          = "TAG"
 	tagHeader          = "TAG"
 	digestHeader       = "DIGEST"
 	digestHeader       = "DIGEST"
+	mountsHeader       = "MOUNTS"
 )
 )
 
 
 type containerContext struct {
 type containerContext struct {
@@ -142,6 +143,20 @@ func (c *containerContext) Label(name string) string {
 	return c.c.Labels[name]
 	return c.c.Labels[name]
 }
 }
 
 
+func (c *containerContext) Mounts() string {
+	c.addHeader(mountsHeader)
+
+	var mounts []string
+	for _, m := range c.c.Mounts {
+		name := m.Name
+		if c.trunc {
+			name = stringutils.Truncate(name, 15)
+		}
+		mounts = append(mounts, name)
+	}
+	return strings.Join(mounts, ",")
+}
+
 type imageContext struct {
 type imageContext struct {
 	baseSubContext
 	baseSubContext
 	trunc  bool
 	trunc  bool

+ 2 - 2
api/client/formatter/custom_test.go

@@ -12,7 +12,7 @@ import (
 
 
 func TestContainerPsContext(t *testing.T) {
 func TestContainerPsContext(t *testing.T) {
 	containerID := stringid.GenerateRandomID()
 	containerID := stringid.GenerateRandomID()
-	unix := time.Now().Unix()
+	unix := time.Now().Add(-65 * time.Second).Unix()
 
 
 	var ctx containerContext
 	var ctx containerContext
 	cases := []struct {
 	cases := []struct {
@@ -55,7 +55,7 @@ func TestContainerPsContext(t *testing.T) {
 		{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
 		{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
 		{types.Container{}, true, "", labelsHeader, ctx.Labels},
 		{types.Container{}, true, "", labelsHeader, ctx.Labels},
 		{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
 		{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
-		{types.Container{Created: unix}, true, "Less than a second", runningForHeader, ctx.RunningFor},
+		{types.Container{Created: unix}, true, "About a minute", runningForHeader, ctx.RunningFor},
 	}
 	}
 
 
 	for _, c := range cases {
 	for _, c := range cases {

+ 5 - 2
api/client/info.go

@@ -50,6 +50,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
 	}
 	}
 	ioutils.FprintfIfNotEmpty(cli.out, "Execution Driver: %s\n", info.ExecutionDriver)
 	ioutils.FprintfIfNotEmpty(cli.out, "Execution Driver: %s\n", info.ExecutionDriver)
 	ioutils.FprintfIfNotEmpty(cli.out, "Logging Driver: %s\n", info.LoggingDriver)
 	ioutils.FprintfIfNotEmpty(cli.out, "Logging Driver: %s\n", info.LoggingDriver)
+	ioutils.FprintfIfNotEmpty(cli.out, "Cgroup Driver: %s\n", info.CgroupDriver)
 
 
 	fmt.Fprintf(cli.out, "Plugins: \n")
 	fmt.Fprintf(cli.out, "Plugins: \n")
 	fmt.Fprintf(cli.out, " Volume:")
 	fmt.Fprintf(cli.out, " Volume:")
@@ -73,7 +74,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
 	fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(info.MemTotal)))
 	fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(info.MemTotal)))
 	ioutils.FprintfIfNotEmpty(cli.out, "Name: %s\n", info.Name)
 	ioutils.FprintfIfNotEmpty(cli.out, "Name: %s\n", info.Name)
 	ioutils.FprintfIfNotEmpty(cli.out, "ID: %s\n", info.ID)
 	ioutils.FprintfIfNotEmpty(cli.out, "ID: %s\n", info.ID)
-
+	fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", info.DockerRootDir)
 	fmt.Fprintf(cli.out, "Debug mode (client): %v\n", utils.IsDebugEnabled())
 	fmt.Fprintf(cli.out, "Debug mode (client): %v\n", utils.IsDebugEnabled())
 	fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug)
 	fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug)
 
 
@@ -82,7 +83,6 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
 		fmt.Fprintf(cli.out, " Goroutines: %d\n", info.NGoroutines)
 		fmt.Fprintf(cli.out, " Goroutines: %d\n", info.NGoroutines)
 		fmt.Fprintf(cli.out, " System Time: %s\n", info.SystemTime)
 		fmt.Fprintf(cli.out, " System Time: %s\n", info.SystemTime)
 		fmt.Fprintf(cli.out, " EventsListeners: %d\n", info.NEventsListener)
 		fmt.Fprintf(cli.out, " EventsListeners: %d\n", info.NEventsListener)
-		fmt.Fprintf(cli.out, " Docker Root Dir: %s\n", info.DockerRootDir)
 	}
 	}
 
 
 	ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HTTPProxy)
 	ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HTTPProxy)
@@ -105,6 +105,9 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
 		if !info.SwapLimit {
 		if !info.SwapLimit {
 			fmt.Fprintln(cli.err, "WARNING: No swap limit support")
 			fmt.Fprintln(cli.err, "WARNING: No swap limit support")
 		}
 		}
+		if !info.KernelMemory {
+			fmt.Fprintln(cli.err, "WARNING: No kernel memory limit support")
+		}
 		if !info.OomKillDisable {
 		if !info.OomKillDisable {
 			fmt.Fprintln(cli.err, "WARNING: No oom kill disable support")
 			fmt.Fprintln(cli.err, "WARNING: No oom kill disable support")
 		}
 		}

+ 56 - 38
api/client/login.go

@@ -9,13 +9,14 @@ import (
 	"strings"
 	"strings"
 
 
 	Cli "github.com/docker/docker/cli"
 	Cli "github.com/docker/docker/cli"
+	"github.com/docker/docker/cliconfig"
+	"github.com/docker/docker/cliconfig/credentials"
 	flag "github.com/docker/docker/pkg/mflag"
 	flag "github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/pkg/term"
 	"github.com/docker/docker/pkg/term"
-	"github.com/docker/engine-api/client"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 )
 )
 
 
-// CmdLogin logs in or registers a user to a Docker registry service.
+// CmdLogin logs in a user to a Docker registry service.
 //
 //
 // If no server is specified, the user will be logged into or registered to the registry's index server.
 // If no server is specified, the user will be logged into or registered to the registry's index server.
 //
 //
@@ -26,7 +27,9 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
 
 
 	flUser := cmd.String([]string{"u", "-username"}, "", "Username")
 	flUser := cmd.String([]string{"u", "-username"}, "", "Username")
 	flPassword := cmd.String([]string{"p", "-password"}, "", "Password")
 	flPassword := cmd.String([]string{"p", "-password"}, "", "Password")
-	flEmail := cmd.String([]string{"e", "-email"}, "", "Email")
+
+	// Deprecated in 1.11: Should be removed in docker 1.13
+	cmd.String([]string{"#e", "#-email"}, "", "Email")
 
 
 	cmd.ParseFlags(args, true)
 	cmd.ParseFlags(args, true)
 
 
@@ -36,32 +39,27 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
 	}
 	}
 
 
 	var serverAddress string
 	var serverAddress string
+	var isDefaultRegistry bool
 	if len(cmd.Args()) > 0 {
 	if len(cmd.Args()) > 0 {
 		serverAddress = cmd.Arg(0)
 		serverAddress = cmd.Arg(0)
 	} else {
 	} else {
 		serverAddress = cli.electAuthServer()
 		serverAddress = cli.electAuthServer()
+		isDefaultRegistry = true
 	}
 	}
 
 
-	authConfig, err := cli.configureAuth(*flUser, *flPassword, *flEmail, serverAddress)
+	authConfig, err := cli.configureAuth(*flUser, *flPassword, serverAddress, isDefaultRegistry)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
 	response, err := cli.client.RegistryLogin(authConfig)
 	response, err := cli.client.RegistryLogin(authConfig)
 	if err != nil {
 	if err != nil {
-		if client.IsErrUnauthorized(err) {
-			delete(cli.configFile.AuthConfigs, serverAddress)
-			if err2 := cli.configFile.Save(); err2 != nil {
-				fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2)
-			}
-		}
 		return err
 		return err
 	}
 	}
 
 
-	if err := cli.configFile.Save(); err != nil {
-		return fmt.Errorf("Error saving config file: %v", err)
+	if err := storeCredentials(cli.configFile, authConfig); err != nil {
+		return fmt.Errorf("Error saving credentials: %v", err)
 	}
 	}
-	fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s\n", cli.configFile.Filename())
 
 
 	if response.Status != "" {
 	if response.Status != "" {
 		fmt.Fprintf(cli.out, "%s\n", response.Status)
 		fmt.Fprintf(cli.out, "%s\n", response.Status)
@@ -77,14 +75,19 @@ func (cli *DockerCli) promptWithDefault(prompt string, configDefault string) {
 	}
 	}
 }
 }
 
 
-func (cli *DockerCli) configureAuth(flUser, flPassword, flEmail, serverAddress string) (types.AuthConfig, error) {
-	authconfig, ok := cli.configFile.AuthConfigs[serverAddress]
-	if !ok {
-		authconfig = types.AuthConfig{}
+func (cli *DockerCli) configureAuth(flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
+	authconfig, err := getCredentials(cli.configFile, serverAddress)
+	if err != nil {
+		return authconfig, err
 	}
 	}
+
 	authconfig.Username = strings.TrimSpace(authconfig.Username)
 	authconfig.Username = strings.TrimSpace(authconfig.Username)
 
 
 	if flUser = strings.TrimSpace(flUser); flUser == "" {
 	if flUser = strings.TrimSpace(flUser); flUser == "" {
+		if isDefaultRegistry {
+			// if this is a defauly registry (docker hub), then display the following message.
+			fmt.Fprintln(cli.out, "Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.")
+		}
 		cli.promptWithDefault("Username", authconfig.Username)
 		cli.promptWithDefault("Username", authconfig.Username)
 		flUser = readInput(cli.in, cli.out)
 		flUser = readInput(cli.in, cli.out)
 		flUser = strings.TrimSpace(flUser)
 		flUser = strings.TrimSpace(flUser)
@@ -114,30 +117,10 @@ func (cli *DockerCli) configureAuth(flUser, flPassword, flEmail, serverAddress s
 		}
 		}
 	}
 	}
 
 
-	// Assume that a different username means they may not want to use
-	// the email from the config file, so prompt it
-	if flUser != authconfig.Username {
-		if flEmail == "" {
-			cli.promptWithDefault("Email", authconfig.Email)
-			flEmail = readInput(cli.in, cli.out)
-			if flEmail == "" {
-				flEmail = authconfig.Email
-			}
-		}
-	} else {
-		// However, if they don't override the username use the
-		// email from the cmd line if specified. IOW, allow
-		// then to change/override them.  And if not specified, just
-		// use what's in the config file
-		if flEmail == "" {
-			flEmail = authconfig.Email
-		}
-	}
 	authconfig.Username = flUser
 	authconfig.Username = flUser
 	authconfig.Password = flPassword
 	authconfig.Password = flPassword
-	authconfig.Email = flEmail
 	authconfig.ServerAddress = serverAddress
 	authconfig.ServerAddress = serverAddress
-	cli.configFile.AuthConfigs[serverAddress] = authconfig
+
 	return authconfig, nil
 	return authconfig, nil
 }
 }
 
 
@@ -150,3 +133,38 @@ func readInput(in io.Reader, out io.Writer) string {
 	}
 	}
 	return string(line)
 	return string(line)
 }
 }
+
+// getCredentials loads the user credentials from a credentials store.
+// The store is determined by the config file settings.
+func getCredentials(c *cliconfig.ConfigFile, serverAddress string) (types.AuthConfig, error) {
+	s := loadCredentialsStore(c)
+	return s.Get(serverAddress)
+}
+
+func getAllCredentials(c *cliconfig.ConfigFile) (map[string]types.AuthConfig, error) {
+	s := loadCredentialsStore(c)
+	return s.GetAll()
+}
+
+// storeCredentials saves the user credentials in a credentials store.
+// The store is determined by the config file settings.
+func storeCredentials(c *cliconfig.ConfigFile, auth types.AuthConfig) error {
+	s := loadCredentialsStore(c)
+	return s.Store(auth)
+}
+
+// eraseCredentials removes the user credentials from a credentials store.
+// The store is determined by the config file settings.
+func eraseCredentials(c *cliconfig.ConfigFile, serverAddress string) error {
+	s := loadCredentialsStore(c)
+	return s.Erase(serverAddress)
+}
+
+// loadCredentialsStore initializes a new credentials store based
+// in the settings provided in the configuration file.
+func loadCredentialsStore(c *cliconfig.ConfigFile) credentials.Store {
+	if c.CredentialsStore != "" {
+		return credentials.NewNativeStore(c)
+	}
+	return credentials.NewFileStore(c)
+}

+ 4 - 3
api/client/logout.go

@@ -25,15 +25,16 @@ func (cli *DockerCli) CmdLogout(args ...string) error {
 		serverAddress = cli.electAuthServer()
 		serverAddress = cli.electAuthServer()
 	}
 	}
 
 
+	// check if we're logged in based on the records in the config file
+	// which means it couldn't have user/pass cause they may be in the creds store
 	if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok {
 	if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok {
 		fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress)
 		fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress)
 		return nil
 		return nil
 	}
 	}
 
 
 	fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress)
 	fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress)
-	delete(cli.configFile.AuthConfigs, serverAddress)
-	if err := cli.configFile.Save(); err != nil {
-		return fmt.Errorf("Failed to save docker config: %v", err)
+	if err := eraseCredentials(cli.configFile, serverAddress); err != nil {
+		fmt.Fprintf(cli.out, "WARNING: could not erase credentials: %v\n", err)
 	}
 	}
 
 
 	return nil
 	return nil

+ 10 - 1
api/client/network.go

@@ -3,6 +3,7 @@ package client
 import (
 import (
 	"fmt"
 	"fmt"
 	"net"
 	"net"
+	"sort"
 	"strings"
 	"strings"
 	"text/tabwriter"
 	"text/tabwriter"
 
 
@@ -50,6 +51,7 @@ func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
 	cmd.Var(flIpamOpt, []string{"-ipam-opt"}, "set IPAM driver specific options")
 	cmd.Var(flIpamOpt, []string{"-ipam-opt"}, "set IPAM driver specific options")
 
 
 	flInternal := cmd.Bool([]string{"-internal"}, false, "restricts external access to the network")
 	flInternal := cmd.Bool([]string{"-internal"}, false, "restricts external access to the network")
+	flIPv6 := cmd.Bool([]string{"-ipv6"}, false, "enable IPv6 networking")
 
 
 	cmd.Require(flag.Exact, 1)
 	cmd.Require(flag.Exact, 1)
 	err := cmd.ParseFlags(args, true)
 	err := cmd.ParseFlags(args, true)
@@ -77,6 +79,7 @@ func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
 		Options:        flOpts.GetAll(),
 		Options:        flOpts.GetAll(),
 		CheckDuplicate: true,
 		CheckDuplicate: true,
 		Internal:       *flInternal,
 		Internal:       *flInternal,
+		EnableIPv6:     *flIPv6,
 	}
 	}
 
 
 	resp, err := cli.client.NetworkCreate(nc)
 	resp, err := cli.client.NetworkCreate(nc)
@@ -192,7 +195,7 @@ func (cli *DockerCli) CmdNetworkLs(args ...string) error {
 	if !*quiet {
 	if !*quiet {
 		fmt.Fprintln(wr, "NETWORK ID\tNAME\tDRIVER")
 		fmt.Fprintln(wr, "NETWORK ID\tNAME\tDRIVER")
 	}
 	}
-
+	sort.Sort(byNetworkName(networkResources))
 	for _, networkResource := range networkResources {
 	for _, networkResource := range networkResources {
 		ID := networkResource.ID
 		ID := networkResource.ID
 		netName := networkResource.Name
 		netName := networkResource.Name
@@ -214,6 +217,12 @@ func (cli *DockerCli) CmdNetworkLs(args ...string) error {
 	return nil
 	return nil
 }
 }
 
 
+type byNetworkName []types.NetworkResource
+
+func (r byNetworkName) Len() int           { return len(r) }
+func (r byNetworkName) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
+func (r byNetworkName) Less(i, j int) bool { return r[i].Name < r[j].Name }
+
 // CmdNetworkInspect inspects the network object for more details
 // CmdNetworkInspect inspects the network object for more details
 //
 //
 // Usage: docker network inspect [OPTIONS] <NETWORK> [NETWORK...]
 // Usage: docker network inspect [OPTIONS] <NETWORK> [NETWORK...]

+ 1 - 1
api/client/pull.go

@@ -56,7 +56,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
 		return err
 		return err
 	}
 	}
 
 
-	authConfig := cli.resolveAuthConfig(cli.configFile.AuthConfigs, repoInfo.Index)
+	authConfig := cli.resolveAuthConfig(repoInfo.Index)
 	requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
 	requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
 
 
 	if isTrusted() && !ref.HasDigest() {
 	if isTrusted() && !ref.HasDigest() {

+ 1 - 1
api/client/push.go

@@ -44,7 +44,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
 		return err
 		return err
 	}
 	}
 	// Resolve the Auth config relevant for this server
 	// Resolve the Auth config relevant for this server
-	authConfig := cli.resolveAuthConfig(cli.configFile.AuthConfigs, repoInfo.Index)
+	authConfig := cli.resolveAuthConfig(repoInfo.Index)
 
 
 	requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "push")
 	requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "push")
 	if isTrusted() {
 	if isTrusted() {

+ 9 - 12
api/client/run.go

@@ -11,7 +11,6 @@ import (
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	Cli "github.com/docker/docker/cli"
 	Cli "github.com/docker/docker/cli"
-	derr "github.com/docker/docker/errors"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
@@ -21,6 +20,11 @@ import (
 	"github.com/docker/libnetwork/resolvconf/dns"
 	"github.com/docker/libnetwork/resolvconf/dns"
 )
 )
 
 
+const (
+	errCmdNotFound          = "Container command not found or does not exist."
+	errCmdCouldNotBeInvoked = "Container command could not be invoked."
+)
+
 func (cid *cidFile) Close() error {
 func (cid *cidFile) Close() error {
 	cid.file.Close()
 	cid.file.Close()
 
 
@@ -46,20 +50,13 @@ func (cid *cidFile) Write(id string) error {
 // return 125 for generic docker daemon failures
 // return 125 for generic docker daemon failures
 func runStartContainerErr(err error) error {
 func runStartContainerErr(err error) error {
 	trimmedErr := strings.Trim(err.Error(), "Error response from daemon: ")
 	trimmedErr := strings.Trim(err.Error(), "Error response from daemon: ")
-	statusError := Cli.StatusError{}
-	derrCmdNotFound := derr.ErrorCodeCmdNotFound.Message()
-	derrCouldNotInvoke := derr.ErrorCodeCmdCouldNotBeInvoked.Message()
-	derrNoSuchImage := derr.ErrorCodeNoSuchImageHash.Message()
-	derrNoSuchImageTag := derr.ErrorCodeNoSuchImageTag.Message()
+	statusError := Cli.StatusError{StatusCode: 125}
+
 	switch trimmedErr {
 	switch trimmedErr {
-	case derrCmdNotFound:
+	case errCmdNotFound:
 		statusError = Cli.StatusError{StatusCode: 127}
 		statusError = Cli.StatusError{StatusCode: 127}
-	case derrCouldNotInvoke:
+	case errCmdCouldNotBeInvoked:
 		statusError = Cli.StatusError{StatusCode: 126}
 		statusError = Cli.StatusError{StatusCode: 126}
-	case derrNoSuchImage, derrNoSuchImageTag:
-		statusError = Cli.StatusError{StatusCode: 125}
-	default:
-		statusError = Cli.StatusError{StatusCode: 125}
 	}
 	}
 	return statusError
 	return statusError
 }
 }

+ 1 - 1
api/client/search.go

@@ -36,7 +36,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
 		return err
 		return err
 	}
 	}
 
 
-	authConfig := cli.resolveAuthConfig(cli.configFile.AuthConfigs, indexInfo)
+	authConfig := cli.resolveAuthConfig(indexInfo)
 	requestPrivilege := cli.registryAuthenticationPrivilegedFunc(indexInfo, "search")
 	requestPrivilege := cli.registryAuthenticationPrivilegedFunc(indexInfo, "search")
 
 
 	encodedAuth, err := encodeAuthToBase64(authConfig)
 	encodedAuth, err := encodeAuthToBase64(authConfig)

+ 117 - 255
api/client/stats.go

@@ -1,10 +1,8 @@
 package client
 package client
 
 
 import (
 import (
-	"encoding/json"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
-	"sort"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 	"text/tabwriter"
 	"text/tabwriter"
@@ -16,125 +14,8 @@ import (
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types/events"
 	"github.com/docker/engine-api/types/events"
 	"github.com/docker/engine-api/types/filters"
 	"github.com/docker/engine-api/types/filters"
-	"github.com/docker/go-units"
 )
 )
 
 
-type containerStats struct {
-	Name             string
-	CPUPercentage    float64
-	Memory           float64
-	MemoryLimit      float64
-	MemoryPercentage float64
-	NetworkRx        float64
-	NetworkTx        float64
-	BlockRead        float64
-	BlockWrite       float64
-	mu               sync.RWMutex
-	err              error
-}
-
-type stats struct {
-	mu sync.Mutex
-	cs []*containerStats
-}
-
-func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
-	responseBody, err := cli.client.ContainerStats(context.Background(), s.Name, streamStats)
-	if err != nil {
-		s.mu.Lock()
-		s.err = err
-		s.mu.Unlock()
-		return
-	}
-	defer responseBody.Close()
-
-	var (
-		previousCPU    uint64
-		previousSystem uint64
-		dec            = json.NewDecoder(responseBody)
-		u              = make(chan error, 1)
-	)
-	go func() {
-		for {
-			var v *types.StatsJSON
-			if err := dec.Decode(&v); err != nil {
-				u <- err
-				return
-			}
-
-			var memPercent = 0.0
-			var cpuPercent = 0.0
-
-			// MemoryStats.Limit will never be 0 unless the container is not running and we haven't
-			// got any data from cgroup
-			if v.MemoryStats.Limit != 0 {
-				memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
-			}
-
-			previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
-			previousSystem = v.PreCPUStats.SystemUsage
-			cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
-			blkRead, blkWrite := calculateBlockIO(v.BlkioStats)
-			s.mu.Lock()
-			s.CPUPercentage = cpuPercent
-			s.Memory = float64(v.MemoryStats.Usage)
-			s.MemoryLimit = float64(v.MemoryStats.Limit)
-			s.MemoryPercentage = memPercent
-			s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
-			s.BlockRead = float64(blkRead)
-			s.BlockWrite = float64(blkWrite)
-			s.mu.Unlock()
-			u <- nil
-			if !streamStats {
-				return
-			}
-		}
-	}()
-	for {
-		select {
-		case <-time.After(2 * time.Second):
-			// zero out the values if we have not received an update within
-			// the specified duration.
-			s.mu.Lock()
-			s.CPUPercentage = 0
-			s.Memory = 0
-			s.MemoryPercentage = 0
-			s.MemoryLimit = 0
-			s.NetworkRx = 0
-			s.NetworkTx = 0
-			s.BlockRead = 0
-			s.BlockWrite = 0
-			s.mu.Unlock()
-		case err := <-u:
-			if err != nil {
-				s.mu.Lock()
-				s.err = err
-				s.mu.Unlock()
-				return
-			}
-		}
-		if !streamStats {
-			return
-		}
-	}
-}
-
-func (s *containerStats) Display(w io.Writer) error {
-	s.mu.RLock()
-	defer s.mu.RUnlock()
-	if s.err != nil {
-		return s.err
-	}
-	fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\n",
-		s.Name,
-		s.CPUPercentage,
-		units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
-		s.MemoryPercentage,
-		units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx),
-		units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite))
-	return nil
-}
-
 // CmdStats displays a live stream of resource usage statistics for one or more containers.
 // CmdStats displays a live stream of resource usage statistics for one or more containers.
 //
 //
 // This shows real-time information on CPU usage, memory usage, and network I/O.
 // This shows real-time information on CPU usage, memory usage, and network I/O.
@@ -149,125 +30,143 @@ func (cli *DockerCli) CmdStats(args ...string) error {
 
 
 	names := cmd.Args()
 	names := cmd.Args()
 	showAll := len(names) == 0
 	showAll := len(names) == 0
+	closeChan := make(chan error)
 
 
-	if showAll {
+	// monitorContainerEvents watches for container creation and removal (only
+	// used when calling `docker stats` without arguments).
+	monitorContainerEvents := func(started chan<- struct{}, c chan events.Message) {
+		f := filters.NewArgs()
+		f.Add("type", "container")
+		options := types.EventsOptions{
+			Filters: f,
+		}
+		resBody, err := cli.client.Events(context.Background(), options)
+		// Whether we successfully subscribed to events or not, we can now
+		// unblock the main goroutine.
+		close(started)
+		if err != nil {
+			closeChan <- err
+			return
+		}
+		defer resBody.Close()
+
+		decodeEvents(resBody, func(event events.Message, err error) error {
+			if err != nil {
+				closeChan <- err
+				return nil
+			}
+			c <- event
+			return nil
+		})
+	}
+
+	// waitFirst is a WaitGroup to wait first stat data's reach for each container
+	waitFirst := &sync.WaitGroup{}
+
+	cStats := stats{}
+	// getContainerList simulates creation event for all previously existing
+	// containers (only used when calling `docker stats` without arguments).
+	getContainerList := func() {
 		options := types.ContainerListOptions{
 		options := types.ContainerListOptions{
 			All: *all,
 			All: *all,
 		}
 		}
 		cs, err := cli.client.ContainerList(options)
 		cs, err := cli.client.ContainerList(options)
 		if err != nil {
 		if err != nil {
-			return err
+			closeChan <- err
 		}
 		}
-		for _, c := range cs {
-			names = append(names, c.ID[:12])
+		for _, container := range cs {
+			s := &containerStats{Name: container.ID[:12]}
+			if cStats.add(s) {
+				waitFirst.Add(1)
+				go s.Collect(cli.client, !*noStream, waitFirst)
+			}
 		}
 		}
 	}
 	}
-	if len(names) == 0 && !showAll {
-		return fmt.Errorf("No containers found")
-	}
-	sort.Strings(names)
 
 
-	var (
-		cStats = stats{}
-		w      = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
-	)
-	printHeader := func() {
-		if !*noStream {
-			fmt.Fprint(cli.out, "\033[2J")
-			fmt.Fprint(cli.out, "\033[H")
-		}
-		io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\n")
-	}
-	for _, n := range names {
-		s := &containerStats{Name: n}
-		// no need to lock here since only the main goroutine is running here
-		cStats.cs = append(cStats.cs, s)
-		go s.Collect(cli, !*noStream)
-	}
-	closeChan := make(chan error)
 	if showAll {
 	if showAll {
-		type watch struct {
-			cid   string
-			event string
-			err   error
-		}
-		getNewContainers := func(c chan<- watch) {
-			f := filters.NewArgs()
-			f.Add("type", "container")
-			options := types.EventsOptions{
-				Filters: f,
-			}
-			resBody, err := cli.client.Events(context.Background(), options)
-			if err != nil {
-				c <- watch{err: err}
-				return
+		// If no names were specified, start a long running goroutine which
+		// monitors container events. We make sure we're subscribed before
+		// retrieving the list of running containers to avoid a race where we
+		// would "miss" a creation.
+		started := make(chan struct{})
+		eh := eventHandler{handlers: make(map[string]func(events.Message))}
+		eh.Handle("create", func(e events.Message) {
+			if *all {
+				s := &containerStats{Name: e.ID[:12]}
+				if cStats.add(s) {
+					waitFirst.Add(1)
+					go s.Collect(cli.client, !*noStream, waitFirst)
+				}
 			}
 			}
-			defer resBody.Close()
+		})
 
 
-			decodeEvents(resBody, func(event events.Message, err error) error {
-				if err != nil {
-					c <- watch{err: err}
-					return nil
-				}
+		eh.Handle("start", func(e events.Message) {
+			s := &containerStats{Name: e.ID[:12]}
+			if cStats.add(s) {
+				waitFirst.Add(1)
+				go s.Collect(cli.client, !*noStream, waitFirst)
+			}
+		})
 
 
-				c <- watch{event.ID[:12], event.Action, nil}
-				return nil
-			})
-		}
-		go func(stopChan chan<- error) {
-			cChan := make(chan watch)
-			go getNewContainers(cChan)
-			for {
-				c := <-cChan
-				if c.err != nil {
-					stopChan <- c.err
-					return
-				}
-				switch c.event {
-				case "create":
-					s := &containerStats{Name: c.cid}
-					cStats.mu.Lock()
-					cStats.cs = append(cStats.cs, s)
-					cStats.mu.Unlock()
-					go s.Collect(cli, !*noStream)
-				case "stop":
-				case "die":
-					if !*all {
-						var remove int
-						// cStats cannot be O(1) with a map cause ranging over it would cause
-						// containers in stats to move up and down in the list...:(
-						cStats.mu.Lock()
-						for i, s := range cStats.cs {
-							if s.Name == c.cid {
-								remove = i
-								break
-							}
-						}
-						cStats.cs = append(cStats.cs[:remove], cStats.cs[remove+1:]...)
-						cStats.mu.Unlock()
-					}
-				}
+		eh.Handle("die", func(e events.Message) {
+			if !*all {
+				cStats.remove(e.ID[:12])
 			}
 			}
-		}(closeChan)
+		})
+
+		eventChan := make(chan events.Message)
+		go eh.Watch(eventChan)
+		go monitorContainerEvents(started, eventChan)
+		defer close(eventChan)
+		<-started
+
+		// Start a short-lived goroutine to retrieve the initial list of
+		// containers.
+		getContainerList()
 	} else {
 	} else {
+		// Artificially send creation events for the containers we were asked to
+		// monitor (same code path than we use when monitoring all containers).
+		for _, name := range names {
+			s := &containerStats{Name: name}
+			if cStats.add(s) {
+				waitFirst.Add(1)
+				go s.Collect(cli.client, !*noStream, waitFirst)
+			}
+		}
+
+		// We don't expect any asynchronous errors: closeChan can be closed.
 		close(closeChan)
 		close(closeChan)
-	}
-	// do a quick pause so that any failed connections for containers that do not exist are able to be
-	// evicted before we display the initial or default values.
-	time.Sleep(1500 * time.Millisecond)
-	var errs []string
-	cStats.mu.Lock()
-	for _, c := range cStats.cs {
-		c.mu.Lock()
-		if c.err != nil {
-			errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
+
+		// Do a quick pause to detect any error with the provided list of
+		// container names.
+		time.Sleep(1500 * time.Millisecond)
+		var errs []string
+		cStats.mu.Lock()
+		for _, c := range cStats.cs {
+			c.mu.Lock()
+			if c.err != nil {
+				errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
+			}
+			c.mu.Unlock()
+		}
+		cStats.mu.Unlock()
+		if len(errs) > 0 {
+			return fmt.Errorf("%s", strings.Join(errs, ", "))
 		}
 		}
-		c.mu.Unlock()
 	}
 	}
-	cStats.mu.Unlock()
-	if len(errs) > 0 {
-		return fmt.Errorf("%s", strings.Join(errs, ", "))
+
+	// before print to screen, make sure each container get at least one valid stat data
+	waitFirst.Wait()
+
+	w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
+	printHeader := func() {
+		if !*noStream {
+			fmt.Fprint(cli.out, "\033[2J")
+			fmt.Fprint(cli.out, "\033[H")
+		}
+		io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS\n")
 	}
 	}
+
 	for range time.Tick(500 * time.Millisecond) {
 	for range time.Tick(500 * time.Millisecond) {
 		printHeader()
 		printHeader()
 		toRemove := []int{}
 		toRemove := []int{}
@@ -307,40 +206,3 @@ func (cli *DockerCli) CmdStats(args ...string) error {
 	}
 	}
 	return nil
 	return nil
 }
 }
-
-func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
-	var (
-		cpuPercent = 0.0
-		// calculate the change for the cpu usage of the container in between readings
-		cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
-		// calculate the change for the entire system between readings
-		systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
-	)
-
-	if systemDelta > 0.0 && cpuDelta > 0.0 {
-		cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
-	}
-	return cpuPercent
-}
-
-func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
-	for _, bioEntry := range blkio.IoServiceBytesRecursive {
-		switch strings.ToLower(bioEntry.Op) {
-		case "read":
-			blkRead = blkRead + bioEntry.Value
-		case "write":
-			blkWrite = blkWrite + bioEntry.Value
-		}
-	}
-	return
-}
-
-func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
-	var rx, tx float64
-
-	for _, v := range network {
-		rx += float64(v.RxBytes)
-		tx += float64(v.TxBytes)
-	}
-	return rx, tx
-}

+ 217 - 0
api/client/stats_helpers.go

@@ -0,0 +1,217 @@
+package client
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/docker/engine-api/client"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/go-units"
+	"golang.org/x/net/context"
+)
+
+type containerStats struct {
+	Name             string
+	CPUPercentage    float64
+	Memory           float64
+	MemoryLimit      float64
+	MemoryPercentage float64
+	NetworkRx        float64
+	NetworkTx        float64
+	BlockRead        float64
+	BlockWrite       float64
+	PidsCurrent      uint64
+	mu               sync.RWMutex
+	err              error
+}
+
+type stats struct {
+	mu sync.Mutex
+	cs []*containerStats
+}
+
+func (s *stats) add(cs *containerStats) bool {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	if _, exists := s.isKnownContainer(cs.Name); !exists {
+		s.cs = append(s.cs, cs)
+		return true
+	}
+	return false
+}
+
+func (s *stats) remove(id string) {
+	s.mu.Lock()
+	if i, exists := s.isKnownContainer(id); exists {
+		s.cs = append(s.cs[:i], s.cs[i+1:]...)
+	}
+	s.mu.Unlock()
+}
+
+func (s *stats) isKnownContainer(cid string) (int, bool) {
+	for i, c := range s.cs {
+		if c.Name == cid {
+			return i, true
+		}
+	}
+	return -1, false
+}
+
+func (s *containerStats) Collect(cli client.APIClient, streamStats bool, waitFirst *sync.WaitGroup) {
+	var (
+		getFirst       bool
+		previousCPU    uint64
+		previousSystem uint64
+		u              = make(chan error, 1)
+	)
+
+	defer func() {
+		// if error happens and we get nothing of stats, release wait group whatever
+		if !getFirst {
+			getFirst = true
+			waitFirst.Done()
+		}
+	}()
+
+	responseBody, err := cli.ContainerStats(context.Background(), s.Name, streamStats)
+	if err != nil {
+		s.mu.Lock()
+		s.err = err
+		s.mu.Unlock()
+		return
+	}
+	defer responseBody.Close()
+
+	dec := json.NewDecoder(responseBody)
+	go func() {
+		for {
+			var v *types.StatsJSON
+			if err := dec.Decode(&v); err != nil {
+				u <- err
+				return
+			}
+
+			var memPercent = 0.0
+			var cpuPercent = 0.0
+
+			// MemoryStats.Limit will never be 0 unless the container is not running and we haven't
+			// got any data from cgroup
+			if v.MemoryStats.Limit != 0 {
+				memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
+			}
+
+			previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
+			previousSystem = v.PreCPUStats.SystemUsage
+			cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
+			blkRead, blkWrite := calculateBlockIO(v.BlkioStats)
+			s.mu.Lock()
+			s.CPUPercentage = cpuPercent
+			s.Memory = float64(v.MemoryStats.Usage)
+			s.MemoryLimit = float64(v.MemoryStats.Limit)
+			s.MemoryPercentage = memPercent
+			s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
+			s.BlockRead = float64(blkRead)
+			s.BlockWrite = float64(blkWrite)
+			s.mu.Unlock()
+			u <- nil
+			if !streamStats {
+				return
+			}
+		}
+	}()
+	for {
+		select {
+		case <-time.After(2 * time.Second):
+			// zero out the values if we have not received an update within
+			// the specified duration.
+			s.mu.Lock()
+			s.CPUPercentage = 0
+			s.Memory = 0
+			s.MemoryPercentage = 0
+			s.MemoryLimit = 0
+			s.NetworkRx = 0
+			s.NetworkTx = 0
+			s.BlockRead = 0
+			s.BlockWrite = 0
+			s.mu.Unlock()
+			// if this is the first stat you get, release WaitGroup
+			if !getFirst {
+				getFirst = true
+				waitFirst.Done()
+			}
+		case err := <-u:
+			if err != nil {
+				s.mu.Lock()
+				s.err = err
+				s.mu.Unlock()
+				return
+			}
+			// if this is the first stat you get, release WaitGroup
+			if !getFirst {
+				getFirst = true
+				waitFirst.Done()
+			}
+		}
+		if !streamStats {
+			return
+		}
+	}
+}
+
+func (s *containerStats) Display(w io.Writer) error {
+	s.mu.RLock()
+	defer s.mu.RUnlock()
+	if s.err != nil {
+		return s.err
+	}
+	fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n",
+		s.Name,
+		s.CPUPercentage,
+		units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
+		s.MemoryPercentage,
+		units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx),
+		units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite),
+		s.PidsCurrent)
+	return nil
+}
+
+func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
+	var (
+		cpuPercent = 0.0
+		// calculate the change for the cpu usage of the container in between readings
+		cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
+		// calculate the change for the entire system between readings
+		systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
+	)
+
+	if systemDelta > 0.0 && cpuDelta > 0.0 {
+		cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
+	}
+	return cpuPercent
+}
+
+func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
+	for _, bioEntry := range blkio.IoServiceBytesRecursive {
+		switch strings.ToLower(bioEntry.Op) {
+		case "read":
+			blkRead = blkRead + bioEntry.Value
+		case "write":
+			blkWrite = blkWrite + bioEntry.Value
+		}
+	}
+	return
+}
+
+func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
+	var rx, tx float64
+
+	for _, v := range network {
+		rx += float64(v.RxBytes)
+		tx += float64(v.TxBytes)
+	}
+	return rx, tx
+}

+ 2 - 1
api/client/stats_unit_test.go

@@ -19,6 +19,7 @@ func TestDisplay(t *testing.T) {
 		NetworkTx:        800 * 1024 * 1024,
 		NetworkTx:        800 * 1024 * 1024,
 		BlockRead:        100 * 1024 * 1024,
 		BlockRead:        100 * 1024 * 1024,
 		BlockWrite:       800 * 1024 * 1024,
 		BlockWrite:       800 * 1024 * 1024,
+		PidsCurrent:      1,
 		mu:               sync.RWMutex{},
 		mu:               sync.RWMutex{},
 	}
 	}
 	var b bytes.Buffer
 	var b bytes.Buffer
@@ -26,7 +27,7 @@ func TestDisplay(t *testing.T) {
 		t.Fatalf("c.Display() gave error: %s", err)
 		t.Fatalf("c.Display() gave error: %s", err)
 	}
 	}
 	got := b.String()
 	got := b.String()
-	want := "app\t30.00%\t104.9 MB / 2.147 GB\t4.88%\t104.9 MB / 838.9 MB\t104.9 MB / 838.9 MB\n"
+	want := "app\t30.00%\t104.9 MB / 2.147 GB\t4.88%\t104.9 MB / 838.9 MB\t104.9 MB / 838.9 MB\t1\n"
 	if got != want {
 	if got != want {
 		t.Fatalf("c.Display() = %q, want %q", got, want)
 		t.Fatalf("c.Display() = %q, want %q", got, want)
 	}
 	}

+ 54 - 36
api/client/trust.go

@@ -107,7 +107,10 @@ func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
 	return scs.auth.Username, scs.auth.Password
 	return scs.auth.Username, scs.auth.Password
 }
 }
 
 
-func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig) (*client.NotaryRepository, error) {
+// getNotaryRepository returns a NotaryRepository which stores all the
+// information needed to operate on a notary repository.
+// It creates a HTTP transport providing authentication support.
+func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig, actions ...string) (*client.NotaryRepository, error) {
 	server, err := trustServer(repoInfo.Index)
 	server, err := trustServer(repoInfo.Index)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -169,7 +172,7 @@ func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, aut
 	}
 	}
 
 
 	creds := simpleCredentialStore{auth: authConfig}
 	creds := simpleCredentialStore{auth: authConfig}
-	tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), "push", "pull")
+	tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), actions...)
 	basicHandler := auth.NewBasicHandler(creds)
 	basicHandler := auth.NewBasicHandler(creds)
 	modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
 	modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
 	tr := transport.NewTransport(base, modifiers...)
 	tr := transport.NewTransport(base, modifiers...)
@@ -235,7 +238,7 @@ func (cli *DockerCli) trustedReference(ref reference.NamedTagged) (reference.Can
 	}
 	}
 
 
 	// Resolve the Auth config relevant for this server
 	// Resolve the Auth config relevant for this server
-	authConfig := cli.resolveAuthConfig(cli.configFile.AuthConfigs, repoInfo.Index)
+	authConfig := cli.resolveAuthConfig(repoInfo.Index)
 
 
 	notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
 	notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
 	if err != nil {
 	if err != nil {
@@ -302,7 +305,7 @@ func notaryError(repoName string, err error) error {
 func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig types.AuthConfig, requestPrivilege apiclient.RequestPrivilegeFunc) error {
 func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig types.AuthConfig, requestPrivilege apiclient.RequestPrivilegeFunc) error {
 	var refs []target
 	var refs []target
 
 
-	notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
+	notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig, "pull")
 	if err != nil {
 	if err != nil {
 		fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
 		fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
 		return err
 		return err
@@ -372,60 +375,74 @@ func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string,
 
 
 	defer responseBody.Close()
 	defer responseBody.Close()
 
 
-	targets := []target{}
+	// If it is a trusted push we would like to find the target entry which match the
+	// tag provided in the function and then do an AddTarget later.
+	target := &client.Target{}
+	// Count the times of calling for handleTarget,
+	// if it is called more that once, that should be considered an error in a trusted push.
+	cnt := 0
 	handleTarget := func(aux *json.RawMessage) {
 	handleTarget := func(aux *json.RawMessage) {
+		cnt++
+		if cnt > 1 {
+			// handleTarget should only be called one. This will be treated as an error.
+			return
+		}
+
 		var pushResult distribution.PushResult
 		var pushResult distribution.PushResult
 		err := json.Unmarshal(*aux, &pushResult)
 		err := json.Unmarshal(*aux, &pushResult)
 		if err == nil && pushResult.Tag != "" && pushResult.Digest.Validate() == nil {
 		if err == nil && pushResult.Tag != "" && pushResult.Digest.Validate() == nil {
-			targets = append(targets, target{
-				reference: registry.ParseReference(pushResult.Tag),
-				digest:    pushResult.Digest,
-				size:      int64(pushResult.Size),
-			})
+			h, err := hex.DecodeString(pushResult.Digest.Hex())
+			if err != nil {
+				target = nil
+				return
+			}
+			target.Name = registry.ParseReference(pushResult.Tag).String()
+			target.Hashes = data.Hashes{string(pushResult.Digest.Algorithm()): h}
+			target.Length = int64(pushResult.Size)
 		}
 		}
 	}
 	}
 
 
-	err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, handleTarget)
-	if err != nil {
+	// We want trust signatures to always take an explicit tag,
+	// otherwise it will act as an untrusted push.
+	if tag == "" {
+		if err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil); err != nil {
+			return err
+		}
+		fmt.Fprintln(cli.out, "No tag specified, skipping trust metadata push")
+		return nil
+	}
+
+	if err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, handleTarget); err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	if tag == "" {
-		fmt.Fprintf(cli.out, "No tag specified, skipping trust metadata push\n")
-		return nil
+	if cnt > 1 {
+		return fmt.Errorf("internal error: only one call to handleTarget expected")
 	}
 	}
-	if len(targets) == 0 {
-		fmt.Fprintf(cli.out, "No targets found, skipping trust metadata push\n")
+
+	if target == nil {
+		fmt.Fprintln(cli.out, "No targets found, please provide a specific tag in order to sign it")
 		return nil
 		return nil
 	}
 	}
 
 
-	fmt.Fprintf(cli.out, "Signing and pushing trust metadata\n")
+	fmt.Fprintln(cli.out, "Signing and pushing trust metadata")
 
 
-	repo, err := cli.getNotaryRepository(repoInfo, authConfig)
+	repo, err := cli.getNotaryRepository(repoInfo, authConfig, "push", "pull")
 	if err != nil {
 	if err != nil {
 		fmt.Fprintf(cli.out, "Error establishing connection to notary repository: %s\n", err)
 		fmt.Fprintf(cli.out, "Error establishing connection to notary repository: %s\n", err)
 		return err
 		return err
 	}
 	}
 
 
-	for _, target := range targets {
-		h, err := hex.DecodeString(target.digest.Hex())
-		if err != nil {
-			return err
-		}
-		t := &client.Target{
-			Name: target.reference.String(),
-			Hashes: data.Hashes{
-				string(target.digest.Algorithm()): h,
-			},
-			Length: int64(target.size),
-		}
-		if err := repo.AddTarget(t, releasesRole); err != nil {
-			return err
-		}
+	if err := repo.AddTarget(target, releasesRole); err != nil {
+		return err
 	}
 	}
 
 
 	err = repo.Publish()
 	err = repo.Publish()
-	if _, ok := err.(client.ErrRepoNotInitialized); !ok {
+	if err == nil {
+		fmt.Fprintf(cli.out, "Successfully signed %q:%s\n", repoInfo.FullName(), tag)
+		return nil
+	} else if _, ok := err.(client.ErrRepoNotInitialized); !ok {
+		fmt.Fprintf(cli.out, "Failed to sign %q:%s - %s\n", repoInfo.FullName(), tag, err.Error())
 		return notaryError(repoInfo.FullName(), err)
 		return notaryError(repoInfo.FullName(), err)
 	}
 	}
 
 
@@ -444,7 +461,8 @@ func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string,
 		rootKeyID = rootPublicKey.ID()
 		rootKeyID = rootPublicKey.ID()
 	}
 	}
 
 
-	if err := repo.Initialize(rootKeyID); err != nil {
+	// Initialize the notary repository with a remotely managed snapshot key
+	if err := repo.Initialize(rootKeyID, data.CanonicalSnapshotRole); err != nil {
 		return notaryError(repoInfo.FullName(), err)
 		return notaryError(repoInfo.FullName(), err)
 	}
 	}
 	fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName())
 	fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName())

+ 12 - 1
api/client/update.go

@@ -6,6 +6,7 @@ import (
 
 
 	Cli "github.com/docker/docker/cli"
 	Cli "github.com/docker/docker/cli"
 	flag "github.com/docker/docker/pkg/mflag"
 	flag "github.com/docker/docker/pkg/mflag"
+	"github.com/docker/docker/runconfig/opts"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/go-units"
 	"github.com/docker/go-units"
 )
 )
@@ -25,6 +26,7 @@ func (cli *DockerCli) CmdUpdate(args ...string) error {
 	flMemoryReservation := cmd.String([]string{"-memory-reservation"}, "", "Memory soft limit")
 	flMemoryReservation := cmd.String([]string{"-memory-reservation"}, "", "Memory soft limit")
 	flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
 	flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
 	flKernelMemory := cmd.String([]string{"-kernel-memory"}, "", "Kernel memory limit")
 	flKernelMemory := cmd.String([]string{"-kernel-memory"}, "", "Kernel memory limit")
+	flRestartPolicy := cmd.String([]string{"-restart"}, "", "Restart policy to apply when a container exits")
 
 
 	cmd.Require(flag.Min, 1)
 	cmd.Require(flag.Min, 1)
 	cmd.ParseFlags(args, true)
 	cmd.ParseFlags(args, true)
@@ -69,6 +71,14 @@ func (cli *DockerCli) CmdUpdate(args ...string) error {
 		}
 		}
 	}
 	}
 
 
+	var restartPolicy container.RestartPolicy
+	if *flRestartPolicy != "" {
+		restartPolicy, err = opts.ParseRestartPolicy(*flRestartPolicy)
+		if err != nil {
+			return err
+		}
+	}
+
 	resources := container.Resources{
 	resources := container.Resources{
 		BlkioWeight:       *flBlkioWeight,
 		BlkioWeight:       *flBlkioWeight,
 		CpusetCpus:        *flCpusetCpus,
 		CpusetCpus:        *flCpusetCpus,
@@ -83,7 +93,8 @@ func (cli *DockerCli) CmdUpdate(args ...string) error {
 	}
 	}
 
 
 	updateConfig := container.UpdateConfig{
 	updateConfig := container.UpdateConfig{
-		Resources: resources,
+		Resources:     resources,
+		RestartPolicy: restartPolicy,
 	}
 	}
 
 
 	names := cmd.Args()
 	names := cmd.Args()

+ 12 - 30
api/client/utils.go

@@ -10,7 +10,6 @@ import (
 	gosignal "os/signal"
 	gosignal "os/signal"
 	"path/filepath"
 	"path/filepath"
 	"runtime"
 	"runtime"
-	"strings"
 	"time"
 	"time"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
@@ -49,7 +48,7 @@ func (cli *DockerCli) registryAuthenticationPrivilegedFunc(index *registrytypes.
 	return func() (string, error) {
 	return func() (string, error) {
 		fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
 		fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
 		indexServer := registry.GetAuthConfigKey(index)
 		indexServer := registry.GetAuthConfigKey(index)
-		authConfig, err := cli.configureAuth("", "", "", indexServer)
+		authConfig, err := cli.configureAuth("", "", indexServer, false)
 		if err != nil {
 		if err != nil {
 			return "", err
 			return "", err
 		}
 		}
@@ -59,6 +58,10 @@ func (cli *DockerCli) registryAuthenticationPrivilegedFunc(index *registrytypes.
 
 
 func (cli *DockerCli) resizeTty(id string, isExec bool) {
 func (cli *DockerCli) resizeTty(id string, isExec bool) {
 	height, width := cli.getTtySize()
 	height, width := cli.getTtySize()
+	cli.resizeTtyTo(id, height, width, isExec)
+}
+
+func (cli *DockerCli) resizeTtyTo(id string, height, width int, isExec bool) {
 	if height == 0 && width == 0 {
 	if height == 0 && width == 0 {
 		return
 		return
 	}
 	}
@@ -181,38 +184,17 @@ func copyToFile(outfile string, r io.Reader) error {
 // resolveAuthConfig is like registry.ResolveAuthConfig, but if using the
 // resolveAuthConfig is like registry.ResolveAuthConfig, but if using the
 // default index, it uses the default index name for the daemon's platform,
 // default index, it uses the default index name for the daemon's platform,
 // not the client's platform.
 // not the client's platform.
-func (cli *DockerCli) resolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
+func (cli *DockerCli) resolveAuthConfig(index *registrytypes.IndexInfo) types.AuthConfig {
 	configKey := index.Name
 	configKey := index.Name
 	if index.Official {
 	if index.Official {
 		configKey = cli.electAuthServer()
 		configKey = cli.electAuthServer()
 	}
 	}
 
 
-	// First try the happy case
-	if c, found := authConfigs[configKey]; found || index.Official {
-		return c
-	}
-
-	convertToHostname := func(url string) string {
-		stripped := url
-		if strings.HasPrefix(url, "http://") {
-			stripped = strings.Replace(url, "http://", "", 1)
-		} else if strings.HasPrefix(url, "https://") {
-			stripped = strings.Replace(url, "https://", "", 1)
-		}
-
-		nameParts := strings.SplitN(stripped, "/", 2)
-
-		return nameParts[0]
-	}
-
-	// Maybe they have a legacy config file, we will iterate the keys converting
-	// them to the new format and testing
-	for registry, ac := range authConfigs {
-		if configKey == convertToHostname(registry) {
-			return ac
-		}
-	}
+	a, _ := getCredentials(cli.configFile, configKey)
+	return a
+}
 
 
-	// When all else fails, return an empty auth config
-	return types.AuthConfig{}
+func (cli *DockerCli) retrieveAuthConfigs() map[string]types.AuthConfig {
+	acs, _ := getAllCredentials(cli.configFile)
+	return acs
 }
 }

+ 10 - 0
api/client/volume.go

@@ -2,6 +2,7 @@ package client
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"sort"
 	"text/tabwriter"
 	"text/tabwriter"
 
 
 	Cli "github.com/docker/docker/cli"
 	Cli "github.com/docker/docker/cli"
@@ -72,6 +73,7 @@ func (cli *DockerCli) CmdVolumeLs(args ...string) error {
 		fmt.Fprintf(w, "\n")
 		fmt.Fprintf(w, "\n")
 	}
 	}
 
 
+	sort.Sort(byVolumeName(volumes.Volumes))
 	for _, vol := range volumes.Volumes {
 	for _, vol := range volumes.Volumes {
 		if *quiet {
 		if *quiet {
 			fmt.Fprintln(w, vol.Name)
 			fmt.Fprintln(w, vol.Name)
@@ -83,6 +85,14 @@ func (cli *DockerCli) CmdVolumeLs(args ...string) error {
 	return nil
 	return nil
 }
 }
 
 
+type byVolumeName []*types.Volume
+
+func (r byVolumeName) Len() int      { return len(r) }
+func (r byVolumeName) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
+func (r byVolumeName) Less(i, j int) bool {
+	return r[i].Name < r[j].Name
+}
+
 // CmdVolumeInspect displays low-level information on one or more volumes.
 // CmdVolumeInspect displays low-level information on one or more volumes.
 //
 //
 // Usage: docker volume inspect [OPTIONS] VOLUME [VOLUME...]
 // Usage: docker volume inspect [OPTIONS] VOLUME [VOLUME...]

+ 0 - 3
api/common.go

@@ -23,9 +23,6 @@ const (
 	// MinVersion represents Minimum REST API version supported
 	// MinVersion represents Minimum REST API version supported
 	MinVersion version.Version = "1.12"
 	MinVersion version.Version = "1.12"
 
 
-	// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
-	DefaultDockerfileName string = "Dockerfile"
-
 	// NoBaseImageSpecifier is the symbol used by the FROM
 	// NoBaseImageSpecifier is the symbol used by the FROM
 	// command to specify that no base image is to be used.
 	// command to specify that no base image is to be used.
 	NoBaseImageSpecifier string = "scratch"
 	NoBaseImageSpecifier string = "scratch"

+ 1 - 1
api/common_test.go

@@ -310,7 +310,7 @@ func TestLoadOrCreateTrustKeyCreateKey(t *testing.T) {
 	}
 	}
 
 
 	// With the need to create the folder hierarchy as tmpKeyFie is in a path
 	// With the need to create the folder hierarchy as tmpKeyFie is in a path
-	// where some folder do not exists.
+	// where some folders do not exist.
 	tmpKeyFile = filepath.Join(tmpKeyFolderPath, "folder/hierarchy/keyfile")
 	tmpKeyFile = filepath.Join(tmpKeyFolderPath, "folder/hierarchy/keyfile")
 
 
 	if key, err := LoadOrCreateTrustKey(tmpKeyFile); err != nil || key == nil {
 	if key, err := LoadOrCreateTrustKey(tmpKeyFile); err != nil || key == nil {

+ 69 - 0
api/server/httputils/errors.go

@@ -0,0 +1,69 @@
+package httputils
+
+import (
+	"net/http"
+	"strings"
+
+	"github.com/Sirupsen/logrus"
+)
+
+// httpStatusError is an interface
+// that errors with custom status codes
+// implement to tell the api layer
+// which response status to set.
+type httpStatusError interface {
+	HTTPErrorStatusCode() int
+}
+
+// inputValidationError is an interface
+// that errors generated by invalid
+// inputs can implement to tell the
+// api layer to set a 400 status code
+// in the response.
+type inputValidationError interface {
+	IsValidationError() bool
+}
+
+// WriteError decodes a specific docker error and sends it in the response.
+func WriteError(w http.ResponseWriter, err error) {
+	if err == nil || w == nil {
+		logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
+		return
+	}
+
+	var statusCode int
+	errMsg := err.Error()
+
+	switch e := err.(type) {
+	case httpStatusError:
+		statusCode = e.HTTPErrorStatusCode()
+	case inputValidationError:
+		statusCode = http.StatusBadRequest
+	default:
+		// FIXME: this is brittle and should not be necessary, but we still need to identify if
+		// there are errors falling back into this logic.
+		// If we need to differentiate between different possible error types,
+		// we should create appropriate error types that implement the httpStatusError interface.
+		errStr := strings.ToLower(errMsg)
+		for keyword, status := range map[string]int{
+			"not found":             http.StatusNotFound,
+			"no such":               http.StatusNotFound,
+			"bad parameter":         http.StatusBadRequest,
+			"conflict":              http.StatusConflict,
+			"impossible":            http.StatusNotAcceptable,
+			"wrong login/password":  http.StatusUnauthorized,
+			"hasn't been activated": http.StatusForbidden,
+		} {
+			if strings.Contains(errStr, keyword) {
+				statusCode = status
+				break
+			}
+		}
+	}
+
+	if statusCode == 0 {
+		statusCode = http.StatusInternalServerError
+	}
+
+	http.Error(w, errMsg, statusCode)
+}

+ 2 - 76
api/server/httputils/httputils.go

@@ -9,8 +9,6 @@ import (
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
-	"github.com/Sirupsen/logrus"
-	"github.com/docker/distribution/registry/api/errcode"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/pkg/version"
 	"github.com/docker/docker/pkg/version"
 )
 )
@@ -19,7 +17,7 @@ import (
 const APIVersionKey = "api-version"
 const APIVersionKey = "api-version"
 
 
 // APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
 // APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
-// Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
+// Any function that has the appropriate signature can be registered as a API endpoint (e.g. getVersion).
 type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
 type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
 
 
 // HijackConnection interrupts the http response writer to get the
 // HijackConnection interrupts the http response writer to get the
@@ -77,7 +75,7 @@ func ParseForm(r *http.Request) error {
 	return nil
 	return nil
 }
 }
 
 
-// ParseMultipartForm ensure the request form is parsed, even with invalid content types.
+// ParseMultipartForm ensures the request form is parsed, even with invalid content types.
 func ParseMultipartForm(r *http.Request) error {
 func ParseMultipartForm(r *http.Request) error {
 	if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
 	if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
 		return err
 		return err
@@ -85,78 +83,6 @@ func ParseMultipartForm(r *http.Request) error {
 	return nil
 	return nil
 }
 }
 
 
-// WriteError decodes a specific docker error and sends it in the response.
-func WriteError(w http.ResponseWriter, err error) {
-	if err == nil || w == nil {
-		logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
-		return
-	}
-
-	statusCode := http.StatusInternalServerError
-	errMsg := err.Error()
-
-	// Based on the type of error we get we need to process things
-	// slightly differently to extract the error message.
-	// In the 'errcode.*' cases there are two different type of
-	// error that could be returned. errocode.ErrorCode is the base
-	// type of error object - it is just an 'int' that can then be
-	// used as the look-up key to find the message. errorcode.Error
-	// extends errorcode.Error by adding error-instance specific
-	// data, like 'details' or variable strings to be inserted into
-	// the message.
-	//
-	// Ideally, we should just be able to call err.Error() for all
-	// cases but the errcode package doesn't support that yet.
-	//
-	// Additionally, in both errcode cases, there might be an http
-	// status code associated with it, and if so use it.
-	switch err.(type) {
-	case errcode.ErrorCode:
-		daError, _ := err.(errcode.ErrorCode)
-		statusCode = daError.Descriptor().HTTPStatusCode
-		errMsg = daError.Message()
-
-	case errcode.Error:
-		// For reference, if you're looking for a particular error
-		// then you can do something like :
-		//   import ( derr "github.com/docker/docker/errors" )
-		//   if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
-
-		daError, _ := err.(errcode.Error)
-		statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
-		errMsg = daError.Message
-
-	default:
-		// This part of will be removed once we've
-		// converted everything over to use the errcode package
-
-		// FIXME: this is brittle and should not be necessary.
-		// If we need to differentiate between different possible error types,
-		// we should create appropriate error types with clearly defined meaning
-		errStr := strings.ToLower(err.Error())
-		for keyword, status := range map[string]int{
-			"not found":             http.StatusNotFound,
-			"no such":               http.StatusNotFound,
-			"bad parameter":         http.StatusBadRequest,
-			"conflict":              http.StatusConflict,
-			"impossible":            http.StatusNotAcceptable,
-			"wrong login/password":  http.StatusUnauthorized,
-			"hasn't been activated": http.StatusForbidden,
-		} {
-			if strings.Contains(errStr, keyword) {
-				statusCode = status
-				break
-			}
-		}
-	}
-
-	if statusCode == 0 {
-		statusCode = http.StatusInternalServerError
-	}
-
-	http.Error(w, errMsg, statusCode)
-}
-
 // WriteJSON writes the value v to the http response stream as json with standard json encoding.
 // WriteJSON writes the value v to the http response stream as json with standard json encoding.
 func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
 func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")

+ 16 - 170
api/server/middleware.go

@@ -1,195 +1,41 @@
 package server
 package server
 
 
 import (
 import (
-	"bufio"
-	"encoding/json"
-	"io"
-	"net/http"
-	"runtime"
-	"strings"
-
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
+	"github.com/docker/docker/api/server/middleware"
 	"github.com/docker/docker/dockerversion"
 	"github.com/docker/docker/dockerversion"
-	"github.com/docker/docker/errors"
 	"github.com/docker/docker/pkg/authorization"
 	"github.com/docker/docker/pkg/authorization"
-	"github.com/docker/docker/pkg/ioutils"
-	"github.com/docker/docker/pkg/version"
-	"golang.org/x/net/context"
 )
 )
 
 
-// middleware is an adapter to allow the use of ordinary functions as Docker API filters.
-// Any function that has the appropriate signature can be register as a middleware.
-type middleware func(handler httputils.APIFunc) httputils.APIFunc
-
-// debugRequestMiddleware dumps the request to logger
-func debugRequestMiddleware(handler httputils.APIFunc) httputils.APIFunc {
-	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-		logrus.Debugf("%s %s", r.Method, r.RequestURI)
-
-		if r.Method != "POST" {
-			return handler(ctx, w, r, vars)
-		}
-		if err := httputils.CheckForJSON(r); err != nil {
-			return handler(ctx, w, r, vars)
-		}
-		maxBodySize := 4096 // 4KB
-		if r.ContentLength > int64(maxBodySize) {
-			return handler(ctx, w, r, vars)
-		}
-
-		body := r.Body
-		bufReader := bufio.NewReaderSize(body, maxBodySize)
-		r.Body = ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
-
-		b, err := bufReader.Peek(maxBodySize)
-		if err != io.EOF {
-			// either there was an error reading, or the buffer is full (in which case the request is too large)
-			return handler(ctx, w, r, vars)
-		}
-
-		var postForm map[string]interface{}
-		if err := json.Unmarshal(b, &postForm); err == nil {
-			if _, exists := postForm["password"]; exists {
-				postForm["password"] = "*****"
-			}
-			formStr, errMarshal := json.Marshal(postForm)
-			if errMarshal == nil {
-				logrus.Debugf("form data: %s", string(formStr))
-			} else {
-				logrus.Debugf("form data: %q", postForm)
-			}
-		}
-
-		return handler(ctx, w, r, vars)
-	}
-}
-
-// authorizationMiddleware perform authorization on the request.
-func (s *Server) authorizationMiddleware(handler httputils.APIFunc) httputils.APIFunc {
-	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-		// FIXME: fill when authN gets in
-		// User and UserAuthNMethod are taken from AuthN plugins
-		// Currently tracked in https://github.com/docker/docker/pull/13994
-		user := ""
-		userAuthNMethod := ""
-		authCtx := authorization.NewCtx(s.authZPlugins, user, userAuthNMethod, r.Method, r.RequestURI)
-
-		if err := authCtx.AuthZRequest(w, r); err != nil {
-			logrus.Errorf("AuthZRequest for %s %s returned error: %s", r.Method, r.RequestURI, err)
-			return err
-		}
-
-		rw := authorization.NewResponseModifier(w)
-
-		if err := handler(ctx, rw, r, vars); err != nil {
-			logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.RequestURI, err)
-			return err
-		}
-
-		if err := authCtx.AuthZResponse(rw, r); err != nil {
-			logrus.Errorf("AuthZResponse for %s %s returned error: %s", r.Method, r.RequestURI, err)
-			return err
-		}
-		return nil
-	}
-}
-
-// userAgentMiddleware checks the User-Agent header looking for a valid docker client spec.
-func (s *Server) userAgentMiddleware(handler httputils.APIFunc) httputils.APIFunc {
-	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-		if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
-			dockerVersion := version.Version(s.cfg.Version)
-
-			userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
-
-			// v1.20 onwards includes the GOOS of the client after the version
-			// such as Docker/1.7.0 (linux)
-			if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") {
-				userAgent[1] = strings.Split(userAgent[1], " ")[0]
-			}
-
-			if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) {
-				logrus.Warnf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
-			}
-		}
-		return handler(ctx, w, r, vars)
-	}
-}
-
-// corsMiddleware sets the CORS header expectations in the server.
-func (s *Server) corsMiddleware(handler httputils.APIFunc) httputils.APIFunc {
-	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-		// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
-		// otherwise, all head values will be passed to HTTP handler
-		corsHeaders := s.cfg.CorsHeaders
-		if corsHeaders == "" && s.cfg.EnableCors {
-			corsHeaders = "*"
-		}
-
-		if corsHeaders != "" {
-			writeCorsHeaders(w, r, corsHeaders)
-		}
-		return handler(ctx, w, r, vars)
-	}
-}
-
-// versionMiddleware checks the api version requirements before passing the request to the server handler.
-func versionMiddleware(handler httputils.APIFunc) httputils.APIFunc {
-	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-		apiVersion := version.Version(vars["version"])
-		if apiVersion == "" {
-			apiVersion = api.DefaultVersion
-		}
-
-		if apiVersion.GreaterThan(api.DefaultVersion) {
-			return errors.ErrorCodeNewerClientVersion.WithArgs(apiVersion, api.DefaultVersion)
-		}
-		if apiVersion.LessThan(api.MinVersion) {
-			return errors.ErrorCodeOldClientVersion.WithArgs(apiVersion, api.MinVersion)
-		}
-
-		w.Header().Set("Server", "Docker/"+dockerversion.Version+" ("+runtime.GOOS+")")
-		ctx = context.WithValue(ctx, httputils.APIVersionKey, apiVersion)
-		return handler(ctx, w, r, vars)
-	}
-}
-
 // handleWithGlobalMiddlwares wraps the handler function for a request with
 // handleWithGlobalMiddlwares wraps the handler function for a request with
 // the server's global middlewares. The order of the middlewares is backwards,
 // the server's global middlewares. The order of the middlewares is backwards,
 // meaning that the first in the list will be evaluated last.
 // meaning that the first in the list will be evaluated last.
-//
-// Example: handleWithGlobalMiddlewares(s.getContainersName)
-//
-//	s.loggingMiddleware(
-//		s.userAgentMiddleware(
-//			s.corsMiddleware(
-//				versionMiddleware(s.getContainersName)
-//			)
-//		)
-//	)
-// )
 func (s *Server) handleWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc {
 func (s *Server) handleWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc {
-	middlewares := []middleware{
-		versionMiddleware,
-		s.corsMiddleware,
-		s.userAgentMiddleware,
+	next := handler
+
+	handleVersion := middleware.NewVersionMiddleware(dockerversion.Version, api.DefaultVersion, api.MinVersion)
+	next = handleVersion(next)
+
+	if s.cfg.EnableCors {
+		handleCORS := middleware.NewCORSMiddleware(s.cfg.CorsHeaders)
+		next = handleCORS(next)
 	}
 	}
 
 
+	handleUserAgent := middleware.NewUserAgentMiddleware(s.cfg.Version)
+	next = handleUserAgent(next)
+
 	// Only want this on debug level
 	// Only want this on debug level
 	if s.cfg.Logging && logrus.GetLevel() == logrus.DebugLevel {
 	if s.cfg.Logging && logrus.GetLevel() == logrus.DebugLevel {
-		middlewares = append(middlewares, debugRequestMiddleware)
+		next = middleware.DebugRequestMiddleware(next)
 	}
 	}
 
 
 	if len(s.cfg.AuthorizationPluginNames) > 0 {
 	if len(s.cfg.AuthorizationPluginNames) > 0 {
 		s.authZPlugins = authorization.NewPlugins(s.cfg.AuthorizationPluginNames)
 		s.authZPlugins = authorization.NewPlugins(s.cfg.AuthorizationPluginNames)
-		middlewares = append(middlewares, s.authorizationMiddleware)
+		handleAuthorization := middleware.NewAuthorizationMiddleware(s.authZPlugins)
+		next = handleAuthorization(next)
 	}
 	}
 
 
-	h := handler
-	for _, m := range middlewares {
-		h = m(h)
-	}
-	return h
+	return next
 }
 }

+ 42 - 0
api/server/middleware/authorization.go

@@ -0,0 +1,42 @@
+package middleware
+
+import (
+	"net/http"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/api/server/httputils"
+	"github.com/docker/docker/pkg/authorization"
+	"golang.org/x/net/context"
+)
+
+// NewAuthorizationMiddleware creates a new Authorization middleware.
+func NewAuthorizationMiddleware(plugins []authorization.Plugin) Middleware {
+	return func(handler httputils.APIFunc) httputils.APIFunc {
+		return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+			// FIXME: fill when authN gets in
+			// User and UserAuthNMethod are taken from AuthN plugins
+			// Currently tracked in https://github.com/docker/docker/pull/13994
+			user := ""
+			userAuthNMethod := ""
+			authCtx := authorization.NewCtx(plugins, user, userAuthNMethod, r.Method, r.RequestURI)
+
+			if err := authCtx.AuthZRequest(w, r); err != nil {
+				logrus.Errorf("AuthZRequest for %s %s returned error: %s", r.Method, r.RequestURI, err)
+				return err
+			}
+
+			rw := authorization.NewResponseModifier(w)
+
+			if err := handler(ctx, rw, r, vars); err != nil {
+				logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.RequestURI, err)
+				return err
+			}
+
+			if err := authCtx.AuthZResponse(rw, r); err != nil {
+				logrus.Errorf("AuthZResponse for %s %s returned error: %s", r.Method, r.RequestURI, err)
+				return err
+			}
+			return nil
+		}
+	}
+}

+ 33 - 0
api/server/middleware/cors.go

@@ -0,0 +1,33 @@
+package middleware
+
+import (
+	"net/http"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/api/server/httputils"
+	"golang.org/x/net/context"
+)
+
+// NewCORSMiddleware creates a new CORS middleware.
+func NewCORSMiddleware(defaultHeaders string) Middleware {
+	return func(handler httputils.APIFunc) httputils.APIFunc {
+		return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+			// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
+			// otherwise, all head values will be passed to HTTP handler
+			corsHeaders := defaultHeaders
+			if corsHeaders == "" {
+				corsHeaders = "*"
+			}
+
+			writeCorsHeaders(w, r, corsHeaders)
+			return handler(ctx, w, r, vars)
+		}
+	}
+}
+
+func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) {
+	logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders)
+	w.Header().Add("Access-Control-Allow-Origin", corsHeaders)
+	w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth")
+	w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS")
+}

+ 56 - 0
api/server/middleware/debug.go

@@ -0,0 +1,56 @@
+package middleware
+
+import (
+	"bufio"
+	"encoding/json"
+	"io"
+	"net/http"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/api/server/httputils"
+	"github.com/docker/docker/pkg/ioutils"
+	"golang.org/x/net/context"
+)
+
+// DebugRequestMiddleware dumps the request to logger
+func DebugRequestMiddleware(handler httputils.APIFunc) httputils.APIFunc {
+	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+		logrus.Debugf("Calling %s %s", r.Method, r.RequestURI)
+
+		if r.Method != "POST" {
+			return handler(ctx, w, r, vars)
+		}
+		if err := httputils.CheckForJSON(r); err != nil {
+			return handler(ctx, w, r, vars)
+		}
+		maxBodySize := 4096 // 4KB
+		if r.ContentLength > int64(maxBodySize) {
+			return handler(ctx, w, r, vars)
+		}
+
+		body := r.Body
+		bufReader := bufio.NewReaderSize(body, maxBodySize)
+		r.Body = ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
+
+		b, err := bufReader.Peek(maxBodySize)
+		if err != io.EOF {
+			// either there was an error reading, or the buffer is full (in which case the request is too large)
+			return handler(ctx, w, r, vars)
+		}
+
+		var postForm map[string]interface{}
+		if err := json.Unmarshal(b, &postForm); err == nil {
+			if _, exists := postForm["password"]; exists {
+				postForm["password"] = "*****"
+			}
+			formStr, errMarshal := json.Marshal(postForm)
+			if errMarshal == nil {
+				logrus.Debugf("form data: %s", string(formStr))
+			} else {
+				logrus.Debugf("form data: %q", postForm)
+			}
+		}
+
+		return handler(ctx, w, r, vars)
+	}
+}

+ 7 - 0
api/server/middleware/middleware.go

@@ -0,0 +1,7 @@
+package middleware
+
+import "github.com/docker/docker/api/server/httputils"
+
+// Middleware is an adapter to allow the use of ordinary functions as Docker API filters.
+// Any function that has the appropriate signature can be registered as a middleware.
+type Middleware func(handler httputils.APIFunc) httputils.APIFunc

+ 35 - 0
api/server/middleware/user_agent.go

@@ -0,0 +1,35 @@
+package middleware
+
+import (
+	"net/http"
+	"strings"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/api/server/httputils"
+	"github.com/docker/docker/pkg/version"
+	"golang.org/x/net/context"
+)
+
+// NewUserAgentMiddleware creates a new UserAgent middleware.
+func NewUserAgentMiddleware(versionCheck string) Middleware {
+	serverVersion := version.Version(versionCheck)
+
+	return func(handler httputils.APIFunc) httputils.APIFunc {
+		return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+			if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
+				userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
+
+				// v1.20 onwards includes the GOOS of the client after the version
+				// such as Docker/1.7.0 (linux)
+				if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") {
+					userAgent[1] = strings.Split(userAgent[1], " ")[0]
+				}
+
+				if len(userAgent) == 2 && !serverVersion.Equal(version.Version(userAgent[1])) {
+					logrus.Debugf("Client and server don't have the same version (client: %s, server: %s)", userAgent[1], serverVersion)
+				}
+			}
+			return handler(ctx, w, r, vars)
+		}
+	}
+}

+ 45 - 0
api/server/middleware/version.go

@@ -0,0 +1,45 @@
+package middleware
+
+import (
+	"fmt"
+	"net/http"
+	"runtime"
+
+	"github.com/docker/docker/api/server/httputils"
+	"github.com/docker/docker/pkg/version"
+	"golang.org/x/net/context"
+)
+
+type badRequestError struct {
+	error
+}
+
+func (badRequestError) HTTPErrorStatusCode() int {
+	return http.StatusBadRequest
+}
+
+// NewVersionMiddleware creates a new Version middleware.
+func NewVersionMiddleware(versionCheck string, defaultVersion, minVersion version.Version) Middleware {
+	serverVersion := version.Version(versionCheck)
+
+	return func(handler httputils.APIFunc) httputils.APIFunc {
+		return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+			apiVersion := version.Version(vars["version"])
+			if apiVersion == "" {
+				apiVersion = defaultVersion
+			}
+
+			if apiVersion.GreaterThan(defaultVersion) {
+				return badRequestError{fmt.Errorf("client is newer than server (client API version: %s, server API version: %s)", apiVersion, defaultVersion)}
+			}
+			if apiVersion.LessThan(minVersion) {
+				return badRequestError{fmt.Errorf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", apiVersion, minVersion)}
+			}
+
+			header := fmt.Sprintf("Docker/%s (%s)", serverVersion, runtime.GOOS)
+			w.Header().Set("Server", header)
+			ctx = context.WithValue(ctx, httputils.APIVersionKey, apiVersion)
+			return handler(ctx, w, r, vars)
+		}
+	}
+}

+ 16 - 9
api/server/middleware_test.go → api/server/middleware/version_test.go

@@ -1,13 +1,13 @@
-package server
+package middleware
 
 
 import (
 import (
 	"net/http"
 	"net/http"
 	"net/http/httptest"
 	"net/http/httptest"
+	"strings"
 	"testing"
 	"testing"
 
 
-	"github.com/docker/distribution/registry/api/errcode"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
-	"github.com/docker/docker/errors"
+	"github.com/docker/docker/pkg/version"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
@@ -19,7 +19,10 @@ func TestVersionMiddleware(t *testing.T) {
 		return nil
 		return nil
 	}
 	}
 
 
-	h := versionMiddleware(handler)
+	defaultVersion := version.Version("1.10.0")
+	minVersion := version.Version("1.2.0")
+	m := NewVersionMiddleware(defaultVersion.String(), defaultVersion, minVersion)
+	h := m(handler)
 
 
 	req, _ := http.NewRequest("GET", "/containers/json", nil)
 	req, _ := http.NewRequest("GET", "/containers/json", nil)
 	resp := httptest.NewRecorder()
 	resp := httptest.NewRecorder()
@@ -37,7 +40,10 @@ func TestVersionMiddlewareWithErrors(t *testing.T) {
 		return nil
 		return nil
 	}
 	}
 
 
-	h := versionMiddleware(handler)
+	defaultVersion := version.Version("1.10.0")
+	minVersion := version.Version("1.2.0")
+	m := NewVersionMiddleware(defaultVersion.String(), defaultVersion, minVersion)
+	h := m(handler)
 
 
 	req, _ := http.NewRequest("GET", "/containers/json", nil)
 	req, _ := http.NewRequest("GET", "/containers/json", nil)
 	resp := httptest.NewRecorder()
 	resp := httptest.NewRecorder()
@@ -45,13 +51,14 @@ func TestVersionMiddlewareWithErrors(t *testing.T) {
 
 
 	vars := map[string]string{"version": "0.1"}
 	vars := map[string]string{"version": "0.1"}
 	err := h(ctx, resp, req, vars)
 	err := h(ctx, resp, req, vars)
-	if derr, ok := err.(errcode.Error); !ok || derr.ErrorCode() != errors.ErrorCodeOldClientVersion {
-		t.Fatalf("Expected ErrorCodeOldClientVersion, got %v", err)
+
+	if !strings.Contains(err.Error(), "client version 0.1 is too old. Minimum supported API version is 1.2.0") {
+		t.Fatalf("Expected too old client error, got %v", err)
 	}
 	}
 
 
 	vars["version"] = "100000"
 	vars["version"] = "100000"
 	err = h(ctx, resp, req, vars)
 	err = h(ctx, resp, req, vars)
-	if derr, ok := err.(errcode.Error); !ok || derr.ErrorCode() != errors.ErrorCodeNewerClientVersion {
-		t.Fatalf("Expected ErrorCodeNewerClientVersion, got %v", err)
+	if !strings.Contains(err.Error(), "client is newer than server") {
+		t.Fatalf("Expected client newer than server error, got %v", err)
 	}
 	}
 }
 }

+ 4 - 2
api/server/profiler.go

@@ -9,8 +9,10 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
 )
 )
 
 
-func profilerSetup(mainRouter *mux.Router, path string) {
-	var r = mainRouter.PathPrefix(path).Subrouter()
+const debugPathPrefix = "/debug/"
+
+func profilerSetup(mainRouter *mux.Router) {
+	var r = mainRouter.PathPrefix(debugPathPrefix).Subrouter()
 	r.HandleFunc("/vars", expVars)
 	r.HandleFunc("/vars", expVars)
 	r.HandleFunc("/pprof/", pprof.Index)
 	r.HandleFunc("/pprof/", pprof.Index)
 	r.HandleFunc("/pprof/cmdline", pprof.Cmdline)
 	r.HandleFunc("/pprof/cmdline", pprof.Cmdline)

+ 6 - 6
api/server/router/build/build_routes.go

@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/base64"
 	"encoding/base64"
 	"encoding/json"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"net/http"
 	"net/http"
@@ -17,7 +16,6 @@ import (
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/progress"
 	"github.com/docker/docker/pkg/progress"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/streamformatter"
-	"github.com/docker/docker/utils"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/go-units"
 	"github.com/docker/go-units"
@@ -60,11 +58,11 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
 		options.ShmSize = shmSize
 		options.ShmSize = shmSize
 	}
 	}
 
 
-	if i := container.IsolationLevel(r.FormValue("isolation")); i != "" {
-		if !container.IsolationLevel.IsValid(i) {
+	if i := container.Isolation(r.FormValue("isolation")); i != "" {
+		if !container.Isolation.IsValid(i) {
 			return nil, fmt.Errorf("Unsupported isolation: %q", i)
 			return nil, fmt.Errorf("Unsupported isolation: %q", i)
 		}
 		}
-		options.IsolationLevel = i
+		options.Isolation = i
 	}
 	}
 
 
 	var buildUlimits = []*units.Ulimit{}
 	var buildUlimits = []*units.Ulimit{}
@@ -117,7 +115,7 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *
 		if !output.Flushed() {
 		if !output.Flushed() {
 			return err
 			return err
 		}
 		}
-		_, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
+		_, err = w.Write(sf.FormatError(err))
 		if err != nil {
 		if err != nil {
 			logrus.Warnf("could not write error response: %v", err)
 			logrus.Warnf("could not write error response: %v", err)
 		}
 		}
@@ -159,6 +157,8 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *
 		buildOptions.Dockerfile = dockerfileName
 		buildOptions.Dockerfile = dockerfileName
 	}
 	}
 
 
+	buildOptions.AuthConfigs = authConfigs
+
 	out = output
 	out = output
 	if buildOptions.SuppressOutput {
 	if buildOptions.SuppressOutput {
 		out = notVerboseBuffer
 		out = notVerboseBuffer

+ 1 - 2
api/server/router/container/backend.go

@@ -5,7 +5,6 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/backend"
-	"github.com/docker/docker/daemon/exec"
 	"github.com/docker/docker/pkg/archive"
 	"github.com/docker/docker/pkg/archive"
 	"github.com/docker/docker/pkg/version"
 	"github.com/docker/docker/pkg/version"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
@@ -15,7 +14,7 @@ import (
 // execBackend includes functions to implement to provide exec functionality.
 // execBackend includes functions to implement to provide exec functionality.
 type execBackend interface {
 type execBackend interface {
 	ContainerExecCreate(config *types.ExecConfig) (string, error)
 	ContainerExecCreate(config *types.ExecConfig) (string, error)
-	ContainerExecInspect(id string) (*exec.Config, error)
+	ContainerExecInspect(id string) (*backend.ExecInspect, error)
 	ContainerExecResize(name string, height, width int) error
 	ContainerExecResize(name string, height, width int) error
 	ContainerExecStart(name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error
 	ContainerExecStart(name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error
 	ExecExists(name string) (bool, error)
 	ExecExists(name string) (bool, error)

+ 13 - 9
api/server/router/container/container_routes.go

@@ -11,15 +11,12 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
-	"github.com/docker/distribution/registry/api/errcode"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/backend"
-	derr "github.com/docker/docker/errors"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/term"
 	"github.com/docker/docker/pkg/term"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
-	"github.com/docker/docker/utils"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/filters"
 	"github.com/docker/engine-api/types/filters"
@@ -126,7 +123,7 @@ func (s *containerRouter) getContainersLogs(ctx context.Context, w http.Response
 			// The client may be expecting all of the data we're sending to
 			// The client may be expecting all of the data we're sending to
 			// be multiplexed, so send it through OutStream, which will
 			// be multiplexed, so send it through OutStream, which will
 			// have been set up to handle that if needed.
 			// have been set up to handle that if needed.
-			fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %s\n", utils.GetErrorMessage(err))
+			fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %v\n", err)
 		default:
 		default:
 			return err
 			return err
 		}
 		}
@@ -182,6 +179,10 @@ func (s *containerRouter) postContainersStop(ctx context.Context, w http.Respons
 	return nil
 	return nil
 }
 }
 
 
+type errContainerIsRunning interface {
+	ContainerIsRunning() bool
+}
+
 func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := httputils.ParseForm(r); err != nil {
 	if err := httputils.ParseForm(r); err != nil {
 		return err
 		return err
@@ -199,15 +200,17 @@ func (s *containerRouter) postContainersKill(ctx context.Context, w http.Respons
 	}
 	}
 
 
 	if err := s.backend.ContainerKill(name, uint64(sig)); err != nil {
 	if err := s.backend.ContainerKill(name, uint64(sig)); err != nil {
-		theErr, isDerr := err.(errcode.ErrorCoder)
-		isStopped := isDerr && theErr.ErrorCode() == derr.ErrorCodeNotRunning
+		var isStopped bool
+		if e, ok := err.(errContainerIsRunning); ok {
+			isStopped = !e.ContainerIsRunning()
+		}
 
 
 		// Return error that's not caused because the container is stopped.
 		// Return error that's not caused because the container is stopped.
 		// Return error if the container is not running and the api is >= 1.20
 		// Return error if the container is not running and the api is >= 1.20
 		// to keep backwards compatibility.
 		// to keep backwards compatibility.
 		version := httputils.VersionFromContext(ctx)
 		version := httputils.VersionFromContext(ctx)
 		if version.GreaterThanOrEqualTo("1.20") || !isStopped {
 		if version.GreaterThanOrEqualTo("1.20") || !isStopped {
-			return fmt.Errorf("Cannot kill container %s: %v", name, utils.GetErrorMessage(err))
+			return fmt.Errorf("Cannot kill container %s: %v", name, err)
 		}
 		}
 	}
 	}
 
 
@@ -322,7 +325,8 @@ func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.Respon
 	}
 	}
 
 
 	hostConfig := &container.HostConfig{
 	hostConfig := &container.HostConfig{
-		Resources: updateConfig.Resources,
+		Resources:     updateConfig.Resources,
+		RestartPolicy: updateConfig.RestartPolicy,
 	}
 	}
 
 
 	name := vars["name"]
 	name := vars["name"]
@@ -429,7 +433,7 @@ func (s *containerRouter) postContainersAttach(ctx context.Context, w http.Respo
 
 
 	hijacker, ok := w.(http.Hijacker)
 	hijacker, ok := w.(http.Hijacker)
 	if !ok {
 	if !ok {
-		return derr.ErrorCodeNoHijackConnection.WithArgs(containerName)
+		return fmt.Errorf("error attaching to container %s, hijack connection missing", containerName)
 	}
 	}
 
 
 	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
 	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {

+ 2 - 3
api/server/router/container/exec.go

@@ -10,7 +10,6 @@ import (
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/pkg/stdcopy"
 	"github.com/docker/docker/pkg/stdcopy"
-	"github.com/docker/docker/utils"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -46,7 +45,7 @@ func (s *containerRouter) postContainerExecCreate(ctx context.Context, w http.Re
 	// Register an instance of Exec in container.
 	// Register an instance of Exec in container.
 	id, err := s.backend.ContainerExecCreate(execConfig)
 	id, err := s.backend.ContainerExecCreate(execConfig)
 	if err != nil {
 	if err != nil {
-		logrus.Errorf("Error setting up exec command in container %s: %s", name, utils.GetErrorMessage(err))
+		logrus.Errorf("Error setting up exec command in container %s: %v", name, err)
 		return err
 		return err
 	}
 	}
 
 
@@ -113,7 +112,7 @@ func (s *containerRouter) postContainerExecStart(ctx context.Context, w http.Res
 		if execStartCheck.Detach {
 		if execStartCheck.Detach {
 			return err
 			return err
 		}
 		}
-		logrus.Errorf("Error running exec in container: %v\n", utils.GetErrorMessage(err))
+		logrus.Errorf("Error running exec in container: %v\n", err)
 	}
 	}
 	return nil
 	return nil
 }
 }

+ 0 - 1
api/server/router/image/backend.go

@@ -20,7 +20,6 @@ type Backend interface {
 
 
 type containerBackend interface {
 type containerBackend interface {
 	Commit(name string, config *types.ContainerCommitConfig) (imageID string, err error)
 	Commit(name string, config *types.ContainerCommitConfig) (imageID string, err error)
-	Exists(containerName string) bool
 }
 }
 
 
 type imageBackend interface {
 type imageBackend interface {

+ 4 - 4
api/server/router/image/image.go

@@ -4,14 +4,14 @@ import "github.com/docker/docker/api/server/router"
 
 
 // imageRouter is a router to talk with the image controller
 // imageRouter is a router to talk with the image controller
 type imageRouter struct {
 type imageRouter struct {
-	daemon Backend
-	routes []router.Route
+	backend Backend
+	routes  []router.Route
 }
 }
 
 
 // NewRouter initializes a new image router
 // NewRouter initializes a new image router
-func NewRouter(daemon Backend) router.Router {
+func NewRouter(backend Backend) router.Router {
 	r := &imageRouter{
 	r := &imageRouter{
-		daemon: daemon,
+		backend: backend,
 	}
 	}
 	r.initRoutes()
 	r.initRoutes()
 	return r
 	return r

+ 12 - 17
api/server/router/image/image_routes.go

@@ -14,7 +14,6 @@ import (
 	"github.com/docker/distribution/registry/api/errcode"
 	"github.com/docker/distribution/registry/api/errcode"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/builder/dockerfile"
 	"github.com/docker/docker/builder/dockerfile"
-	derr "github.com/docker/docker/errors"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
@@ -49,10 +48,6 @@ func (s *imageRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *
 		c = &container.Config{}
 		c = &container.Config{}
 	}
 	}
 
 
-	if !s.daemon.Exists(cname) {
-		return derr.ErrorCodeNoSuchContainer.WithArgs(cname)
-	}
-
 	newConfig, err := dockerfile.BuildFromConfig(c, r.Form["changes"])
 	newConfig, err := dockerfile.BuildFromConfig(c, r.Form["changes"])
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -68,7 +63,7 @@ func (s *imageRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *
 		MergeConfigs: true,
 		MergeConfigs: true,
 	}
 	}
 
 
-	imgID, err := s.daemon.Commit(cname, commitCfg)
+	imgID, err := s.backend.Commit(cname, commitCfg)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -134,7 +129,7 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
 					}
 					}
 				}
 				}
 
 
-				err = s.daemon.PullImage(ref, metaHeaders, authConfig, output)
+				err = s.backend.PullImage(ref, metaHeaders, authConfig, output)
 			}
 			}
 		}
 		}
 		// Check the error from pulling an image to make sure the request
 		// Check the error from pulling an image to make sure the request
@@ -175,7 +170,7 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
 			return err
 			return err
 		}
 		}
 
 
-		err = s.daemon.ImportImage(src, newRef, message, r.Body, output, newConfig)
+		err = s.backend.ImportImage(src, newRef, message, r.Body, output, newConfig)
 	}
 	}
 	if err != nil {
 	if err != nil {
 		if !output.Flushed() {
 		if !output.Flushed() {
@@ -233,7 +228,7 @@ func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter,
 
 
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
 
 
-	if err := s.daemon.PushImage(ref, metaHeaders, authConfig, output); err != nil {
+	if err := s.backend.PushImage(ref, metaHeaders, authConfig, output); err != nil {
 		if !output.Flushed() {
 		if !output.Flushed() {
 			return err
 			return err
 		}
 		}
@@ -259,7 +254,7 @@ func (s *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r
 		names = r.Form["names"]
 		names = r.Form["names"]
 	}
 	}
 
 
-	if err := s.daemon.ExportImage(names, output); err != nil {
+	if err := s.backend.ExportImage(names, output); err != nil {
 		if !output.Flushed() {
 		if !output.Flushed() {
 			return err
 			return err
 		}
 		}
@@ -275,7 +270,7 @@ func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter,
 	}
 	}
 	quiet := httputils.BoolValueOrDefault(r, "quiet", true)
 	quiet := httputils.BoolValueOrDefault(r, "quiet", true)
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
-	return s.daemon.LoadImage(r.Body, w, quiet)
+	return s.backend.LoadImage(r.Body, w, quiet)
 }
 }
 
 
 func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@@ -292,7 +287,7 @@ func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r
 	force := httputils.BoolValue(r, "force")
 	force := httputils.BoolValue(r, "force")
 	prune := !httputils.BoolValue(r, "noprune")
 	prune := !httputils.BoolValue(r, "noprune")
 
 
-	list, err := s.daemon.ImageDelete(name, force, prune)
+	list, err := s.backend.ImageDelete(name, force, prune)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -301,7 +296,7 @@ func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r
 }
 }
 
 
 func (s *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (s *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-	imageInspect, err := s.daemon.LookupImage(vars["name"])
+	imageInspect, err := s.backend.LookupImage(vars["name"])
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -315,7 +310,7 @@ func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter,
 	}
 	}
 
 
 	// FIXME: The filter parameter could just be a match filter
 	// FIXME: The filter parameter could just be a match filter
-	images, err := s.daemon.Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
+	images, err := s.backend.Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -325,7 +320,7 @@ func (s *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter,
 
 
 func (s *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (s *imageRouter) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	name := vars["name"]
 	name := vars["name"]
-	history, err := s.daemon.ImageHistory(name)
+	history, err := s.backend.ImageHistory(name)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -348,7 +343,7 @@ func (s *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter,
 			return err
 			return err
 		}
 		}
 	}
 	}
-	if err := s.daemon.TagImage(newTag, vars["name"]); err != nil {
+	if err := s.backend.TagImage(newTag, vars["name"]); err != nil {
 		return err
 		return err
 	}
 	}
 	w.WriteHeader(http.StatusCreated)
 	w.WriteHeader(http.StatusCreated)
@@ -378,7 +373,7 @@ func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter
 			headers[k] = v
 			headers[k] = v
 		}
 		}
 	}
 	}
-	query, err := s.daemon.SearchRegistryForImages(r.Form.Get("term"), config, headers)
+	query, err := s.backend.SearchRegistryForImages(r.Form.Get("term"), config, headers)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 1 - 3
api/server/router/network/backend.go

@@ -8,13 +8,11 @@ import (
 // Backend is all the methods that need to be implemented
 // Backend is all the methods that need to be implemented
 // to provide network specific functionality.
 // to provide network specific functionality.
 type Backend interface {
 type Backend interface {
-	NetworkControllerEnabled() bool
-
 	FindNetwork(idName string) (libnetwork.Network, error)
 	FindNetwork(idName string) (libnetwork.Network, error)
 	GetNetworkByName(idName string) (libnetwork.Network, error)
 	GetNetworkByName(idName string) (libnetwork.Network, error)
 	GetNetworksByID(partialID string) []libnetwork.Network
 	GetNetworksByID(partialID string) []libnetwork.Network
 	GetAllNetworks() []libnetwork.Network
 	GetAllNetworks() []libnetwork.Network
-	CreateNetwork(name, driver string, ipam network.IPAM, options map[string]string, internal bool) (libnetwork.Network, error)
+	CreateNetwork(name, driver string, ipam network.IPAM, options map[string]string, internal bool, enableIPv6 bool) (libnetwork.Network, error)
 	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
 	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
 	DisconnectContainerFromNetwork(containerName string, network libnetwork.Network, force bool) error
 	DisconnectContainerFromNetwork(containerName string, network libnetwork.Network, force bool) error
 	DeleteNetwork(name string) error
 	DeleteNetwork(name string) error

+ 2 - 2
api/server/router/network/filter.go

@@ -84,8 +84,8 @@ func filterNetworkByID(nws []libnetwork.Network, id string) (retNws []libnetwork
 	return retNws, nil
 	return retNws, nil
 }
 }
 
 
-// filterAllNetworks filter network list according to user specified filter
-// and return user chosen networks
+// filterAllNetworks filters network list according to user specified filter
+// and returns user chosen networks
 func filterNetworks(nws []libnetwork.Network, filter filters.Args) ([]libnetwork.Network, error) {
 func filterNetworks(nws []libnetwork.Network, filter filters.Args) ([]libnetwork.Network, error) {
 	// if filter is empty, return original network list
 	// if filter is empty, return original network list
 	if filter.Len() == 0 {
 	if filter.Len() == 0 {

+ 7 - 25
api/server/router/network/network.go

@@ -1,13 +1,6 @@
 package network
 package network
 
 
-import (
-	"net/http"
-
-	"github.com/docker/docker/api/server/httputils"
-	"github.com/docker/docker/api/server/router"
-	"github.com/docker/docker/errors"
-	"golang.org/x/net/context"
-)
+import "github.com/docker/docker/api/server/router"
 
 
 // networkRouter is a router to talk with the network controller
 // networkRouter is a router to talk with the network controller
 type networkRouter struct {
 type networkRouter struct {
@@ -32,24 +25,13 @@ func (r *networkRouter) Routes() []router.Route {
 func (r *networkRouter) initRoutes() {
 func (r *networkRouter) initRoutes() {
 	r.routes = []router.Route{
 	r.routes = []router.Route{
 		// GET
 		// GET
-		router.NewGetRoute("/networks", r.controllerEnabledMiddleware(r.getNetworksList)),
-		router.NewGetRoute("/networks/{id:.*}", r.controllerEnabledMiddleware(r.getNetwork)),
+		router.NewGetRoute("/networks", r.getNetworksList),
+		router.NewGetRoute("/networks/{id:.*}", r.getNetwork),
 		// POST
 		// POST
-		router.NewPostRoute("/networks/create", r.controllerEnabledMiddleware(r.postNetworkCreate)),
-		router.NewPostRoute("/networks/{id:.*}/connect", r.controllerEnabledMiddleware(r.postNetworkConnect)),
-		router.NewPostRoute("/networks/{id:.*}/disconnect", r.controllerEnabledMiddleware(r.postNetworkDisconnect)),
+		router.NewPostRoute("/networks/create", r.postNetworkCreate),
+		router.NewPostRoute("/networks/{id:.*}/connect", r.postNetworkConnect),
+		router.NewPostRoute("/networks/{id:.*}/disconnect", r.postNetworkDisconnect),
 		// DELETE
 		// DELETE
-		router.NewDeleteRoute("/networks/{id:.*}", r.controllerEnabledMiddleware(r.deleteNetwork)),
-	}
-}
-
-func (r *networkRouter) controllerEnabledMiddleware(handler httputils.APIFunc) httputils.APIFunc {
-	if r.backend.NetworkControllerEnabled() {
-		return handler
+		router.NewDeleteRoute("/networks/{id:.*}", r.deleteNetwork),
 	}
 	}
-	return networkControllerDisabled
-}
-
-func networkControllerDisabled(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-	return errors.ErrorNetworkControllerNotEnabled.WithArgs()
 }
 }

+ 3 - 1
api/server/router/network/network_routes.go

@@ -91,7 +91,7 @@ func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWr
 		warning = fmt.Sprintf("Network with name %s (id : %s) already exists", nw.Name(), nw.ID())
 		warning = fmt.Sprintf("Network with name %s (id : %s) already exists", nw.Name(), nw.ID())
 	}
 	}
 
 
-	nw, err = n.backend.CreateNetwork(create.Name, create.Driver, create.IPAM, create.Options, create.Internal)
+	nw, err = n.backend.CreateNetwork(create.Name, create.Driver, create.IPAM, create.Options, create.Internal, create.EnableIPv6)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -160,6 +160,8 @@ func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource {
 	r.ID = nw.ID()
 	r.ID = nw.ID()
 	r.Scope = nw.Info().Scope()
 	r.Scope = nw.Info().Scope()
 	r.Driver = nw.Type()
 	r.Driver = nw.Type()
+	r.EnableIPv6 = nw.Info().IPv6Enabled()
+	r.Internal = nw.Info().Internal()
 	r.Options = nw.Info().DriverOptions()
 	r.Options = nw.Info().DriverOptions()
 	r.Containers = make(map[string]types.EndpointResource)
 	r.Containers = make(map[string]types.EndpointResource)
 	buildIpamResources(r, nw)
 	buildIpamResources(r, nw)

+ 1 - 1
api/server/router_swapper.go

@@ -7,7 +7,7 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
 )
 )
 
 
-// routerSwapper is an http.Handler that allow you to swap
+// routerSwapper is an http.Handler that allows you to swap
 // mux routers.
 // mux routers.
 type routerSwapper struct {
 type routerSwapper struct {
 	mu     sync.Mutex
 	mu     sync.Mutex

+ 35 - 84
api/server/server.go

@@ -9,17 +9,7 @@ import (
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/router"
 	"github.com/docker/docker/api/server/router"
-	"github.com/docker/docker/api/server/router/build"
-	"github.com/docker/docker/api/server/router/container"
-	"github.com/docker/docker/api/server/router/image"
-	"github.com/docker/docker/api/server/router/network"
-	"github.com/docker/docker/api/server/router/system"
-	"github.com/docker/docker/api/server/router/volume"
-	"github.com/docker/docker/builder/dockerfile"
-	"github.com/docker/docker/daemon"
 	"github.com/docker/docker/pkg/authorization"
 	"github.com/docker/docker/pkg/authorization"
-	"github.com/docker/docker/utils"
-	"github.com/docker/go-connections/sockets"
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -37,7 +27,6 @@ type Config struct {
 	Version                  string
 	Version                  string
 	SocketGroup              string
 	SocketGroup              string
 	TLSConfig                *tls.Config
 	TLSConfig                *tls.Config
-	Addrs                    []Addr
 }
 }
 
 
 // Server contains instance details for the server
 // Server contains instance details for the server
@@ -49,27 +38,25 @@ type Server struct {
 	routerSwapper *routerSwapper
 	routerSwapper *routerSwapper
 }
 }
 
 
-// Addr contains string representation of address and its protocol (tcp, unix...).
-type Addr struct {
-	Proto string
-	Addr  string
-}
-
 // New returns a new instance of the server based on the specified configuration.
 // New returns a new instance of the server based on the specified configuration.
 // It allocates resources which will be needed for ServeAPI(ports, unix-sockets).
 // It allocates resources which will be needed for ServeAPI(ports, unix-sockets).
-func New(cfg *Config) (*Server, error) {
-	s := &Server{
+func New(cfg *Config) *Server {
+	return &Server{
 		cfg: cfg,
 		cfg: cfg,
 	}
 	}
-	for _, addr := range cfg.Addrs {
-		srv, err := s.newServer(addr.Proto, addr.Addr)
-		if err != nil {
-			return nil, err
+}
+
+// Accept sets a listener the server accepts connections into.
+func (s *Server) Accept(addr string, listeners ...net.Listener) {
+	for _, listener := range listeners {
+		httpServer := &HTTPServer{
+			srv: &http.Server{
+				Addr: addr,
+			},
+			l: listener,
 		}
 		}
-		logrus.Debugf("Server created for HTTP on %s (%s)", addr.Proto, addr.Addr)
-		s.servers = append(s.servers, srv...)
+		s.servers = append(s.servers, httpServer)
 	}
 	}
-	return s, nil
 }
 }
 
 
 // Close closes servers and thus stop receiving requests
 // Close closes servers and thus stop receiving requests
@@ -84,8 +71,6 @@ func (s *Server) Close() {
 // serveAPI loops through all initialized servers and spawns goroutine
 // serveAPI loops through all initialized servers and spawns goroutine
 // with Server method for each. It sets createMux() as Handler also.
 // with Server method for each. It sets createMux() as Handler also.
 func (s *Server) serveAPI() error {
 func (s *Server) serveAPI() error {
-	s.initRouterSwapper()
-
 	var chErrors = make(chan error, len(s.servers))
 	var chErrors = make(chan error, len(s.servers))
 	for _, srv := range s.servers {
 	for _, srv := range s.servers {
 		srv.srv.Handler = s.routerSwapper
 		srv.srv.Handler = s.routerSwapper
@@ -127,31 +112,8 @@ func (s *HTTPServer) Close() error {
 	return s.l.Close()
 	return s.l.Close()
 }
 }
 
 
-func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) {
-	logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders)
-	w.Header().Add("Access-Control-Allow-Origin", corsHeaders)
-	w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth")
-	w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS")
-}
-
-func (s *Server) initTCPSocket(addr string) (l net.Listener, err error) {
-	if s.cfg.TLSConfig == nil || s.cfg.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert {
-		logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
-	}
-	if l, err = sockets.NewTCPSocket(addr, s.cfg.TLSConfig); err != nil {
-		return nil, err
-	}
-	if err := allocateDaemonPort(addr); err != nil {
-		return nil, err
-	}
-	return
-}
-
 func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc {
 func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 	return func(w http.ResponseWriter, r *http.Request) {
-		// log the handler call
-		logrus.Debugf("Calling %s %s", r.Method, r.URL.Path)
-
 		// Define the context that we'll pass around to share info
 		// Define the context that we'll pass around to share info
 		// like the docker-request-id.
 		// like the docker-request-id.
 		//
 		//
@@ -168,33 +130,31 @@ func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc {
 		}
 		}
 
 
 		if err := handlerFunc(ctx, w, r, vars); err != nil {
 		if err := handlerFunc(ctx, w, r, vars); err != nil {
-			logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.URL.Path, utils.GetErrorMessage(err))
+			logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err)
 			httputils.WriteError(w, err)
 			httputils.WriteError(w, err)
 		}
 		}
 	}
 	}
 }
 }
 
 
-// InitRouters initializes a list of routers for the server.
-func (s *Server) InitRouters(d *daemon.Daemon) {
-	s.addRouter(container.NewRouter(d))
-	s.addRouter(image.NewRouter(d))
-	s.addRouter(network.NewRouter(d))
-	s.addRouter(system.NewRouter(d))
-	s.addRouter(volume.NewRouter(d))
-	s.addRouter(build.NewRouter(dockerfile.NewBuildManager(d)))
-}
+// InitRouter initializes the list of routers for the server.
+// This method also enables the Go profiler if enableProfiler is true.
+func (s *Server) InitRouter(enableProfiler bool, routers ...router.Router) {
+	for _, r := range routers {
+		s.routers = append(s.routers, r)
+	}
 
 
-// addRouter adds a new router to the server.
-func (s *Server) addRouter(r router.Router) {
-	s.routers = append(s.routers, r)
+	m := s.createMux()
+	if enableProfiler {
+		profilerSetup(m)
+	}
+	s.routerSwapper = &routerSwapper{
+		router: m,
+	}
 }
 }
 
 
 // createMux initializes the main router the server uses.
 // createMux initializes the main router the server uses.
 func (s *Server) createMux() *mux.Router {
 func (s *Server) createMux() *mux.Router {
 	m := mux.NewRouter()
 	m := mux.NewRouter()
-	if utils.IsDebugEnabled() {
-		profilerSetup(m, "/debug/")
-	}
 
 
 	logrus.Debugf("Registering routers")
 	logrus.Debugf("Registering routers")
 	for _, apiRouter := range s.routers {
 	for _, apiRouter := range s.routers {
@@ -222,23 +182,14 @@ func (s *Server) Wait(waitChan chan error) {
 	waitChan <- nil
 	waitChan <- nil
 }
 }
 
 
-func (s *Server) initRouterSwapper() {
-	s.routerSwapper = &routerSwapper{
-		router: s.createMux(),
-	}
+// DisableProfiler reloads the server mux without adding the profiler routes.
+func (s *Server) DisableProfiler() {
+	s.routerSwapper.Swap(s.createMux())
 }
 }
 
 
-// Reload reads configuration changes and modifies the
-// server according to those changes.
-// Currently, only the --debug configuration is taken into account.
-func (s *Server) Reload(config *daemon.Config) {
-	debugEnabled := utils.IsDebugEnabled()
-	switch {
-	case debugEnabled && !config.Debug: // disable debug
-		utils.DisableDebug()
-		s.routerSwapper.Swap(s.createMux())
-	case config.Debug && !debugEnabled: // enable debug
-		utils.EnableDebug()
-		s.routerSwapper.Swap(s.createMux())
-	}
+// EnableProfiler reloads the server mux adding the profiler routes.
+func (s *Server) EnableProfiler() {
+	m := s.createMux()
+	profilerSetup(m)
+	s.routerSwapper.Swap(m)
 }
 }

+ 25 - 0
api/types/backend/backend.go

@@ -42,3 +42,28 @@ type ContainerStatsConfig struct {
 	Stop      <-chan bool
 	Stop      <-chan bool
 	Version   string
 	Version   string
 }
 }
+
+// ExecInspect holds information about a running process started
+// with docker exec.
+type ExecInspect struct {
+	ID            string
+	Running       bool
+	ExitCode      *int
+	ProcessConfig *ExecProcessConfig
+	OpenStdin     bool
+	OpenStderr    bool
+	OpenStdout    bool
+	CanRemove     bool
+	ContainerID   string
+	DetachKeys    []byte
+}
+
+// ExecProcessConfig holds information about the exec process
+// running on the host.
+type ExecProcessConfig struct {
+	Tty        bool     `json:"tty"`
+	Entrypoint string   `json:"entrypoint"`
+	Arguments  []string `json:"arguments"`
+	Privileged *bool    `json:"privileged,omitempty"`
+	User       string   `json:"user,omitempty"`
+}

+ 6 - 1
builder/builder.go

@@ -14,6 +14,11 @@ import (
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
 )
 )
 
 
+const (
+	// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
+	DefaultDockerfileName string = "Dockerfile"
+)
+
 // Context represents a file system tree.
 // Context represents a file system tree.
 type Context interface {
 type Context interface {
 	// Close allows to signal that the filesystem tree won't be used anymore.
 	// Close allows to signal that the filesystem tree won't be used anymore.
@@ -141,7 +146,7 @@ type Image interface {
 // ImageCache abstracts an image cache store.
 // ImageCache abstracts an image cache store.
 // (parent image, child runconfig) -> child image
 // (parent image, child runconfig) -> child image
 type ImageCache interface {
 type ImageCache interface {
-	// GetCachedImage returns a reference to a cached image whose parent equals `parent`
+	// GetCachedImageOnBuild returns a reference to a cached image whose parent equals `parent`
 	// and runconfig equals `cfg`. A cache miss is expected to return an empty ID and a nil error.
 	// and runconfig equals `cfg`. A cache miss is expected to return an empty ID and a nil error.
 	GetCachedImageOnBuild(parentID string, cfg *container.Config) (imageID string, err error)
 	GetCachedImageOnBuild(parentID string, cfg *container.Config) (imageID string, err error)
 }
 }

+ 260 - 0
builder/context.go

@@ -0,0 +1,260 @@
+package builder
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+
+	"github.com/docker/docker/pkg/archive"
+	"github.com/docker/docker/pkg/fileutils"
+	"github.com/docker/docker/pkg/gitutils"
+	"github.com/docker/docker/pkg/httputils"
+	"github.com/docker/docker/pkg/ioutils"
+	"github.com/docker/docker/pkg/progress"
+	"github.com/docker/docker/pkg/streamformatter"
+)
+
+// ValidateContextDirectory checks if all the contents of the directory
+// can be read and returns an error if some files can't be read
+// symlinks which point to non-existing files don't trigger an error
+func ValidateContextDirectory(srcPath string, excludes []string) error {
+	contextRoot, err := getContextRoot(srcPath)
+	if err != nil {
+		return err
+	}
+	return filepath.Walk(contextRoot, func(filePath string, f os.FileInfo, err error) error {
+		// skip this directory/file if it's not in the path, it won't get added to the context
+		if relFilePath, err := filepath.Rel(contextRoot, filePath); err != nil {
+			return err
+		} else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil {
+			return err
+		} else if skip {
+			if f.IsDir() {
+				return filepath.SkipDir
+			}
+			return nil
+		}
+
+		if err != nil {
+			if os.IsPermission(err) {
+				return fmt.Errorf("can't stat '%s'", filePath)
+			}
+			if os.IsNotExist(err) {
+				return nil
+			}
+			return err
+		}
+
+		// skip checking if symlinks point to non-existing files, such symlinks can be useful
+		// also skip named pipes, because they hanging on open
+		if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
+			return nil
+		}
+
+		if !f.IsDir() {
+			currentFile, err := os.Open(filePath)
+			if err != nil && os.IsPermission(err) {
+				return fmt.Errorf("no permission to read from '%s'", filePath)
+			}
+			currentFile.Close()
+		}
+		return nil
+	})
+}
+
+// GetContextFromReader will read the contents of the given reader as either a
+// Dockerfile or tar archive. Returns a tar archive used as a context and a
+// path to the Dockerfile inside the tar.
+func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
+	buf := bufio.NewReader(r)
+
+	magic, err := buf.Peek(archive.HeaderSize)
+	if err != nil && err != io.EOF {
+		return nil, "", fmt.Errorf("failed to peek context header from STDIN: %v", err)
+	}
+
+	if archive.IsArchive(magic) {
+		return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil
+	}
+
+	// Input should be read as a Dockerfile.
+	tmpDir, err := ioutil.TempDir("", "docker-build-context-")
+	if err != nil {
+		return nil, "", fmt.Errorf("unbale to create temporary context directory: %v", err)
+	}
+
+	f, err := os.Create(filepath.Join(tmpDir, DefaultDockerfileName))
+	if err != nil {
+		return nil, "", err
+	}
+	_, err = io.Copy(f, buf)
+	if err != nil {
+		f.Close()
+		return nil, "", err
+	}
+
+	if err := f.Close(); err != nil {
+		return nil, "", err
+	}
+	if err := r.Close(); err != nil {
+		return nil, "", err
+	}
+
+	tar, err := archive.Tar(tmpDir, archive.Uncompressed)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return ioutils.NewReadCloserWrapper(tar, func() error {
+		err := tar.Close()
+		os.RemoveAll(tmpDir)
+		return err
+	}), DefaultDockerfileName, nil
+
+}
+
+// GetContextFromGitURL uses a Git URL as context for a `docker build`. The
+// git repo is cloned into a temporary directory used as the context directory.
+// Returns the absolute path to the temporary context directory, the relative
+// path of the dockerfile in that context directory, and a non-nil error on
+// success.
+func GetContextFromGitURL(gitURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
+	if _, err := exec.LookPath("git"); err != nil {
+		return "", "", fmt.Errorf("unable to find 'git': %v", err)
+	}
+	if absContextDir, err = gitutils.Clone(gitURL); err != nil {
+		return "", "", fmt.Errorf("unable to 'git clone' to temporary context directory: %v", err)
+	}
+
+	return getDockerfileRelPath(absContextDir, dockerfileName)
+}
+
+// GetContextFromURL uses a remote URL as context for a `docker build`. The
+// remote resource is downloaded as either a Dockerfile or a tar archive.
+// Returns the tar archive used for the context and a path of the
+// dockerfile inside the tar.
+func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.ReadCloser, string, error) {
+	response, err := httputils.Download(remoteURL)
+	if err != nil {
+		return nil, "", fmt.Errorf("unable to download remote context %s: %v", remoteURL, err)
+	}
+	progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(out, true)
+
+	// Pass the response body through a progress reader.
+	progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL))
+
+	return GetContextFromReader(ioutils.NewReadCloserWrapper(progReader, func() error { return response.Body.Close() }), dockerfileName)
+}
+
+// GetContextFromLocalDir uses the given local directory as context for a
+// `docker build`. Returns the absolute path to the local context directory,
+// the relative path of the dockerfile in that context directory, and a non-nil
+// error on success.
+func GetContextFromLocalDir(localDir, dockerfileName string) (absContextDir, relDockerfile string, err error) {
+	// When using a local context directory, when the Dockerfile is specified
+	// with the `-f/--file` option then it is considered relative to the
+	// current directory and not the context directory.
+	if dockerfileName != "" {
+		if dockerfileName, err = filepath.Abs(dockerfileName); err != nil {
+			return "", "", fmt.Errorf("unable to get absolute path to Dockerfile: %v", err)
+		}
+	}
+
+	return getDockerfileRelPath(localDir, dockerfileName)
+}
+
+// getDockerfileRelPath uses the given context directory for a `docker build`
+// and returns the absolute path to the context directory, the relative path of
+// the dockerfile in that context directory, and a non-nil error on success.
+func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDir, relDockerfile string, err error) {
+	if absContextDir, err = filepath.Abs(givenContextDir); err != nil {
+		return "", "", fmt.Errorf("unable to get absolute context directory: %v", err)
+	}
+
+	// The context dir might be a symbolic link, so follow it to the actual
+	// target directory.
+	//
+	// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
+	// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
+	// paths (those starting with \\). This hack means that when using links
+	// on UNC paths, they will not be followed.
+	if !isUNC(absContextDir) {
+		absContextDir, err = filepath.EvalSymlinks(absContextDir)
+		if err != nil {
+			return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
+		}
+	}
+
+	stat, err := os.Lstat(absContextDir)
+	if err != nil {
+		return "", "", fmt.Errorf("unable to stat context directory %q: %v", absContextDir, err)
+	}
+
+	if !stat.IsDir() {
+		return "", "", fmt.Errorf("context must be a directory: %s", absContextDir)
+	}
+
+	absDockerfile := givenDockerfile
+	if absDockerfile == "" {
+		// No -f/--file was specified so use the default relative to the
+		// context directory.
+		absDockerfile = filepath.Join(absContextDir, DefaultDockerfileName)
+
+		// Just to be nice ;-) look for 'dockerfile' too but only
+		// use it if we found it, otherwise ignore this check
+		if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) {
+			altPath := filepath.Join(absContextDir, strings.ToLower(DefaultDockerfileName))
+			if _, err = os.Lstat(altPath); err == nil {
+				absDockerfile = altPath
+			}
+		}
+	}
+
+	// If not already an absolute path, the Dockerfile path should be joined to
+	// the base directory.
+	if !filepath.IsAbs(absDockerfile) {
+		absDockerfile = filepath.Join(absContextDir, absDockerfile)
+	}
+
+	// Evaluate symlinks in the path to the Dockerfile too.
+	//
+	// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
+	// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
+	// paths (those starting with \\). This hack means that when using links
+	// on UNC paths, they will not be followed.
+	if !isUNC(absDockerfile) {
+		absDockerfile, err = filepath.EvalSymlinks(absDockerfile)
+		if err != nil {
+			return "", "", fmt.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err)
+		}
+	}
+
+	if _, err := os.Lstat(absDockerfile); err != nil {
+		if os.IsNotExist(err) {
+			return "", "", fmt.Errorf("Cannot locate Dockerfile: %q", absDockerfile)
+		}
+		return "", "", fmt.Errorf("unable to stat Dockerfile: %v", err)
+	}
+
+	if relDockerfile, err = filepath.Rel(absContextDir, absDockerfile); err != nil {
+		return "", "", fmt.Errorf("unable to get relative Dockerfile path: %v", err)
+	}
+
+	if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
+		return "", "", fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir)
+	}
+
+	return absContextDir, relDockerfile, nil
+}
+
+// isUNC returns true if the path is UNC (one starting \\). It always returns
+// false on Linux.
+func isUNC(path string) bool {
+	return runtime.GOOS == "windows" && strings.HasPrefix(path, `\\`)
+}

+ 1 - 1
api/client/utils_unix.go → builder/context_unix.go

@@ -1,6 +1,6 @@
 // +build !windows
 // +build !windows
 
 
-package client
+package builder
 
 
 import (
 import (
 	"path/filepath"
 	"path/filepath"

+ 1 - 1
api/client/utils_windows.go → builder/context_windows.go

@@ -1,6 +1,6 @@
 // +build windows
 // +build windows
 
 
-package client
+package builder
 
 
 import (
 import (
 	"path/filepath"
 	"path/filepath"

+ 37 - 26
builder/dockerfile/dispatchers.go

@@ -19,7 +19,6 @@ import (
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/builder"
 	"github.com/docker/docker/builder"
-	derr "github.com/docker/docker/errors"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/pkg/system"
 	runconfigopts "github.com/docker/docker/runconfig/opts"
 	runconfigopts "github.com/docker/docker/runconfig/opts"
@@ -40,12 +39,12 @@ func nullDispatch(b *Builder, args []string, attributes map[string]bool, origina
 //
 //
 func env(b *Builder, args []string, attributes map[string]bool, original string) error {
 func env(b *Builder, args []string, attributes map[string]bool, original string) error {
 	if len(args) == 0 {
 	if len(args) == 0 {
-		return derr.ErrorCodeAtLeastOneArg.WithArgs("ENV")
+		return errAtLeastOneArgument("ENV")
 	}
 	}
 
 
 	if len(args)%2 != 0 {
 	if len(args)%2 != 0 {
 		// should never get here, but just in case
 		// should never get here, but just in case
-		return derr.ErrorCodeTooManyArgs.WithArgs("ENV")
+		return errTooManyArguments("ENV")
 	}
 	}
 
 
 	if err := b.flags.Parse(); err != nil {
 	if err := b.flags.Parse(); err != nil {
@@ -99,7 +98,7 @@ func env(b *Builder, args []string, attributes map[string]bool, original string)
 // Sets the maintainer metadata.
 // Sets the maintainer metadata.
 func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
 func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
 	if len(args) != 1 {
 	if len(args) != 1 {
-		return derr.ErrorCodeExactlyOneArg.WithArgs("MAINTAINER")
+		return errExactlyOneArgument("MAINTAINER")
 	}
 	}
 
 
 	if err := b.flags.Parse(); err != nil {
 	if err := b.flags.Parse(); err != nil {
@@ -116,11 +115,11 @@ func maintainer(b *Builder, args []string, attributes map[string]bool, original
 //
 //
 func label(b *Builder, args []string, attributes map[string]bool, original string) error {
 func label(b *Builder, args []string, attributes map[string]bool, original string) error {
 	if len(args) == 0 {
 	if len(args) == 0 {
-		return derr.ErrorCodeAtLeastOneArg.WithArgs("LABEL")
+		return errAtLeastOneArgument("LABEL")
 	}
 	}
 	if len(args)%2 != 0 {
 	if len(args)%2 != 0 {
 		// should never get here, but just in case
 		// should never get here, but just in case
-		return derr.ErrorCodeTooManyArgs.WithArgs("LABEL")
+		return errTooManyArguments("LABEL")
 	}
 	}
 
 
 	if err := b.flags.Parse(); err != nil {
 	if err := b.flags.Parse(); err != nil {
@@ -152,7 +151,7 @@ func label(b *Builder, args []string, attributes map[string]bool, original strin
 //
 //
 func add(b *Builder, args []string, attributes map[string]bool, original string) error {
 func add(b *Builder, args []string, attributes map[string]bool, original string) error {
 	if len(args) < 2 {
 	if len(args) < 2 {
-		return derr.ErrorCodeAtLeastTwoArgs.WithArgs("ADD")
+		return errAtLeastOneArgument("ADD")
 	}
 	}
 
 
 	if err := b.flags.Parse(); err != nil {
 	if err := b.flags.Parse(); err != nil {
@@ -168,7 +167,7 @@ func add(b *Builder, args []string, attributes map[string]bool, original string)
 //
 //
 func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
 func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
 	if len(args) < 2 {
 	if len(args) < 2 {
-		return derr.ErrorCodeAtLeastTwoArgs.WithArgs("COPY")
+		return errAtLeastOneArgument("COPY")
 	}
 	}
 
 
 	if err := b.flags.Parse(); err != nil {
 	if err := b.flags.Parse(); err != nil {
@@ -184,7 +183,7 @@ func dispatchCopy(b *Builder, args []string, attributes map[string]bool, origina
 //
 //
 func from(b *Builder, args []string, attributes map[string]bool, original string) error {
 func from(b *Builder, args []string, attributes map[string]bool, original string) error {
 	if len(args) != 1 {
 	if len(args) != 1 {
-		return derr.ErrorCodeExactlyOneArg.WithArgs("FROM")
+		return errExactlyOneArgument("FROM")
 	}
 	}
 
 
 	if err := b.flags.Parse(); err != nil {
 	if err := b.flags.Parse(); err != nil {
@@ -233,7 +232,7 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
 //
 //
 func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
 func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
 	if len(args) == 0 {
 	if len(args) == 0 {
-		return derr.ErrorCodeAtLeastOneArg.WithArgs("ONBUILD")
+		return errAtLeastOneArgument("ONBUILD")
 	}
 	}
 
 
 	if err := b.flags.Parse(); err != nil {
 	if err := b.flags.Parse(); err != nil {
@@ -243,9 +242,9 @@ func onbuild(b *Builder, args []string, attributes map[string]bool, original str
 	triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
 	triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
 	switch triggerInstruction {
 	switch triggerInstruction {
 	case "ONBUILD":
 	case "ONBUILD":
-		return derr.ErrorCodeChainOnBuild
+		return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
 	case "MAINTAINER", "FROM":
 	case "MAINTAINER", "FROM":
-		return derr.ErrorCodeBadOnBuildCmd.WithArgs(triggerInstruction)
+		return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
 	}
 	}
 
 
 	original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "")
 	original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "")
@@ -260,7 +259,7 @@ func onbuild(b *Builder, args []string, attributes map[string]bool, original str
 //
 //
 func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
 func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
 	if len(args) != 1 {
 	if len(args) != 1 {
-		return derr.ErrorCodeExactlyOneArg.WithArgs("WORKDIR")
+		return errExactlyOneArgument("WORKDIR")
 	}
 	}
 
 
 	if err := b.flags.Parse(); err != nil {
 	if err := b.flags.Parse(); err != nil {
@@ -293,7 +292,7 @@ func workdir(b *Builder, args []string, attributes map[string]bool, original str
 //
 //
 func run(b *Builder, args []string, attributes map[string]bool, original string) error {
 func run(b *Builder, args []string, attributes map[string]bool, original string) error {
 	if b.image == "" && !b.noBaseImage {
 	if b.image == "" && !b.noBaseImage {
-		return derr.ErrorCodeMissingFrom
+		return fmt.Errorf("Please provide a source image with `from` prior to run")
 	}
 	}
 
 
 	if err := b.flags.Parse(); err != nil {
 	if err := b.flags.Parse(); err != nil {
@@ -311,20 +310,20 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
 	}
 	}
 
 
 	config := &container.Config{
 	config := &container.Config{
-		Cmd:   strslice.New(args...),
+		Cmd:   strslice.StrSlice(args),
 		Image: b.image,
 		Image: b.image,
 	}
 	}
 
 
 	// stash the cmd
 	// stash the cmd
 	cmd := b.runConfig.Cmd
 	cmd := b.runConfig.Cmd
-	if b.runConfig.Entrypoint.Len() == 0 && b.runConfig.Cmd.Len() == 0 {
+	if len(b.runConfig.Entrypoint) == 0 && len(b.runConfig.Cmd) == 0 {
 		b.runConfig.Cmd = config.Cmd
 		b.runConfig.Cmd = config.Cmd
 	}
 	}
 
 
 	// stash the config environment
 	// stash the config environment
 	env := b.runConfig.Env
 	env := b.runConfig.Env
 
 
-	defer func(cmd *strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
+	defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
 	defer func(env []string) { b.runConfig.Env = env }(env)
 	defer func(env []string) { b.runConfig.Env = env }(env)
 
 
 	// derive the net build-time environment for this run. We let config
 	// derive the net build-time environment for this run. We let config
@@ -367,7 +366,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
 	if len(cmdBuildEnv) > 0 {
 	if len(cmdBuildEnv) > 0 {
 		sort.Strings(cmdBuildEnv)
 		sort.Strings(cmdBuildEnv)
 		tmpEnv := append([]string{fmt.Sprintf("|%d", len(cmdBuildEnv))}, cmdBuildEnv...)
 		tmpEnv := append([]string{fmt.Sprintf("|%d", len(cmdBuildEnv))}, cmdBuildEnv...)
-		saveCmd = strslice.New(append(tmpEnv, saveCmd.Slice()...)...)
+		saveCmd = strslice.StrSlice(append(tmpEnv, saveCmd...))
 	}
 	}
 
 
 	b.runConfig.Cmd = saveCmd
 	b.runConfig.Cmd = saveCmd
@@ -425,7 +424,7 @@ func cmd(b *Builder, args []string, attributes map[string]bool, original string)
 		}
 		}
 	}
 	}
 
 
-	b.runConfig.Cmd = strslice.New(cmdSlice...)
+	b.runConfig.Cmd = strslice.StrSlice(cmdSlice)
 
 
 	if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
 	if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
 		return err
 		return err
@@ -456,16 +455,16 @@ func entrypoint(b *Builder, args []string, attributes map[string]bool, original
 	switch {
 	switch {
 	case attributes["json"]:
 	case attributes["json"]:
 		// ENTRYPOINT ["echo", "hi"]
 		// ENTRYPOINT ["echo", "hi"]
-		b.runConfig.Entrypoint = strslice.New(parsed...)
+		b.runConfig.Entrypoint = strslice.StrSlice(parsed)
 	case len(parsed) == 0:
 	case len(parsed) == 0:
 		// ENTRYPOINT []
 		// ENTRYPOINT []
 		b.runConfig.Entrypoint = nil
 		b.runConfig.Entrypoint = nil
 	default:
 	default:
 		// ENTRYPOINT echo hi
 		// ENTRYPOINT echo hi
 		if runtime.GOOS != "windows" {
 		if runtime.GOOS != "windows" {
-			b.runConfig.Entrypoint = strslice.New("/bin/sh", "-c", parsed[0])
+			b.runConfig.Entrypoint = strslice.StrSlice{"/bin/sh", "-c", parsed[0]}
 		} else {
 		} else {
-			b.runConfig.Entrypoint = strslice.New("cmd", "/S", "/C", parsed[0])
+			b.runConfig.Entrypoint = strslice.StrSlice{"cmd", "/S", "/C", parsed[0]}
 		}
 		}
 	}
 	}
 
 
@@ -491,7 +490,7 @@ func expose(b *Builder, args []string, attributes map[string]bool, original stri
 	portsTab := args
 	portsTab := args
 
 
 	if len(args) == 0 {
 	if len(args) == 0 {
-		return derr.ErrorCodeAtLeastOneArg.WithArgs("EXPOSE")
+		return errAtLeastOneArgument("EXPOSE")
 	}
 	}
 
 
 	if err := b.flags.Parse(); err != nil {
 	if err := b.flags.Parse(); err != nil {
@@ -530,7 +529,7 @@ func expose(b *Builder, args []string, attributes map[string]bool, original stri
 //
 //
 func user(b *Builder, args []string, attributes map[string]bool, original string) error {
 func user(b *Builder, args []string, attributes map[string]bool, original string) error {
 	if len(args) != 1 {
 	if len(args) != 1 {
-		return derr.ErrorCodeExactlyOneArg.WithArgs("USER")
+		return errExactlyOneArgument("USER")
 	}
 	}
 
 
 	if err := b.flags.Parse(); err != nil {
 	if err := b.flags.Parse(); err != nil {
@@ -547,7 +546,7 @@ func user(b *Builder, args []string, attributes map[string]bool, original string
 //
 //
 func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
 func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
 	if len(args) == 0 {
 	if len(args) == 0 {
-		return derr.ErrorCodeAtLeastOneArg.WithArgs("VOLUME")
+		return errAtLeastOneArgument("VOLUME")
 	}
 	}
 
 
 	if err := b.flags.Parse(); err != nil {
 	if err := b.flags.Parse(); err != nil {
@@ -560,7 +559,7 @@ func volume(b *Builder, args []string, attributes map[string]bool, original stri
 	for _, v := range args {
 	for _, v := range args {
 		v = strings.TrimSpace(v)
 		v = strings.TrimSpace(v)
 		if v == "" {
 		if v == "" {
-			return derr.ErrorCodeVolumeEmpty
+			return fmt.Errorf("Volume specified can not be an empty string")
 		}
 		}
 		b.runConfig.Volumes[v] = struct{}{}
 		b.runConfig.Volumes[v] = struct{}{}
 	}
 	}
@@ -631,3 +630,15 @@ func arg(b *Builder, args []string, attributes map[string]bool, original string)
 
 
 	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ARG %s", arg))
 	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ARG %s", arg))
 }
 }
+
+func errAtLeastOneArgument(command string) error {
+	return fmt.Errorf("%s requires at least one argument", command)
+}
+
+func errExactlyOneArgument(command string) error {
+	return fmt.Errorf("%s requires exactly one argument", command)
+}
+
+func errTooManyArguments(command string) error {
+	return fmt.Errorf("Bad input to %s, too many arguments", command)
+}

+ 12 - 13
builder/dockerfile/internals.go

@@ -19,7 +19,6 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
-	"github.com/docker/docker/api"
 	"github.com/docker/docker/builder"
 	"github.com/docker/docker/builder"
 	"github.com/docker/docker/builder/dockerfile/parser"
 	"github.com/docker/docker/builder/dockerfile/parser"
 	"github.com/docker/docker/pkg/archive"
 	"github.com/docker/docker/pkg/archive"
@@ -38,7 +37,7 @@ import (
 	"github.com/docker/engine-api/types/strslice"
 	"github.com/docker/engine-api/types/strslice"
 )
 )
 
 
-func (b *Builder) commit(id string, autoCmd *strslice.StrSlice, comment string) error {
+func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) error {
 	if b.disableCommit {
 	if b.disableCommit {
 		return nil
 		return nil
 	}
 	}
@@ -49,11 +48,11 @@ func (b *Builder) commit(id string, autoCmd *strslice.StrSlice, comment string)
 	if id == "" {
 	if id == "" {
 		cmd := b.runConfig.Cmd
 		cmd := b.runConfig.Cmd
 		if runtime.GOOS != "windows" {
 		if runtime.GOOS != "windows" {
-			b.runConfig.Cmd = strslice.New("/bin/sh", "-c", "#(nop) "+comment)
+			b.runConfig.Cmd = strslice.StrSlice{"/bin/sh", "-c", "#(nop) " + comment}
 		} else {
 		} else {
-			b.runConfig.Cmd = strslice.New("cmd", "/S /C", "REM (nop) "+comment)
+			b.runConfig.Cmd = strslice.StrSlice{"cmd", "/S /C", "REM (nop) " + comment}
 		}
 		}
-		defer func(cmd *strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
+		defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
 
 
 		hit, err := b.probeCache()
 		hit, err := b.probeCache()
 		if err != nil {
 		if err != nil {
@@ -172,11 +171,11 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
 
 
 	cmd := b.runConfig.Cmd
 	cmd := b.runConfig.Cmd
 	if runtime.GOOS != "windows" {
 	if runtime.GOOS != "windows" {
-		b.runConfig.Cmd = strslice.New("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest))
+		b.runConfig.Cmd = strslice.StrSlice{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest)}
 	} else {
 	} else {
-		b.runConfig.Cmd = strslice.New("cmd", "/S", "/C", fmt.Sprintf("REM (nop) %s %s in %s", cmdName, srcHash, dest))
+		b.runConfig.Cmd = strslice.StrSlice{"cmd", "/S", "/C", fmt.Sprintf("REM (nop) %s %s in %s", cmdName, srcHash, dest)}
 	}
 	}
-	defer func(cmd *strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
+	defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
 
 
 	if hit, err := b.probeCache(); err != nil {
 	if hit, err := b.probeCache(); err != nil {
 		return err
 		return err
@@ -506,7 +505,7 @@ func (b *Builder) create() (string, error) {
 
 
 	// TODO: why not embed a hostconfig in builder?
 	// TODO: why not embed a hostconfig in builder?
 	hostConfig := &container.HostConfig{
 	hostConfig := &container.HostConfig{
-		Isolation: b.options.IsolationLevel,
+		Isolation: b.options.Isolation,
 		ShmSize:   b.options.ShmSize,
 		ShmSize:   b.options.ShmSize,
 		Resources: resources,
 		Resources: resources,
 	}
 	}
@@ -528,9 +527,9 @@ func (b *Builder) create() (string, error) {
 	b.tmpContainers[c.ID] = struct{}{}
 	b.tmpContainers[c.ID] = struct{}{}
 	fmt.Fprintf(b.Stdout, " ---> Running in %s\n", stringid.TruncateID(c.ID))
 	fmt.Fprintf(b.Stdout, " ---> Running in %s\n", stringid.TruncateID(c.ID))
 
 
-	if config.Cmd.Len() > 0 {
+	if len(config.Cmd) > 0 {
 		// override the entry point that may have been picked up from the base image
 		// override the entry point that may have been picked up from the base image
-		if err := b.docker.ContainerUpdateCmdOnBuild(c.ID, config.Cmd.Slice()); err != nil {
+		if err := b.docker.ContainerUpdateCmdOnBuild(c.ID, config.Cmd); err != nil {
 			return "", err
 			return "", err
 		}
 		}
 	}
 	}
@@ -568,7 +567,7 @@ func (b *Builder) run(cID string) (err error) {
 	if ret, _ := b.docker.ContainerWait(cID, -1); ret != 0 {
 	if ret, _ := b.docker.ContainerWait(cID, -1); ret != 0 {
 		// TODO: change error type, because jsonmessage.JSONError assumes HTTP
 		// TODO: change error type, because jsonmessage.JSONError assumes HTTP
 		return &jsonmessage.JSONError{
 		return &jsonmessage.JSONError{
-			Message: fmt.Sprintf("The command '%s' returned a non-zero code: %d", b.runConfig.Cmd.ToString(), ret),
+			Message: fmt.Sprintf("The command '%s' returned a non-zero code: %d", strings.Join(b.runConfig.Cmd, " "), ret),
 			Code:    ret,
 			Code:    ret,
 		}
 		}
 	}
 	}
@@ -604,7 +603,7 @@ func (b *Builder) readDockerfile() error {
 	// that then look for 'dockerfile'.  If neither are found then default
 	// that then look for 'dockerfile'.  If neither are found then default
 	// back to 'Dockerfile' and use that in the error message.
 	// back to 'Dockerfile' and use that in the error message.
 	if b.options.Dockerfile == "" {
 	if b.options.Dockerfile == "" {
-		b.options.Dockerfile = api.DefaultDockerfileName
+		b.options.Dockerfile = builder.DefaultDockerfileName
 		if _, _, err := b.context.Stat(b.options.Dockerfile); os.IsNotExist(err) {
 		if _, _, err := b.context.Stat(b.options.Dockerfile); os.IsNotExist(err) {
 			lowercase := strings.ToLower(b.options.Dockerfile)
 			lowercase := strings.ToLower(b.options.Dockerfile)
 			if _, _, err := b.context.Stat(lowercase); err == nil {
 			if _, _, err := b.context.Stat(lowercase); err == nil {

+ 1 - 1
builder/dockerfile/parser/line_parsers.go

@@ -71,7 +71,7 @@ func parseWords(rest string) []string {
 			if unicode.IsSpace(ch) { // skip spaces
 			if unicode.IsSpace(ch) { // skip spaces
 				continue
 				continue
 			}
 			}
-			phase = inWord // found it, fall thru
+			phase = inWord // found it, fall through
 		}
 		}
 		if (phase == inWord || phase == inQuote) && (pos == len(rest)) {
 		if (phase == inWord || phase == inQuote) && (pos == len(rest)) {
 			if blankOK || len(word) > 0 {
 			if blankOK || len(word) > 0 {

+ 1 - 1
builder/dockerfile/parser/utils.go

@@ -118,7 +118,7 @@ func extractBuilderFlags(line string) (string, []string, error) {
 				return line[pos:], words, nil
 				return line[pos:], words, nil
 			}
 			}
 
 
-			phase = inWord // found someting with "--", fall thru
+			phase = inWord // found someting with "--", fall through
 		}
 		}
 		if (phase == inWord || phase == inQuote) && (pos == len(line)) {
 		if (phase == inWord || phase == inQuote) && (pos == len(line)) {
 			if word != "--" && (blankOK || len(word) > 0) {
 			if word != "--" && (blankOK || len(word) > 0) {

+ 1 - 2
builder/remote.go

@@ -8,7 +8,6 @@ import (
 	"io/ioutil"
 	"io/ioutil"
 	"regexp"
 	"regexp"
 
 
-	"github.com/docker/docker/api"
 	"github.com/docker/docker/pkg/archive"
 	"github.com/docker/docker/pkg/archive"
 	"github.com/docker/docker/pkg/httputils"
 	"github.com/docker/docker/pkg/httputils"
 	"github.com/docker/docker/pkg/urlutil"
 	"github.com/docker/docker/pkg/urlutil"
@@ -87,7 +86,7 @@ func DetectContextFromRemoteURL(r io.ReadCloser, remoteURL string, createProgres
 
 
 				// dockerfileName is set to signal that the remote was interpreted as a single Dockerfile, in which case the caller
 				// dockerfileName is set to signal that the remote was interpreted as a single Dockerfile, in which case the caller
 				// should use dockerfileName as the new name for the Dockerfile, irrespective of any other user input.
 				// should use dockerfileName as the new name for the Dockerfile, irrespective of any other user input.
-				dockerfileName = api.DefaultDockerfileName
+				dockerfileName = DefaultDockerfileName
 
 
 				// TODO: return a context without tarsum
 				// TODO: return a context without tarsum
 				return archive.Generate(dockerfileName, string(dockerfile))
 				return archive.Generate(dockerfileName, string(dockerfile))

+ 3 - 3
cli/common.go

@@ -19,7 +19,7 @@ type CommonFlags struct {
 	TrustKey   string
 	TrustKey   string
 }
 }
 
 
-// Command is the struct contains command name and description
+// Command is the struct containing the command name and description
 type Command struct {
 type Command struct {
 	Name        string
 	Name        string
 	Description string
 	Description string
@@ -42,7 +42,7 @@ var dockerCommands = []Command{
 	{"inspect", "Return low-level information on a container or image"},
 	{"inspect", "Return low-level information on a container or image"},
 	{"kill", "Kill a running container"},
 	{"kill", "Kill a running container"},
 	{"load", "Load an image from a tar archive or STDIN"},
 	{"load", "Load an image from a tar archive or STDIN"},
-	{"login", "Register or log in to a Docker registry"},
+	{"login", "Log in to a Docker registry"},
 	{"logout", "Log out from a Docker registry"},
 	{"logout", "Log out from a Docker registry"},
 	{"logs", "Fetch the logs of a container"},
 	{"logs", "Fetch the logs of a container"},
 	{"network", "Manage Docker networks"},
 	{"network", "Manage Docker networks"},
@@ -64,7 +64,7 @@ var dockerCommands = []Command{
 	{"tag", "Tag an image into a repository"},
 	{"tag", "Tag an image into a repository"},
 	{"top", "Display the running processes of a container"},
 	{"top", "Display the running processes of a container"},
 	{"unpause", "Unpause all processes within a container"},
 	{"unpause", "Unpause all processes within a container"},
-	{"update", "Update resources of one or more containers"},
+	{"update", "Update configuration of one or more containers"},
 	{"version", "Show the Docker version information"},
 	{"version", "Show the Docker version information"},
 	{"volume", "Manage Docker volumes"},
 	{"volume", "Manage Docker volumes"},
 	{"wait", "Block until a container stops, then print its exit code"},
 	{"wait", "Block until a container stops, then print its exit code"},

+ 24 - 12
cliconfig/config.go

@@ -17,6 +17,7 @@ import (
 const (
 const (
 	// ConfigFileName is the name of config file
 	// ConfigFileName is the name of config file
 	ConfigFileName = "config.json"
 	ConfigFileName = "config.json"
+	configFileDir  = ".docker"
 	oldConfigfile  = ".dockercfg"
 	oldConfigfile  = ".dockercfg"
 
 
 	// This constant is only used for really old config files when the
 	// This constant is only used for really old config files when the
@@ -31,7 +32,7 @@ var (
 
 
 func init() {
 func init() {
 	if configDir == "" {
 	if configDir == "" {
-		configDir = filepath.Join(homedir.Get(), ".docker")
+		configDir = filepath.Join(homedir.Get(), configFileDir)
 	}
 	}
 }
 }
 
 
@@ -47,12 +48,13 @@ func SetConfigDir(dir string) {
 
 
 // ConfigFile ~/.docker/config.json file info
 // ConfigFile ~/.docker/config.json file info
 type ConfigFile struct {
 type ConfigFile struct {
-	AuthConfigs  map[string]types.AuthConfig `json:"auths"`
-	HTTPHeaders  map[string]string           `json:"HttpHeaders,omitempty"`
-	PsFormat     string                      `json:"psFormat,omitempty"`
-	ImagesFormat string                      `json:"imagesFormat,omitempty"`
-	DetachKeys   string                      `json:"detachKeys,omitempty"`
-	filename     string                      // Note: not serialized - for internal use only
+	AuthConfigs      map[string]types.AuthConfig `json:"auths"`
+	HTTPHeaders      map[string]string           `json:"HttpHeaders,omitempty"`
+	PsFormat         string                      `json:"psFormat,omitempty"`
+	ImagesFormat     string                      `json:"imagesFormat,omitempty"`
+	DetachKeys       string                      `json:"detachKeys,omitempty"`
+	CredentialsStore string                      `json:"credsStore,omitempty"`
+	filename         string                      // Note: not serialized - for internal use only
 }
 }
 
 
 // NewConfigFile initializes an empty configuration file for the given filename 'fn'
 // NewConfigFile initializes an empty configuration file for the given filename 'fn'
@@ -86,11 +88,6 @@ func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		origEmail := strings.Split(arr[1], " = ")
-		if len(origEmail) != 2 {
-			return fmt.Errorf("Invalid Auth config file")
-		}
-		authConfig.Email = origEmail[1]
 		authConfig.ServerAddress = defaultIndexserver
 		authConfig.ServerAddress = defaultIndexserver
 		configFile.AuthConfigs[defaultIndexserver] = authConfig
 		configFile.AuthConfigs[defaultIndexserver] = authConfig
 	} else {
 	} else {
@@ -126,6 +123,13 @@ func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
 	return nil
 	return nil
 }
 }
 
 
+// ContainsAuth returns whether there is authentication configured
+// in this file or not.
+func (configFile *ConfigFile) ContainsAuth() bool {
+	return configFile.CredentialsStore != "" ||
+		(configFile.AuthConfigs != nil && len(configFile.AuthConfigs) > 0)
+}
+
 // LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
 // LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
 // a non-nested reader
 // a non-nested reader
 func LegacyLoadFromReader(configData io.Reader) (*ConfigFile, error) {
 func LegacyLoadFromReader(configData io.Reader) (*ConfigFile, error) {
@@ -249,6 +253,10 @@ func (configFile *ConfigFile) Filename() string {
 
 
 // encodeAuth creates a base64 encoded string to containing authorization information
 // encodeAuth creates a base64 encoded string to containing authorization information
 func encodeAuth(authConfig *types.AuthConfig) string {
 func encodeAuth(authConfig *types.AuthConfig) string {
+	if authConfig.Username == "" && authConfig.Password == "" {
+		return ""
+	}
+
 	authStr := authConfig.Username + ":" + authConfig.Password
 	authStr := authConfig.Username + ":" + authConfig.Password
 	msg := []byte(authStr)
 	msg := []byte(authStr)
 	encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
 	encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
@@ -258,6 +266,10 @@ func encodeAuth(authConfig *types.AuthConfig) string {
 
 
 // decodeAuth decodes a base64 encoded string and returns username and password
 // decodeAuth decodes a base64 encoded string and returns username and password
 func decodeAuth(authStr string) (string, string, error) {
 func decodeAuth(authStr string) (string, string, error) {
+	if authStr == "" {
+		return "", "", nil
+	}
+
 	decLen := base64.StdEncoding.DecodedLen(len(authStr))
 	decLen := base64.StdEncoding.DecodedLen(len(authStr))
 	decoded := make([]byte, decLen)
 	decoded := make([]byte, decLen)
 	authByte := []byte(authStr)
 	authByte := []byte(authStr)

+ 102 - 25
cliconfig/config_test.go

@@ -111,12 +111,9 @@ func TestOldInvalidsAuth(t *testing.T) {
 	invalids := map[string]string{
 	invalids := map[string]string{
 		`username = test`: "The Auth config file is empty",
 		`username = test`: "The Auth config file is empty",
 		`username
 		`username
-password
-email`: "Invalid Auth config file",
+password`: "Invalid Auth config file",
 		`username = test
 		`username = test
 email`: "Invalid auth configuration file",
 email`: "Invalid auth configuration file",
-		`username = am9lam9lOmhlbGxv
-email`: "Invalid Auth config file",
 	}
 	}
 
 
 	tmpHome, err := ioutil.TempDir("", "config-test")
 	tmpHome, err := ioutil.TempDir("", "config-test")
@@ -164,7 +161,7 @@ func TestOldValidAuth(t *testing.T) {
 
 
 	fn := filepath.Join(tmpHome, oldConfigfile)
 	fn := filepath.Join(tmpHome, oldConfigfile)
 	js := `username = am9lam9lOmhlbGxv
 	js := `username = am9lam9lOmhlbGxv
-email = user@example.com`
+	email = user@example.com`
 	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
 	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -176,15 +173,23 @@ email = user@example.com`
 
 
 	// defaultIndexserver is https://index.docker.io/v1/
 	// defaultIndexserver is https://index.docker.io/v1/
 	ac := config.AuthConfigs["https://index.docker.io/v1/"]
 	ac := config.AuthConfigs["https://index.docker.io/v1/"]
-	if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" {
+	if ac.Username != "joejoe" || ac.Password != "hello" {
 		t.Fatalf("Missing data from parsing:\n%q", config)
 		t.Fatalf("Missing data from parsing:\n%q", config)
 	}
 	}
 
 
 	// Now save it and make sure it shows up in new form
 	// Now save it and make sure it shows up in new form
 	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
 	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
 
 
-	if !strings.Contains(configStr, "user@example.com") {
-		t.Fatalf("Should have save in new form: %s", configStr)
+	expConfStr := `{
+	"auths": {
+		"https://index.docker.io/v1/": {
+			"auth": "am9lam9lOmhlbGxv"
+		}
+	}
+}`
+
+	if configStr != expConfStr {
+		t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
 	}
 	}
 }
 }
 
 
@@ -239,15 +244,24 @@ func TestOldJson(t *testing.T) {
 	}
 	}
 
 
 	ac := config.AuthConfigs["https://index.docker.io/v1/"]
 	ac := config.AuthConfigs["https://index.docker.io/v1/"]
-	if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" {
+	if ac.Username != "joejoe" || ac.Password != "hello" {
 		t.Fatalf("Missing data from parsing:\n%q", config)
 		t.Fatalf("Missing data from parsing:\n%q", config)
 	}
 	}
 
 
 	// Now save it and make sure it shows up in new form
 	// Now save it and make sure it shows up in new form
 	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
 	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
 
 
-	if !strings.Contains(configStr, "user@example.com") {
-		t.Fatalf("Should have save in new form: %s", configStr)
+	expConfStr := `{
+	"auths": {
+		"https://index.docker.io/v1/": {
+			"auth": "am9lam9lOmhlbGxv",
+			"email": "user@example.com"
+		}
+	}
+}`
+
+	if configStr != expConfStr {
+		t.Fatalf("Should have save in new form: \n'%s'\n not \n'%s'\n", configStr, expConfStr)
 	}
 	}
 }
 }
 
 
@@ -259,7 +273,7 @@ func TestNewJson(t *testing.T) {
 	defer os.RemoveAll(tmpHome)
 	defer os.RemoveAll(tmpHome)
 
 
 	fn := filepath.Join(tmpHome, ConfigFileName)
 	fn := filepath.Join(tmpHome, ConfigFileName)
-	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
+	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
 	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
 	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -270,15 +284,62 @@ func TestNewJson(t *testing.T) {
 	}
 	}
 
 
 	ac := config.AuthConfigs["https://index.docker.io/v1/"]
 	ac := config.AuthConfigs["https://index.docker.io/v1/"]
-	if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" {
+	if ac.Username != "joejoe" || ac.Password != "hello" {
 		t.Fatalf("Missing data from parsing:\n%q", config)
 		t.Fatalf("Missing data from parsing:\n%q", config)
 	}
 	}
 
 
 	// Now save it and make sure it shows up in new form
 	// Now save it and make sure it shows up in new form
 	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
 	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
 
 
-	if !strings.Contains(configStr, "user@example.com") {
-		t.Fatalf("Should have save in new form: %s", configStr)
+	expConfStr := `{
+	"auths": {
+		"https://index.docker.io/v1/": {
+			"auth": "am9lam9lOmhlbGxv"
+		}
+	}
+}`
+
+	if configStr != expConfStr {
+		t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
+	}
+}
+
+func TestNewJsonNoEmail(t *testing.T) {
+	tmpHome, err := ioutil.TempDir("", "config-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpHome)
+
+	fn := filepath.Join(tmpHome, ConfigFileName)
+	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
+	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
+		t.Fatal(err)
+	}
+
+	config, err := Load(tmpHome)
+	if err != nil {
+		t.Fatalf("Failed loading on empty json file: %q", err)
+	}
+
+	ac := config.AuthConfigs["https://index.docker.io/v1/"]
+	if ac.Username != "joejoe" || ac.Password != "hello" {
+		t.Fatalf("Missing data from parsing:\n%q", config)
+	}
+
+	// Now save it and make sure it shows up in new form
+	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
+
+	expConfStr := `{
+	"auths": {
+		"https://index.docker.io/v1/": {
+			"auth": "am9lam9lOmhlbGxv"
+		}
+	}
+}`
+
+	if configStr != expConfStr {
+		t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
 	}
 	}
 }
 }
 
 
@@ -366,7 +427,7 @@ func TestJsonReaderNoFile(t *testing.T) {
 	}
 	}
 
 
 	ac := config.AuthConfigs["https://index.docker.io/v1/"]
 	ac := config.AuthConfigs["https://index.docker.io/v1/"]
-	if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" {
+	if ac.Username != "joejoe" || ac.Password != "hello" {
 		t.Fatalf("Missing data from parsing:\n%q", config)
 		t.Fatalf("Missing data from parsing:\n%q", config)
 	}
 	}
 
 
@@ -381,7 +442,7 @@ func TestOldJsonReaderNoFile(t *testing.T) {
 	}
 	}
 
 
 	ac := config.AuthConfigs["https://index.docker.io/v1/"]
 	ac := config.AuthConfigs["https://index.docker.io/v1/"]
-	if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" {
+	if ac.Username != "joejoe" || ac.Password != "hello" {
 		t.Fatalf("Missing data from parsing:\n%q", config)
 		t.Fatalf("Missing data from parsing:\n%q", config)
 	}
 	}
 }
 }
@@ -404,7 +465,7 @@ func TestJsonWithPsFormatNoFile(t *testing.T) {
 
 
 func TestJsonSaveWithNoFile(t *testing.T) {
 func TestJsonSaveWithNoFile(t *testing.T) {
 	js := `{
 	js := `{
-		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
+		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } },
 		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
 		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
 }`
 }`
 	config, err := LoadFromReader(strings.NewReader(js))
 	config, err := LoadFromReader(strings.NewReader(js))
@@ -426,9 +487,16 @@ func TestJsonSaveWithNoFile(t *testing.T) {
 		t.Fatalf("Failed saving to file: %q", err)
 		t.Fatalf("Failed saving to file: %q", err)
 	}
 	}
 	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
 	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
-	if !strings.Contains(string(buf), `"auths":`) ||
-		!strings.Contains(string(buf), "user@example.com") {
-		t.Fatalf("Should have save in new form: %s", string(buf))
+	expConfStr := `{
+	"auths": {
+		"https://index.docker.io/v1/": {
+			"auth": "am9lam9lOmhlbGxv"
+		}
+	},
+	"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
+}`
+	if string(buf) != expConfStr {
+		t.Fatalf("Should have save in new form: \n%s\nnot \n%s", string(buf), expConfStr)
 	}
 	}
 }
 }
 
 
@@ -454,14 +522,23 @@ func TestLegacyJsonSaveWithNoFile(t *testing.T) {
 		t.Fatalf("Failed saving to file: %q", err)
 		t.Fatalf("Failed saving to file: %q", err)
 	}
 	}
 	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
 	buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
-	if !strings.Contains(string(buf), `"auths":`) ||
-		!strings.Contains(string(buf), "user@example.com") {
-		t.Fatalf("Should have save in new form: %s", string(buf))
+
+	expConfStr := `{
+	"auths": {
+		"https://index.docker.io/v1/": {
+			"auth": "am9lam9lOmhlbGxv",
+			"email": "user@example.com"
+		}
+	}
+}`
+
+	if string(buf) != expConfStr {
+		t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr)
 	}
 	}
 }
 }
 
 
 func TestEncodeAuth(t *testing.T) {
 func TestEncodeAuth(t *testing.T) {
-	newAuthConfig := &types.AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
+	newAuthConfig := &types.AuthConfig{Username: "ken", Password: "test"}
 	authStr := encodeAuth(newAuthConfig)
 	authStr := encodeAuth(newAuthConfig)
 	decAuthConfig := &types.AuthConfig{}
 	decAuthConfig := &types.AuthConfig{}
 	var err error
 	var err error

+ 17 - 0
cliconfig/credentials/credentials.go

@@ -0,0 +1,17 @@
+package credentials
+
+import (
+	"github.com/docker/engine-api/types"
+)
+
+// Store is the interface that any credentials store must implement.
+type Store interface {
+	// Erase removes credentials from the store for a given server.
+	Erase(serverAddress string) error
+	// Get retrieves credentials from the store for a given server.
+	Get(serverAddress string) (types.AuthConfig, error)
+	// GetAll retrieves all the credentials from the store.
+	GetAll() (map[string]types.AuthConfig, error)
+	// Store saves credentials in the store.
+	Store(authConfig types.AuthConfig) error
+}

+ 22 - 0
cliconfig/credentials/default_store.go

@@ -0,0 +1,22 @@
+package credentials
+
+import (
+	"os/exec"
+
+	"github.com/docker/docker/cliconfig"
+)
+
+// DetectDefaultStore sets the default credentials store
+// if the host includes the default store helper program.
+func DetectDefaultStore(c *cliconfig.ConfigFile) {
+	if c.CredentialsStore != "" {
+		// user defined
+		return
+	}
+
+	if defaultCredentialsStore != "" {
+		if _, err := exec.LookPath(remoteCredentialsPrefix + defaultCredentialsStore); err == nil {
+			c.CredentialsStore = defaultCredentialsStore
+		}
+	}
+}

+ 3 - 0
cliconfig/credentials/default_store_darwin.go

@@ -0,0 +1,3 @@
+package credentials
+
+const defaultCredentialsStore = "osxkeychain"

+ 3 - 0
cliconfig/credentials/default_store_linux.go

@@ -0,0 +1,3 @@
+package credentials
+
+const defaultCredentialsStore = "secretservice"

+ 5 - 0
cliconfig/credentials/default_store_unsupported.go

@@ -0,0 +1,5 @@
+// +build !windows,!darwin,!linux
+
+package credentials
+
+const defaultCredentialsStore = ""

+ 3 - 0
cliconfig/credentials/default_store_windows.go

@@ -0,0 +1,3 @@
+package credentials
+
+const defaultCredentialsStore = "wincred"

+ 67 - 0
cliconfig/credentials/file_store.go

@@ -0,0 +1,67 @@
+package credentials
+
+import (
+	"strings"
+
+	"github.com/docker/docker/cliconfig"
+	"github.com/docker/engine-api/types"
+)
+
+// fileStore implements a credentials store using
+// the docker configuration file to keep the credentials in plain text.
+type fileStore struct {
+	file *cliconfig.ConfigFile
+}
+
+// NewFileStore creates a new file credentials store.
+func NewFileStore(file *cliconfig.ConfigFile) Store {
+	return &fileStore{
+		file: file,
+	}
+}
+
+// Erase removes the given credentials from the file store.
+func (c *fileStore) Erase(serverAddress string) error {
+	delete(c.file.AuthConfigs, serverAddress)
+	return c.file.Save()
+}
+
+// Get retrieves credentials for a specific server from the file store.
+func (c *fileStore) Get(serverAddress string) (types.AuthConfig, error) {
+	authConfig, ok := c.file.AuthConfigs[serverAddress]
+	if !ok {
+		// Maybe they have a legacy config file, we will iterate the keys converting
+		// them to the new format and testing
+		for registry, ac := range c.file.AuthConfigs {
+			if serverAddress == convertToHostname(registry) {
+				return ac, nil
+			}
+		}
+
+		authConfig = types.AuthConfig{}
+	}
+	return authConfig, nil
+}
+
+func (c *fileStore) GetAll() (map[string]types.AuthConfig, error) {
+	return c.file.AuthConfigs, nil
+}
+
+// Store saves the given credentials in the file store.
+func (c *fileStore) Store(authConfig types.AuthConfig) error {
+	c.file.AuthConfigs[authConfig.ServerAddress] = authConfig
+	return c.file.Save()
+}
+
+func convertToHostname(url string) string {
+	stripped := url
+	if strings.HasPrefix(url, "http://") {
+		stripped = strings.Replace(url, "http://", "", 1)
+	} else if strings.HasPrefix(url, "https://") {
+		stripped = strings.Replace(url, "https://", "", 1)
+	}
+
+	nameParts := strings.SplitN(stripped, "/", 2)
+
+	return nameParts[0]
+}

+ 138 - 0
cliconfig/credentials/file_store_test.go

@@ -0,0 +1,138 @@
+package credentials
+
+import (
+	"io/ioutil"
+	"testing"
+
+	"github.com/docker/docker/cliconfig"
+	"github.com/docker/engine-api/types"
+)
+
+func newConfigFile(auths map[string]types.AuthConfig) *cliconfig.ConfigFile {
+	tmp, _ := ioutil.TempFile("", "docker-test")
+	name := tmp.Name()
+	tmp.Close()
+
+	c := cliconfig.NewConfigFile(name)
+	c.AuthConfigs = auths
+	return c
+}
+
+func TestFileStoreAddCredentials(t *testing.T) {
+	f := newConfigFile(make(map[string]types.AuthConfig))
+
+	s := NewFileStore(f)
+	err := s.Store(types.AuthConfig{
+		Auth:          "super_secret_token",
+		Email:         "foo@example.com",
+		ServerAddress: "https://example.com",
+	})
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(f.AuthConfigs) != 1 {
+		t.Fatalf("expected 1 auth config, got %d", len(f.AuthConfigs))
+	}
+
+	a, ok := f.AuthConfigs["https://example.com"]
+	if !ok {
+		t.Fatalf("expected auth for https://example.com, got %v", f.AuthConfigs)
+	}
+	if a.Auth != "super_secret_token" {
+		t.Fatalf("expected auth `super_secret_token`, got %s", a.Auth)
+	}
+	if a.Email != "foo@example.com" {
+		t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
+	}
+}
+
+func TestFileStoreGet(t *testing.T) {
+	f := newConfigFile(map[string]types.AuthConfig{
+		"https://example.com": {
+			Auth:          "super_secret_token",
+			Email:         "foo@example.com",
+			ServerAddress: "https://example.com",
+		},
+	})
+
+	s := NewFileStore(f)
+	a, err := s.Get("https://example.com")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if a.Auth != "super_secret_token" {
+		t.Fatalf("expected auth `super_secret_token`, got %s", a.Auth)
+	}
+	if a.Email != "foo@example.com" {
+		t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
+	}
+}
+
+func TestFileStoreGetAll(t *testing.T) {
+	s1 := "https://example.com"
+	s2 := "https://example2.com"
+	f := newConfigFile(map[string]types.AuthConfig{
+		s1: {
+			Auth:          "super_secret_token",
+			Email:         "foo@example.com",
+			ServerAddress: "https://example.com",
+		},
+		s2: {
+			Auth:          "super_secret_token2",
+			Email:         "foo@example2.com",
+			ServerAddress: "https://example2.com",
+		},
+	})
+
+	s := NewFileStore(f)
+	as, err := s.GetAll()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(as) != 2 {
+		t.Fatalf("wanted 2, got %d", len(as))
+	}
+	if as[s1].Auth != "super_secret_token" {
+		t.Fatalf("expected auth `super_secret_token`, got %s", as[s1].Auth)
+	}
+	if as[s1].Email != "foo@example.com" {
+		t.Fatalf("expected email `foo@example.com`, got %s", as[s1].Email)
+	}
+	if as[s2].Auth != "super_secret_token2" {
+		t.Fatalf("expected auth `super_secret_token2`, got %s", as[s2].Auth)
+	}
+	if as[s2].Email != "foo@example2.com" {
+		t.Fatalf("expected email `foo@example2.com`, got %s", as[s2].Email)
+	}
+}
+
+func TestFileStoreErase(t *testing.T) {
+	f := newConfigFile(map[string]types.AuthConfig{
+		"https://example.com": {
+			Auth:          "super_secret_token",
+			Email:         "foo@example.com",
+			ServerAddress: "https://example.com",
+		},
+	})
+
+	s := NewFileStore(f)
+	err := s.Erase("https://example.com")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// file store never returns errors, check that the auth config is empty
+	a, err := s.Get("https://example.com")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if a.Auth != "" {
+		t.Fatalf("expected empty auth token, got %s", a.Auth)
+	}
+	if a.Email != "" {
+		t.Fatalf("expected empty email, got %s", a.Email)
+	}
+}

+ 180 - 0
cliconfig/credentials/native_store.go

@@ -0,0 +1,180 @@
+package credentials
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"strings"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/cliconfig"
+	"github.com/docker/engine-api/types"
+)
+
+const remoteCredentialsPrefix = "docker-credential-"
+
+// Standarize the not found error, so every helper returns
+// the same message and docker can handle it properly.
+var errCredentialsNotFound = errors.New("credentials not found in native keychain")
+
+// command is an interface that remote executed commands implement.
+type command interface {
+	Output() ([]byte, error)
+	Input(in io.Reader)
+}
+
+// credentialsRequest holds information shared between docker and a remote credential store.
+type credentialsRequest struct {
+	ServerURL string
+	Username  string
+	Password  string
+}
+
+// credentialsGetResponse is the information serialized from a remote store
+// when the plugin sends requests to get the user credentials.
+type credentialsGetResponse struct {
+	Username string
+	Password string
+}
+
+// nativeStore implements a credentials store
+// using native keychain to keep credentials secure.
+// It piggybacks into a file store to keep users' emails.
+type nativeStore struct {
+	commandFn func(args ...string) command
+	fileStore Store
+}
+
+// NewNativeStore creates a new native store that
+// uses a remote helper program to manage credentials.
+func NewNativeStore(file *cliconfig.ConfigFile) Store {
+	return &nativeStore{
+		commandFn: shellCommandFn(file.CredentialsStore),
+		fileStore: NewFileStore(file),
+	}
+}
+
+// Erase removes the given credentials from the native store.
+func (c *nativeStore) Erase(serverAddress string) error {
+	if err := c.eraseCredentialsFromStore(serverAddress); err != nil {
+		return err
+	}
+
+	// Fallback to plain text store to remove email
+	return c.fileStore.Erase(serverAddress)
+}
+
+// Get retrieves credentials for a specific server from the native store.
+func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) {
+	// load user email if it exist or an empty auth config.
+	auth, _ := c.fileStore.Get(serverAddress)
+
+	creds, err := c.getCredentialsFromStore(serverAddress)
+	if err != nil {
+		return auth, err
+	}
+	auth.Username = creds.Username
+	auth.Password = creds.Password
+
+	return auth, nil
+}
+
+// GetAll retrieves all the credentials from the native store.
+func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) {
+	auths, _ := c.fileStore.GetAll()
+
+	for s, ac := range auths {
+		creds, _ := c.getCredentialsFromStore(s)
+		ac.Username = creds.Username
+		ac.Password = creds.Password
+		auths[s] = ac
+	}
+
+	return auths, nil
+}
+
+// Store saves the given credentials in the file store.
+func (c *nativeStore) Store(authConfig types.AuthConfig) error {
+	if err := c.storeCredentialsInStore(authConfig); err != nil {
+		return err
+	}
+	authConfig.Username = ""
+	authConfig.Password = ""
+
+	// Fallback to old credential in plain text to save only the email
+	return c.fileStore.Store(authConfig)
+}
+
+// storeCredentialsInStore executes the command to store the credentials in the native store.
+func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error {
+	cmd := c.commandFn("store")
+	creds := &credentialsRequest{
+		ServerURL: config.ServerAddress,
+		Username:  config.Username,
+		Password:  config.Password,
+	}
+
+	buffer := new(bytes.Buffer)
+	if err := json.NewEncoder(buffer).Encode(creds); err != nil {
+		return err
+	}
+	cmd.Input(buffer)
+
+	out, err := cmd.Output()
+	if err != nil {
+		t := strings.TrimSpace(string(out))
+		logrus.Debugf("error adding credentials - err: %v, out: `%s`", err, t)
+		return fmt.Errorf(t)
+	}
+
+	return nil
+}
+
+// getCredentialsFromStore executes the command to get the credentials from the native store.
+func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthConfig, error) {
+	var ret types.AuthConfig
+
+	cmd := c.commandFn("get")
+	cmd.Input(strings.NewReader(serverAddress))
+
+	out, err := cmd.Output()
+	if err != nil {
+		t := strings.TrimSpace(string(out))
+
+		// do not return an error if the credentials are not
+		// in the keyckain. Let docker ask for new credentials.
+		if t == errCredentialsNotFound.Error() {
+			return ret, nil
+		}
+
+		logrus.Debugf("error getting credentials - err: %v, out: `%s`", err, t)
+		return ret, fmt.Errorf(t)
+	}
+
+	var resp credentialsGetResponse
+	if err := json.NewDecoder(bytes.NewReader(out)).Decode(&resp); err != nil {
+		return ret, err
+	}
+
+	ret.Username = resp.Username
+	ret.Password = resp.Password
+	ret.ServerAddress = serverAddress
+	return ret, nil
+}
+
+// eraseCredentialsFromStore executes the command to remove the server redentails from the native store.
+func (c *nativeStore) eraseCredentialsFromStore(serverURL string) error {
+	cmd := c.commandFn("erase")
+	cmd.Input(strings.NewReader(serverURL))
+
+	out, err := cmd.Output()
+	if err != nil {
+		t := strings.TrimSpace(string(out))
+		logrus.Debugf("error erasing credentials - err: %v, out: `%s`", err, t)
+		return fmt.Errorf(t)
+	}
+
+	return nil
+}

+ 309 - 0
cliconfig/credentials/native_store_test.go

@@ -0,0 +1,309 @@
+package credentials
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"strings"
+	"testing"
+
+	"github.com/docker/engine-api/types"
+)
+
+const (
+	validServerAddress   = "https://index.docker.io/v1"
+	validServerAddress2  = "https://example.com:5002"
+	invalidServerAddress = "https://foobar.example.com"
+	missingCredsAddress  = "https://missing.docker.io/v1"
+)
+
+var errCommandExited = fmt.Errorf("exited 1")
+
+// mockCommand simulates interactions between the docker client and a remote
+// credentials helper.
+// Unit tests inject this mocked command into the remote to control execution.
+type mockCommand struct {
+	arg   string
+	input io.Reader
+}
+
+// Output returns responses from the remote credentials helper.
+// It mocks those reponses based in the input in the mock.
+func (m *mockCommand) Output() ([]byte, error) {
+	in, err := ioutil.ReadAll(m.input)
+	if err != nil {
+		return nil, err
+	}
+	inS := string(in)
+
+	switch m.arg {
+	case "erase":
+		switch inS {
+		case validServerAddress:
+			return nil, nil
+		default:
+			return []byte("error erasing credentials"), errCommandExited
+		}
+	case "get":
+		switch inS {
+		case validServerAddress, validServerAddress2:
+			return []byte(`{"Username": "foo", "Password": "bar"}`), nil
+		case missingCredsAddress:
+			return []byte(errCredentialsNotFound.Error()), errCommandExited
+		case invalidServerAddress:
+			return []byte("error getting credentials"), errCommandExited
+		}
+	case "store":
+		var c credentialsRequest
+		err := json.NewDecoder(strings.NewReader(inS)).Decode(&c)
+		if err != nil {
+			return []byte("error storing credentials"), errCommandExited
+		}
+		switch c.ServerURL {
+		case validServerAddress:
+			return nil, nil
+		default:
+			return []byte("error storing credentials"), errCommandExited
+		}
+	}
+
+	return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errCommandExited
+}
+
+// Input sets the input to send to a remote credentials helper.
+func (m *mockCommand) Input(in io.Reader) {
+	m.input = in
+}
+
+func mockCommandFn(args ...string) command {
+	return &mockCommand{
+		arg: args[0],
+	}
+}
+
+func TestNativeStoreAddCredentials(t *testing.T) {
+	f := newConfigFile(make(map[string]types.AuthConfig))
+	f.CredentialsStore = "mock"
+
+	s := &nativeStore{
+		commandFn: mockCommandFn,
+		fileStore: NewFileStore(f),
+	}
+	err := s.Store(types.AuthConfig{
+		Username:      "foo",
+		Password:      "bar",
+		Email:         "foo@example.com",
+		ServerAddress: validServerAddress,
+	})
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(f.AuthConfigs) != 1 {
+		t.Fatalf("expected 1 auth config, got %d", len(f.AuthConfigs))
+	}
+
+	a, ok := f.AuthConfigs[validServerAddress]
+	if !ok {
+		t.Fatalf("expected auth for %s, got %v", validServerAddress, f.AuthConfigs)
+	}
+	if a.Auth != "" {
+		t.Fatalf("expected auth to be empty, got %s", a.Auth)
+	}
+	if a.Username != "" {
+		t.Fatalf("expected username to be empty, got %s", a.Username)
+	}
+	if a.Password != "" {
+		t.Fatalf("expected password to be empty, got %s", a.Password)
+	}
+	if a.Email != "foo@example.com" {
+		t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
+	}
+}
+
+func TestNativeStoreAddInvalidCredentials(t *testing.T) {
+	f := newConfigFile(make(map[string]types.AuthConfig))
+	f.CredentialsStore = "mock"
+
+	s := &nativeStore{
+		commandFn: mockCommandFn,
+		fileStore: NewFileStore(f),
+	}
+	err := s.Store(types.AuthConfig{
+		Username:      "foo",
+		Password:      "bar",
+		Email:         "foo@example.com",
+		ServerAddress: invalidServerAddress,
+	})
+
+	if err == nil {
+		t.Fatal("expected error, got nil")
+	}
+
+	if err.Error() != "error storing credentials" {
+		t.Fatalf("expected `error storing credentials`, got %v", err)
+	}
+
+	if len(f.AuthConfigs) != 0 {
+		t.Fatalf("expected 0 auth config, got %d", len(f.AuthConfigs))
+	}
+}
+
+func TestNativeStoreGet(t *testing.T) {
+	f := newConfigFile(map[string]types.AuthConfig{
+		validServerAddress: {
+			Email: "foo@example.com",
+		},
+	})
+	f.CredentialsStore = "mock"
+
+	s := &nativeStore{
+		commandFn: mockCommandFn,
+		fileStore: NewFileStore(f),
+	}
+	a, err := s.Get(validServerAddress)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if a.Username != "foo" {
+		t.Fatalf("expected username `foo`, got %s", a.Username)
+	}
+	if a.Password != "bar" {
+		t.Fatalf("expected password `bar`, got %s", a.Password)
+	}
+	if a.Email != "foo@example.com" {
+		t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
+	}
+}
+
+func TestNativeStoreGetAll(t *testing.T) {
+	f := newConfigFile(map[string]types.AuthConfig{
+		validServerAddress: {
+			Email: "foo@example.com",
+		},
+		validServerAddress2: {
+			Email: "foo@example2.com",
+		},
+	})
+	f.CredentialsStore = "mock"
+
+	s := &nativeStore{
+		commandFn: mockCommandFn,
+		fileStore: NewFileStore(f),
+	}
+	as, err := s.GetAll()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(as) != 2 {
+		t.Fatalf("wanted 2, got %d", len(as))
+	}
+
+	if as[validServerAddress].Username != "foo" {
+		t.Fatalf("expected username `foo` for %s, got %s", validServerAddress, as[validServerAddress].Username)
+	}
+	if as[validServerAddress].Password != "bar" {
+		t.Fatalf("expected password `bar` for %s, got %s", validServerAddress, as[validServerAddress].Password)
+	}
+	if as[validServerAddress].Email != "foo@example.com" {
+		t.Fatalf("expected email `foo@example.com` for %s, got %s", validServerAddress, as[validServerAddress].Email)
+	}
+	if as[validServerAddress2].Username != "foo" {
+		t.Fatalf("expected username `foo` for %s, got %s", validServerAddress2, as[validServerAddress2].Username)
+	}
+	if as[validServerAddress2].Password != "bar" {
+		t.Fatalf("expected password `bar` for %s, got %s", validServerAddress2, as[validServerAddress2].Password)
+	}
+	if as[validServerAddress2].Email != "foo@example2.com" {
+		t.Fatalf("expected email `foo@example2.com` for %s, got %s", validServerAddress2, as[validServerAddress2].Email)
+	}
+}
+
+func TestNativeStoreGetMissingCredentials(t *testing.T) {
+	f := newConfigFile(map[string]types.AuthConfig{
+		validServerAddress: {
+			Email: "foo@example.com",
+		},
+	})
+	f.CredentialsStore = "mock"
+
+	s := &nativeStore{
+		commandFn: mockCommandFn,
+		fileStore: NewFileStore(f),
+	}
+	_, err := s.Get(missingCredsAddress)
+	if err != nil {
+		// missing credentials do not produce an error
+		t.Fatal(err)
+	}
+}
+
+func TestNativeStoreGetInvalidAddress(t *testing.T) {
+	f := newConfigFile(map[string]types.AuthConfig{
+		validServerAddress: {
+			Email: "foo@example.com",
+		},
+	})
+	f.CredentialsStore = "mock"
+
+	s := &nativeStore{
+		commandFn: mockCommandFn,
+		fileStore: NewFileStore(f),
+	}
+	_, err := s.Get(invalidServerAddress)
+	if err == nil {
+		t.Fatal("expected error, got nil")
+	}
+
+	if err.Error() != "error getting credentials" {
+		t.Fatalf("expected `error getting credentials`, got %v", err)
+	}
+}
+
+func TestNativeStoreErase(t *testing.T) {
+	f := newConfigFile(map[string]types.AuthConfig{
+		validServerAddress: {
+			Email: "foo@example.com",
+		},
+	})
+	f.CredentialsStore = "mock"
+
+	s := &nativeStore{
+		commandFn: mockCommandFn,
+		fileStore: NewFileStore(f),
+	}
+	err := s.Erase(validServerAddress)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(f.AuthConfigs) != 0 {
+		t.Fatalf("expected 0 auth configs, got %d", len(f.AuthConfigs))
+	}
+}
+
+func TestNativeStoreEraseInvalidAddress(t *testing.T) {
+	f := newConfigFile(map[string]types.AuthConfig{
+		validServerAddress: {
+			Email: "foo@example.com",
+		},
+	})
+	f.CredentialsStore = "mock"
+
+	s := &nativeStore{
+		commandFn: mockCommandFn,
+		fileStore: NewFileStore(f),
+	}
+	err := s.Erase(invalidServerAddress)
+	if err == nil {
+		t.Fatal("expected error, got nil")
+	}
+
+	if err.Error() != "error erasing credentials" {
+		t.Fatalf("expected `error erasing credentials`, got %v", err)
+	}
+}

+ 28 - 0
cliconfig/credentials/shell_command.go

@@ -0,0 +1,28 @@
+package credentials
+
+import (
+	"io"
+	"os/exec"
+)
+
+func shellCommandFn(storeName string) func(args ...string) command {
+	name := remoteCredentialsPrefix + storeName
+	return func(args ...string) command {
+		return &shell{cmd: exec.Command(name, args...)}
+	}
+}
+
+// shell invokes shell commands to talk with a remote credentials helper.
+type shell struct {
+	cmd *exec.Cmd
+}
+
+// Output returns responses from the remote credentials helper.
+func (s *shell) Output() ([]byte, error) {
+	return s.cmd.Output()
+}
+
+// Input sets the input to send to a remote credentials helper.
+func (s *shell) Input(in io.Reader) {
+	s.cmd.Stdin = in
+}

+ 29 - 26
container/container.go

@@ -16,13 +16,12 @@ import (
 	"github.com/docker/docker/daemon/logger"
 	"github.com/docker/docker/daemon/logger"
 	"github.com/docker/docker/daemon/logger/jsonfilelog"
 	"github.com/docker/docker/daemon/logger/jsonfilelog"
 	"github.com/docker/docker/daemon/network"
 	"github.com/docker/docker/daemon/network"
-	derr "github.com/docker/docker/errors"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
+	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/symlink"
 	"github.com/docker/docker/pkg/symlink"
-	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
 	containertypes "github.com/docker/engine-api/types/container"
 	containertypes "github.com/docker/engine-api/types/container"
@@ -185,10 +184,17 @@ func (container *Container) WriteHostConfig() error {
 }
 }
 
 
 // SetupWorkingDirectory sets up the container's working directory as set in container.Config.WorkingDir
 // SetupWorkingDirectory sets up the container's working directory as set in container.Config.WorkingDir
-func (container *Container) SetupWorkingDirectory() error {
+func (container *Container) SetupWorkingDirectory(rootUID, rootGID int) error {
 	if container.Config.WorkingDir == "" {
 	if container.Config.WorkingDir == "" {
 		return nil
 		return nil
 	}
 	}
+
+	// If can't mount container FS at this point (eg Hyper-V Containers on
+	// Windows) bail out now with no action.
+	if !container.canMountFS() {
+		return nil
+	}
+
 	container.Config.WorkingDir = filepath.Clean(container.Config.WorkingDir)
 	container.Config.WorkingDir = filepath.Clean(container.Config.WorkingDir)
 
 
 	pth, err := container.GetResourcePath(container.Config.WorkingDir)
 	pth, err := container.GetResourcePath(container.Config.WorkingDir)
@@ -196,10 +202,10 @@ func (container *Container) SetupWorkingDirectory() error {
 		return err
 		return err
 	}
 	}
 
 
-	if err := system.MkdirAll(pth, 0755); err != nil {
+	if err := idtools.MkdirAllNewAs(pth, 0755, rootUID, rootGID); err != nil {
 		pthInfo, err2 := os.Stat(pth)
 		pthInfo, err2 := os.Stat(pth)
 		if err2 == nil && pthInfo != nil && !pthInfo.IsDir() {
 		if err2 == nil && pthInfo != nil && !pthInfo.IsDir() {
-			return derr.ErrorCodeNotADir.WithArgs(container.Config.WorkingDir)
+			return fmt.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir)
 		}
 		}
 
 
 		return err
 		return err
@@ -277,37 +283,17 @@ func (container *Container) ConfigPath() (string, error) {
 	return container.GetRootResourcePath(configFileName)
 	return container.GetRootResourcePath(configFileName)
 }
 }
 
 
-func validateID(id string) error {
-	if id == "" {
-		return derr.ErrorCodeEmptyID
-	}
-	return nil
-}
-
 // Returns true if the container exposes a certain port
 // Returns true if the container exposes a certain port
 func (container *Container) exposes(p nat.Port) bool {
 func (container *Container) exposes(p nat.Port) bool {
 	_, exists := container.Config.ExposedPorts[p]
 	_, exists := container.Config.ExposedPorts[p]
 	return exists
 	return exists
 }
 }
 
 
-// GetLogConfig returns the log configuration for the container.
-func (container *Container) GetLogConfig(defaultConfig containertypes.LogConfig) containertypes.LogConfig {
-	cfg := container.HostConfig.LogConfig
-	if cfg.Type != "" || len(cfg.Config) > 0 { // container has log driver configured
-		if cfg.Type == "" {
-			cfg.Type = jsonfilelog.Name
-		}
-		return cfg
-	}
-	// Use daemon's default log config for containers
-	return defaultConfig
-}
-
 // StartLogger starts a new logger driver for the container.
 // StartLogger starts a new logger driver for the container.
 func (container *Container) StartLogger(cfg containertypes.LogConfig) (logger.Logger, error) {
 func (container *Container) StartLogger(cfg containertypes.LogConfig) (logger.Logger, error) {
 	c, err := logger.GetLogDriver(cfg.Type)
 	c, err := logger.GetLogDriver(cfg.Type)
 	if err != nil {
 	if err != nil {
-		return nil, derr.ErrorCodeLoggingFactory.WithArgs(err)
+		return nil, fmt.Errorf("Failed to get logging factory: %v", err)
 	}
 	}
 	ctx := logger.Context{
 	ctx := logger.Context{
 		Config:              cfg.Config,
 		Config:              cfg.Config,
@@ -594,3 +580,20 @@ func (container *Container) InitDNSHostConfig() {
 		container.HostConfig.DNSOptions = make([]string, 0)
 		container.HostConfig.DNSOptions = make([]string, 0)
 	}
 	}
 }
 }
+
+// UpdateMonitor updates monitor configure for running container
+func (container *Container) UpdateMonitor(restartPolicy containertypes.RestartPolicy) {
+	monitor := container.monitor
+	// No need to update monitor if container hasn't got one
+	// monitor will be generated correctly according to container
+	if monitor == nil {
+		return
+	}
+
+	monitor.mux.Lock()
+	// to check whether restart policy has changed.
+	if restartPolicy.Name != "" && !monitor.restartPolicy.IsSame(&restartPolicy) {
+		monitor.restartPolicy = restartPolicy
+	}
+	monitor.mux.Unlock()
+}

+ 26 - 18
container/container_unix.go

@@ -14,7 +14,6 @@ import (
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/daemon/execdriver"
 	"github.com/docker/docker/daemon/execdriver"
-	derr "github.com/docker/docker/errors"
 	"github.com/docker/docker/pkg/chrootarchive"
 	"github.com/docker/docker/pkg/chrootarchive"
 	"github.com/docker/docker/pkg/symlink"
 	"github.com/docker/docker/pkg/symlink"
 	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/pkg/system"
@@ -34,6 +33,11 @@ import (
 // DefaultSHMSize is the default size (64MB) of the SHM which will be mounted in the container
 // DefaultSHMSize is the default size (64MB) of the SHM which will be mounted in the container
 const DefaultSHMSize int64 = 67108864
 const DefaultSHMSize int64 = 67108864
 
 
+var (
+	errInvalidEndpoint = fmt.Errorf("invalid endpoint while building port map info")
+	errInvalidNetwork  = fmt.Errorf("invalid network settings while building port map info")
+)
+
 // Container holds the fields specific to unixen implementations.
 // Container holds the fields specific to unixen implementations.
 // See CommonContainer for standard fields common to all containers.
 // See CommonContainer for standard fields common to all containers.
 type Container struct {
 type Container struct {
@@ -46,6 +50,7 @@ type Container struct {
 	ShmPath         string
 	ShmPath         string
 	ResolvConfPath  string
 	ResolvConfPath  string
 	SeccompProfile  string
 	SeccompProfile  string
+	NoNewPrivileges bool
 }
 }
 
 
 // CreateDaemonEnvironment returns the list of all environment variables given the list of
 // CreateDaemonEnvironment returns the list of all environment variables given the list of
@@ -116,12 +121,12 @@ func (container *Container) GetEndpointInNetwork(n libnetwork.Network) (libnetwo
 
 
 func (container *Container) buildPortMapInfo(ep libnetwork.Endpoint) error {
 func (container *Container) buildPortMapInfo(ep libnetwork.Endpoint) error {
 	if ep == nil {
 	if ep == nil {
-		return derr.ErrorCodeEmptyEndpoint
+		return errInvalidEndpoint
 	}
 	}
 
 
 	networkSettings := container.NetworkSettings
 	networkSettings := container.NetworkSettings
 	if networkSettings == nil {
 	if networkSettings == nil {
-		return derr.ErrorCodeEmptyNetwork
+		return errInvalidNetwork
 	}
 	}
 
 
 	if len(networkSettings.Ports) == 0 {
 	if len(networkSettings.Ports) == 0 {
@@ -151,7 +156,7 @@ func getEndpointPortMapInfo(ep libnetwork.Endpoint) (nat.PortMap, error) {
 			for _, tp := range exposedPorts {
 			for _, tp := range exposedPorts {
 				natPort, err := nat.NewPort(tp.Proto.String(), strconv.Itoa(int(tp.Port)))
 				natPort, err := nat.NewPort(tp.Proto.String(), strconv.Itoa(int(tp.Port)))
 				if err != nil {
 				if err != nil {
-					return pm, derr.ErrorCodeParsingPort.WithArgs(tp.Port, err)
+					return pm, fmt.Errorf("Error parsing Port value(%v):%v", tp.Port, err)
 				}
 				}
 				pm[natPort] = nil
 				pm[natPort] = nil
 			}
 			}
@@ -195,12 +200,12 @@ func getSandboxPortMapInfo(sb libnetwork.Sandbox) nat.PortMap {
 // BuildEndpointInfo sets endpoint-related fields on container.NetworkSettings based on the provided network and endpoint.
 // BuildEndpointInfo sets endpoint-related fields on container.NetworkSettings based on the provided network and endpoint.
 func (container *Container) BuildEndpointInfo(n libnetwork.Network, ep libnetwork.Endpoint) error {
 func (container *Container) BuildEndpointInfo(n libnetwork.Network, ep libnetwork.Endpoint) error {
 	if ep == nil {
 	if ep == nil {
-		return derr.ErrorCodeEmptyEndpoint
+		return errInvalidEndpoint
 	}
 	}
 
 
 	networkSettings := container.NetworkSettings
 	networkSettings := container.NetworkSettings
 	if networkSettings == nil {
 	if networkSettings == nil {
-		return derr.ErrorCodeEmptyNetwork
+		return errInvalidNetwork
 	}
 	}
 
 
 	epInfo := ep.Info()
 	epInfo := ep.Info()
@@ -285,7 +290,6 @@ func (container *Container) BuildJoinOptions(n libnetwork.Network) ([]libnetwork
 // BuildCreateEndpointOptions builds endpoint options from a given network.
 // BuildCreateEndpointOptions builds endpoint options from a given network.
 func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epConfig *network.EndpointSettings, sb libnetwork.Sandbox) ([]libnetwork.EndpointOption, error) {
 func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epConfig *network.EndpointSettings, sb libnetwork.Sandbox) ([]libnetwork.EndpointOption, error) {
 	var (
 	var (
-		portSpecs     = make(nat.PortSet)
 		bindings      = make(nat.PortMap)
 		bindings      = make(nat.PortMap)
 		pbList        []types.PortBinding
 		pbList        []types.PortBinding
 		exposeList    []types.TransportPort
 		exposeList    []types.TransportPort
@@ -338,10 +342,6 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC
 		return createOptions, nil
 		return createOptions, nil
 	}
 	}
 
 
-	if container.Config.ExposedPorts != nil {
-		portSpecs = container.Config.ExposedPorts
-	}
-
 	if container.HostConfig.PortBindings != nil {
 	if container.HostConfig.PortBindings != nil {
 		for p, b := range container.HostConfig.PortBindings {
 		for p, b := range container.HostConfig.PortBindings {
 			bindings[p] = []nat.PortBinding{}
 			bindings[p] = []nat.PortBinding{}
@@ -354,6 +354,7 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC
 		}
 		}
 	}
 	}
 
 
+	portSpecs := container.Config.ExposedPorts
 	ports := make([]nat.Port, len(portSpecs))
 	ports := make([]nat.Port, len(portSpecs))
 	var i int
 	var i int
 	for p := range portSpecs {
 	for p := range portSpecs {
@@ -377,7 +378,7 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC
 				portStart, portEnd, err = newP.Range()
 				portStart, portEnd, err = newP.Range()
 			}
 			}
 			if err != nil {
 			if err != nil {
-				return nil, derr.ErrorCodeHostPort.WithArgs(binding[i].HostPort, err)
+				return nil, fmt.Errorf("Error parsing HostPort value(%s):%v", binding[i].HostPort, err)
 			}
 			}
 			pbCopy.HostPort = uint16(portStart)
 			pbCopy.HostPort = uint16(portStart)
 			pbCopy.HostPortEnd = uint16(portEnd)
 			pbCopy.HostPortEnd = uint16(portEnd)
@@ -498,11 +499,6 @@ func (container *Container) ShmResourcePath() (string, error) {
 	return container.GetRootResourcePath("shm")
 	return container.GetRootResourcePath("shm")
 }
 }
 
 
-// MqueueResourcePath returns path to mqueue
-func (container *Container) MqueueResourcePath() (string, error) {
-	return container.GetRootResourcePath("mqueue")
-}
-
 // HasMountFor checks if path is a mountpoint
 // HasMountFor checks if path is a mountpoint
 func (container *Container) HasMountFor(path string) bool {
 func (container *Container) HasMountFor(path string) bool {
 	_, exists := container.MountPoints[path]
 	_, exists := container.MountPoints[path]
@@ -564,10 +560,11 @@ func updateCommand(c *execdriver.Command, resources containertypes.Resources) {
 	c.Resources.KernelMemory = resources.KernelMemory
 	c.Resources.KernelMemory = resources.KernelMemory
 }
 }
 
 
-// UpdateContainer updates resources of a container.
+// UpdateContainer updates configuration of a container.
 func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfig) error {
 func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfig) error {
 	container.Lock()
 	container.Lock()
 
 
+	// update resources of container
 	resources := hostConfig.Resources
 	resources := hostConfig.Resources
 	cResources := &container.HostConfig.Resources
 	cResources := &container.HostConfig.Resources
 	if resources.BlkioWeight != 0 {
 	if resources.BlkioWeight != 0 {
@@ -600,6 +597,11 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi
 	if resources.KernelMemory != 0 {
 	if resources.KernelMemory != 0 {
 		cResources.KernelMemory = resources.KernelMemory
 		cResources.KernelMemory = resources.KernelMemory
 	}
 	}
+
+	// update HostConfig of container
+	if hostConfig.RestartPolicy.Name != "" {
+		container.HostConfig.RestartPolicy = hostConfig.RestartPolicy
+	}
 	container.Unlock()
 	container.Unlock()
 
 
 	// If container is not running, update hostConfig struct is enough,
 	// If container is not running, update hostConfig struct is enough,
@@ -722,3 +724,9 @@ func (container *Container) TmpfsMounts() []execdriver.Mount {
 func cleanResourcePath(path string) string {
 func cleanResourcePath(path string) string {
 	return filepath.Join(string(os.PathSeparator), path)
 	return filepath.Join(string(os.PathSeparator), path)
 }
 }
+
+// canMountFS determines if the file system for the container
+// can be mounted locally. A no-op on non-Windows platforms
+func (container *Container) canMountFS() bool {
+	return true
+}

+ 25 - 3
container/container_windows.go

@@ -3,12 +3,13 @@
 package container
 package container
 
 
 import (
 import (
+	"fmt"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
 
 
 	"github.com/docker/docker/daemon/execdriver"
 	"github.com/docker/docker/daemon/execdriver"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
-	"github.com/docker/engine-api/types/container"
+	containertypes "github.com/docker/engine-api/types/container"
 )
 )
 
 
 // Container holds fields specific to the Windows implementation. See
 // Container holds fields specific to the Windows implementation. See
@@ -45,8 +46,22 @@ func (container *Container) TmpfsMounts() []execdriver.Mount {
 	return nil
 	return nil
 }
 }
 
 
-// UpdateContainer updates resources of a container
-func (container *Container) UpdateContainer(hostConfig *container.HostConfig) error {
+// UpdateContainer updates configuration of a container
+func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfig) error {
+	container.Lock()
+	defer container.Unlock()
+	resources := hostConfig.Resources
+	if resources.BlkioWeight != 0 || resources.CPUShares != 0 ||
+		resources.CPUPeriod != 0 || resources.CPUQuota != 0 ||
+		resources.CpusetCpus != "" || resources.CpusetMems != "" ||
+		resources.Memory != 0 || resources.MemorySwap != 0 ||
+		resources.MemoryReservation != 0 || resources.KernelMemory != 0 {
+		return fmt.Errorf("Resource updating isn't supported on Windows")
+	}
+	// update HostConfig of container
+	if hostConfig.RestartPolicy.Name != "" {
+		container.HostConfig.RestartPolicy = hostConfig.RestartPolicy
+	}
 	return nil
 	return nil
 }
 }
 
 
@@ -68,3 +83,10 @@ func cleanResourcePath(path string) string {
 	}
 	}
 	return filepath.Join(string(os.PathSeparator), path)
 	return filepath.Join(string(os.PathSeparator), path)
 }
 }
+
+// canMountFS determines if the file system for the container
+// can be mounted locally. In the case of Windows, this is not possible
+// for Hyper-V containers during WORKDIR execution for example.
+func (container *Container) canMountFS() bool {
+	return !containertypes.Isolation.IsHyperV(container.HostConfig.Isolation)
+}

+ 6 - 10
container/monitor.go

@@ -1,6 +1,7 @@
 package container
 package container
 
 
 import (
 import (
+	"fmt"
 	"io"
 	"io"
 	"os/exec"
 	"os/exec"
 	"strings"
 	"strings"
@@ -10,10 +11,8 @@ import (
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/daemon/execdriver"
 	"github.com/docker/docker/daemon/execdriver"
-	derr "github.com/docker/docker/errors"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
-	"github.com/docker/docker/utils"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
 )
 )
 
 
@@ -79,11 +78,11 @@ type containerMonitor struct {
 
 
 // StartMonitor initializes a containerMonitor for this container with the provided supervisor and restart policy
 // StartMonitor initializes a containerMonitor for this container with the provided supervisor and restart policy
 // and starts the container's process.
 // and starts the container's process.
-func (container *Container) StartMonitor(s supervisor, policy container.RestartPolicy) error {
+func (container *Container) StartMonitor(s supervisor) error {
 	container.monitor = &containerMonitor{
 	container.monitor = &containerMonitor{
 		supervisor:    s,
 		supervisor:    s,
 		container:     container,
 		container:     container,
-		restartPolicy: policy,
+		restartPolicy: container.HostConfig.RestartPolicy,
 		timeIncrement: defaultTimeIncrement,
 		timeIncrement: defaultTimeIncrement,
 		stopChan:      make(chan struct{}),
 		stopChan:      make(chan struct{}),
 		startSignal:   make(chan struct{}),
 		startSignal:   make(chan struct{}),
@@ -126,9 +125,6 @@ func (m *containerMonitor) Close() error {
 	// Cleanup networking and mounts
 	// Cleanup networking and mounts
 	m.supervisor.Cleanup(m.container)
 	m.supervisor.Cleanup(m.container)
 
 
-	// FIXME: here is race condition between two RUN instructions in Dockerfile
-	// because they share same runconfig and change image. Must be fixed
-	// in builder/builder.go
 	if err := m.container.ToDisk(); err != nil {
 	if err := m.container.ToDisk(); err != nil {
 		logrus.Errorf("Error dumping container %s state to disk: %s", m.container.ID, err)
 		logrus.Errorf("Error dumping container %s state to disk: %s", m.container.ID, err)
 
 
@@ -190,7 +186,7 @@ func (m *containerMonitor) start() error {
 				if m.container.RestartCount == 0 {
 				if m.container.RestartCount == 0 {
 					m.container.ExitCode = 127
 					m.container.ExitCode = 127
 					m.resetContainer(false)
 					m.resetContainer(false)
-					return derr.ErrorCodeCmdNotFound
+					return fmt.Errorf("Container command not found or does not exist.")
 				}
 				}
 			}
 			}
 			// set to 126 for container cmd can't be invoked errors
 			// set to 126 for container cmd can't be invoked errors
@@ -198,7 +194,7 @@ func (m *containerMonitor) start() error {
 				if m.container.RestartCount == 0 {
 				if m.container.RestartCount == 0 {
 					m.container.ExitCode = 126
 					m.container.ExitCode = 126
 					m.resetContainer(false)
 					m.resetContainer(false)
-					return derr.ErrorCodeCmdCouldNotBeInvoked
+					return fmt.Errorf("Container command could not be invoked.")
 				}
 				}
 			}
 			}
 
 
@@ -206,7 +202,7 @@ func (m *containerMonitor) start() error {
 				m.container.ExitCode = -1
 				m.container.ExitCode = -1
 				m.resetContainer(false)
 				m.resetContainer(false)
 
 
-				return derr.ErrorCodeCantStart.WithArgs(m.container.ID, utils.GetErrorMessage(err))
+				return fmt.Errorf("Cannot start container %s: %v", m.container.ID, err)
 			}
 			}
 
 
 			logrus.Errorf("Error running container: %s", err)
 			logrus.Errorf("Error running container: %s", err)

+ 7 - 7
container/state.go

@@ -6,7 +6,6 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/docker/docker/daemon/execdriver"
 	"github.com/docker/docker/daemon/execdriver"
-	derr "github.com/docker/docker/errors"
 	"github.com/docker/go-units"
 	"github.com/docker/go-units"
 )
 )
 
 
@@ -113,17 +112,17 @@ func wait(waitChan <-chan struct{}, timeout time.Duration) error {
 	}
 	}
 	select {
 	select {
 	case <-time.After(timeout):
 	case <-time.After(timeout):
-		return derr.ErrorCodeTimedOut.WithArgs(timeout)
+		return fmt.Errorf("Timed out: %v", timeout)
 	case <-waitChan:
 	case <-waitChan:
 		return nil
 		return nil
 	}
 	}
 }
 }
 
 
-// waitRunning waits until state is running. If state is already
+// WaitRunning waits until state is running. If state is already
 // running it returns immediately. If you want wait forever you must
 // running it returns immediately. If you want wait forever you must
 // supply negative timeout. Returns pid, that was passed to
 // supply negative timeout. Returns pid, that was passed to
 // SetRunning.
 // SetRunning.
-func (s *State) waitRunning(timeout time.Duration) (int, error) {
+func (s *State) WaitRunning(timeout time.Duration) (int, error) {
 	s.Lock()
 	s.Lock()
 	if s.Running {
 	if s.Running {
 		pid := s.Pid
 		pid := s.Pid
@@ -256,14 +255,15 @@ func (s *State) IsRestarting() bool {
 }
 }
 
 
 // SetRemovalInProgress sets the container state as being removed.
 // SetRemovalInProgress sets the container state as being removed.
-func (s *State) SetRemovalInProgress() error {
+// It returns true if the container was already in that state.
+func (s *State) SetRemovalInProgress() bool {
 	s.Lock()
 	s.Lock()
 	defer s.Unlock()
 	defer s.Unlock()
 	if s.RemovalInProgress {
 	if s.RemovalInProgress {
-		return derr.ErrorCodeAlreadyRemoving
+		return true
 	}
 	}
 	s.RemovalInProgress = true
 	s.RemovalInProgress = true
-	return nil
+	return false
 }
 }
 
 
 // ResetRemovalInProgress make the RemovalInProgress state to false.
 // ResetRemovalInProgress make the RemovalInProgress state to false.

+ 5 - 5
container/state_test.go

@@ -14,7 +14,7 @@ func TestStateRunStop(t *testing.T) {
 		started := make(chan struct{})
 		started := make(chan struct{})
 		var pid int64
 		var pid int64
 		go func() {
 		go func() {
-			runPid, _ := s.waitRunning(-1 * time.Second)
+			runPid, _ := s.WaitRunning(-1 * time.Second)
 			atomic.StoreInt64(&pid, int64(runPid))
 			atomic.StoreInt64(&pid, int64(runPid))
 			close(started)
 			close(started)
 		}()
 		}()
@@ -41,8 +41,8 @@ func TestStateRunStop(t *testing.T) {
 		if runPid != i+100 {
 		if runPid != i+100 {
 			t.Fatalf("Pid %v, expected %v", runPid, i+100)
 			t.Fatalf("Pid %v, expected %v", runPid, i+100)
 		}
 		}
-		if pid, err := s.waitRunning(-1 * time.Second); err != nil || pid != i+100 {
-			t.Fatalf("waitRunning returned pid: %v, err: %v, expected pid: %v, err: %v", pid, err, i+100, nil)
+		if pid, err := s.WaitRunning(-1 * time.Second); err != nil || pid != i+100 {
+			t.Fatalf("WaitRunning returned pid: %v, err: %v, expected pid: %v, err: %v", pid, err, i+100, nil)
 		}
 		}
 
 
 		stopped := make(chan struct{})
 		stopped := make(chan struct{})
@@ -82,7 +82,7 @@ func TestStateTimeoutWait(t *testing.T) {
 	s := NewState()
 	s := NewState()
 	started := make(chan struct{})
 	started := make(chan struct{})
 	go func() {
 	go func() {
-		s.waitRunning(100 * time.Millisecond)
+		s.WaitRunning(100 * time.Millisecond)
 		close(started)
 		close(started)
 	}()
 	}()
 	select {
 	select {
@@ -98,7 +98,7 @@ func TestStateTimeoutWait(t *testing.T) {
 
 
 	stopped := make(chan struct{})
 	stopped := make(chan struct{})
 	go func() {
 	go func() {
-		s.waitRunning(100 * time.Millisecond)
+		s.WaitRunning(100 * time.Millisecond)
 		close(stopped)
 		close(stopped)
 	}()
 	}()
 	select {
 	select {

+ 0 - 0
contrib/README → contrib/README.md


+ 3 - 5
contrib/apparmor/main.go

@@ -11,8 +11,7 @@ import (
 )
 )
 
 
 type profileData struct {
 type profileData struct {
-	MajorVersion int
-	MinorVersion int
+	Version int
 }
 }
 
 
 func main() {
 func main() {
@@ -23,13 +22,12 @@ func main() {
 	// parse the arg
 	// parse the arg
 	apparmorProfilePath := os.Args[1]
 	apparmorProfilePath := os.Args[1]
 
 
-	majorVersion, minorVersion, err := aaparser.GetVersion()
+	version, err := aaparser.GetVersion()
 	if err != nil {
 	if err != nil {
 		log.Fatal(err)
 		log.Fatal(err)
 	}
 	}
 	data := profileData{
 	data := profileData{
-		MajorVersion: majorVersion,
-		MinorVersion: minorVersion,
+		Version: version,
 	}
 	}
 	fmt.Printf("apparmor_parser is of version %+v\n", data)
 	fmt.Printf("apparmor_parser is of version %+v\n", data)
 
 

+ 16 - 16
contrib/apparmor/template.go

@@ -20,11 +20,11 @@ profile /usr/bin/docker (attach_disconnected, complain) {
 
 
   umount,
   umount,
   pivot_root,
   pivot_root,
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
+{{if ge .Version 209000}}
   signal (receive) peer=@{profile_name},
   signal (receive) peer=@{profile_name},
   signal (receive) peer=unconfined,
   signal (receive) peer=unconfined,
   signal (send),
   signal (send),
-{{end}}{{end}}
+{{end}}
   network,
   network,
   capability,
   capability,
   owner /** rw,
   owner /** rw,
@@ -46,12 +46,12 @@ profile /usr/bin/docker (attach_disconnected, complain) {
   /etc/ld.so.cache r,
   /etc/ld.so.cache r,
   /etc/passwd r,
   /etc/passwd r,
 
 
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
+{{if ge .Version 209000}}
   ptrace peer=@{profile_name},
   ptrace peer=@{profile_name},
   ptrace (read) peer=docker-default,
   ptrace (read) peer=docker-default,
   deny ptrace (trace) peer=docker-default,
   deny ptrace (trace) peer=docker-default,
   deny ptrace peer=/usr/bin/docker///bin/ps,
   deny ptrace peer=/usr/bin/docker///bin/ps,
-{{end}}{{end}}
+{{end}}
 
 
   /usr/lib/** rm,
   /usr/lib/** rm,
   /lib/** rm,
   /lib/** rm,
@@ -72,11 +72,11 @@ profile /usr/bin/docker (attach_disconnected, complain) {
   /sbin/zfs rCx,
   /sbin/zfs rCx,
   /sbin/apparmor_parser rCx,
   /sbin/apparmor_parser rCx,
 
 
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
+{{if ge .Version 209000}}
   # Transitions
   # Transitions
   change_profile -> docker-*,
   change_profile -> docker-*,
   change_profile -> unconfined,
   change_profile -> unconfined,
-{{end}}{{end}}
+{{end}}
 
 
   profile /bin/cat (complain) {
   profile /bin/cat (complain) {
     /etc/ld.so.cache r,
     /etc/ld.so.cache r,
@@ -98,10 +98,10 @@ profile /usr/bin/docker (attach_disconnected, complain) {
     /dev/null rw,
     /dev/null rw,
     /bin/ps mr,
     /bin/ps mr,
 
 
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
+{{if ge .Version 209000}}
     # We don't need ptrace so we'll deny and ignore the error.
     # We don't need ptrace so we'll deny and ignore the error.
     deny ptrace (read, trace),
     deny ptrace (read, trace),
-{{end}}{{end}}
+{{end}}
 
 
     # Quiet dac_override denials
     # Quiet dac_override denials
     deny capability dac_override,
     deny capability dac_override,
@@ -119,15 +119,15 @@ profile /usr/bin/docker (attach_disconnected, complain) {
     /proc/tty/drivers r,
     /proc/tty/drivers r,
   }
   }
   profile /sbin/iptables (complain) {
   profile /sbin/iptables (complain) {
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
+{{if ge .Version 209000}}
     signal (receive) peer=/usr/bin/docker,
     signal (receive) peer=/usr/bin/docker,
-{{end}}{{end}}
+{{end}}
     capability net_admin,
     capability net_admin,
   }
   }
   profile /sbin/auplink flags=(attach_disconnected, complain) {
   profile /sbin/auplink flags=(attach_disconnected, complain) {
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
+{{if ge .Version 209000}}
     signal (receive) peer=/usr/bin/docker,
     signal (receive) peer=/usr/bin/docker,
-{{end}}{{end}}
+{{end}}
     capability sys_admin,
     capability sys_admin,
     capability dac_override,
     capability dac_override,
 
 
@@ -146,9 +146,9 @@ profile /usr/bin/docker (attach_disconnected, complain) {
     /proc/[0-9]*/mounts rw,
     /proc/[0-9]*/mounts rw,
   }
   }
   profile /sbin/modprobe /bin/kmod (complain) {
   profile /sbin/modprobe /bin/kmod (complain) {
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
+{{if ge .Version 209000}}
     signal (receive) peer=/usr/bin/docker,
     signal (receive) peer=/usr/bin/docker,
-{{end}}{{end}}
+{{end}}
     capability sys_module,
     capability sys_module,
     /etc/ld.so.cache r,
     /etc/ld.so.cache r,
     /lib/** rm,
     /lib/** rm,
@@ -162,9 +162,9 @@ profile /usr/bin/docker (attach_disconnected, complain) {
   }
   }
   # xz works via pipes, so we do not need access to the filesystem.
   # xz works via pipes, so we do not need access to the filesystem.
   profile /usr/bin/xz (complain) {
   profile /usr/bin/xz (complain) {
-{{if ge .MajorVersion 2}}{{if ge .MinorVersion 9}}
+{{if ge .Version 209000}}
     signal (receive) peer=/usr/bin/docker,
     signal (receive) peer=/usr/bin/docker,
-{{end}}{{end}}
+{{end}}
     /etc/ld.so.cache r,
     /etc/ld.so.cache r,
     /lib/** rm,
     /lib/** rm,
     /usr/bin/xz rm,
     /usr/bin/xz rm,

+ 16 - 0
contrib/check-config.sh

@@ -115,6 +115,17 @@ check_device() {
 	fi
 	fi
 }
 }
 
 
+check_distro_userns() {
+	source /etc/os-release 2>/dev/null || /bin/true
+	if [[ "${ID}" =~ ^(centos|rhel)$ && "${VERSION_ID}" =~ ^7 ]]; then
+		# this is a CentOS7 or RHEL7 system
+		grep -q "user_namespace.enable=1" /proc/cmdline || {
+			# no user namespace support enabled
+			wrap_bad "  (RHEL7/CentOS7" "User namespaces disabled; add 'user_namespace.enable=1' to boot command line)"
+		}
+	fi
+}
+
 if [ ! -e "$CONFIG" ]; then
 if [ ! -e "$CONFIG" ]; then
 	wrap_warning "warning: $CONFIG does not exist, searching other paths for kernel config ..."
 	wrap_warning "warning: $CONFIG does not exist, searching other paths for kernel config ..."
 	for tryConfig in "${possibleConfigs[@]}"; do
 	for tryConfig in "${possibleConfigs[@]}"; do
@@ -171,6 +182,7 @@ flags=(
 	NAMESPACES {NET,PID,IPC,UTS}_NS
 	NAMESPACES {NET,PID,IPC,UTS}_NS
 	DEVPTS_MULTIPLE_INSTANCES
 	DEVPTS_MULTIPLE_INSTANCES
 	CGROUPS CGROUP_CPUACCT CGROUP_DEVICE CGROUP_FREEZER CGROUP_SCHED CPUSETS MEMCG
 	CGROUPS CGROUP_CPUACCT CGROUP_DEVICE CGROUP_FREEZER CGROUP_SCHED CPUSETS MEMCG
+	KEYS
 	MACVLAN VETH BRIDGE BRIDGE_NETFILTER
 	MACVLAN VETH BRIDGE BRIDGE_NETFILTER
 	NF_NAT_IPV4 IP_NF_FILTER IP_NF_TARGET_MASQUERADE
 	NF_NAT_IPV4 IP_NF_FILTER IP_NF_TARGET_MASQUERADE
 	NETFILTER_XT_MATCH_{ADDRTYPE,CONNTRACK}
 	NETFILTER_XT_MATCH_{ADDRTYPE,CONNTRACK}
@@ -185,10 +197,14 @@ echo
 echo 'Optional Features:'
 echo 'Optional Features:'
 {
 {
 	check_flags USER_NS
 	check_flags USER_NS
+	check_distro_userns
 }
 }
 {
 {
 	check_flags SECCOMP
 	check_flags SECCOMP
 }
 }
+{
+	check_flags CGROUP_PIDS
+}
 {
 {
 	check_flags MEMCG_KMEM MEMCG_SWAP MEMCG_SWAP_ENABLED
 	check_flags MEMCG_KMEM MEMCG_SWAP MEMCG_SWAP_ENABLED
 	if  is_set MEMCG_SWAP && ! is_set MEMCG_SWAP_ENABLED; then
 	if  is_set MEMCG_SWAP && ! is_set MEMCG_SWAP_ENABLED; then

+ 33 - 16
contrib/completion/bash/docker

@@ -395,7 +395,9 @@ __docker_complete_isolation() {
 __docker_complete_log_drivers() {
 __docker_complete_log_drivers() {
 	COMPREPLY=( $( compgen -W "
 	COMPREPLY=( $( compgen -W "
 		awslogs
 		awslogs
+		etwlogs
 		fluentd
 		fluentd
+		gcplogs
 		gelf
 		gelf
 		journald
 		journald
 		json-file
 		json-file
@@ -409,13 +411,14 @@ __docker_complete_log_options() {
 	# see docs/reference/logging/index.md
 	# see docs/reference/logging/index.md
 	local awslogs_options="awslogs-region awslogs-group awslogs-stream"
 	local awslogs_options="awslogs-region awslogs-group awslogs-stream"
 	local fluentd_options="env fluentd-address labels tag"
 	local fluentd_options="env fluentd-address labels tag"
+	local gcplogs_options="env gcp-log-cmd gcp-project labels"
 	local gelf_options="env gelf-address labels tag"
 	local gelf_options="env gelf-address labels tag"
 	local journald_options="env labels tag"
 	local journald_options="env labels tag"
 	local json_file_options="env labels max-file max-size"
 	local json_file_options="env labels max-file max-size"
 	local syslog_options="syslog-address syslog-tls-ca-cert syslog-tls-cert syslog-tls-key syslog-tls-skip-verify syslog-facility tag"
 	local syslog_options="syslog-address syslog-tls-ca-cert syslog-tls-cert syslog-tls-key syslog-tls-skip-verify syslog-facility tag"
 	local splunk_options="env labels splunk-caname splunk-capath splunk-index splunk-insecureskipverify splunk-source splunk-sourcetype splunk-token splunk-url tag"
 	local splunk_options="env labels splunk-caname splunk-capath splunk-index splunk-insecureskipverify splunk-source splunk-sourcetype splunk-token splunk-url tag"
 
 
-	local all_options="$fluentd_options $gelf_options $journald_options $json_file_options $syslog_options $splunk_options"
+	local all_options="$fluentd_options $gcplogs_options $gelf_options $journald_options $json_file_options $syslog_options $splunk_options"
 
 
 	case $(__docker_value_of_option --log-driver) in
 	case $(__docker_value_of_option --log-driver) in
 		'')
 		'')
@@ -427,6 +430,9 @@ __docker_complete_log_options() {
 		fluentd)
 		fluentd)
 			COMPREPLY=( $( compgen -W "$fluentd_options" -S = -- "$cur" ) )
 			COMPREPLY=( $( compgen -W "$fluentd_options" -S = -- "$cur" ) )
 			;;
 			;;
+		gcplogs)
+			COMPREPLY=( $( compgen -W "$gcplogs_options" -S = -- "$cur" ) )
+			;;
 		gelf)
 		gelf)
 			COMPREPLY=( $( compgen -W "$gelf_options" -S = -- "$cur" ) )
 			COMPREPLY=( $( compgen -W "$gelf_options" -S = -- "$cur" ) )
 			;;
 			;;
@@ -515,6 +521,22 @@ __docker_complete_log_levels() {
 	COMPREPLY=( $( compgen -W "debug info warn error fatal" -- "$cur" ) )
 	COMPREPLY=( $( compgen -W "debug info warn error fatal" -- "$cur" ) )
 }
 }
 
 
+__docker_complete_restart() {
+	case "$prev" in
+		--restart)
+			case "$cur" in
+				on-failure:*)
+					;;
+				*)
+					COMPREPLY=( $( compgen -W "always no on-failure on-failure: unless-stopped" -- "$cur") )
+					;;
+			esac
+			return
+			;;
+	esac
+	return 1
+}
+
 # a selection of the available signals that is most likely of interest in the
 # a selection of the available signals that is most likely of interest in the
 # context of docker containers.
 # context of docker containers.
 __docker_complete_signals() {
 __docker_complete_signals() {
@@ -794,7 +816,7 @@ _docker_daemon() {
  			return
  			return
  			;;
  			;;
  	esac
  	esac
- 
+
  	local key=$(__docker_map_key_of_current_option '--storage-opt')
  	local key=$(__docker_map_key_of_current_option '--storage-opt')
  	case "$key" in
  	case "$key" in
  		dm.@(blkdiscard|override_udev_sync_check|use_deferred_@(removal|deletion)))
  		dm.@(blkdiscard|override_udev_sync_check|use_deferred_@(removal|deletion)))
@@ -1188,14 +1210,14 @@ _docker_load() {
 
 
 _docker_login() {
 _docker_login() {
 	case "$prev" in
 	case "$prev" in
-		--email|-e|--password|-p|--username|-u)
+		--password|-p|--username|-u)
 			return
 			return
 			;;
 			;;
 	esac
 	esac
 
 
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
-			COMPREPLY=( $( compgen -W "--email -e --help --password -p --username -u" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--help --password -p --username -u" -- "$cur" ) )
 			;;
 			;;
 	esac
 	esac
 }
 }
@@ -1275,7 +1297,7 @@ _docker_network_connect() {
 
 
 _docker_network_create() {
 _docker_network_create() {
 	case "$prev" in
 	case "$prev" in
-		--aux-address|--gateway|--ip-range|--ipam-opt|--opt|-o|--subnet)
+		--aux-address|--gateway|--internal|--ip-range|--ipam-opt|--ipv6|--opt|-o|--subnet)
 			return
 			return
 			;;
 			;;
 		--ipam-driver)
 		--ipam-driver)
@@ -1294,7 +1316,7 @@ _docker_network_create() {
 
 
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
-			COMPREPLY=( $( compgen -W "--aux-address --driver -d --gateway --help --internal --ip-range --ipam-driver --ipam-opt --opt -o --subnet" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--aux-address --driver -d --gateway --help --internal --ip-range --ipam-driver --ipam-opt --ipv6 --opt -o --subnet" -- "$cur" ) )
 			;;
 			;;
 	esac
 	esac
 }
 }
@@ -1615,6 +1637,7 @@ _docker_run() {
 		--net-alias
 		--net-alias
 		--oom-score-adj
 		--oom-score-adj
 		--pid
 		--pid
+		--pids-limit
 		--publish -p
 		--publish -p
 		--restart
 		--restart
 		--security-opt
 		--security-opt
@@ -1657,6 +1680,7 @@ _docker_run() {
 
 
 
 
 	__docker_complete_log_driver_options && return
 	__docker_complete_log_driver_options && return
+	__docker_complete_restart && return
 
 
 	case "$prev" in
 	case "$prev" in
 		--add-host)
 		--add-host)
@@ -1754,16 +1778,6 @@ _docker_run() {
 			esac
 			esac
 			return
 			return
 			;;
 			;;
-		--restart)
-			case "$cur" in
-				on-failure:*)
-					;;
-				*)
-					COMPREPLY=( $( compgen -W "always no on-failure on-failure: unless-stopped" -- "$cur") )
-					;;
-			esac
-			return
-			;;
 		--security-opt)
 		--security-opt)
 			case "$cur" in
 			case "$cur" in
 				label:*:*)
 				label:*:*)
@@ -1938,6 +1952,7 @@ _docker_update() {
 		--memory -m
 		--memory -m
 		--memory-reservation
 		--memory-reservation
 		--memory-swap
 		--memory-swap
+		--restart
 	"
 	"
 
 
 	local boolean_options="
 	local boolean_options="
@@ -1946,6 +1961,8 @@ _docker_update() {
 
 
 	local all_options="$options_with_args $boolean_options"
 	local all_options="$options_with_args $boolean_options"
 
 
+	__docker_complete_restart && return
+
 	case "$prev" in
 	case "$prev" in
 		$(__docker_to_extglob "$options_with_args") )
 		$(__docker_to_extglob "$options_with_args") )
 			return
 			return

+ 2 - 4
contrib/completion/fish/docker.fish

@@ -221,8 +221,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from load' -l help -d 'Print
 complete -c docker -A -f -n '__fish_seen_subcommand_from load' -s i -l input -d 'Read from a tar archive file, instead of STDIN'
 complete -c docker -A -f -n '__fish_seen_subcommand_from load' -s i -l input -d 'Read from a tar archive file, instead of STDIN'
 
 
 # login
 # login
-complete -c docker -f -n '__fish_docker_no_subcommand' -a login -d 'Register or log in to a Docker registry server'
-complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s e -l email -d 'Email'
+complete -c docker -f -n '__fish_docker_no_subcommand' -a login -d 'Log in to a Docker registry server'
 complete -c docker -A -f -n '__fish_seen_subcommand_from login' -l help -d 'Print usage'
 complete -c docker -A -f -n '__fish_seen_subcommand_from login' -l help -d 'Print usage'
 complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s p -l password -d 'Password'
 complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s p -l password -d 'Password'
 complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s u -l username -d 'Username'
 complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s u -l username -d 'Username'
@@ -290,6 +289,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -l help -d 'Print u
 complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -s l -l link -d 'Remove the specified link and not the underlying container'
 complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -s l -l link -d 'Remove the specified link and not the underlying container'
 complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -s v -l volumes -d 'Remove the volumes associated with the container'
 complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -s v -l volumes -d 'Remove the volumes associated with the container'
 complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -a '(__fish_print_docker_containers stopped)' -d "Container"
 complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -a '(__fish_print_docker_containers stopped)' -d "Container"
+complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -s f -l force -a '(__fish_print_docker_containers all)' -d "Container"
 
 
 # rmi
 # rmi
 complete -c docker -f -n '__fish_docker_no_subcommand' -a rmi -d 'Remove one or more images'
 complete -c docker -f -n '__fish_docker_no_subcommand' -a rmi -d 'Remove one or more images'
@@ -398,5 +398,3 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -a version -d 'Show the D
 complete -c docker -f -n '__fish_docker_no_subcommand' -a wait -d 'Block until a container stops, then print its exit code'
 complete -c docker -f -n '__fish_docker_no_subcommand' -a wait -d 'Block until a container stops, then print its exit code'
 complete -c docker -A -f -n '__fish_seen_subcommand_from wait' -l help -d 'Print usage'
 complete -c docker -A -f -n '__fish_seen_subcommand_from wait' -l help -d 'Print usage'
 complete -c docker -A -f -n '__fish_seen_subcommand_from wait' -a '(__fish_print_docker_containers running)' -d "Container"
 complete -c docker -A -f -n '__fish_seen_subcommand_from wait' -a '(__fish_print_docker_containers running)' -d "Container"
-
-

+ 35 - 32
contrib/completion/zsh/_docker

@@ -201,6 +201,7 @@ __docker_get_log_options() {
 
 
     awslogs_options=("awslogs-region" "awslogs-group" "awslogs-stream")
     awslogs_options=("awslogs-region" "awslogs-group" "awslogs-stream")
     fluentd_options=("env" "fluentd-address" "labels" "tag")
     fluentd_options=("env" "fluentd-address" "labels" "tag")
+    gcplogs_options=("env" "gcp-log-cmd" "gcp-project" "labels")
     gelf_options=("env" "gelf-address" "labels" "tag")
     gelf_options=("env" "gelf-address" "labels" "tag")
     journald_options=("env" "labels")
     journald_options=("env" "labels")
     json_file_options=("env" "labels" "max-file" "max-size")
     json_file_options=("env" "labels" "max-file" "max-size")
@@ -209,6 +210,7 @@ __docker_get_log_options() {
 
 
     [[ $log_driver = (awslogs|all) ]] && _describe -t awslogs-options "awslogs options" awslogs_options "$@" && ret=0
     [[ $log_driver = (awslogs|all) ]] && _describe -t awslogs-options "awslogs options" awslogs_options "$@" && ret=0
     [[ $log_driver = (fluentd|all) ]] && _describe -t fluentd-options "fluentd options" fluentd_options "$@" && ret=0
     [[ $log_driver = (fluentd|all) ]] && _describe -t fluentd-options "fluentd options" fluentd_options "$@" && ret=0
+    [[ $log_driver = (gcplogs|all) ]] && _describe -t gcplogs-options "gcplogs options" gcplogs_options "$@" && ret=0
     [[ $log_driver = (gelf|all) ]] && _describe -t gelf-options "gelf options" gelf_options "$@" && ret=0
     [[ $log_driver = (gelf|all) ]] && _describe -t gelf-options "gelf options" gelf_options "$@" && ret=0
     [[ $log_driver = (journald|all) ]] && _describe -t journald-options "journald options" journald_options "$@" && ret=0
     [[ $log_driver = (journald|all) ]] && _describe -t journald-options "journald options" journald_options "$@" && ret=0
     [[ $log_driver = (json-file|all) ]] && _describe -t json-file-options "json-file options" json_file_options "$@" && ret=0
     [[ $log_driver = (json-file|all) ]] && _describe -t json-file-options "json-file options" json_file_options "$@" && ret=0
@@ -325,14 +327,15 @@ __docker_network_subcommand() {
         (create)
         (create)
             _arguments $(__docker_arguments) -A '-*' \
             _arguments $(__docker_arguments) -A '-*' \
                 $opts_help \
                 $opts_help \
-                "($help)*--aux-address[Auxiliary ipv4 or ipv6 addresses used by network driver]:key=IP: " \
+                "($help)*--aux-address[Auxiliary IPv4 or IPv6 addresses used by network driver]:key=IP: " \
                 "($help -d --driver)"{-d=,--driver=}"[Driver to manage the Network]:driver:(null host bridge overlay)" \
                 "($help -d --driver)"{-d=,--driver=}"[Driver to manage the Network]:driver:(null host bridge overlay)" \
-                "($help)*--gateway=[ipv4 or ipv6 Gateway for the master subnet]:IP: " \
+                "($help)*--gateway=[IPv4 or IPv6 Gateway for the master subnet]:IP: " \
                 "($help)--internal[Restricts external access to the network]" \
                 "($help)--internal[Restricts external access to the network]" \
                 "($help)*--ip-range=[Allocate container ip from a sub-range]:IP/mask: " \
                 "($help)*--ip-range=[Allocate container ip from a sub-range]:IP/mask: " \
                 "($help)--ipam-driver=[IP Address Management Driver]:driver:(default)" \
                 "($help)--ipam-driver=[IP Address Management Driver]:driver:(default)" \
-                "($help)*--ipam-opt=[Set custom IPAM plugin options]:opt=value: " \
-                "($help)*"{-o=,--opt=}"[Set driver specific options]:opt=value: " \
+                "($help)*--ipam-opt=[Custom IPAM plugin options]:opt=value: " \
+                "($help)--ipv6[Enable IPv6 networking]" \
+                "($help)*"{-o=,--opt=}"[Driver specific options]:opt=value: " \
                 "($help)*--subnet=[Subnet in CIDR format that represents a network segment]:IP/mask: " \
                 "($help)*--subnet=[Subnet in CIDR format that represents a network segment]:IP/mask: " \
                 "($help -)1:Network Name: " && ret=0
                 "($help -)1:Network Name: " && ret=0
             ;;
             ;;
@@ -421,9 +424,9 @@ __docker_volume_subcommand() {
         (create)
         (create)
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
-                "($help -d --driver)"{-d=,--driver=}"[Specify volume driver name]:Driver name:(local)" \
-                "($help)--name=[Specify volume name]" \
-                "($help)*"{-o=,--opt=}"[Set driver specific options]:Driver option: " && ret=0
+                "($help -d --driver)"{-d=,--driver=}"[Volume driver name]:Driver name:(local)" \
+                "($help)--name=[Volume name]" \
+                "($help)*"{-o=,--opt=}"[Driver specific options]:Driver option: " && ret=0
             ;;
             ;;
         (inspect)
         (inspect)
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
@@ -483,8 +486,8 @@ __docker_subcommand() {
     opts_help=("(: -)--help[Print usage]")
     opts_help=("(: -)--help[Print usage]")
     opts_build_create_run=(
     opts_build_create_run=(
         "($help)--cgroup-parent=[Parent cgroup for the container]:cgroup: "
         "($help)--cgroup-parent=[Parent cgroup for the container]:cgroup: "
-        "($help)--isolation=[]:isolation:(default hyperv process)"
-        "($help)*--shm-size=[Size of '/dev/shm'. The format is '<number><unit>'. Default is '64m'.]:shm size: "
+        "($help)--isolation=[Container isolation technology]:isolation:(default hyperv process)"
+        "($help)*--shm-size=[Size of '/dev/shm' (format is '<number><unit>')]:shm size: "
         "($help)*--ulimit=[ulimit options]:ulimit: "
         "($help)*--ulimit=[ulimit options]:ulimit: "
     )
     )
     opts_build_create_run_update=(
     opts_build_create_run_update=(
@@ -508,10 +511,10 @@ __docker_subcommand() {
         "($help)*--device-read-iops=[Limit the read rate (IO per second) from a device]:device:IO rate: "
         "($help)*--device-read-iops=[Limit the read rate (IO per second) from a device]:device:IO rate: "
         "($help)*--device-write-bps=[Limit the write rate (bytes per second) to a device]:device:IO rate: "
         "($help)*--device-write-bps=[Limit the write rate (bytes per second) to a device]:device:IO rate: "
         "($help)*--device-write-iops=[Limit the write rate (IO per second) to a device]:device:IO rate: "
         "($help)*--device-write-iops=[Limit the write rate (IO per second) to a device]:device:IO rate: "
-        "($help)*--dns=[Set custom DNS servers]:DNS server: "
-        "($help)*--dns-opt=[Set custom DNS options]:DNS option: "
-        "($help)*--dns-search=[Set custom DNS search domains]:DNS domains: "
-        "($help)*"{-e=,--env=}"[Set environment variables]:environment variable: "
+        "($help)*--dns=[Custom DNS servers]:DNS server: "
+        "($help)*--dns-opt=[Custom DNS options]:DNS option: "
+        "($help)*--dns-search=[Custom DNS search domains]:DNS domains: "
+        "($help)*"{-e=,--env=}"[Environment variables]:environment variable: "
         "($help)--entrypoint=[Overwrite the default entrypoint of the image]:entry point: "
         "($help)--entrypoint=[Overwrite the default entrypoint of the image]:entry point: "
         "($help)*--env-file=[Read environment variables from a file]:environment file:_files"
         "($help)*--env-file=[Read environment variables from a file]:environment file:_files"
         "($help)*--expose=[Expose a port from the container without publishing it]: "
         "($help)*--expose=[Expose a port from the container without publishing it]: "
@@ -522,7 +525,7 @@ __docker_subcommand() {
         "($help)--ip6=[Container IPv6 address]:IPv6: "
         "($help)--ip6=[Container IPv6 address]:IPv6: "
         "($help)--ipc=[IPC namespace to use]:IPC namespace: "
         "($help)--ipc=[IPC namespace to use]:IPC namespace: "
         "($help)*--link=[Add link to another container]:link:->link"
         "($help)*--link=[Add link to another container]:link:->link"
-        "($help)*"{-l=,--label=}"[Set meta data on a container]:label: "
+        "($help)*"{-l=,--label=}"[Container metadata]:label: "
         "($help)--log-driver=[Default driver for container logs]:Logging driver:(json-file syslog journald gelf fluentd awslogs splunk none)"
         "($help)--log-driver=[Default driver for container logs]:Logging driver:(json-file syslog journald gelf fluentd awslogs splunk none)"
         "($help)*--log-opt=[Log driver specific options]:log driver options:__docker_log_options"
         "($help)*--log-opt=[Log driver specific options]:log driver options:__docker_log_options"
         "($help)--mac-address=[Container MAC address]:MAC address: "
         "($help)--mac-address=[Container MAC address]:MAC address: "
@@ -531,6 +534,7 @@ __docker_subcommand() {
         "($help)*--net-alias=[Add network-scoped alias for the container]:alias: "
         "($help)*--net-alias=[Add network-scoped alias for the container]:alias: "
         "($help)--oom-kill-disable[Disable OOM Killer]"
         "($help)--oom-kill-disable[Disable OOM Killer]"
         "($help)--oom-score-adj[Tune the host's OOM preferences for containers (accepts -1000 to 1000)]"
         "($help)--oom-score-adj[Tune the host's OOM preferences for containers (accepts -1000 to 1000)]"
+        "($help)--pids-limit[Tune container pids limit (set -1 for unlimited)]"
         "($help -P --publish-all)"{-P,--publish-all}"[Publish all exposed ports]"
         "($help -P --publish-all)"{-P,--publish-all}"[Publish all exposed ports]"
         "($help)*"{-p=,--publish=}"[Expose a container's port to the host]:port:_ports"
         "($help)*"{-p=,--publish=}"[Expose a container's port to the host]:port:_ports"
         "($help)--pid=[PID namespace to use]:PID: "
         "($help)--pid=[PID namespace to use]:PID: "
@@ -548,11 +552,11 @@ __docker_subcommand() {
     )
     )
     opts_create_run_update=(
     opts_create_run_update=(
         "($help)--blkio-weight=[Block IO (relative weight), between 10 and 1000]:Block IO weight:(10 100 500 1000)"
         "($help)--blkio-weight=[Block IO (relative weight), between 10 and 1000]:Block IO weight:(10 100 500 1000)"
-        "($help)--kernel-memory=[Kernel memory limit in bytes.]:Memory limit: "
+        "($help)--kernel-memory=[Kernel memory limit in bytes]:Memory limit: "
         "($help)--memory-reservation=[Memory soft limit]:Memory limit: "
         "($help)--memory-reservation=[Memory soft limit]:Memory limit: "
     )
     )
     opts_attach_exec_run_start=(
     opts_attach_exec_run_start=(
-        "($help)--detach-keys=[Specify the escape key sequence used to detach a container]:sequence:__docker_complete_detach_keys"
+        "($help)--detach-keys=[Escape key sequence used to detach a container]:sequence:__docker_complete_detach_keys"
     )
     )
 
 
     case "$words[1]" in
     case "$words[1]" in
@@ -569,7 +573,7 @@ __docker_subcommand() {
                 $opts_help \
                 $opts_help \
                 $opts_build_create_run \
                 $opts_build_create_run \
                 $opts_build_create_run_update \
                 $opts_build_create_run_update \
-                "($help)*--build-arg[Set build-time variables]:<varname>=<value>: " \
+                "($help)*--build-arg[Build-time variables]:<varname>=<value>: " \
                 "($help -f --file)"{-f=,--file=}"[Name of the Dockerfile]:Dockerfile:_files" \
                 "($help -f --file)"{-f=,--file=}"[Name of the Dockerfile]:Dockerfile:_files" \
                 "($help)--force-rm[Always remove intermediate containers]" \
                 "($help)--force-rm[Always remove intermediate containers]" \
                 "($help)--no-cache[Do not use cache when building the image]" \
                 "($help)--no-cache[Do not use cache when building the image]" \
@@ -592,7 +596,7 @@ __docker_subcommand() {
         (cp)
         (cp)
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
-                "($help -L --follow-link)"{-L,--follow-link}"[Always follow symbol link in SRC_PATH]" \
+                "($help -L --follow-link)"{-L,--follow-link}"[Always follow symbol link]" \
                 "($help -)1:container:->container" \
                 "($help -)1:container:->container" \
                 "($help -)2:hostpath:_files" && ret=0
                 "($help -)2:hostpath:_files" && ret=0
             case $state in
             case $state in
@@ -630,23 +634,23 @@ __docker_subcommand() {
         (daemon)
         (daemon)
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
-                "($help)--api-cors-header=[Set CORS headers in the remote API]:CORS headers: " \
-                "($help)*--authorization-plugin=[Set authorization plugins to load]" \
+                "($help)--api-cors-header=[CORS headers in the remote API]:CORS headers: " \
+                "($help)*--authorization-plugin=[Authorization plugins to load]" \
                 "($help -b --bridge)"{-b=,--bridge=}"[Attach containers to a network bridge]:bridge:_net_interfaces" \
                 "($help -b --bridge)"{-b=,--bridge=}"[Attach containers to a network bridge]:bridge:_net_interfaces" \
-                "($help)--bip=[Specify network bridge IP]" \
-                "($help)--cgroup-parent=[Set parent cgroup for all containers]:cgroup: " \
+                "($help)--bip=[Network bridge IP]:IP address: " \
+                "($help)--cgroup-parent=[Parent cgroup for all containers]:cgroup: " \
                 "($help -D --debug)"{-D,--debug}"[Enable debug mode]" \
                 "($help -D --debug)"{-D,--debug}"[Enable debug mode]" \
                 "($help)--default-gateway[Container default gateway IPv4 address]:IPv4 address: " \
                 "($help)--default-gateway[Container default gateway IPv4 address]:IPv4 address: " \
                 "($help)--default-gateway-v6[Container default gateway IPv6 address]:IPv6 address: " \
                 "($help)--default-gateway-v6[Container default gateway IPv6 address]:IPv6 address: " \
                 "($help)--cluster-store=[URL of the distributed storage backend]:Cluster Store:->cluster-store" \
                 "($help)--cluster-store=[URL of the distributed storage backend]:Cluster Store:->cluster-store" \
                 "($help)--cluster-advertise=[Address of the daemon instance to advertise]:Instance to advertise (host\:port): " \
                 "($help)--cluster-advertise=[Address of the daemon instance to advertise]:Instance to advertise (host\:port): " \
-                "($help)*--cluster-store-opt=[Set cluster options]:Cluster options:->cluster-store-options" \
+                "($help)*--cluster-store-opt=[Cluster options]:Cluster options:->cluster-store-options" \
                 "($help)*--dns=[DNS server to use]:DNS: " \
                 "($help)*--dns=[DNS server to use]:DNS: " \
                 "($help)*--dns-search=[DNS search domains to use]:DNS search: " \
                 "($help)*--dns-search=[DNS search domains to use]:DNS search: " \
                 "($help)*--dns-opt=[DNS options to use]:DNS option: " \
                 "($help)*--dns-opt=[DNS options to use]:DNS option: " \
-                "($help)*--default-ulimit=[Set default ulimit settings for containers]:ulimit: " \
+                "($help)*--default-ulimit=[Default ulimit settings for containers]:ulimit: " \
                 "($help)--disable-legacy-registry[Do not contact legacy registries]" \
                 "($help)--disable-legacy-registry[Do not contact legacy registries]" \
-                "($help)*--exec-opt=[Set exec driver options]:exec driver options: " \
+                "($help)*--exec-opt=[Exec driver options]:exec driver options: " \
                 "($help)--exec-root=[Root of the Docker execdriver]:path:_directories" \
                 "($help)--exec-root=[Root of the Docker execdriver]:path:_directories" \
                 "($help)--fixed-cidr=[IPv4 subnet for fixed IPs]:IPv4 subnet: " \
                 "($help)--fixed-cidr=[IPv4 subnet for fixed IPs]:IPv4 subnet: " \
                 "($help)--fixed-cidr-v6=[IPv6 subnet for fixed IPs]:IPv6 subnet: " \
                 "($help)--fixed-cidr-v6=[IPv6 subnet for fixed IPs]:IPv6 subnet: " \
@@ -660,17 +664,17 @@ __docker_subcommand() {
                 "($help)--ip-masq[Enable IP masquerading]" \
                 "($help)--ip-masq[Enable IP masquerading]" \
                 "($help)--iptables[Enable addition of iptables rules]" \
                 "($help)--iptables[Enable addition of iptables rules]" \
                 "($help)--ipv6[Enable IPv6 networking]" \
                 "($help)--ipv6[Enable IPv6 networking]" \
-                "($help -l --log-level)"{-l=,--log-level=}"[Set the logging level]:level:(debug info warn error fatal)" \
-                "($help)*--label=[Set key=value labels to the daemon]:label: " \
+                "($help -l --log-level)"{-l=,--log-level=}"[Logging level]:level:(debug info warn error fatal)" \
+                "($help)*--label=[Key=value labels]:label: " \
                 "($help)--log-driver=[Default driver for container logs]:Logging driver:(json-file syslog journald gelf fluentd awslogs splunk none)" \
                 "($help)--log-driver=[Default driver for container logs]:Logging driver:(json-file syslog journald gelf fluentd awslogs splunk none)" \
                 "($help)*--log-opt=[Log driver specific options]:log driver options:__docker_log_options" \
                 "($help)*--log-opt=[Log driver specific options]:log driver options:__docker_log_options" \
-                "($help)--mtu=[Set the containers network MTU]:mtu:(0 576 1420 1500 9000)" \
+                "($help)--mtu=[Network MTU]:mtu:(0 576 1420 1500 9000)" \
                 "($help -p --pidfile)"{-p=,--pidfile=}"[Path to use for daemon PID file]:PID file:_files" \
                 "($help -p --pidfile)"{-p=,--pidfile=}"[Path to use for daemon PID file]:PID file:_files" \
                 "($help)--raw-logs[Full timestamps without ANSI coloring]" \
                 "($help)--raw-logs[Full timestamps without ANSI coloring]" \
                 "($help)*--registry-mirror=[Preferred Docker registry mirror]:registry mirror: " \
                 "($help)*--registry-mirror=[Preferred Docker registry mirror]:registry mirror: " \
                 "($help -s --storage-driver)"{-s=,--storage-driver=}"[Storage driver to use]:driver:(aufs devicemapper btrfs zfs overlay)" \
                 "($help -s --storage-driver)"{-s=,--storage-driver=}"[Storage driver to use]:driver:(aufs devicemapper btrfs zfs overlay)" \
                 "($help)--selinux-enabled[Enable selinux support]" \
                 "($help)--selinux-enabled[Enable selinux support]" \
-                "($help)*--storage-opt=[Set storage driver options]:storage driver options: " \
+                "($help)*--storage-opt=[Storage driver options]:storage driver options: " \
                 "($help)--tls[Use TLS]" \
                 "($help)--tls[Use TLS]" \
                 "($help)--tlscacert=[Trust certs signed only by this CA]:PEM file:_files -g "*.(pem|crt)"" \
                 "($help)--tlscacert=[Trust certs signed only by this CA]:PEM file:_files -g "*.(pem|crt)"" \
                 "($help)--tlscert=[Path to TLS certificate file]:PEM file:_files -g "*.(pem|crt)"" \
                 "($help)--tlscert=[Path to TLS certificate file]:PEM file:_files -g "*.(pem|crt)"" \
@@ -768,7 +772,7 @@ __docker_subcommand() {
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
                 "($help)*"{-c=,--change=}"[Apply Dockerfile instruction to the created image]:Dockerfile:_files" \
                 "($help)*"{-c=,--change=}"[Apply Dockerfile instruction to the created image]:Dockerfile:_files" \
-                "($help -m --message)"{-m=,--message=}"[Set commit message for imported image]:message: " \
+                "($help -m --message)"{-m=,--message=}"[Commit message for imported image]:message: " \
                 "($help -):URL:(- http:// file://)" \
                 "($help -):URL:(- http:// file://)" \
                 "($help -): :__docker_repositories_with_tags" && ret=0
                 "($help -): :__docker_repositories_with_tags" && ret=0
             ;;
             ;;
@@ -811,7 +815,6 @@ __docker_subcommand() {
         (login)
         (login)
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
-                "($help -e --email)"{-e=,--email=}"[Email]:email: " \
                 "($help -p --password)"{-p=,--password=}"[Password]:password: " \
                 "($help -p --password)"{-p=,--password=}"[Password]:password: " \
                 "($help -u --user)"{-u=,--user=}"[Username]:username: " \
                 "($help -u --user)"{-u=,--user=}"[Username]:username: " \
                 "($help -)1:server: " && ret=0
                 "($help -)1:server: " && ret=0
@@ -1048,7 +1051,7 @@ _docker() {
         "($help)--config[Location of client config files]:path:_directories" \
         "($help)--config[Location of client config files]:path:_directories" \
         "($help -D --debug)"{-D,--debug}"[Enable debug mode]" \
         "($help -D --debug)"{-D,--debug}"[Enable debug mode]" \
         "($help -H --host)"{-H=,--host=}"[tcp://host:port to bind/connect to]:host: " \
         "($help -H --host)"{-H=,--host=}"[tcp://host:port to bind/connect to]:host: " \
-        "($help -l --log-level)"{-l=,--log-level=}"[Set the logging level]:level:(debug info warn error fatal)" \
+        "($help -l --log-level)"{-l=,--log-level=}"[Logging level]:level:(debug info warn error fatal)" \
         "($help)--tls[Use TLS]" \
         "($help)--tls[Use TLS]" \
         "($help)--tlscacert=[Trust certs signed only by this CA]:PEM file:_files -g "*.(pem|crt)"" \
         "($help)--tlscacert=[Trust certs signed only by this CA]:PEM file:_files -g "*.(pem|crt)"" \
         "($help)--tlscert=[Path to TLS certificate file]:PEM file:_files -g "*.(pem|crt)"" \
         "($help)--tlscert=[Path to TLS certificate file]:PEM file:_files -g "*.(pem|crt)"" \

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