ソースを参照

Merge pull request #25045 from tiborvass/cherry-picks-1.12.0-rc5

Cherry picks for 1.12.0 rc5
Tibor Vass 9 年 前
コミット
05444a56af
100 ファイル変更1740 行追加913 行削除
  1. 2 2
      CONTRIBUTING.md
  2. 2 2
      Dockerfile
  3. 2 2
      Dockerfile.aarch64
  4. 2 2
      Dockerfile.armhf
  5. 1 1
      Dockerfile.gccgo
  6. 2 2
      Dockerfile.ppc64le
  7. 1 1
      Dockerfile.s390x
  8. 2 2
      Dockerfile.simple
  9. 1 1
      Dockerfile.windows
  10. 6 0
      Makefile
  11. 23 9
      api/client/cli.go
  12. 10 0
      api/client/container/stats.go
  13. 4 0
      api/client/container/stats_helpers.go
  14. 1 1
      api/client/container/utils.go
  15. 1 1
      api/client/exec.go
  16. 2 4
      api/client/info.go
  17. 1 1
      api/client/inspect.go
  18. 7 7
      api/client/network/create.go
  19. 1 1
      api/client/network/list.go
  20. 0 31
      api/client/node/accept.go
  21. 0 1
      api/client/node/cmd.go
  22. 2 1
      api/client/node/demote.go
  23. 1 1
      api/client/node/inspect.go
  24. 3 5
      api/client/node/list.go
  25. 19 9
      api/client/node/opts.go
  26. 2 1
      api/client/node/promote.go
  27. 1 9
      api/client/node/tasks.go
  28. 45 15
      api/client/node/update.go
  29. 5 1
      api/client/plugin/disable.go
  30. 5 1
      api/client/plugin/enable.go
  31. 6 3
      api/client/plugin/install.go
  32. 9 1
      api/client/service/create.go
  33. 26 18
      api/client/service/inspect.go
  34. 1 1
      api/client/service/list.go
  35. 95 57
      api/client/service/opts.go
  36. 34 21
      api/client/service/opts_test.go
  37. 1 1
      api/client/service/remove.go
  38. 1 9
      api/client/service/tasks.go
  39. 191 55
      api/client/service/update.go
  40. 115 8
      api/client/service/update_test.go
  41. 26 7
      api/client/stack/deploy.go
  42. 7 3
      api/client/stack/opts.go
  43. 1 1
      api/client/stack/tasks.go
  44. 1 0
      api/client/swarm/cmd.go
  45. 14 33
      api/client/swarm/init.go
  46. 1 10
      api/client/swarm/inspect.go
  47. 26 16
      api/client/swarm/join.go
  48. 110 0
      api/client/swarm/join_token.go
  49. 2 2
      api/client/swarm/leave.go
  50. 3 88
      api/client/swarm/opts.go
  51. 0 99
      api/client/swarm/opts_test.go
  52. 0 19
      api/client/swarm/secret.go
  53. 6 19
      api/client/swarm/update.go
  54. 203 0
      api/client/system/info.go
  55. 27 6
      api/client/task/print.go
  56. 20 1
      api/client/volume/cmd.go
  57. 41 1
      api/client/volume/create.go
  58. 9 0
      api/client/volume/inspect.go
  59. 14 1
      api/client/volume/list.go
  60. 11 0
      api/client/volume/remove.go
  61. 12 0
      api/server/router/network/network_routes.go
  62. 1 1
      api/server/router/swarm/backend.go
  63. 21 1
      api/server/router/swarm/cluster_routes.go
  64. 1 6
      cli/cli.go
  65. 10 3
      cli/cobraadaptor/adaptor.go
  66. 5 3
      cmd/dockerd/daemon.go
  67. 1 1
      cmd/dockerd/daemon_plugin_support.go
  68. 1 0
      cmd/dockerd/daemon_unix.go
  69. 1 1
      cmd/dockerd/docker.go
  70. 4 1
      container/health.go
  71. 1 1
      contrib/builder/deb/amd64/debian-jessie/Dockerfile
  72. 1 1
      contrib/builder/deb/amd64/debian-stretch/Dockerfile
  73. 1 1
      contrib/builder/deb/amd64/debian-wheezy/Dockerfile
  74. 1 1
      contrib/builder/deb/amd64/ubuntu-precise/Dockerfile
  75. 1 1
      contrib/builder/deb/amd64/ubuntu-trusty/Dockerfile
  76. 1 1
      contrib/builder/deb/amd64/ubuntu-wily/Dockerfile
  77. 1 1
      contrib/builder/deb/amd64/ubuntu-xenial/Dockerfile
  78. 1 1
      contrib/builder/deb/armhf/debian-jessie/Dockerfile
  79. 1 1
      contrib/builder/deb/armhf/raspbian-jessie/Dockerfile
  80. 1 1
      contrib/builder/deb/armhf/ubuntu-trusty/Dockerfile
  81. 1 1
      contrib/builder/rpm/amd64/centos-7/Dockerfile
  82. 1 1
      contrib/builder/rpm/amd64/fedora-22/Dockerfile
  83. 1 1
      contrib/builder/rpm/amd64/fedora-23/Dockerfile
  84. 1 1
      contrib/builder/rpm/amd64/fedora-24/Dockerfile
  85. 1 1
      contrib/builder/rpm/amd64/opensuse-13.2/Dockerfile
  86. 1 1
      contrib/builder/rpm/amd64/oraclelinux-6/Dockerfile
  87. 1 1
      contrib/builder/rpm/amd64/oraclelinux-7/Dockerfile
  88. 5 1
      contrib/check-config.sh
  89. 70 45
      contrib/completion/bash/docker
  90. 53 37
      contrib/completion/zsh/_docker
  91. 29 0
      contrib/init/systemd/docker.service.rpm
  92. 2 2
      contrib/init/sysvinit-redhat/docker
  93. 1 1
      contrib/syscall-test/ns.c
  94. 1 1
      contrib/syscall-test/userns.c
  95. 280 114
      daemon/cluster/cluster.go
  96. 0 7
      daemon/cluster/convert/node.go
  97. 61 3
      daemon/cluster/convert/service.go
  98. 6 74
      daemon/cluster/convert/swarm.go
  99. 1 0
      daemon/cluster/convert/task.go
  100. 2 0
      daemon/cluster/executor/backend.go

+ 2 - 2
CONTRIBUTING.md

@@ -378,7 +378,7 @@ The rules:
 5. Document _all_ declarations and methods, even private ones. Declare
 5. Document _all_ declarations and methods, even private ones. Declare
    expectations, caveats and anything else that may be important. If a type
    expectations, caveats and anything else that may be important. If a type
    gets exported, having the comments already there will ensure it's ready.
    gets exported, having the comments already there will ensure it's ready.
-6. Variable name length should be proportional to it's context and no longer.
+6. Variable name length should be proportional to its context and no longer.
    `noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
    `noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
    In practice, short methods will have short variable names and globals will
    In practice, short methods will have short variable names and globals will
    have longer names.
    have longer names.
@@ -386,7 +386,7 @@ The rules:
    and re-examine why you need a compound name. If you still think you need a
    and re-examine why you need a compound name. If you still think you need a
    compound name, lose the underscore.
    compound name, lose the underscore.
 8. No utils or helpers packages. If a function is not general enough to
 8. No utils or helpers packages. If a function is not general enough to
-   warrant it's own package, it has not been written generally enough to be a
+   warrant its own package, it has not been written generally enough to be a
    part of a util package. Just leave it unexported and well-documented.
    part of a util package. Just leave it unexported and well-documented.
 9. All tests should run with `go test` and outside tooling should not be
 9. All tests should run with `go test` and outside tooling should not be
    required. No, we don't need another unit testing framework. Assertion
    required. No, we don't need another unit testing framework. Assertion

+ 2 - 2
Dockerfile

@@ -120,7 +120,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.6.2
+ENV GO_VERSION 1.6.3
 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
 
 
@@ -244,7 +244,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install containerd
 # Install containerd
-ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
+ENV CONTAINERD_COMMIT 0ac3cd1be170d180b2baed755e8f0da547ceb267
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \

+ 2 - 2
Dockerfile.aarch64

@@ -97,7 +97,7 @@ 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 GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 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 \
 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="$(go env GOROOT)" ./make.bash
 	&& GOOS=linux GOARCH=arm64 GOROOT_BOOTSTRAP="$(go env GOROOT)" ./make.bash
@@ -192,7 +192,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install containerd
 # Install containerd
-ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
+ENV CONTAINERD_COMMIT 0ac3cd1be170d180b2baed755e8f0da547ceb267
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \

+ 2 - 2
Dockerfile.armhf

@@ -65,7 +65,7 @@ RUN cd /usr/local/lvm2 \
 # see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
 # see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
 
 
 # Install Go
 # Install Go
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-armv6l.tar.gz" \
 RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-armv6l.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
@@ -200,7 +200,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install containerd
 # Install containerd
-ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
+ENV CONTAINERD_COMMIT 0ac3cd1be170d180b2baed755e8f0da547ceb267
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \

+ 1 - 1
Dockerfile.gccgo

@@ -85,7 +85,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install containerd
 # Install containerd
-ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
+ENV CONTAINERD_COMMIT 0ac3cd1be170d180b2baed755e8f0da547ceb267
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \

+ 2 - 2
Dockerfile.ppc64le

@@ -89,7 +89,7 @@ RUN set -x \
 
 
 ## BUILD GOLANG 1.6
 ## BUILD GOLANG 1.6
 # NOTE: ppc64le has compatibility issues with older versions of go, so make sure the version >= 1.6
 # NOTE: ppc64le has compatibility issues with older versions of go, so make sure the version >= 1.6
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 ENV GO_DOWNLOAD_URL https://golang.org/dl/go${GO_VERSION}.src.tar.gz
 ENV GO_DOWNLOAD_URL https://golang.org/dl/go${GO_VERSION}.src.tar.gz
 ENV GO_DOWNLOAD_SHA256 787b0b750d037016a30c6ed05a8a70a91b2e9db4bd9b1a2453aa502a63f1bccc
 ENV GO_DOWNLOAD_SHA256 787b0b750d037016a30c6ed05a8a70a91b2e9db4bd9b1a2453aa502a63f1bccc
 ENV GOROOT_BOOTSTRAP /usr/local
 ENV GOROOT_BOOTSTRAP /usr/local
@@ -215,7 +215,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install containerd
 # Install containerd
-ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
+ENV CONTAINERD_COMMIT 0ac3cd1be170d180b2baed755e8f0da547ceb267
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \

+ 1 - 1
Dockerfile.s390x

@@ -208,7 +208,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install containerd
 # Install containerd
-ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
+ENV CONTAINERD_COMMIT 0ac3cd1be170d180b2baed755e8f0da547ceb267
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \

+ 2 - 2
Dockerfile.simple

@@ -49,7 +49,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.6.2
+ENV GO_VERSION 1.6.3
 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
@@ -68,7 +68,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install containerd
 # Install containerd
-ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
+ENV CONTAINERD_COMMIT 0ac3cd1be170d180b2baed755e8f0da547ceb267
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
 	&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \

+ 1 - 1
Dockerfile.windows

@@ -34,7 +34,7 @@ FROM windowsservercore
 # Environment variable notes:
 # Environment variable notes:
 #  - GO_VERSION must consistent with 'Dockerfile' used by Linux'.
 #  - GO_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 GO_VERSION=1.6.2 \
+ENV GO_VERSION=1.6.3 \
     GIT_LOCATION=https://github.com/git-for-windows/git/releases/download/v2.7.2.windows.1/Git-2.7.2-64-bit.exe \
     GIT_LOCATION=https://github.com/git-for-windows/git/releases/download/v2.7.2.windows.1/Git-2.7.2-64-bit.exe \
     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

+ 6 - 0
Makefile

@@ -115,6 +115,12 @@ test-unit: build ## run the unit tests
 validate: build ## validate DCO, Seccomp profile generation, gofmt,\n./pkg/ isolation, golint, tests, tomls, go vet and vendor 
 validate: build ## validate DCO, Seccomp profile generation, gofmt,\n./pkg/ isolation, golint, tests, tomls, go vet and vendor 
 	$(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-default-seccomp validate-gofmt validate-pkg validate-lint validate-test validate-toml validate-vet validate-vendor
 	$(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-default-seccomp validate-gofmt validate-pkg validate-lint validate-test validate-toml validate-vet validate-vendor
 
 
+manpages: ## Generate man pages from go source and markdown
+	docker build -t docker-manpage-dev -f man/Dockerfile .
+	docker run \
+		-v $(PWD):/go/src/github.com/docker/docker/ \
+		docker-manpage-dev
+
 help: ## this help
 help: ## this help
 	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
 	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
 
 

+ 23 - 9
api/client/cli.go

@@ -47,8 +47,10 @@ type DockerCli struct {
 	isTerminalOut bool
 	isTerminalOut bool
 	// client is the http client that performs all API operations
 	// client is the http client that performs all API operations
 	client client.APIClient
 	client client.APIClient
-	// state holds the terminal state
-	state *term.State
+	// state holds the terminal input state
+	inState *term.State
+	// outState holds the terminal output state
+	outState *term.State
 }
 }
 
 
 // Initialize calls the init function that will setup the configuration for the client
 // Initialize calls the init function that will setup the configuration for the client
@@ -124,19 +126,31 @@ func (cli *DockerCli) ImagesFormat() string {
 }
 }
 
 
 func (cli *DockerCli) setRawTerminal() error {
 func (cli *DockerCli) setRawTerminal() error {
-	if cli.isTerminalIn && os.Getenv("NORAW") == "" {
-		state, err := term.SetRawTerminal(cli.inFd)
-		if err != nil {
-			return err
+	if os.Getenv("NORAW") == "" {
+		if cli.isTerminalIn {
+			state, err := term.SetRawTerminal(cli.inFd)
+			if err != nil {
+				return err
+			}
+			cli.inState = state
+		}
+		if cli.isTerminalOut {
+			state, err := term.SetRawTerminalOutput(cli.outFd)
+			if err != nil {
+				return err
+			}
+			cli.outState = state
 		}
 		}
-		cli.state = state
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
 func (cli *DockerCli) restoreTerminal(in io.Closer) error {
 func (cli *DockerCli) restoreTerminal(in io.Closer) error {
-	if cli.state != nil {
-		term.RestoreTerminal(cli.inFd, cli.state)
+	if cli.inState != nil {
+		term.RestoreTerminal(cli.inFd, cli.inState)
+	}
+	if cli.outState != nil {
+		term.RestoreTerminal(cli.outFd, cli.outState)
 	}
 	}
 	// WARNING: DO NOT REMOVE THE OS CHECK !!!
 	// WARNING: DO NOT REMOVE THE OS CHECK !!!
 	// For some reason this Close call blocks on darwin..
 	// For some reason this Close call blocks on darwin..

+ 10 - 0
api/client/container/stats.go

@@ -192,13 +192,23 @@ func runStats(dockerCli *client.DockerCli, opts *statsOptions) error {
 
 
 	for range time.Tick(500 * time.Millisecond) {
 	for range time.Tick(500 * time.Millisecond) {
 		printHeader()
 		printHeader()
+		toRemove := []string{}
 		cStats.mu.Lock()
 		cStats.mu.Lock()
 		for _, s := range cStats.cs {
 		for _, s := range cStats.cs {
 			if err := s.Display(w); err != nil && !opts.noStream {
 			if err := s.Display(w); err != nil && !opts.noStream {
 				logrus.Debugf("stats: got error for %s: %v", s.Name, err)
 				logrus.Debugf("stats: got error for %s: %v", s.Name, err)
+				if err == io.EOF {
+					toRemove = append(toRemove, s.Name)
+				}
 			}
 			}
 		}
 		}
 		cStats.mu.Unlock()
 		cStats.mu.Unlock()
+		for _, name := range toRemove {
+			cStats.remove(name)
+		}
+		if len(cStats.cs) == 0 && !showAll {
+			return nil
+		}
 		w.Flush()
 		w.Flush()
 		if opts.noStream {
 		if opts.noStream {
 			break
 			break

+ 4 - 0
api/client/container/stats_helpers.go

@@ -97,6 +97,10 @@ func (s *containerStats) Collect(ctx context.Context, cli client.APIClient, stre
 			if err := dec.Decode(&v); err != nil {
 			if err := dec.Decode(&v); err != nil {
 				dec = json.NewDecoder(io.MultiReader(dec.Buffered(), responseBody))
 				dec = json.NewDecoder(io.MultiReader(dec.Buffered(), responseBody))
 				u <- err
 				u <- err
+				if err == io.EOF {
+					break
+				}
+				time.Sleep(100 * time.Millisecond)
 				continue
 				continue
 			}
 			}
 
 

+ 1 - 1
api/client/container/utils.go

@@ -7,7 +7,7 @@ import (
 	clientapi "github.com/docker/engine-api/client"
 	clientapi "github.com/docker/engine-api/client"
 )
 )
 
 
-// getExitCode perform an inspect on the container. It returns
+// getExitCode performs an inspect on the container. It returns
 // the running state and the exit code.
 // the running state and the exit code.
 func getExitCode(dockerCli *client.DockerCli, ctx context.Context, containerID string) (bool, int, error) {
 func getExitCode(dockerCli *client.DockerCli, ctx context.Context, containerID string) (bool, int, error) {
 	c, err := dockerCli.Client().ContainerInspect(ctx, containerID)
 	c, err := dockerCli.Client().ContainerInspect(ctx, containerID)

+ 1 - 1
api/client/exec.go

@@ -17,7 +17,7 @@ import (
 //
 //
 // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
 // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
 func (cli *DockerCli) CmdExec(args ...string) error {
 func (cli *DockerCli) CmdExec(args ...string) error {
-	cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
+	cmd := Cli.Subcmd("exec", []string{"[OPTIONS] CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
 	detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
 	detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
 
 
 	execConfig, err := ParseExec(cmd, args)
 	execConfig, err := ParseExec(cmd, args)

+ 2 - 4
api/client/info.go

@@ -74,14 +74,12 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
 		if info.Swarm.Error != "" {
 		if info.Swarm.Error != "" {
 			fmt.Fprintf(cli.out, " Error: %v\n", info.Swarm.Error)
 			fmt.Fprintf(cli.out, " Error: %v\n", info.Swarm.Error)
 		}
 		}
+		fmt.Fprintf(cli.out, " Is Manager: %v\n", info.Swarm.ControlAvailable)
 		if info.Swarm.ControlAvailable {
 		if info.Swarm.ControlAvailable {
-			fmt.Fprintf(cli.out, " IsManager: Yes\n")
 			fmt.Fprintf(cli.out, " Managers: %d\n", info.Swarm.Managers)
 			fmt.Fprintf(cli.out, " Managers: %d\n", info.Swarm.Managers)
 			fmt.Fprintf(cli.out, " Nodes: %d\n", info.Swarm.Nodes)
 			fmt.Fprintf(cli.out, " Nodes: %d\n", info.Swarm.Nodes)
-			ioutils.FprintfIfNotEmpty(cli.out, " CACertHash: %s\n", info.Swarm.CACertHash)
-		} else {
-			fmt.Fprintf(cli.out, " IsManager: No\n")
 		}
 		}
+		fmt.Fprintf(cli.out, " Node Address: %s\n", info.Swarm.NodeAddr)
 	}
 	}
 
 
 	if len(info.Runtimes) > 0 {
 	if len(info.Runtimes) > 0 {

+ 1 - 1
api/client/inspect.go

@@ -15,7 +15,7 @@ import (
 //
 //
 // Usage: docker inspect [OPTIONS] CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK...]
 // Usage: docker inspect [OPTIONS] CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK...]
 func (cli *DockerCli) CmdInspect(args ...string) error {
 func (cli *DockerCli) CmdInspect(args ...string) error {
-	cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK...]"}, Cli.DockerCommands["inspect"].Description, true)
+	cmd := Cli.Subcmd("inspect", []string{"[OPTIONS] CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK...]"}, Cli.DockerCommands["inspect"].Description, true)
 	tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
 	tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
 	inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image, container or task)")
 	inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image, container or task)")
 	size := cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes if the type is container")
 	size := cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes if the type is container")

+ 7 - 7
api/client/network/create.go

@@ -53,16 +53,16 @@ func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
 	flags.StringVarP(&opts.driver, "driver", "d", "bridge", "Driver to manage the Network")
 	flags.StringVarP(&opts.driver, "driver", "d", "bridge", "Driver to manage the Network")
 	flags.VarP(&opts.driverOpts, "opt", "o", "Set driver specific options")
 	flags.VarP(&opts.driverOpts, "opt", "o", "Set driver specific options")
 	flags.StringSliceVar(&opts.labels, "label", []string{}, "Set metadata on a network")
 	flags.StringSliceVar(&opts.labels, "label", []string{}, "Set metadata on a network")
-	flags.BoolVar(&opts.internal, "internal", false, "restricts external access to the network")
-	flags.BoolVar(&opts.ipv6, "ipv6", false, "enable IPv6 networking")
+	flags.BoolVar(&opts.internal, "internal", false, "Restrict external access to the network")
+	flags.BoolVar(&opts.ipv6, "ipv6", false, "Enable IPv6 networking")
 
 
 	flags.StringVar(&opts.ipamDriver, "ipam-driver", "default", "IP Address Management Driver")
 	flags.StringVar(&opts.ipamDriver, "ipam-driver", "default", "IP Address Management Driver")
-	flags.StringSliceVar(&opts.ipamSubnet, "subnet", []string{}, "subnet in CIDR format that represents a network segment")
-	flags.StringSliceVar(&opts.ipamIPRange, "ip-range", []string{}, "allocate container ip from a sub-range")
-	flags.StringSliceVar(&opts.ipamGateway, "gateway", []string{}, "ipv4 or ipv6 Gateway for the master subnet")
+	flags.StringSliceVar(&opts.ipamSubnet, "subnet", []string{}, "Subnet in CIDR format that represents a network segment")
+	flags.StringSliceVar(&opts.ipamIPRange, "ip-range", []string{}, "Allocate container ip from a sub-range")
+	flags.StringSliceVar(&opts.ipamGateway, "gateway", []string{}, "IPv4 or IPv6 Gateway for the master subnet")
 
 
-	flags.Var(&opts.ipamAux, "aux-address", "auxiliary ipv4 or ipv6 addresses used by Network driver")
-	flags.Var(&opts.ipamOpt, "ipam-opt", "set IPAM driver specific options")
+	flags.Var(&opts.ipamAux, "aux-address", "Auxiliary IPv4 or IPv6 addresses used by Network driver")
+	flags.Var(&opts.ipamOpt, "ipam-opt", "Set IPAM driver specific options")
 
 
 	return cmd
 	return cmd
 }
 }

+ 1 - 1
api/client/network/list.go

@@ -31,7 +31,7 @@ func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
 	var opts listOptions
 	var opts listOptions
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
-		Use:     "ls",
+		Use:     "ls [OPTIONS]",
 		Aliases: []string{"list"},
 		Aliases: []string{"list"},
 		Short:   "List networks",
 		Short:   "List networks",
 		Args:    cli.NoArgs,
 		Args:    cli.NoArgs,

+ 0 - 31
api/client/node/accept.go

@@ -1,31 +0,0 @@
-package node
-
-import (
-	"fmt"
-
-	"github.com/docker/docker/api/client"
-	"github.com/docker/docker/cli"
-	"github.com/docker/engine-api/types/swarm"
-	"github.com/spf13/cobra"
-)
-
-func newAcceptCommand(dockerCli *client.DockerCli) *cobra.Command {
-	return &cobra.Command{
-		Use:   "accept NODE [NODE...]",
-		Short: "Accept a node in the swarm",
-		Args:  cli.RequiresMinArgs(1),
-		RunE: func(cmd *cobra.Command, args []string) error {
-			return runAccept(dockerCli, args)
-		},
-	}
-}
-
-func runAccept(dockerCli *client.DockerCli, nodes []string) error {
-	accept := func(node *swarm.Node) {
-		node.Spec.Membership = swarm.NodeMembershipAccepted
-	}
-	success := func(nodeID string) {
-		fmt.Fprintf(dockerCli.Out(), "Node %s accepted in the swarm.\n", nodeID)
-	}
-	return updateNodes(dockerCli, nodes, accept, success)
-}

+ 0 - 1
api/client/node/cmd.go

@@ -23,7 +23,6 @@ func NewNodeCommand(dockerCli *client.DockerCli) *cobra.Command {
 		},
 		},
 	}
 	}
 	cmd.AddCommand(
 	cmd.AddCommand(
-		newAcceptCommand(dockerCli),
 		newDemoteCommand(dockerCli),
 		newDemoteCommand(dockerCli),
 		newInspectCommand(dockerCli),
 		newInspectCommand(dockerCli),
 		newListCommand(dockerCli),
 		newListCommand(dockerCli),

+ 2 - 1
api/client/node/demote.go

@@ -21,8 +21,9 @@ func newDemoteCommand(dockerCli *client.DockerCli) *cobra.Command {
 }
 }
 
 
 func runDemote(dockerCli *client.DockerCli, nodes []string) error {
 func runDemote(dockerCli *client.DockerCli, nodes []string) error {
-	demote := func(node *swarm.Node) {
+	demote := func(node *swarm.Node) error {
 		node.Spec.Role = swarm.NodeRoleWorker
 		node.Spec.Role = swarm.NodeRoleWorker
+		return nil
 	}
 	}
 	success := func(nodeID string) {
 	success := func(nodeID string) {
 		fmt.Fprintf(dockerCli.Out(), "Manager %s demoted in the swarm.\n", nodeID)
 		fmt.Fprintf(dockerCli.Out(), "Manager %s demoted in the swarm.\n", nodeID)

+ 1 - 1
api/client/node/inspect.go

@@ -37,7 +37,7 @@ func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
 
 
 	flags := cmd.Flags()
 	flags := cmd.Flags()
 	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
 	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
-	flags.BoolVarP(&opts.pretty, "pretty", "p", false, "Print the information in a human friendly format.")
+	flags.BoolVar(&opts.pretty, "pretty", false, "Print the information in a human friendly format.")
 	return cmd
 	return cmd
 }
 }
 
 

+ 3 - 5
api/client/node/list.go

@@ -16,7 +16,7 @@ import (
 )
 )
 
 
 const (
 const (
-	listItemFmt = "%s\t%s\t%s\t%s\t%s\t%s\n"
+	listItemFmt = "%s\t%s\t%s\t%s\t%s\n"
 )
 )
 
 
 type listOptions struct {
 type listOptions struct {
@@ -28,7 +28,7 @@ func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
 	opts := listOptions{filter: opts.NewFilterOpt()}
 	opts := listOptions{filter: opts.NewFilterOpt()}
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
-		Use:     "ls",
+		Use:     "ls [OPTIONS]",
 		Aliases: []string{"list"},
 		Aliases: []string{"list"},
 		Short:   "List nodes in the swarm",
 		Short:   "List nodes in the swarm",
 		Args:    cli.NoArgs,
 		Args:    cli.NoArgs,
@@ -74,11 +74,10 @@ func printTable(out io.Writer, nodes []swarm.Node, info types.Info) {
 	// Ignore flushing errors
 	// Ignore flushing errors
 	defer writer.Flush()
 	defer writer.Flush()
 
 
-	fmt.Fprintf(writer, listItemFmt, "ID", "HOSTNAME", "MEMBERSHIP", "STATUS", "AVAILABILITY", "MANAGER STATUS")
+	fmt.Fprintf(writer, listItemFmt, "ID", "HOSTNAME", "STATUS", "AVAILABILITY", "MANAGER STATUS")
 	for _, node := range nodes {
 	for _, node := range nodes {
 		name := node.Description.Hostname
 		name := node.Description.Hostname
 		availability := string(node.Spec.Availability)
 		availability := string(node.Spec.Availability)
-		membership := string(node.Spec.Membership)
 
 
 		reachability := ""
 		reachability := ""
 		if node.ManagerStatus != nil {
 		if node.ManagerStatus != nil {
@@ -99,7 +98,6 @@ func printTable(out io.Writer, nodes []swarm.Node, info types.Info) {
 			listItemFmt,
 			listItemFmt,
 			ID,
 			ID,
 			name,
 			name,
-			client.PrettyPrint(membership),
 			client.PrettyPrint(string(node.Status.State)),
 			client.PrettyPrint(string(node.Status.State)),
 			client.PrettyPrint(availability),
 			client.PrettyPrint(availability),
 			client.PrettyPrint(reachability))
 			client.PrettyPrint(reachability))

+ 19 - 9
api/client/node/opts.go

@@ -4,18 +4,36 @@ import (
 	"fmt"
 	"fmt"
 	"strings"
 	"strings"
 
 
+	"github.com/docker/docker/opts"
+	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/docker/engine-api/types/swarm"
 	"github.com/docker/engine-api/types/swarm"
 )
 )
 
 
 type nodeOptions struct {
 type nodeOptions struct {
+	annotations
 	role         string
 	role         string
-	membership   string
 	availability string
 	availability string
 }
 }
 
 
+type annotations struct {
+	name   string
+	labels opts.ListOpts
+}
+
+func newNodeOptions() *nodeOptions {
+	return &nodeOptions{
+		annotations: annotations{
+			labels: opts.NewListOpts(nil),
+		},
+	}
+}
+
 func (opts *nodeOptions) ToNodeSpec() (swarm.NodeSpec, error) {
 func (opts *nodeOptions) ToNodeSpec() (swarm.NodeSpec, error) {
 	var spec swarm.NodeSpec
 	var spec swarm.NodeSpec
 
 
+	spec.Annotations.Name = opts.annotations.name
+	spec.Annotations.Labels = runconfigopts.ConvertKVStringsToMap(opts.annotations.labels.GetAll())
+
 	switch swarm.NodeRole(strings.ToLower(opts.role)) {
 	switch swarm.NodeRole(strings.ToLower(opts.role)) {
 	case swarm.NodeRoleWorker:
 	case swarm.NodeRoleWorker:
 		spec.Role = swarm.NodeRoleWorker
 		spec.Role = swarm.NodeRoleWorker
@@ -26,14 +44,6 @@ func (opts *nodeOptions) ToNodeSpec() (swarm.NodeSpec, error) {
 		return swarm.NodeSpec{}, fmt.Errorf("invalid role %q, only worker and manager are supported", opts.role)
 		return swarm.NodeSpec{}, fmt.Errorf("invalid role %q, only worker and manager are supported", opts.role)
 	}
 	}
 
 
-	switch swarm.NodeMembership(strings.ToLower(opts.membership)) {
-	case swarm.NodeMembershipAccepted:
-		spec.Membership = swarm.NodeMembershipAccepted
-	case "":
-	default:
-		return swarm.NodeSpec{}, fmt.Errorf("invalid membership %q, only accepted is supported", opts.membership)
-	}
-
 	switch swarm.NodeAvailability(strings.ToLower(opts.availability)) {
 	switch swarm.NodeAvailability(strings.ToLower(opts.availability)) {
 	case swarm.NodeAvailabilityActive:
 	case swarm.NodeAvailabilityActive:
 		spec.Availability = swarm.NodeAvailabilityActive
 		spec.Availability = swarm.NodeAvailabilityActive

+ 2 - 1
api/client/node/promote.go

@@ -21,8 +21,9 @@ func newPromoteCommand(dockerCli *client.DockerCli) *cobra.Command {
 }
 }
 
 
 func runPromote(dockerCli *client.DockerCli, nodes []string) error {
 func runPromote(dockerCli *client.DockerCli, nodes []string) error {
-	promote := func(node *swarm.Node) {
+	promote := func(node *swarm.Node) error {
 		node.Spec.Role = swarm.NodeRoleManager
 		node.Spec.Role = swarm.NodeRoleManager
+		return nil
 	}
 	}
 	success := func(nodeID string) {
 	success := func(nodeID string) {
 		fmt.Fprintf(dockerCli.Out(), "Node %s promoted to a manager in the swarm.\n", nodeID)
 		fmt.Fprintf(dockerCli.Out(), "Node %s promoted to a manager in the swarm.\n", nodeID)

+ 1 - 9
api/client/node/tasks.go

@@ -9,13 +9,11 @@ import (
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
-	"github.com/docker/engine-api/types/swarm"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
 type tasksOptions struct {
 type tasksOptions struct {
 	nodeID    string
 	nodeID    string
-	all       bool
 	noResolve bool
 	noResolve bool
 	filter    opts.FilterOpt
 	filter    opts.FilterOpt
 }
 }
@@ -33,8 +31,7 @@ func newTasksCommand(dockerCli *client.DockerCli) *cobra.Command {
 		},
 		},
 	}
 	}
 	flags := cmd.Flags()
 	flags := cmd.Flags()
-	flags.BoolVarP(&opts.all, "all", "a", false, "Display all instances")
-	flags.BoolVarP(&opts.noResolve, "no-resolve", "n", false, "Do not map IDs to Names")
+	flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
 	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
 	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
 
 
 	return cmd
 	return cmd
@@ -55,11 +52,6 @@ func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
 
 
 	filter := opts.filter.Value()
 	filter := opts.filter.Value()
 	filter.Add("node", node.ID)
 	filter.Add("node", node.ID)
-	if !opts.all && !filter.Include("desired-state") {
-		filter.Add("desired-state", string(swarm.TaskStateRunning))
-		filter.Add("desired-state", string(swarm.TaskStateAccepted))
-	}
-
 	tasks, err := client.TaskList(
 	tasks, err := client.TaskList(
 		ctx,
 		ctx,
 		types.TaskListOptions{Filter: filter})
 		types.TaskListOptions{Filter: filter})

+ 45 - 15
api/client/node/update.go

@@ -5,6 +5,8 @@ import (
 
 
 	"github.com/docker/docker/api/client"
 	"github.com/docker/docker/api/client"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
+	"github.com/docker/docker/opts"
+	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/docker/engine-api/types/swarm"
 	"github.com/docker/engine-api/types/swarm"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"github.com/spf13/pflag"
@@ -12,7 +14,7 @@ import (
 )
 )
 
 
 func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
 func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
-	var opts nodeOptions
+	nodeOpts := newNodeOptions()
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
 		Use:   "update [OPTIONS] NODE",
 		Use:   "update [OPTIONS] NODE",
@@ -24,9 +26,11 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
 	}
 	}
 
 
 	flags := cmd.Flags()
 	flags := cmd.Flags()
-	flags.StringVar(&opts.role, flagRole, "", "Role of the node (worker/manager)")
-	flags.StringVar(&opts.membership, flagMembership, "", "Membership of the node (accepted/rejected)")
-	flags.StringVar(&opts.availability, flagAvailability, "", "Availability of the node (active/pause/drain)")
+	flags.StringVar(&nodeOpts.role, flagRole, "", "Role of the node (worker/manager)")
+	flags.StringVar(&nodeOpts.availability, flagAvailability, "", "Availability of the node (active/pause/drain)")
+	flags.Var(&nodeOpts.annotations.labels, flagLabelAdd, "Add or update a node label (key=value)")
+	labelKeys := opts.NewListOpts(nil)
+	flags.Var(&labelKeys, flagLabelRemove, "Remove a node label if exists")
 	return cmd
 	return cmd
 }
 }
 
 
@@ -37,7 +41,7 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, nodeID string)
 	return updateNodes(dockerCli, []string{nodeID}, mergeNodeUpdate(flags), success)
 	return updateNodes(dockerCli, []string{nodeID}, mergeNodeUpdate(flags), success)
 }
 }
 
 
-func updateNodes(dockerCli *client.DockerCli, nodes []string, mergeNode func(node *swarm.Node), success func(nodeID string)) error {
+func updateNodes(dockerCli *client.DockerCli, nodes []string, mergeNode func(node *swarm.Node) error, success func(nodeID string)) error {
 	client := dockerCli.Client()
 	client := dockerCli.Client()
 	ctx := context.Background()
 	ctx := context.Background()
 
 
@@ -47,7 +51,10 @@ func updateNodes(dockerCli *client.DockerCli, nodes []string, mergeNode func(nod
 			return err
 			return err
 		}
 		}
 
 
-		mergeNode(&node)
+		err = mergeNode(&node)
+		if err != nil {
+			return err
+		}
 		err = client.NodeUpdate(ctx, node.ID, node.Version, node.Spec)
 		err = client.NodeUpdate(ctx, node.ID, node.Version, node.Spec)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -57,27 +64,50 @@ func updateNodes(dockerCli *client.DockerCli, nodes []string, mergeNode func(nod
 	return nil
 	return nil
 }
 }
 
 
-func mergeNodeUpdate(flags *pflag.FlagSet) func(*swarm.Node) {
-	return func(node *swarm.Node) {
+func mergeNodeUpdate(flags *pflag.FlagSet) func(*swarm.Node) error {
+	return func(node *swarm.Node) error {
 		spec := &node.Spec
 		spec := &node.Spec
 
 
 		if flags.Changed(flagRole) {
 		if flags.Changed(flagRole) {
-			str, _ := flags.GetString(flagRole)
+			str, err := flags.GetString(flagRole)
+			if err != nil {
+				return err
+			}
 			spec.Role = swarm.NodeRole(str)
 			spec.Role = swarm.NodeRole(str)
 		}
 		}
-		if flags.Changed(flagMembership) {
-			str, _ := flags.GetString(flagMembership)
-			spec.Membership = swarm.NodeMembership(str)
-		}
 		if flags.Changed(flagAvailability) {
 		if flags.Changed(flagAvailability) {
-			str, _ := flags.GetString(flagAvailability)
+			str, err := flags.GetString(flagAvailability)
+			if err != nil {
+				return err
+			}
 			spec.Availability = swarm.NodeAvailability(str)
 			spec.Availability = swarm.NodeAvailability(str)
 		}
 		}
+		if spec.Annotations.Labels == nil {
+			spec.Annotations.Labels = make(map[string]string)
+		}
+		if flags.Changed(flagLabelAdd) {
+			labels := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll()
+			for k, v := range runconfigopts.ConvertKVStringsToMap(labels) {
+				spec.Annotations.Labels[k] = v
+			}
+		}
+		if flags.Changed(flagLabelRemove) {
+			keys := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll()
+			for _, k := range keys {
+				// if a key doesn't exist, fail the command explicitly
+				if _, exists := spec.Annotations.Labels[k]; !exists {
+					return fmt.Errorf("key %s doesn't exist in node's labels", k)
+				}
+				delete(spec.Annotations.Labels, k)
+			}
+		}
+		return nil
 	}
 	}
 }
 }
 
 
 const (
 const (
 	flagRole         = "role"
 	flagRole         = "role"
-	flagMembership   = "membership"
 	flagAvailability = "availability"
 	flagAvailability = "availability"
+	flagLabelAdd     = "label-add"
+	flagLabelRemove  = "label-rm"
 )
 )

+ 5 - 1
api/client/plugin/disable.go

@@ -37,5 +37,9 @@ func runDisable(dockerCli *client.DockerCli, name string) error {
 	if !ok {
 	if !ok {
 		return fmt.Errorf("invalid name: %s", named.String())
 		return fmt.Errorf("invalid name: %s", named.String())
 	}
 	}
-	return dockerCli.Client().PluginDisable(context.Background(), ref.String())
+	if err := dockerCli.Client().PluginDisable(context.Background(), ref.String()); err != nil {
+		return err
+	}
+	fmt.Fprintln(dockerCli.Out(), name)
+	return nil
 }
 }

+ 5 - 1
api/client/plugin/enable.go

@@ -37,5 +37,9 @@ func runEnable(dockerCli *client.DockerCli, name string) error {
 	if !ok {
 	if !ok {
 		return fmt.Errorf("invalid name: %s", named.String())
 		return fmt.Errorf("invalid name: %s", named.String())
 	}
 	}
-	return dockerCli.Client().PluginEnable(context.Background(), ref.String())
+	if err := dockerCli.Client().PluginEnable(context.Background(), ref.String()); err != nil {
+		return err
+	}
+	fmt.Fprintln(dockerCli.Out(), name)
+	return nil
 }
 }

+ 6 - 3
api/client/plugin/install.go

@@ -25,7 +25,7 @@ type pluginOptions struct {
 func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command {
 func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command {
 	var options pluginOptions
 	var options pluginOptions
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
-		Use:   "install PLUGIN",
+		Use:   "install [OPTIONS] PLUGIN",
 		Short: "Install a plugin",
 		Short: "Install a plugin",
 		Args:  cli.RequiresMinArgs(1), // TODO: allow for set args
 		Args:  cli.RequiresMinArgs(1), // TODO: allow for set args
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
@@ -78,8 +78,11 @@ func runInstall(dockerCli *client.DockerCli, opts pluginOptions) error {
 		// TODO: Rename PrivilegeFunc, it has nothing to do with privileges
 		// TODO: Rename PrivilegeFunc, it has nothing to do with privileges
 		PrivilegeFunc: registryAuthFunc,
 		PrivilegeFunc: registryAuthFunc,
 	}
 	}
-
-	return dockerCli.Client().PluginInstall(ctx, ref.String(), options)
+	if err := dockerCli.Client().PluginInstall(ctx, ref.String(), options); err != nil {
+		return err
+	}
+	fmt.Fprintln(dockerCli.Out(), opts.name)
+	return nil
 }
 }
 
 
 func acceptPrivileges(dockerCli *client.DockerCli, name string) func(privileges types.PluginPrivileges) (bool, error) {
 func acceptPrivileges(dockerCli *client.DockerCli, name string) func(privileges types.PluginPrivileges) (bool, error) {

+ 9 - 1
api/client/service/create.go

@@ -28,7 +28,15 @@ func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
 	flags := cmd.Flags()
 	flags := cmd.Flags()
 	flags.StringVar(&opts.mode, flagMode, "replicated", "Service mode (replicated or global)")
 	flags.StringVar(&opts.mode, flagMode, "replicated", "Service mode (replicated or global)")
 	addServiceFlags(cmd, opts)
 	addServiceFlags(cmd, opts)
-	cmd.Flags().SetInterspersed(false)
+
+	flags.VarP(&opts.labels, flagLabel, "l", "Service labels")
+	flags.VarP(&opts.env, flagEnv, "e", "Set environment variables")
+	flags.Var(&opts.mounts, flagMount, "Attach a mount to the service")
+	flags.StringSliceVar(&opts.constraints, flagConstraint, []string{}, "Placement constraints")
+	flags.StringSliceVar(&opts.networks, flagNetwork, []string{}, "Network attachments")
+	flags.VarP(&opts.endpoint.ports, flagPublish, "p", "Publish a port as a node port")
+
+	flags.SetInterspersed(false)
 	return cmd
 	return cmd
 }
 }
 
 

+ 26 - 18
api/client/service/inspect.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"strings"
 	"strings"
+	"time"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
@@ -42,7 +43,7 @@ func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
 
 
 	flags := cmd.Flags()
 	flags := cmd.Flags()
 	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
 	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
-	flags.BoolVarP(&opts.pretty, "pretty", "p", false, "Print the information in a human friendly format.")
+	flags.BoolVar(&opts.pretty, "pretty", false, "Print the information in a human friendly format.")
 	return cmd
 	return cmd
 }
 }
 
 
@@ -101,8 +102,18 @@ func printService(out io.Writer, service swarm.Service) {
 			fmt.Fprintf(out, " Replicas:\t%d\n", *service.Spec.Mode.Replicated.Replicas)
 			fmt.Fprintf(out, " Replicas:\t%d\n", *service.Spec.Mode.Replicated.Replicas)
 		}
 		}
 	}
 	}
+
+	if service.UpdateStatus.State != "" {
+		fmt.Fprintln(out, "Update status:")
+		fmt.Fprintf(out, " State:\t\t%s\n", service.UpdateStatus.State)
+		fmt.Fprintf(out, " Started:\t%s ago\n", strings.ToLower(units.HumanDuration(time.Since(service.UpdateStatus.StartedAt))))
+		if service.UpdateStatus.State == swarm.UpdateStateCompleted {
+			fmt.Fprintf(out, " Completed:\t%s ago\n", strings.ToLower(units.HumanDuration(time.Since(service.UpdateStatus.CompletedAt))))
+		}
+		fmt.Fprintf(out, " Message:\t%s\n", service.UpdateStatus.Message)
+	}
+
 	fmt.Fprintln(out, "Placement:")
 	fmt.Fprintln(out, "Placement:")
-	fmt.Fprintln(out, " Strategy:\tSpread")
 	if service.Spec.TaskTemplate.Placement != nil && len(service.Spec.TaskTemplate.Placement.Constraints) > 0 {
 	if service.Spec.TaskTemplate.Placement != nil && len(service.Spec.TaskTemplate.Placement.Constraints) > 0 {
 		ioutils.FprintfIfNotEmpty(out, " Constraints\t: %s\n", strings.Join(service.Spec.TaskTemplate.Placement.Constraints, ", "))
 		ioutils.FprintfIfNotEmpty(out, " Constraints\t: %s\n", strings.Join(service.Spec.TaskTemplate.Placement.Constraints, ", "))
 	}
 	}
@@ -111,27 +122,27 @@ func printService(out io.Writer, service swarm.Service) {
 	if service.Spec.UpdateConfig.Delay.Nanoseconds() > 0 {
 	if service.Spec.UpdateConfig.Delay.Nanoseconds() > 0 {
 		fmt.Fprintf(out, " Delay:\t\t%s\n", service.Spec.UpdateConfig.Delay)
 		fmt.Fprintf(out, " Delay:\t\t%s\n", service.Spec.UpdateConfig.Delay)
 	}
 	}
+	fmt.Fprintf(out, " On failure:\t%s\n", service.Spec.UpdateConfig.FailureAction)
 	fmt.Fprintf(out, "ContainerSpec:\n")
 	fmt.Fprintf(out, "ContainerSpec:\n")
 	printContainerSpec(out, service.Spec.TaskTemplate.ContainerSpec)
 	printContainerSpec(out, service.Spec.TaskTemplate.ContainerSpec)
 
 
-	if service.Spec.TaskTemplate.Resources != nil {
+	resources := service.Spec.TaskTemplate.Resources
+	if resources != nil {
 		fmt.Fprintln(out, "Resources:")
 		fmt.Fprintln(out, "Resources:")
-		printResources := func(out io.Writer, r *swarm.Resources) {
+		printResources := func(out io.Writer, requirement string, r *swarm.Resources) {
+			if r == nil || (r.MemoryBytes == 0 && r.NanoCPUs == 0) {
+				return
+			}
+			fmt.Fprintf(out, " %s:\n", requirement)
 			if r.NanoCPUs != 0 {
 			if r.NanoCPUs != 0 {
-				fmt.Fprintf(out, " CPU:\t\t%g\n", float64(r.NanoCPUs)/1e9)
+				fmt.Fprintf(out, "  CPU:\t\t%g\n", float64(r.NanoCPUs)/1e9)
 			}
 			}
 			if r.MemoryBytes != 0 {
 			if r.MemoryBytes != 0 {
-				fmt.Fprintf(out, " Memory:\t\t%s\n", units.BytesSize(float64(r.MemoryBytes)))
+				fmt.Fprintf(out, "  Memory:\t%s\n", units.BytesSize(float64(r.MemoryBytes)))
 			}
 			}
 		}
 		}
-		if service.Spec.TaskTemplate.Resources.Reservations != nil {
-			fmt.Fprintln(out, "Reservations:")
-			printResources(out, service.Spec.TaskTemplate.Resources.Reservations)
-		}
-		if service.Spec.TaskTemplate.Resources.Limits != nil {
-			fmt.Fprintln(out, "Limits:")
-			printResources(out, service.Spec.TaskTemplate.Resources.Limits)
-		}
+		printResources(out, "Reservations", resources.Reservations)
+		printResources(out, "Limits", resources.Limits)
 	}
 	}
 	if len(service.Spec.Networks) > 0 {
 	if len(service.Spec.Networks) > 0 {
 		fmt.Fprintf(out, "Networks:")
 		fmt.Fprintf(out, "Networks:")
@@ -143,7 +154,7 @@ func printService(out io.Writer, service swarm.Service) {
 	if len(service.Endpoint.Ports) > 0 {
 	if len(service.Endpoint.Ports) > 0 {
 		fmt.Fprintln(out, "Ports:")
 		fmt.Fprintln(out, "Ports:")
 		for _, port := range service.Endpoint.Ports {
 		for _, port := range service.Endpoint.Ports {
-			fmt.Fprintf(out, " Name = %s\n", port.Name)
+			ioutils.FprintfIfNotEmpty(out, " Name = %s\n", port.Name)
 			fmt.Fprintf(out, " Protocol = %s\n", port.Protocol)
 			fmt.Fprintf(out, " Protocol = %s\n", port.Protocol)
 			fmt.Fprintf(out, " TargetPort = %d\n", port.TargetPort)
 			fmt.Fprintf(out, " TargetPort = %d\n", port.TargetPort)
 			fmt.Fprintf(out, " PublishedPort = %d\n", port.PublishedPort)
 			fmt.Fprintf(out, " PublishedPort = %d\n", port.PublishedPort)
@@ -153,9 +164,6 @@ func printService(out io.Writer, service swarm.Service) {
 
 
 func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) {
 func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) {
 	fmt.Fprintf(out, " Image:\t\t%s\n", containerSpec.Image)
 	fmt.Fprintf(out, " Image:\t\t%s\n", containerSpec.Image)
-	if len(containerSpec.Command) > 0 {
-		fmt.Fprintf(out, " Command:\t%s\n", strings.Join(containerSpec.Command, " "))
-	}
 	if len(containerSpec.Args) > 0 {
 	if len(containerSpec.Args) > 0 {
 		fmt.Fprintf(out, " Args:\t\t%s\n", strings.Join(containerSpec.Args, " "))
 		fmt.Fprintf(out, " Args:\t\t%s\n", strings.Join(containerSpec.Args, " "))
 	}
 	}

+ 1 - 1
api/client/service/list.go

@@ -30,7 +30,7 @@ func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
 	opts := listOptions{filter: opts.NewFilterOpt()}
 	opts := listOptions{filter: opts.NewFilterOpt()}
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
-		Use:     "ls",
+		Use:     "ls [OPTIONS]",
 		Aliases: []string{"list"},
 		Aliases: []string{"list"},
 		Short:   "List services",
 		Short:   "List services",
 		Args:    cli.NoArgs,
 		Args:    cli.NoArgs,

+ 95 - 57
api/client/service/opts.go

@@ -16,11 +16,6 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
-var (
-	// DefaultReplicas is the default replicas to use for a replicated service
-	DefaultReplicas uint64 = 1
-)
-
 type int64Value interface {
 type int64Value interface {
 	Value() int64
 	Value() int64
 }
 }
@@ -176,39 +171,44 @@ func (m *MountOpt) Set(value string) error {
 		}
 		}
 	}
 	}
 
 
+	mount.Type = swarm.MountTypeVolume // default to volume mounts
 	// Set writable as the default
 	// Set writable as the default
 	for _, field := range fields {
 	for _, field := range fields {
 		parts := strings.SplitN(field, "=", 2)
 		parts := strings.SplitN(field, "=", 2)
-		if len(parts) == 1 && strings.ToLower(parts[0]) == "readonly" {
-			mount.ReadOnly = true
-			continue
-		}
+		key := strings.ToLower(parts[0])
+
+		if len(parts) == 1 {
+			if key == "readonly" || key == "ro" {
+				mount.ReadOnly = true
+				continue
+			}
 
 
-		if len(parts) == 1 && strings.ToLower(parts[0]) == "volume-nocopy" {
-			volumeOptions().NoCopy = true
-			continue
+			if key == "volume-nocopy" {
+				volumeOptions().NoCopy = true
+				continue
+			}
 		}
 		}
 
 
 		if len(parts) != 2 {
 		if len(parts) != 2 {
 			return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
 			return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
 		}
 		}
 
 
-		key, value := parts[0], parts[1]
-		switch strings.ToLower(key) {
+		value := parts[1]
+		switch key {
 		case "type":
 		case "type":
-			mount.Type = swarm.MountType(strings.ToUpper(value))
-		case "source":
+			mount.Type = swarm.MountType(strings.ToLower(value))
+		case "source", "name", "src":
 			mount.Source = value
 			mount.Source = value
-		case "target":
+		case "target", "dst", "dest", "destination", "path":
 			mount.Target = value
 			mount.Target = value
-		case "readonly":
+		case "readonly", "ro":
 			ro, err := strconv.ParseBool(value)
 			ro, err := strconv.ParseBool(value)
 			if err != nil {
 			if err != nil {
 				return fmt.Errorf("invalid value for readonly: %s", value)
 				return fmt.Errorf("invalid value for readonly: %s", value)
 			}
 			}
 			mount.ReadOnly = ro
 			mount.ReadOnly = ro
 		case "bind-propagation":
 		case "bind-propagation":
-			bindOptions().Propagation = swarm.MountPropagation(strings.ToUpper(value))
+			bindOptions().Propagation = swarm.MountPropagation(strings.ToLower(value))
 		case "volume-nocopy":
 		case "volume-nocopy":
 			volumeOptions().NoCopy, err = strconv.ParseBool(value)
 			volumeOptions().NoCopy, err = strconv.ParseBool(value)
 			if err != nil {
 			if err != nil {
@@ -218,7 +218,7 @@ func (m *MountOpt) Set(value string) error {
 			setValueOnMap(volumeOptions().Labels, value)
 			setValueOnMap(volumeOptions().Labels, value)
 		case "volume-driver":
 		case "volume-driver":
 			volumeOptions().DriverConfig.Name = value
 			volumeOptions().DriverConfig.Name = value
-		case "volume-driver-opt":
+		case "volume-opt":
 			if volumeOptions().DriverConfig.Options == nil {
 			if volumeOptions().DriverConfig.Options == nil {
 				volumeOptions().DriverConfig.Options = make(map[string]string)
 				volumeOptions().DriverConfig.Options = make(map[string]string)
 			}
 			}
@@ -240,10 +240,10 @@ func (m *MountOpt) Set(value string) error {
 		return fmt.Errorf("source is required when specifying volume-* options")
 		return fmt.Errorf("source is required when specifying volume-* options")
 	}
 	}
 
 
-	if mount.Type == swarm.MountType("BIND") && mount.VolumeOptions != nil {
+	if mount.Type == swarm.MountTypeBind && mount.VolumeOptions != nil {
 		return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", swarm.MountTypeBind)
 		return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", swarm.MountTypeBind)
 	}
 	}
-	if mount.Type == swarm.MountType("VOLUME") && mount.BindOptions != nil {
+	if mount.Type == swarm.MountTypeVolume && mount.BindOptions != nil {
 		return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", swarm.MountTypeVolume)
 		return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", swarm.MountTypeVolume)
 	}
 	}
 
 
@@ -274,6 +274,7 @@ func (m *MountOpt) Value() []swarm.Mount {
 type updateOptions struct {
 type updateOptions struct {
 	parallelism uint64
 	parallelism uint64
 	delay       time.Duration
 	delay       time.Duration
+	onFailure   string
 }
 }
 
 
 type resourceOptions struct {
 type resourceOptions struct {
@@ -358,6 +359,27 @@ func convertPortToPortConfig(
 	return ports
 	return ports
 }
 }
 
 
+type logDriverOptions struct {
+	name string
+	opts opts.ListOpts
+}
+
+func newLogDriverOptions() logDriverOptions {
+	return logDriverOptions{opts: opts.NewListOpts(runconfigopts.ValidateEnv)}
+}
+
+func (ldo *logDriverOptions) toLogDriver() *swarm.Driver {
+	if ldo.name == "" {
+		return nil
+	}
+
+	// set the log driver only if specified.
+	return &swarm.Driver{
+		Name:    ldo.name,
+		Options: runconfigopts.ConvertKVStringsToMap(ldo.opts.GetAll()),
+	}
+}
+
 // ValidatePort validates a string is in the expected format for a port definition
 // ValidatePort validates a string is in the expected format for a port definition
 func ValidatePort(value string) (string, error) {
 func ValidatePort(value string) (string, error) {
 	portMappings, err := nat.ParsePortSpec(value)
 	portMappings, err := nat.ParsePortSpec(value)
@@ -373,7 +395,6 @@ type serviceOptions struct {
 	name    string
 	name    string
 	labels  opts.ListOpts
 	labels  opts.ListOpts
 	image   string
 	image   string
-	command []string
 	args    []string
 	args    []string
 	env     opts.ListOpts
 	env     opts.ListOpts
 	workdir string
 	workdir string
@@ -393,6 +414,8 @@ type serviceOptions struct {
 	endpoint      endpointOptions
 	endpoint      endpointOptions
 
 
 	registryAuth bool
 	registryAuth bool
+
+	logDriver logDriverOptions
 }
 }
 
 
 func newServiceOptions() *serviceOptions {
 func newServiceOptions() *serviceOptions {
@@ -402,6 +425,7 @@ func newServiceOptions() *serviceOptions {
 		endpoint: endpointOptions{
 		endpoint: endpointOptions{
 			ports: opts.NewListOpts(ValidatePort),
 			ports: opts.NewListOpts(ValidatePort),
 		},
 		},
+		logDriver: newLogDriverOptions(),
 	}
 	}
 }
 }
 
 
@@ -416,7 +440,6 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
 		TaskTemplate: swarm.TaskSpec{
 		TaskTemplate: swarm.TaskSpec{
 			ContainerSpec: swarm.ContainerSpec{
 			ContainerSpec: swarm.ContainerSpec{
 				Image:           opts.image,
 				Image:           opts.image,
-				Command:         opts.command,
 				Args:            opts.args,
 				Args:            opts.args,
 				Env:             opts.env.GetAll(),
 				Env:             opts.env.GetAll(),
 				Dir:             opts.workdir,
 				Dir:             opts.workdir,
@@ -429,11 +452,13 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
 			Placement: &swarm.Placement{
 			Placement: &swarm.Placement{
 				Constraints: opts.constraints,
 				Constraints: opts.constraints,
 			},
 			},
+			LogDriver: opts.logDriver.toLogDriver(),
 		},
 		},
 		Mode: swarm.ServiceMode{},
 		Mode: swarm.ServiceMode{},
 		UpdateConfig: &swarm.UpdateConfig{
 		UpdateConfig: &swarm.UpdateConfig{
-			Parallelism: opts.update.parallelism,
-			Delay:       opts.update.delay,
+			Parallelism:   opts.update.parallelism,
+			Delay:         opts.update.delay,
+			FailureAction: opts.update.onFailure,
 		},
 		},
 		Networks:     convertNetworks(opts.networks),
 		Networks:     convertNetworks(opts.networks),
 		EndpointSpec: opts.endpoint.ToEndpointSpec(),
 		EndpointSpec: opts.endpoint.ToEndpointSpec(),
@@ -461,12 +486,9 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
 func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
 func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
 	flags := cmd.Flags()
 	flags := cmd.Flags()
 	flags.StringVar(&opts.name, flagName, "", "Service name")
 	flags.StringVar(&opts.name, flagName, "", "Service name")
-	flags.VarP(&opts.labels, flagLabel, "l", "Service labels")
 
 
-	flags.VarP(&opts.env, "env", "e", "Set environment variables")
 	flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
 	flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
 	flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID")
 	flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID")
-	flags.VarP(&opts.mounts, flagMount, "m", "Attach a mount to the service")
 
 
 	flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs")
 	flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs")
 	flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")
 	flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")
@@ -481,39 +503,55 @@ func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
 	flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up")
 	flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up")
 	flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evaluate the restart policy")
 	flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evaluate the restart policy")
 
 
-	flags.StringSliceVar(&opts.constraints, flagConstraint, []string{}, "Placement constraints")
-
-	flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 0, "Maximum number of tasks updated simultaneously")
+	flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 1, "Maximum number of tasks updated simultaneously (0 to update all at once)")
 	flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates")
 	flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates")
+	flags.StringVar(&opts.update.onFailure, flagUpdateFailureAction, "pause", "Action on update failure (pause|continue)")
 
 
-	flags.StringSliceVar(&opts.networks, flagNetwork, []string{}, "Network attachments")
 	flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "Endpoint mode (vip or dnsrr)")
 	flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "Endpoint mode (vip or dnsrr)")
-	flags.VarP(&opts.endpoint.ports, flagPublish, "p", "Publish a port as a node port")
 
 
-	flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "Send registry authentication details to Swarm agents")
+	flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "Send registry authentication details to swarm agents")
+
+	flags.StringVar(&opts.logDriver.name, flagLogDriver, "", "Logging driver for service")
+	flags.Var(&opts.logDriver.opts, flagLogOpt, "Logging driver options")
 }
 }
 
 
 const (
 const (
-	flagConstraint         = "constraint"
-	flagEndpointMode       = "endpoint-mode"
-	flagLabel              = "label"
-	flagLimitCPU           = "limit-cpu"
-	flagLimitMemory        = "limit-memory"
-	flagMode               = "mode"
-	flagMount              = "mount"
-	flagName               = "name"
-	flagNetwork            = "network"
-	flagPublish            = "publish"
-	flagReplicas           = "replicas"
-	flagReserveCPU         = "reserve-cpu"
-	flagReserveMemory      = "reserve-memory"
-	flagRestartCondition   = "restart-condition"
-	flagRestartDelay       = "restart-delay"
-	flagRestartMaxAttempts = "restart-max-attempts"
-	flagRestartWindow      = "restart-window"
-	flagStopGracePeriod    = "stop-grace-period"
-	flagUpdateDelay        = "update-delay"
-	flagUpdateParallelism  = "update-parallelism"
-	flagUser               = "user"
-	flagRegistryAuth       = "registry-auth"
+	flagConstraint          = "constraint"
+	flagConstraintRemove    = "constraint-rm"
+	flagConstraintAdd       = "constraint-add"
+	flagEndpointMode        = "endpoint-mode"
+	flagEnv                 = "env"
+	flagEnvRemove           = "env-rm"
+	flagEnvAdd              = "env-add"
+	flagLabel               = "label"
+	flagLabelRemove         = "label-rm"
+	flagLabelAdd            = "label-add"
+	flagLimitCPU            = "limit-cpu"
+	flagLimitMemory         = "limit-memory"
+	flagMode                = "mode"
+	flagMount               = "mount"
+	flagMountRemove         = "mount-rm"
+	flagMountAdd            = "mount-add"
+	flagName                = "name"
+	flagNetwork             = "network"
+	flagNetworkRemove       = "network-rm"
+	flagNetworkAdd          = "network-add"
+	flagPublish             = "publish"
+	flagPublishRemove       = "publish-rm"
+	flagPublishAdd          = "publish-add"
+	flagReplicas            = "replicas"
+	flagReserveCPU          = "reserve-cpu"
+	flagReserveMemory       = "reserve-memory"
+	flagRestartCondition    = "restart-condition"
+	flagRestartDelay        = "restart-delay"
+	flagRestartMaxAttempts  = "restart-max-attempts"
+	flagRestartWindow       = "restart-window"
+	flagStopGracePeriod     = "stop-grace-period"
+	flagUpdateDelay         = "update-delay"
+	flagUpdateFailureAction = "update-failure-action"
+	flagUpdateParallelism   = "update-parallelism"
+	flagUser                = "user"
+	flagRegistryAuth        = "with-registry-auth"
+	flagLogDriver           = "log-driver"
+	flagLogOpt              = "log-opt"
 )
 )

+ 34 - 21
api/client/service/opts_test.go

@@ -61,60 +61,73 @@ func TestMountOptString(t *testing.T) {
 	mount := MountOpt{
 	mount := MountOpt{
 		values: []swarm.Mount{
 		values: []swarm.Mount{
 			{
 			{
-				Type:   swarm.MountType("BIND"),
+				Type:   swarm.MountTypeBind,
 				Source: "/home/path",
 				Source: "/home/path",
 				Target: "/target",
 				Target: "/target",
 			},
 			},
 			{
 			{
-				Type:   swarm.MountType("VOLUME"),
+				Type:   swarm.MountTypeVolume,
 				Source: "foo",
 				Source: "foo",
 				Target: "/target/foo",
 				Target: "/target/foo",
 			},
 			},
 		},
 		},
 	}
 	}
-	expected := "BIND /home/path /target, VOLUME foo /target/foo"
+	expected := "bind /home/path /target, volume foo /target/foo"
 	assert.Equal(t, mount.String(), expected)
 	assert.Equal(t, mount.String(), expected)
 }
 }
 
 
 func TestMountOptSetNoError(t *testing.T) {
 func TestMountOptSetNoError(t *testing.T) {
-	var mount MountOpt
-	assert.NilError(t, mount.Set("type=bind,target=/target,source=/foo"))
-
-	mounts := mount.Value()
-	assert.Equal(t, len(mounts), 1)
-	assert.Equal(t, mounts[0], swarm.Mount{
-		Type:   swarm.MountType("BIND"),
-		Source: "/foo",
-		Target: "/target",
-	})
+	for _, testcase := range []string{
+		// tests several aliases that should have same result.
+		"type=bind,target=/target,source=/source",
+		"type=bind,src=/source,dst=/target",
+		"type=bind,name=/source,dst=/target",
+		"type=bind,name=/source,path=/target",
+	} {
+		var mount MountOpt
+
+		assert.NilError(t, mount.Set(testcase))
+
+		mounts := mount.Value()
+		assert.Equal(t, len(mounts), 1)
+		assert.Equal(t, mounts[0], swarm.Mount{
+			Type:   swarm.MountTypeBind,
+			Source: "/source",
+			Target: "/target",
+		})
+	}
 }
 }
 
 
-func TestMountOptSetErrorNoType(t *testing.T) {
+// TestMountOptDefaultType ensures that a mount without the type defaults to a
+// volume mount.
+func TestMountOptDefaultType(t *testing.T) {
 	var mount MountOpt
 	var mount MountOpt
-	assert.Error(t, mount.Set("target=/target,source=/foo"), "type is required")
+	assert.NilError(t, mount.Set("target=/target,source=/foo"))
+	assert.Equal(t, mount.values[0].Type, swarm.MountTypeVolume)
 }
 }
 
 
 func TestMountOptSetErrorNoTarget(t *testing.T) {
 func TestMountOptSetErrorNoTarget(t *testing.T) {
 	var mount MountOpt
 	var mount MountOpt
-	assert.Error(t, mount.Set("type=VOLUME,source=/foo"), "target is required")
+	assert.Error(t, mount.Set("type=volume,source=/foo"), "target is required")
 }
 }
 
 
 func TestMountOptSetErrorInvalidKey(t *testing.T) {
 func TestMountOptSetErrorInvalidKey(t *testing.T) {
 	var mount MountOpt
 	var mount MountOpt
-	assert.Error(t, mount.Set("type=VOLUME,bogus=foo"), "unexpected key 'bogus'")
+	assert.Error(t, mount.Set("type=volume,bogus=foo"), "unexpected key 'bogus'")
 }
 }
 
 
 func TestMountOptSetErrorInvalidField(t *testing.T) {
 func TestMountOptSetErrorInvalidField(t *testing.T) {
 	var mount MountOpt
 	var mount MountOpt
-	assert.Error(t, mount.Set("type=VOLUME,bogus"), "invalid field 'bogus'")
+	assert.Error(t, mount.Set("type=volume,bogus"), "invalid field 'bogus'")
 }
 }
 
 
-func TestMountOptSetErrorInvalidWritable(t *testing.T) {
+func TestMountOptSetErrorInvalidReadOnly(t *testing.T) {
 	var mount MountOpt
 	var mount MountOpt
-	assert.Error(t, mount.Set("type=VOLUME,readonly=no"), "invalid value for readonly: no")
+	assert.Error(t, mount.Set("type=volume,readonly=no"), "invalid value for readonly: no")
+	assert.Error(t, mount.Set("type=volume,readonly=invalid"), "invalid value for readonly: invalid")
 }
 }
 
 
-func TestMountOptDefaultEnableWritable(t *testing.T) {
+func TestMountOptDefaultEnableReadOnly(t *testing.T) {
 	var m MountOpt
 	var m MountOpt
 	assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo"))
 	assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo"))
 	assert.Equal(t, m.values[0].ReadOnly, false)
 	assert.Equal(t, m.values[0].ReadOnly, false)

+ 1 - 1
api/client/service/remove.go

@@ -13,7 +13,7 @@ import (
 func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
 func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
-		Use:     "rm [OPTIONS] SERVICE",
+		Use:     "rm [OPTIONS] SERVICE [SERVICE...]",
 		Aliases: []string{"remove"},
 		Aliases: []string{"remove"},
 		Short:   "Remove a service",
 		Short:   "Remove a service",
 		Args:    cli.RequiresMinArgs(1),
 		Args:    cli.RequiresMinArgs(1),

+ 1 - 9
api/client/service/tasks.go

@@ -10,13 +10,11 @@ import (
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
-	"github.com/docker/engine-api/types/swarm"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
 type tasksOptions struct {
 type tasksOptions struct {
 	serviceID string
 	serviceID string
-	all       bool
 	noResolve bool
 	noResolve bool
 	filter    opts.FilterOpt
 	filter    opts.FilterOpt
 }
 }
@@ -34,8 +32,7 @@ func newTasksCommand(dockerCli *client.DockerCli) *cobra.Command {
 		},
 		},
 	}
 	}
 	flags := cmd.Flags()
 	flags := cmd.Flags()
-	flags.BoolVarP(&opts.all, "all", "a", false, "Display all tasks")
-	flags.BoolVarP(&opts.noResolve, "no-resolve", "n", false, "Do not map IDs to Names")
+	flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
 	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
 	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
 
 
 	return cmd
 	return cmd
@@ -52,11 +49,6 @@ func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
 
 
 	filter := opts.filter.Value()
 	filter := opts.filter.Value()
 	filter.Add("service", service.ID)
 	filter.Add("service", service.ID)
-	if !opts.all && !filter.Include("desired-state") {
-		filter.Add("desired-state", string(swarm.TaskStateRunning))
-		filter.Add("desired-state", string(swarm.TaskStateAccepted))
-	}
-
 	if filter.Include("node") {
 	if filter.Include("node") {
 		nodeFilters := filter.Get("node")
 		nodeFilters := filter.Get("node")
 		for _, nodeFilter := range nodeFilters {
 		for _, nodeFilter := range nodeFilters {

+ 191 - 55
api/client/service/update.go

@@ -2,6 +2,7 @@ package service
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"strings"
 	"time"
 	"time"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -13,6 +14,7 @@ import (
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types/swarm"
 	"github.com/docker/engine-api/types/swarm"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
+	shlex "github.com/flynn-archive/go-shlex"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"github.com/spf13/pflag"
 )
 )
@@ -31,12 +33,28 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
 
 
 	flags := cmd.Flags()
 	flags := cmd.Flags()
 	flags.String("image", "", "Service image tag")
 	flags.String("image", "", "Service image tag")
-	flags.StringSlice("command", []string{}, "Service command")
-	flags.StringSlice("arg", []string{}, "Service command args")
+	flags.String("args", "", "Service command args")
 	addServiceFlags(cmd, opts)
 	addServiceFlags(cmd, opts)
+
+	flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable")
+	flags.Var(newListOptsVar(), flagLabelRemove, "Remove a label by its key")
+	flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path")
+	flags.Var(newListOptsVar(), flagPublishRemove, "Remove a published port by its target port")
+	flags.Var(newListOptsVar(), flagNetworkRemove, "Remove a network by name")
+	flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint")
+	flags.Var(&opts.labels, flagLabelAdd, "Add or update service labels")
+	flags.Var(&opts.env, flagEnvAdd, "Add or update environment variables")
+	flags.Var(&opts.mounts, flagMountAdd, "Add or update a mount on a service")
+	flags.StringSliceVar(&opts.constraints, flagConstraintAdd, []string{}, "Add or update placement constraints")
+	flags.StringSliceVar(&opts.networks, flagNetworkAdd, []string{}, "Add or update network attachments")
+	flags.Var(&opts.endpoint.ports, flagPublishAdd, "Add or update a published port")
 	return cmd
 	return cmd
 }
 }
 
 
+func newListOptsVar() *opts.ListOpts {
+	return opts.NewListOptsRef(&[]string{}, nil)
+}
+
 func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID string) error {
 func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID string) error {
 	apiClient := dockerCli.Client()
 	apiClient := dockerCli.Client()
 	ctx := context.Background()
 	ctx := context.Background()
@@ -85,19 +103,6 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
 		}
 		}
 	}
 	}
 
 
-	updateListOpts := func(flag string, field *[]string) {
-		if flags.Changed(flag) {
-			value := flags.Lookup(flag).Value.(*opts.ListOpts)
-			*field = value.GetAll()
-		}
-	}
-
-	updateSlice := func(flag string, field *[]string) {
-		if flags.Changed(flag) {
-			*field, _ = flags.GetStringSlice(flag)
-		}
-	}
-
 	updateInt64Value := func(flag string, field *int64) {
 	updateInt64Value := func(flag string, field *int64) {
 		if flags.Changed(flag) {
 		if flags.Changed(flag) {
 			*field = flags.Lookup(flag).Value.(int64Value).Value()
 			*field = flags.Lookup(flag).Value.(int64Value).Value()
@@ -141,9 +146,8 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
 	updateString(flagName, &spec.Name)
 	updateString(flagName, &spec.Name)
 	updateLabels(flags, &spec.Labels)
 	updateLabels(flags, &spec.Labels)
 	updateString("image", &cspec.Image)
 	updateString("image", &cspec.Image)
-	updateSlice("command", &cspec.Command)
-	updateSlice("arg", &cspec.Args)
-	updateListOpts("env", &cspec.Env)
+	updateStringToSlice(flags, "args", &cspec.Args)
+	updateEnvironment(flags, &cspec.Env)
 	updateString("workdir", &cspec.Dir)
 	updateString("workdir", &cspec.Dir)
 	updateString(flagUser, &cspec.User)
 	updateString(flagUser, &cspec.User)
 	updateMounts(flags, &cspec.Mounts)
 	updateMounts(flags, &cspec.Mounts)
@@ -176,21 +180,24 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
 		updateDurationOpt((flagRestartWindow), task.RestartPolicy.Window)
 		updateDurationOpt((flagRestartWindow), task.RestartPolicy.Window)
 	}
 	}
 
 
-	if flags.Changed(flagConstraint) {
-		task.Placement = &swarm.Placement{}
-		updateSlice(flagConstraint, &task.Placement.Constraints)
+	if anyChanged(flags, flagConstraintAdd, flagConstraintRemove) {
+		if task.Placement == nil {
+			task.Placement = &swarm.Placement{}
+		}
+		updatePlacement(flags, task.Placement)
 	}
 	}
 
 
 	if err := updateReplicas(flags, &spec.Mode); err != nil {
 	if err := updateReplicas(flags, &spec.Mode); err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay) {
+	if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateFailureAction) {
 		if spec.UpdateConfig == nil {
 		if spec.UpdateConfig == nil {
 			spec.UpdateConfig = &swarm.UpdateConfig{}
 			spec.UpdateConfig = &swarm.UpdateConfig{}
 		}
 		}
 		updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism)
 		updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism)
 		updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay)
 		updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay)
+		updateString(flagUpdateFailureAction, &spec.UpdateConfig.FailureAction)
 	}
 	}
 
 
 	updateNetworks(flags, &spec.Networks)
 	updateNetworks(flags, &spec.Networks)
@@ -199,27 +206,29 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
 		spec.EndpointSpec.Mode = swarm.ResolutionMode(value)
 		spec.EndpointSpec.Mode = swarm.ResolutionMode(value)
 	}
 	}
 
 
-	if flags.Changed(flagPublish) {
+	if anyChanged(flags, flagPublishAdd, flagPublishRemove) {
 		if spec.EndpointSpec == nil {
 		if spec.EndpointSpec == nil {
 			spec.EndpointSpec = &swarm.EndpointSpec{}
 			spec.EndpointSpec = &swarm.EndpointSpec{}
 		}
 		}
 		updatePorts(flags, &spec.EndpointSpec.Ports)
 		updatePorts(flags, &spec.EndpointSpec.Ports)
 	}
 	}
-	return nil
-}
 
 
-func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
-	if !flags.Changed(flagLabel) {
-		return
+	if err := updateLogDriver(flags, &spec.TaskTemplate); err != nil {
+		return err
 	}
 	}
 
 
-	values := flags.Lookup(flagLabel).Value.(*opts.ListOpts).GetAll()
+	return nil
+}
 
 
-	localLabels := map[string]string{}
-	for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
-		localLabels[key] = value
+func updateStringToSlice(flags *pflag.FlagSet, flag string, field *[]string) error {
+	if !flags.Changed(flag) {
+		return nil
 	}
 	}
-	*field = localLabels
+
+	value, _ := flags.GetString(flag)
+	valueSlice, err := shlex.Split(value)
+	*field = valueSlice
+	return err
 }
 }
 
 
 func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
 func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
@@ -231,42 +240,145 @@ func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
 	return false
 	return false
 }
 }
 
 
-// TODO: should this override by destination path, or does swarm handle that?
-func updateMounts(flags *pflag.FlagSet, mounts *[]swarm.Mount) {
-	if !flags.Changed(flagMount) {
-		return
+func updatePlacement(flags *pflag.FlagSet, placement *swarm.Placement) {
+	field, _ := flags.GetStringSlice(flagConstraintAdd)
+	placement.Constraints = append(placement.Constraints, field...)
+
+	toRemove := buildToRemoveSet(flags, flagConstraintRemove)
+	placement.Constraints = removeItems(placement.Constraints, toRemove, itemKey)
+}
+
+func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
+	if flags.Changed(flagLabelAdd) {
+		if *field == nil {
+			*field = map[string]string{}
+		}
+
+		values := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll()
+		for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
+			(*field)[key] = value
+		}
+	}
+
+	if *field != nil && flags.Changed(flagLabelRemove) {
+		toRemove := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll()
+		for _, label := range toRemove {
+			delete(*field, label)
+		}
 	}
 	}
+}
 
 
-	*mounts = flags.Lookup(flagMount).Value.(*MountOpt).Value()
+func updateEnvironment(flags *pflag.FlagSet, field *[]string) {
+	if flags.Changed(flagEnvAdd) {
+		value := flags.Lookup(flagEnvAdd).Value.(*opts.ListOpts)
+		*field = append(*field, value.GetAll()...)
+	}
+	toRemove := buildToRemoveSet(flags, flagEnvRemove)
+	*field = removeItems(*field, toRemove, envKey)
 }
 }
 
 
-// TODO: should this override by name, or does swarm handle that?
-func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) {
-	if !flags.Changed(flagPublish) {
-		return
+func envKey(value string) string {
+	kv := strings.SplitN(value, "=", 2)
+	return kv[0]
+}
+
+func itemKey(value string) string {
+	return value
+}
+
+func buildToRemoveSet(flags *pflag.FlagSet, flag string) map[string]struct{} {
+	var empty struct{}
+	toRemove := make(map[string]struct{})
+
+	if !flags.Changed(flag) {
+		return toRemove
 	}
 	}
 
 
-	values := flags.Lookup(flagPublish).Value.(*opts.ListOpts).GetAll()
-	ports, portBindings, _ := nat.ParsePortSpecs(values)
+	toRemoveSlice := flags.Lookup(flag).Value.(*opts.ListOpts).GetAll()
+	for _, key := range toRemoveSlice {
+		toRemove[key] = empty
+	}
+	return toRemove
+}
 
 
-	var localPortConfig []swarm.PortConfig
-	for port := range ports {
-		localPortConfig = append(localPortConfig, convertPortToPortConfig(port, portBindings)...)
+func removeItems(
+	seq []string,
+	toRemove map[string]struct{},
+	keyFunc func(string) string,
+) []string {
+	newSeq := []string{}
+	for _, item := range seq {
+		if _, exists := toRemove[keyFunc(item)]; !exists {
+			newSeq = append(newSeq, item)
+		}
 	}
 	}
-	*portConfig = localPortConfig
+	return newSeq
 }
 }
 
 
-func updateNetworks(flags *pflag.FlagSet, attachments *[]swarm.NetworkAttachmentConfig) {
-	if !flags.Changed(flagNetwork) {
+func updateMounts(flags *pflag.FlagSet, mounts *[]swarm.Mount) {
+	if flags.Changed(flagMountAdd) {
+		values := flags.Lookup(flagMountAdd).Value.(*MountOpt).Value()
+		*mounts = append(*mounts, values...)
+	}
+	toRemove := buildToRemoveSet(flags, flagMountRemove)
+
+	newMounts := []swarm.Mount{}
+	for _, mount := range *mounts {
+		if _, exists := toRemove[mount.Target]; !exists {
+			newMounts = append(newMounts, mount)
+		}
+	}
+	*mounts = newMounts
+}
+
+func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) {
+	if flags.Changed(flagPublishAdd) {
+		values := flags.Lookup(flagPublishAdd).Value.(*opts.ListOpts).GetAll()
+		ports, portBindings, _ := nat.ParsePortSpecs(values)
+
+		for port := range ports {
+			*portConfig = append(*portConfig, convertPortToPortConfig(port, portBindings)...)
+		}
+	}
+
+	if !flags.Changed(flagPublishRemove) {
 		return
 		return
 	}
 	}
-	networks, _ := flags.GetStringSlice(flagNetwork)
+	toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.ListOpts).GetAll()
+	newPorts := []swarm.PortConfig{}
+portLoop:
+	for _, port := range *portConfig {
+		for _, rawTargetPort := range toRemove {
+			targetPort := nat.Port(rawTargetPort)
+			if equalPort(targetPort, port) {
+				continue portLoop
+			}
+		}
+		newPorts = append(newPorts, port)
+	}
+	*portConfig = newPorts
+}
 
 
-	var localAttachments []swarm.NetworkAttachmentConfig
-	for _, network := range networks {
-		localAttachments = append(localAttachments, swarm.NetworkAttachmentConfig{Target: network})
+func equalPort(targetPort nat.Port, port swarm.PortConfig) bool {
+	return (string(port.Protocol) == targetPort.Proto() &&
+		port.TargetPort == uint32(targetPort.Int()))
+}
+
+func updateNetworks(flags *pflag.FlagSet, attachments *[]swarm.NetworkAttachmentConfig) {
+	if flags.Changed(flagNetworkAdd) {
+		networks, _ := flags.GetStringSlice(flagNetworkAdd)
+		for _, network := range networks {
+			*attachments = append(*attachments, swarm.NetworkAttachmentConfig{Target: network})
+		}
+	}
+	toRemove := buildToRemoveSet(flags, flagNetworkRemove)
+	newNetworks := []swarm.NetworkAttachmentConfig{}
+	for _, network := range *attachments {
+		if _, exists := toRemove[network.Target]; !exists {
+			newNetworks = append(newNetworks, network)
+		}
 	}
 	}
-	*attachments = localAttachments
+	*attachments = newNetworks
 }
 }
 
 
 func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
 func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
@@ -280,3 +392,27 @@ func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error
 	serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value()
 	serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value()
 	return nil
 	return nil
 }
 }
+
+// updateLogDriver updates the log driver only if the log driver flag is set.
+// All options will be replaced with those provided on the command line.
+func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error {
+	if !flags.Changed(flagLogDriver) {
+		return nil
+	}
+
+	name, err := flags.GetString(flagLogDriver)
+	if err != nil {
+		return err
+	}
+
+	if name == "" {
+		return nil
+	}
+
+	taskTemplate.LogDriver = &swarm.Driver{
+		Name:    name,
+		Options: runconfigopts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetAll()),
+	}
+
+	return nil
+}

+ 115 - 8
api/client/service/update_test.go

@@ -7,20 +7,127 @@ import (
 	"github.com/docker/engine-api/types/swarm"
 	"github.com/docker/engine-api/types/swarm"
 )
 )
 
 
-func TestUpdateServiceCommandAndArgs(t *testing.T) {
+func TestUpdateServiceArgs(t *testing.T) {
 	flags := newUpdateCommand(nil).Flags()
 	flags := newUpdateCommand(nil).Flags()
-	flags.Set("command", "the")
-	flags.Set("command", "new")
-	flags.Set("command", "command")
-	flags.Set("arg", "the")
-	flags.Set("arg", "new args")
+	flags.Set("args", "the \"new args\"")
 
 
 	spec := &swarm.ServiceSpec{}
 	spec := &swarm.ServiceSpec{}
 	cspec := &spec.TaskTemplate.ContainerSpec
 	cspec := &spec.TaskTemplate.ContainerSpec
-	cspec.Command = []string{"old", "command"}
 	cspec.Args = []string{"old", "args"}
 	cspec.Args = []string{"old", "args"}
 
 
 	updateService(flags, spec)
 	updateService(flags, spec)
-	assert.EqualStringSlice(t, cspec.Command, []string{"the", "new", "command"})
 	assert.EqualStringSlice(t, cspec.Args, []string{"the", "new args"})
 	assert.EqualStringSlice(t, cspec.Args, []string{"the", "new args"})
 }
 }
+
+func TestUpdateLabels(t *testing.T) {
+	flags := newUpdateCommand(nil).Flags()
+	flags.Set("label-add", "toadd=newlabel")
+	flags.Set("label-rm", "toremove")
+
+	labels := map[string]string{
+		"toremove": "thelabeltoremove",
+		"tokeep":   "value",
+	}
+
+	updateLabels(flags, &labels)
+	assert.Equal(t, len(labels), 2)
+	assert.Equal(t, labels["tokeep"], "value")
+	assert.Equal(t, labels["toadd"], "newlabel")
+}
+
+func TestUpdateLabelsRemoveALabelThatDoesNotExist(t *testing.T) {
+	flags := newUpdateCommand(nil).Flags()
+	flags.Set("label-rm", "dne")
+
+	labels := map[string]string{"foo": "theoldlabel"}
+	updateLabels(flags, &labels)
+	assert.Equal(t, len(labels), 1)
+}
+
+func TestUpdatePlacement(t *testing.T) {
+	flags := newUpdateCommand(nil).Flags()
+	flags.Set("constraint-add", "node=toadd")
+	flags.Set("constraint-rm", "node!=toremove")
+
+	placement := &swarm.Placement{
+		Constraints: []string{"node!=toremove", "container=tokeep"},
+	}
+
+	updatePlacement(flags, placement)
+	assert.Equal(t, len(placement.Constraints), 2)
+	assert.Equal(t, placement.Constraints[0], "container=tokeep")
+	assert.Equal(t, placement.Constraints[1], "node=toadd")
+}
+
+func TestUpdateEnvironment(t *testing.T) {
+	flags := newUpdateCommand(nil).Flags()
+	flags.Set("env-add", "toadd=newenv")
+	flags.Set("env-rm", "toremove")
+
+	envs := []string{"toremove=theenvtoremove", "tokeep=value"}
+
+	updateEnvironment(flags, &envs)
+	assert.Equal(t, len(envs), 2)
+	assert.Equal(t, envs[0], "tokeep=value")
+	assert.Equal(t, envs[1], "toadd=newenv")
+}
+
+func TestUpdateEnvironmentWithDuplicateValues(t *testing.T) {
+	flags := newUpdateCommand(nil).Flags()
+	flags.Set("env-add", "foo=newenv")
+	flags.Set("env-add", "foo=dupe")
+	flags.Set("env-rm", "foo")
+
+	envs := []string{"foo=value"}
+
+	updateEnvironment(flags, &envs)
+	assert.Equal(t, len(envs), 0)
+}
+
+func TestUpdateMounts(t *testing.T) {
+	flags := newUpdateCommand(nil).Flags()
+	flags.Set("mount-add", "type=volume,target=/toadd")
+	flags.Set("mount-rm", "/toremove")
+
+	mounts := []swarm.Mount{
+		{Target: "/toremove", Type: swarm.MountTypeBind},
+		{Target: "/tokeep", Type: swarm.MountTypeBind},
+	}
+
+	updateMounts(flags, &mounts)
+	assert.Equal(t, len(mounts), 2)
+	assert.Equal(t, mounts[0].Target, "/tokeep")
+	assert.Equal(t, mounts[1].Target, "/toadd")
+}
+
+func TestUpdateNetworks(t *testing.T) {
+	flags := newUpdateCommand(nil).Flags()
+	flags.Set("network-add", "toadd")
+	flags.Set("network-rm", "toremove")
+
+	attachments := []swarm.NetworkAttachmentConfig{
+		{Target: "toremove", Aliases: []string{"foo"}},
+		{Target: "tokeep"},
+	}
+
+	updateNetworks(flags, &attachments)
+	assert.Equal(t, len(attachments), 2)
+	assert.Equal(t, attachments[0].Target, "tokeep")
+	assert.Equal(t, attachments[1].Target, "toadd")
+}
+
+func TestUpdatePorts(t *testing.T) {
+	flags := newUpdateCommand(nil).Flags()
+	flags.Set("publish-add", "1000:1000")
+	flags.Set("publish-rm", "333/udp")
+
+	portConfigs := []swarm.PortConfig{
+		{TargetPort: 333, Protocol: swarm.PortConfigProtocolUDP},
+		{TargetPort: 555},
+	}
+
+	updatePorts(flags, &portConfigs)
+	assert.Equal(t, len(portConfigs), 2)
+	assert.Equal(t, portConfigs[0].TargetPort, uint32(555))
+	assert.Equal(t, portConfigs[1].TargetPort, uint32(1000))
+}

+ 26 - 7
api/client/stack/deploy.go

@@ -21,8 +21,9 @@ const (
 )
 )
 
 
 type deployOptions struct {
 type deployOptions struct {
-	bundlefile string
-	namespace  string
+	bundlefile       string
+	namespace        string
+	sendRegistryAuth bool
 }
 }
 
 
 func newDeployCommand(dockerCli *client.DockerCli) *cobra.Command {
 func newDeployCommand(dockerCli *client.DockerCli) *cobra.Command {
@@ -41,6 +42,7 @@ func newDeployCommand(dockerCli *client.DockerCli) *cobra.Command {
 
 
 	flags := cmd.Flags()
 	flags := cmd.Flags()
 	addBundlefileFlag(&opts.bundlefile, flags)
 	addBundlefileFlag(&opts.bundlefile, flags)
+	addRegistryAuthFlag(&opts.sendRegistryAuth, flags)
 	return cmd
 	return cmd
 }
 }
 
 
@@ -56,7 +58,7 @@ func runDeploy(dockerCli *client.DockerCli, opts deployOptions) error {
 	if err := updateNetworks(ctx, dockerCli, networks, opts.namespace); err != nil {
 	if err := updateNetworks(ctx, dockerCli, networks, opts.namespace); err != nil {
 		return err
 		return err
 	}
 	}
-	return deployServices(ctx, dockerCli, bundle.Services, opts.namespace)
+	return deployServices(ctx, dockerCli, bundle.Services, opts.namespace, opts.sendRegistryAuth)
 }
 }
 
 
 func getUniqueNetworkNames(services map[string]bundlefile.Service) []string {
 func getUniqueNetworkNames(services map[string]bundlefile.Service) []string {
@@ -129,6 +131,7 @@ func deployServices(
 	dockerCli *client.DockerCli,
 	dockerCli *client.DockerCli,
 	services map[string]bundlefile.Service,
 	services map[string]bundlefile.Service,
 	namespace string,
 	namespace string,
+	sendAuth bool,
 ) error {
 ) error {
 	apiClient := dockerCli.Client()
 	apiClient := dockerCli.Client()
 	out := dockerCli.Out()
 	out := dockerCli.Out()
@@ -181,24 +184,40 @@ func deployServices(
 			cspec.User = *service.User
 			cspec.User = *service.User
 		}
 		}
 
 
+		encodedAuth := ""
+		if sendAuth {
+			// Retrieve encoded auth token from the image reference
+			image := serviceSpec.TaskTemplate.ContainerSpec.Image
+			encodedAuth, err = dockerCli.RetrieveAuthTokenFromImage(ctx, image)
+			if err != nil {
+				return err
+			}
+		}
+
 		if service, exists := existingServiceMap[name]; exists {
 		if service, exists := existingServiceMap[name]; exists {
 			fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
 			fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
 
 
-			// TODO(nishanttotla): Pass auth token
+			updateOpts := types.ServiceUpdateOptions{}
+			if sendAuth {
+				updateOpts.EncodedRegistryAuth = encodedAuth
+			}
 			if err := apiClient.ServiceUpdate(
 			if err := apiClient.ServiceUpdate(
 				ctx,
 				ctx,
 				service.ID,
 				service.ID,
 				service.Version,
 				service.Version,
 				serviceSpec,
 				serviceSpec,
-				types.ServiceUpdateOptions{},
+				updateOpts,
 			); err != nil {
 			); err != nil {
 				return err
 				return err
 			}
 			}
 		} else {
 		} else {
 			fmt.Fprintf(out, "Creating service %s\n", name)
 			fmt.Fprintf(out, "Creating service %s\n", name)
 
 
-			// TODO(nishanttotla): Pass headers with X-Registry-Auth
-			if _, err := apiClient.ServiceCreate(ctx, serviceSpec, types.ServiceCreateOptions{}); err != nil {
+			createOpts := types.ServiceCreateOptions{}
+			if sendAuth {
+				createOpts.EncodedRegistryAuth = encodedAuth
+			}
+			if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil {
 				return err
 				return err
 			}
 			}
 		}
 		}

+ 7 - 3
api/client/stack/opts.go

@@ -12,12 +12,16 @@ import (
 )
 )
 
 
 func addBundlefileFlag(opt *string, flags *pflag.FlagSet) {
 func addBundlefileFlag(opt *string, flags *pflag.FlagSet) {
-	flags.StringVarP(
+	flags.StringVar(
 		opt,
 		opt,
-		"bundle", "f", "",
+		"file", "",
 		"Path to a Distributed Application Bundle file (Default: STACK.dab)")
 		"Path to a Distributed Application Bundle file (Default: STACK.dab)")
 }
 }
 
 
+func addRegistryAuthFlag(opt *bool, flags *pflag.FlagSet) {
+	flags.BoolVar(opt, "with-registry-auth", false, "Send registry authentication details to Swarm agents")
+}
+
 func loadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefile.Bundlefile, error) {
 func loadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefile.Bundlefile, error) {
 	defaultPath := fmt.Sprintf("%s.dab", namespace)
 	defaultPath := fmt.Sprintf("%s.dab", namespace)
 
 
@@ -26,7 +30,7 @@ func loadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefil
 	}
 	}
 	if _, err := os.Stat(path); err != nil {
 	if _, err := os.Stat(path); err != nil {
 		return nil, fmt.Errorf(
 		return nil, fmt.Errorf(
-			"Bundle %s not found. Specify the path with -f or --bundle",
+			"Bundle %s not found. Specify the path with --file",
 			path)
 			path)
 	}
 	}
 
 

+ 1 - 1
api/client/stack/tasks.go

@@ -38,7 +38,7 @@ func newTasksCommand(dockerCli *client.DockerCli) *cobra.Command {
 	}
 	}
 	flags := cmd.Flags()
 	flags := cmd.Flags()
 	flags.BoolVarP(&opts.all, "all", "a", false, "Display all tasks")
 	flags.BoolVarP(&opts.all, "all", "a", false, "Display all tasks")
-	flags.BoolVarP(&opts.noResolve, "no-resolve", "n", false, "Do not map IDs to Names")
+	flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
 	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
 	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
 
 
 	return cmd
 	return cmd

+ 1 - 0
api/client/swarm/cmd.go

@@ -22,6 +22,7 @@ func NewSwarmCommand(dockerCli *client.DockerCli) *cobra.Command {
 	cmd.AddCommand(
 	cmd.AddCommand(
 		newInitCommand(dockerCli),
 		newInitCommand(dockerCli),
 		newJoinCommand(dockerCli),
 		newJoinCommand(dockerCli),
+		newJoinTokenCommand(dockerCli),
 		newUpdateCommand(dockerCli),
 		newUpdateCommand(dockerCli),
 		newLeaveCommand(dockerCli),
 		newLeaveCommand(dockerCli),
 		newInspectCommand(dockerCli),
 		newInspectCommand(dockerCli),

+ 14 - 33
api/client/swarm/init.go

@@ -1,7 +1,9 @@
 package swarm
 package swarm
 
 
 import (
 import (
+	"errors"
 	"fmt"
 	"fmt"
+	"strings"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
@@ -21,21 +23,20 @@ const (
 
 
 type initOptions struct {
 type initOptions struct {
 	swarmOptions
 	swarmOptions
-	listenAddr      NodeAddrOption
+	listenAddr NodeAddrOption
+	// Not a NodeAddrOption because it has no default port.
+	advertiseAddr   string
 	forceNewCluster bool
 	forceNewCluster bool
 }
 }
 
 
 func newInitCommand(dockerCli *client.DockerCli) *cobra.Command {
 func newInitCommand(dockerCli *client.DockerCli) *cobra.Command {
 	opts := initOptions{
 	opts := initOptions{
 		listenAddr: NewListenAddrOption(),
 		listenAddr: NewListenAddrOption(),
-		swarmOptions: swarmOptions{
-			autoAccept: NewAutoAcceptOption(),
-		},
 	}
 	}
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
-		Use:   "init",
-		Short: "Initialize a Swarm",
+		Use:   "init [OPTIONS]",
+		Short: "Initialize a swarm",
 		Args:  cli.NoArgs,
 		Args:  cli.NoArgs,
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runInit(dockerCli, cmd.Flags(), opts)
 			return runInit(dockerCli, cmd.Flags(), opts)
@@ -43,7 +44,8 @@ func newInitCommand(dockerCli *client.DockerCli) *cobra.Command {
 	}
 	}
 
 
 	flags := cmd.Flags()
 	flags := cmd.Flags()
-	flags.Var(&opts.listenAddr, "listen-addr", "Listen address")
+	flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: <ip|interface>[:port])")
+	flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: <ip|interface>[:port])")
 	flags.BoolVar(&opts.forceNewCluster, "force-new-cluster", false, "Force create a new cluster from current state.")
 	flags.BoolVar(&opts.forceNewCluster, "force-new-cluster", false, "Force create a new cluster from current state.")
 	addSwarmFlags(flags, &opts.swarmOptions)
 	addSwarmFlags(flags, &opts.swarmOptions)
 	return cmd
 	return cmd
@@ -53,43 +55,22 @@ func runInit(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts initOptions
 	client := dockerCli.Client()
 	client := dockerCli.Client()
 	ctx := context.Background()
 	ctx := context.Background()
 
 
-	// If no secret was specified, we create a random one
-	if !flags.Changed("secret") {
-		opts.secret = generateRandomSecret()
-		fmt.Fprintf(dockerCli.Out(), "No --secret provided. Generated random secret:\n\t%s\n\n", opts.secret)
-	}
-
 	req := swarm.InitRequest{
 	req := swarm.InitRequest{
 		ListenAddr:      opts.listenAddr.String(),
 		ListenAddr:      opts.listenAddr.String(),
+		AdvertiseAddr:   opts.advertiseAddr,
 		ForceNewCluster: opts.forceNewCluster,
 		ForceNewCluster: opts.forceNewCluster,
 		Spec:            opts.swarmOptions.ToSpec(),
 		Spec:            opts.swarmOptions.ToSpec(),
 	}
 	}
 
 
 	nodeID, err := client.SwarmInit(ctx, req)
 	nodeID, err := client.SwarmInit(ctx, req)
 	if err != nil {
 	if err != nil {
+		if strings.Contains(err.Error(), "could not choose an IP address to advertise") || strings.Contains(err.Error(), "could not find the system's IP address") {
+			return errors.New(err.Error() + " - specify one with --advertise-addr")
+		}
 		return err
 		return err
 	}
 	}
 
 
 	fmt.Fprintf(dockerCli.Out(), "Swarm initialized: current node (%s) is now a manager.\n\n", nodeID)
 	fmt.Fprintf(dockerCli.Out(), "Swarm initialized: current node (%s) is now a manager.\n\n", nodeID)
 
 
-	// Fetch CAHash and Address from the API
-	info, err := client.Info(ctx)
-	if err != nil {
-		return err
-	}
-
-	node, _, err := client.NodeInspectWithRaw(ctx, nodeID)
-	if err != nil {
-		return err
-	}
-
-	if node.ManagerStatus != nil && info.Swarm.CACertHash != "" {
-		var secretArgs string
-		if opts.secret != "" {
-			secretArgs = "--secret " + opts.secret
-		}
-		fmt.Fprintf(dockerCli.Out(), "To add a worker to this swarm, run the following command:\n\tdocker swarm join %s \\\n\t--ca-hash %s \\\n\t%s\n", secretArgs, info.Swarm.CACertHash, node.ManagerStatus.Addr)
-	}
-
-	return nil
+	return printJoinCommand(ctx, dockerCli, nodeID, true, true)
 }
 }

+ 1 - 10
api/client/swarm/inspect.go

@@ -11,7 +11,6 @@ import (
 
 
 type inspectOptions struct {
 type inspectOptions struct {
 	format string
 	format string
-	//	pretty  bool
 }
 }
 
 
 func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
 func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
@@ -19,19 +18,15 @@ func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
 		Use:   "inspect [OPTIONS]",
 		Use:   "inspect [OPTIONS]",
-		Short: "Inspect the Swarm",
+		Short: "Inspect the swarm",
 		Args:  cli.NoArgs,
 		Args:  cli.NoArgs,
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
-			// if opts.pretty && len(opts.format) > 0 {
-			//	return fmt.Errorf("--format is incompatible with human friendly format")
-			// }
 			return runInspect(dockerCli, opts)
 			return runInspect(dockerCli, opts)
 		},
 		},
 	}
 	}
 
 
 	flags := cmd.Flags()
 	flags := cmd.Flags()
 	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
 	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
-	//flags.BoolVarP(&opts.pretty, "pretty", "h", false, "Print the information in a human friendly format.")
 	return cmd
 	return cmd
 }
 }
 
 
@@ -48,9 +43,5 @@ func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
 		return swarm, nil, nil
 		return swarm, nil, nil
 	}
 	}
 
 
-	//	if !opts.pretty {
 	return inspect.Inspect(dockerCli.Out(), []string{""}, opts.format, getRef)
 	return inspect.Inspect(dockerCli.Out(), []string{""}, opts.format, getRef)
-	//	}
-
-	//return printHumanFriendly(dockerCli.Out(), opts.refs, getRef)
 }
 }

+ 26 - 16
api/client/swarm/join.go

@@ -2,6 +2,7 @@ package swarm
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"strings"
 
 
 	"github.com/docker/docker/api/client"
 	"github.com/docker/docker/api/client"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
@@ -13,9 +14,9 @@ import (
 type joinOptions struct {
 type joinOptions struct {
 	remote     string
 	remote     string
 	listenAddr NodeAddrOption
 	listenAddr NodeAddrOption
-	manager    bool
-	secret     string
-	CACertHash string
+	// Not a NodeAddrOption because it has no default port.
+	advertiseAddr string
+	token         string
 }
 }
 
 
 func newJoinCommand(dockerCli *client.DockerCli) *cobra.Command {
 func newJoinCommand(dockerCli *client.DockerCli) *cobra.Command {
@@ -25,7 +26,7 @@ func newJoinCommand(dockerCli *client.DockerCli) *cobra.Command {
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
 		Use:   "join [OPTIONS] HOST:PORT",
 		Use:   "join [OPTIONS] HOST:PORT",
-		Short: "Join a Swarm as a node and/or manager",
+		Short: "Join a swarm as a node and/or manager",
 		Args:  cli.ExactArgs(1),
 		Args:  cli.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			opts.remote = args[0]
 			opts.remote = args[0]
@@ -34,10 +35,9 @@ func newJoinCommand(dockerCli *client.DockerCli) *cobra.Command {
 	}
 	}
 
 
 	flags := cmd.Flags()
 	flags := cmd.Flags()
-	flags.Var(&opts.listenAddr, flagListenAddr, "Listen address")
-	flags.BoolVar(&opts.manager, "manager", false, "Try joining as a manager.")
-	flags.StringVar(&opts.secret, flagSecret, "", "Secret for node acceptance")
-	flags.StringVar(&opts.CACertHash, "ca-hash", "", "Hash of the Root Certificate Authority certificate used for trusted join")
+	flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: <ip|interface>[:port])")
+	flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: <ip|interface>[:port])")
+	flags.StringVar(&opts.token, flagToken, "", "Token for entry into the swarm")
 	return cmd
 	return cmd
 }
 }
 
 
@@ -46,20 +46,30 @@ func runJoin(dockerCli *client.DockerCli, opts joinOptions) error {
 	ctx := context.Background()
 	ctx := context.Background()
 
 
 	req := swarm.JoinRequest{
 	req := swarm.JoinRequest{
-		Manager:     opts.manager,
-		Secret:      opts.secret,
-		ListenAddr:  opts.listenAddr.String(),
-		RemoteAddrs: []string{opts.remote},
-		CACertHash:  opts.CACertHash,
+		JoinToken:     opts.token,
+		ListenAddr:    opts.listenAddr.String(),
+		AdvertiseAddr: opts.advertiseAddr,
+		RemoteAddrs:   []string{opts.remote},
 	}
 	}
 	err := client.SwarmJoin(ctx, req)
 	err := client.SwarmJoin(ctx, req)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	if opts.manager {
-		fmt.Fprintln(dockerCli.Out(), "This node joined a Swarm as a manager.")
+
+	info, err := client.Info(ctx)
+	if err != nil {
+		return err
+	}
+
+	_, _, err = client.NodeInspectWithRaw(ctx, info.Swarm.NodeID)
+	if err != nil {
+		// TODO(aaronl): is there a better way to do this?
+		if strings.Contains(err.Error(), "This node is not a swarm manager.") {
+			fmt.Fprintln(dockerCli.Out(), "This node joined a swarm as a worker.")
+		}
 	} else {
 	} else {
-		fmt.Fprintln(dockerCli.Out(), "This node joined a Swarm as a worker.")
+		fmt.Fprintln(dockerCli.Out(), "This node joined a swarm as a manager.")
 	}
 	}
+
 	return nil
 	return nil
 }
 }

+ 110 - 0
api/client/swarm/join_token.go

@@ -0,0 +1,110 @@
+package swarm
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/spf13/cobra"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/engine-api/types/swarm"
+	"golang.org/x/net/context"
+)
+
+const (
+	flagRotate = "rotate"
+	flagQuiet  = "quiet"
+)
+
+func newJoinTokenCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var rotate, quiet bool
+
+	cmd := &cobra.Command{
+		Use:   "join-token [-q] [--rotate] (worker|manager)",
+		Short: "Manage join tokens",
+		Args:  cli.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			if args[0] != "worker" && args[0] != "manager" {
+				return errors.New("unknown role " + args[0])
+			}
+
+			client := dockerCli.Client()
+			ctx := context.Background()
+
+			if rotate {
+				var flags swarm.UpdateFlags
+
+				swarm, err := client.SwarmInspect(ctx)
+				if err != nil {
+					return err
+				}
+
+				if args[0] == "worker" {
+					flags.RotateWorkerToken = true
+				} else if args[0] == "manager" {
+					flags.RotateManagerToken = true
+				}
+
+				err = client.SwarmUpdate(ctx, swarm.Version, swarm.Spec, flags)
+				if err != nil {
+					return err
+				}
+			}
+
+			swarm, err := client.SwarmInspect(ctx)
+			if err != nil {
+				return err
+			}
+
+			if quiet {
+				if args[0] == "worker" {
+					fmt.Fprintln(dockerCli.Out(), swarm.JoinTokens.Worker)
+				} else if args[0] == "manager" {
+					fmt.Fprintln(dockerCli.Out(), swarm.JoinTokens.Manager)
+				}
+			} else {
+				info, err := client.Info(ctx)
+				if err != nil {
+					return err
+				}
+				return printJoinCommand(ctx, dockerCli, info.Swarm.NodeID, args[0] == "worker", args[0] == "manager")
+			}
+			return nil
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.BoolVar(&rotate, flagRotate, false, "Rotate join token")
+	flags.BoolVarP(&quiet, flagQuiet, "q", false, "Only display token")
+
+	return cmd
+}
+
+func printJoinCommand(ctx context.Context, dockerCli *client.DockerCli, nodeID string, worker bool, manager bool) error {
+	client := dockerCli.Client()
+
+	swarm, err := client.SwarmInspect(ctx)
+	if err != nil {
+		return err
+	}
+
+	node, _, err := client.NodeInspectWithRaw(ctx, nodeID)
+	if err != nil {
+		return err
+	}
+
+	if node.ManagerStatus != nil {
+		if worker {
+			fmt.Fprintf(dockerCli.Out(), "To add a worker to this swarm, run the following command:\n    docker swarm join \\\n    --token %s \\\n    %s\n", swarm.JoinTokens.Worker, node.ManagerStatus.Addr)
+		}
+		if manager {
+			if worker {
+				fmt.Fprintln(dockerCli.Out())
+			}
+			fmt.Fprintf(dockerCli.Out(), "To add a manager to this swarm, run the following command:\n    docker swarm join \\\n    --token %s \\\n    %s\n", swarm.JoinTokens.Manager, node.ManagerStatus.Addr)
+		}
+	}
+
+	return nil
+}

+ 2 - 2
api/client/swarm/leave.go

@@ -18,8 +18,8 @@ func newLeaveCommand(dockerCli *client.DockerCli) *cobra.Command {
 	opts := leaveOptions{}
 	opts := leaveOptions{}
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
-		Use:   "leave",
-		Short: "Leave a Swarm",
+		Use:   "leave [OPTIONS]",
+		Short: "Leave a swarm",
 		Args:  cli.NoArgs,
 		Args:  cli.NoArgs,
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runLeave(dockerCli, opts)
 			return runLeave(dockerCli, opts)

+ 3 - 88
api/client/swarm/opts.go

@@ -15,29 +15,16 @@ import (
 const (
 const (
 	defaultListenAddr = "0.0.0.0:2377"
 	defaultListenAddr = "0.0.0.0:2377"
 
 
-	worker  = "WORKER"
-	manager = "MANAGER"
-	none    = "NONE"
-
-	flagAutoAccept          = "auto-accept"
 	flagCertExpiry          = "cert-expiry"
 	flagCertExpiry          = "cert-expiry"
 	flagDispatcherHeartbeat = "dispatcher-heartbeat"
 	flagDispatcherHeartbeat = "dispatcher-heartbeat"
 	flagListenAddr          = "listen-addr"
 	flagListenAddr          = "listen-addr"
-	flagSecret              = "secret"
+	flagAdvertiseAddr       = "advertise-addr"
+	flagToken               = "token"
 	flagTaskHistoryLimit    = "task-history-limit"
 	flagTaskHistoryLimit    = "task-history-limit"
 	flagExternalCA          = "external-ca"
 	flagExternalCA          = "external-ca"
 )
 )
 
 
-var (
-	defaultPolicies = []swarm.Policy{
-		{Role: worker, Autoaccept: true},
-		{Role: manager, Autoaccept: false},
-	}
-)
-
 type swarmOptions struct {
 type swarmOptions struct {
-	autoAccept          AutoAcceptOption
-	secret              string
 	taskHistoryLimit    int64
 	taskHistoryLimit    int64
 	dispatcherHeartbeat time.Duration
 	dispatcherHeartbeat time.Duration
 	nodeCertExpiry      time.Duration
 	nodeCertExpiry      time.Duration
@@ -84,71 +71,6 @@ func NewListenAddrOption() NodeAddrOption {
 	return NewNodeAddrOption(defaultListenAddr)
 	return NewNodeAddrOption(defaultListenAddr)
 }
 }
 
 
-// AutoAcceptOption is a value type for auto-accept policy
-type AutoAcceptOption struct {
-	values map[string]struct{}
-}
-
-// String prints a string representation of this option
-func (o *AutoAcceptOption) String() string {
-	keys := []string{}
-	for key := range o.values {
-		keys = append(keys, fmt.Sprintf("%s=true", strings.ToLower(key)))
-	}
-	return strings.Join(keys, ", ")
-}
-
-// Set sets a new value on this option
-func (o *AutoAcceptOption) Set(acceptValues string) error {
-	for _, value := range strings.Split(acceptValues, ",") {
-		value = strings.ToUpper(value)
-		switch value {
-		case none, worker, manager:
-			o.values[value] = struct{}{}
-		default:
-			return fmt.Errorf("must be one / combination of %s, %s; or NONE", worker, manager)
-		}
-	}
-	// NONE must stand alone, so if any non-NONE setting exist with it, error with conflict
-	if o.isPresent(none) && len(o.values) > 1 {
-		return fmt.Errorf("value NONE cannot be specified alongside other node types")
-	}
-	return nil
-}
-
-// Type returns the type of this option
-func (o *AutoAcceptOption) Type() string {
-	return "auto-accept"
-}
-
-// Policies returns a representation of this option for the api
-func (o *AutoAcceptOption) Policies(secret *string) []swarm.Policy {
-	policies := []swarm.Policy{}
-	for _, p := range defaultPolicies {
-		if len(o.values) != 0 {
-			if _, ok := o.values[string(p.Role)]; ok {
-				p.Autoaccept = true
-			} else {
-				p.Autoaccept = false
-			}
-		}
-		p.Secret = secret
-		policies = append(policies, p)
-	}
-	return policies
-}
-
-// isPresent returns whether the key exists in the set or not
-func (o *AutoAcceptOption) isPresent(key string) bool {
-	_, c := o.values[key]
-	return c
-}
-
-// NewAutoAcceptOption returns a new auto-accept option
-func NewAutoAcceptOption() AutoAcceptOption {
-	return AutoAcceptOption{values: make(map[string]struct{})}
-}
-
 // ExternalCAOption is a Value type for parsing external CA specifications.
 // ExternalCAOption is a Value type for parsing external CA specifications.
 type ExternalCAOption struct {
 type ExternalCAOption struct {
 	values []*swarm.ExternalCA
 	values []*swarm.ExternalCA
@@ -239,9 +161,7 @@ func parseExternalCA(caSpec string) (*swarm.ExternalCA, error) {
 }
 }
 
 
 func addSwarmFlags(flags *pflag.FlagSet, opts *swarmOptions) {
 func addSwarmFlags(flags *pflag.FlagSet, opts *swarmOptions) {
-	flags.Var(&opts.autoAccept, flagAutoAccept, "Auto acceptance policy (worker, manager or none)")
-	flags.StringVar(&opts.secret, flagSecret, "", "Set secret value needed to join a cluster")
-	flags.Int64Var(&opts.taskHistoryLimit, flagTaskHistoryLimit, 10, "Task history retention limit")
+	flags.Int64Var(&opts.taskHistoryLimit, flagTaskHistoryLimit, 5, "Task history retention limit")
 	flags.DurationVar(&opts.dispatcherHeartbeat, flagDispatcherHeartbeat, time.Duration(5*time.Second), "Dispatcher heartbeat period")
 	flags.DurationVar(&opts.dispatcherHeartbeat, flagDispatcherHeartbeat, time.Duration(5*time.Second), "Dispatcher heartbeat period")
 	flags.DurationVar(&opts.nodeCertExpiry, flagCertExpiry, time.Duration(90*24*time.Hour), "Validity period for node certificates")
 	flags.DurationVar(&opts.nodeCertExpiry, flagCertExpiry, time.Duration(90*24*time.Hour), "Validity period for node certificates")
 	flags.Var(&opts.externalCA, flagExternalCA, "Specifications of one or more certificate signing endpoints")
 	flags.Var(&opts.externalCA, flagExternalCA, "Specifications of one or more certificate signing endpoints")
@@ -249,11 +169,6 @@ func addSwarmFlags(flags *pflag.FlagSet, opts *swarmOptions) {
 
 
 func (opts *swarmOptions) ToSpec() swarm.Spec {
 func (opts *swarmOptions) ToSpec() swarm.Spec {
 	spec := swarm.Spec{}
 	spec := swarm.Spec{}
-	if opts.secret != "" {
-		spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(&opts.secret)
-	} else {
-		spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(nil)
-	}
 	spec.Orchestration.TaskHistoryRetentionLimit = opts.taskHistoryLimit
 	spec.Orchestration.TaskHistoryRetentionLimit = opts.taskHistoryLimit
 	spec.Dispatcher.HeartbeatPeriod = uint64(opts.dispatcherHeartbeat.Nanoseconds())
 	spec.Dispatcher.HeartbeatPeriod = uint64(opts.dispatcherHeartbeat.Nanoseconds())
 	spec.CAConfig.NodeCertExpiry = opts.nodeCertExpiry
 	spec.CAConfig.NodeCertExpiry = opts.nodeCertExpiry

+ 0 - 99
api/client/swarm/opts_test.go

@@ -4,7 +4,6 @@ import (
 	"testing"
 	"testing"
 
 
 	"github.com/docker/docker/pkg/testutil/assert"
 	"github.com/docker/docker/pkg/testutil/assert"
-	"github.com/docker/engine-api/types/swarm"
 )
 )
 
 
 func TestNodeAddrOptionSetHostAndPort(t *testing.T) {
 func TestNodeAddrOptionSetHostAndPort(t *testing.T) {
@@ -36,101 +35,3 @@ func TestNodeAddrOptionSetInvalidFormat(t *testing.T) {
 	opt := NewListenAddrOption()
 	opt := NewListenAddrOption()
 	assert.Error(t, opt.Set("http://localhost:4545"), "Invalid")
 	assert.Error(t, opt.Set("http://localhost:4545"), "Invalid")
 }
 }
-
-func TestAutoAcceptOptionSetWorker(t *testing.T) {
-	opt := NewAutoAcceptOption()
-	assert.NilError(t, opt.Set("worker"))
-	assert.Equal(t, opt.isPresent(worker), true)
-}
-
-func TestAutoAcceptOptionSetManager(t *testing.T) {
-	opt := NewAutoAcceptOption()
-	assert.NilError(t, opt.Set("manager"))
-	assert.Equal(t, opt.isPresent(manager), true)
-}
-
-func TestAutoAcceptOptionSetInvalid(t *testing.T) {
-	opt := NewAutoAcceptOption()
-	assert.Error(t, opt.Set("bogus"), "must be one / combination")
-}
-
-func TestAutoAcceptOptionSetEmpty(t *testing.T) {
-	opt := NewAutoAcceptOption()
-	assert.Error(t, opt.Set(""), "must be one / combination")
-}
-
-func TestAutoAcceptOptionSetNone(t *testing.T) {
-	opt := NewAutoAcceptOption()
-	assert.NilError(t, opt.Set("none"))
-	assert.Equal(t, opt.isPresent(manager), false)
-	assert.Equal(t, opt.isPresent(worker), false)
-}
-
-func TestAutoAcceptOptionSetTwo(t *testing.T) {
-	opt := NewAutoAcceptOption()
-	assert.NilError(t, opt.Set("worker,manager"))
-	assert.Equal(t, opt.isPresent(manager), true)
-	assert.Equal(t, opt.isPresent(worker), true)
-}
-
-func TestAutoAcceptOptionSetConflict(t *testing.T) {
-	opt := NewAutoAcceptOption()
-	assert.Error(t, opt.Set("none,manager"), "value NONE cannot be specified alongside other node types")
-
-	opt = NewAutoAcceptOption()
-	assert.Error(t, opt.Set("none,worker"), "value NONE cannot be specified alongside other node types")
-
-	opt = NewAutoAcceptOption()
-	assert.Error(t, opt.Set("worker,none,manager"), "value NONE cannot be specified alongside other node types")
-
-	opt = NewAutoAcceptOption()
-	assert.Error(t, opt.Set("worker,manager,none"), "value NONE cannot be specified alongside other node types")
-}
-
-func TestAutoAcceptOptionPoliciesDefault(t *testing.T) {
-	opt := NewAutoAcceptOption()
-	secret := "thesecret"
-
-	policies := opt.Policies(&secret)
-	assert.Equal(t, len(policies), 2)
-	assert.Equal(t, policies[0], swarm.Policy{
-		Role:       worker,
-		Autoaccept: true,
-		Secret:     &secret,
-	})
-	assert.Equal(t, policies[1], swarm.Policy{
-		Role:       manager,
-		Autoaccept: false,
-		Secret:     &secret,
-	})
-}
-
-func TestAutoAcceptOptionPoliciesWithManager(t *testing.T) {
-	opt := NewAutoAcceptOption()
-	secret := "thesecret"
-
-	assert.NilError(t, opt.Set("manager"))
-
-	policies := opt.Policies(&secret)
-	assert.Equal(t, len(policies), 2)
-	assert.Equal(t, policies[0], swarm.Policy{
-		Role:       worker,
-		Autoaccept: false,
-		Secret:     &secret,
-	})
-	assert.Equal(t, policies[1], swarm.Policy{
-		Role:       manager,
-		Autoaccept: true,
-		Secret:     &secret,
-	})
-}
-
-func TestAutoAcceptOptionString(t *testing.T) {
-	opt := NewAutoAcceptOption()
-	assert.NilError(t, opt.Set("manager"))
-	assert.NilError(t, opt.Set("worker"))
-
-	repr := opt.String()
-	assert.Contains(t, repr, "worker=true")
-	assert.Contains(t, repr, "manager=true")
-}

+ 0 - 19
api/client/swarm/secret.go

@@ -1,19 +0,0 @@
-package swarm
-
-import (
-	cryptorand "crypto/rand"
-	"fmt"
-	"math/big"
-)
-
-func generateRandomSecret() string {
-	var secretBytes [generatedSecretEntropyBytes]byte
-
-	if _, err := cryptorand.Read(secretBytes[:]); err != nil {
-		panic(fmt.Errorf("failed to read random bytes: %v", err))
-	}
-
-	var nn big.Int
-	nn.SetBytes(secretBytes[:])
-	return fmt.Sprintf("%0[1]*s", maxGeneratedSecretLength, nn.Text(generatedSecretBase))
-}

+ 6 - 19
api/client/swarm/update.go

@@ -13,11 +13,11 @@ import (
 )
 )
 
 
 func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
 func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
-	opts := swarmOptions{autoAccept: NewAutoAcceptOption()}
+	opts := swarmOptions{}
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
-		Use:   "update",
-		Short: "Update the Swarm",
+		Use:   "update [OPTIONS]",
+		Short: "Update the swarm",
 		Args:  cli.NoArgs,
 		Args:  cli.NoArgs,
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runUpdate(dockerCli, cmd.Flags(), opts)
 			return runUpdate(dockerCli, cmd.Flags(), opts)
@@ -32,6 +32,8 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts swarmOpti
 	client := dockerCli.Client()
 	client := dockerCli.Client()
 	ctx := context.Background()
 	ctx := context.Background()
 
 
+	var updateFlags swarm.UpdateFlags
+
 	swarm, err := client.SwarmInspect(ctx)
 	swarm, err := client.SwarmInspect(ctx)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -42,7 +44,7 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts swarmOpti
 		return err
 		return err
 	}
 	}
 
 
-	err = client.SwarmUpdate(ctx, swarm.Version, swarm.Spec)
+	err = client.SwarmUpdate(ctx, swarm.Version, swarm.Spec, updateFlags)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -55,21 +57,6 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts swarmOpti
 func mergeSwarm(swarm *swarm.Swarm, flags *pflag.FlagSet) error {
 func mergeSwarm(swarm *swarm.Swarm, flags *pflag.FlagSet) error {
 	spec := &swarm.Spec
 	spec := &swarm.Spec
 
 
-	if flags.Changed(flagAutoAccept) {
-		value := flags.Lookup(flagAutoAccept).Value.(*AutoAcceptOption)
-		spec.AcceptancePolicy.Policies = value.Policies(nil)
-	}
-
-	var psecret *string
-	if flags.Changed(flagSecret) {
-		secret, _ := flags.GetString(flagSecret)
-		psecret = &secret
-	}
-
-	for i := range spec.AcceptancePolicy.Policies {
-		spec.AcceptancePolicy.Policies[i].Secret = psecret
-	}
-
 	if flags.Changed(flagTaskHistoryLimit) {
 	if flags.Changed(flagTaskHistoryLimit) {
 		spec.Orchestration.TaskHistoryRetentionLimit, _ = flags.GetInt64(flagTaskHistoryLimit)
 		spec.Orchestration.TaskHistoryRetentionLimit, _ = flags.GetInt64(flagTaskHistoryLimit)
 	}
 	}

+ 203 - 0
api/client/system/info.go

@@ -0,0 +1,203 @@
+package system
+
+import (
+	"fmt"
+	"strings"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/pkg/ioutils"
+	"github.com/docker/docker/utils"
+	"github.com/docker/engine-api/types/swarm"
+	"github.com/docker/go-units"
+	"github.com/spf13/cobra"
+)
+
+// NewInfoCommand creates a new cobra.Command for `docker info`
+func NewInfoCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "info",
+		Short: "Display system-wide information",
+		Args:  cli.ExactArgs(0),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runInfo(dockerCli)
+		},
+	}
+	return cmd
+
+}
+
+func runInfo(dockerCli *client.DockerCli) error {
+	info, err := dockerCli.Client().Info(context.Background())
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(dockerCli.Out(), "Containers: %d\n", info.Containers)
+	fmt.Fprintf(dockerCli.Out(), " Running: %d\n", info.ContainersRunning)
+	fmt.Fprintf(dockerCli.Out(), " Paused: %d\n", info.ContainersPaused)
+	fmt.Fprintf(dockerCli.Out(), " Stopped: %d\n", info.ContainersStopped)
+	fmt.Fprintf(dockerCli.Out(), "Images: %d\n", info.Images)
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Server Version: %s\n", info.ServerVersion)
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Storage Driver: %s\n", info.Driver)
+	if info.DriverStatus != nil {
+		for _, pair := range info.DriverStatus {
+			fmt.Fprintf(dockerCli.Out(), " %s: %s\n", pair[0], pair[1])
+
+			// print a warning if devicemapper is using a loopback file
+			if pair[0] == "Data loop file" {
+				fmt.Fprintln(dockerCli.Err(), " WARNING: Usage of loopback devices is strongly discouraged for production use. Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.")
+			}
+		}
+
+	}
+	if info.SystemStatus != nil {
+		for _, pair := range info.SystemStatus {
+			fmt.Fprintf(dockerCli.Out(), "%s: %s\n", pair[0], pair[1])
+		}
+	}
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Logging Driver: %s\n", info.LoggingDriver)
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Cgroup Driver: %s\n", info.CgroupDriver)
+
+	fmt.Fprintf(dockerCli.Out(), "Plugins: \n")
+	fmt.Fprintf(dockerCli.Out(), " Volume:")
+	fmt.Fprintf(dockerCli.Out(), " %s", strings.Join(info.Plugins.Volume, " "))
+	fmt.Fprintf(dockerCli.Out(), "\n")
+	fmt.Fprintf(dockerCli.Out(), " Network:")
+	fmt.Fprintf(dockerCli.Out(), " %s", strings.Join(info.Plugins.Network, " "))
+	fmt.Fprintf(dockerCli.Out(), "\n")
+
+	if len(info.Plugins.Authorization) != 0 {
+		fmt.Fprintf(dockerCli.Out(), " Authorization:")
+		fmt.Fprintf(dockerCli.Out(), " %s", strings.Join(info.Plugins.Authorization, " "))
+		fmt.Fprintf(dockerCli.Out(), "\n")
+	}
+
+	fmt.Fprintf(dockerCli.Out(), "Swarm: %v\n", info.Swarm.LocalNodeState)
+	if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive {
+		fmt.Fprintf(dockerCli.Out(), " NodeID: %s\n", info.Swarm.NodeID)
+		if info.Swarm.Error != "" {
+			fmt.Fprintf(dockerCli.Out(), " Error: %v\n", info.Swarm.Error)
+		}
+		fmt.Fprintf(dockerCli.Out(), " Is Manager: %v\n", info.Swarm.ControlAvailable)
+		if info.Swarm.ControlAvailable {
+			fmt.Fprintf(dockerCli.Out(), " Managers: %d\n", info.Swarm.Managers)
+			fmt.Fprintf(dockerCli.Out(), " Nodes: %d\n", info.Swarm.Nodes)
+		}
+		fmt.Fprintf(dockerCli.Out(), " Node Address: %s\n", info.Swarm.NodeAddr)
+	}
+
+	if len(info.Runtimes) > 0 {
+		fmt.Fprintf(dockerCli.Out(), "Runtimes:")
+		for name := range info.Runtimes {
+			fmt.Fprintf(dockerCli.Out(), " %s", name)
+		}
+		fmt.Fprint(dockerCli.Out(), "\n")
+		fmt.Fprintf(dockerCli.Out(), "Default Runtime: %s\n", info.DefaultRuntime)
+	}
+
+	fmt.Fprintf(dockerCli.Out(), "Security Options:")
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), " %s", strings.Join(info.SecurityOptions, " "))
+	fmt.Fprintf(dockerCli.Out(), "\n")
+
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Kernel Version: %s\n", info.KernelVersion)
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Operating System: %s\n", info.OperatingSystem)
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), "OSType: %s\n", info.OSType)
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Architecture: %s\n", info.Architecture)
+	fmt.Fprintf(dockerCli.Out(), "CPUs: %d\n", info.NCPU)
+	fmt.Fprintf(dockerCli.Out(), "Total Memory: %s\n", units.BytesSize(float64(info.MemTotal)))
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Name: %s\n", info.Name)
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), "ID: %s\n", info.ID)
+	fmt.Fprintf(dockerCli.Out(), "Docker Root Dir: %s\n", info.DockerRootDir)
+	fmt.Fprintf(dockerCli.Out(), "Debug Mode (client): %v\n", utils.IsDebugEnabled())
+	fmt.Fprintf(dockerCli.Out(), "Debug Mode (server): %v\n", info.Debug)
+
+	if info.Debug {
+		fmt.Fprintf(dockerCli.Out(), " File Descriptors: %d\n", info.NFd)
+		fmt.Fprintf(dockerCli.Out(), " Goroutines: %d\n", info.NGoroutines)
+		fmt.Fprintf(dockerCli.Out(), " System Time: %s\n", info.SystemTime)
+		fmt.Fprintf(dockerCli.Out(), " EventsListeners: %d\n", info.NEventsListener)
+	}
+
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Http Proxy: %s\n", info.HTTPProxy)
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), "Https Proxy: %s\n", info.HTTPSProxy)
+	ioutils.FprintfIfNotEmpty(dockerCli.Out(), "No Proxy: %s\n", info.NoProxy)
+
+	if info.IndexServerAddress != "" {
+		u := dockerCli.ConfigFile().AuthConfigs[info.IndexServerAddress].Username
+		if len(u) > 0 {
+			fmt.Fprintf(dockerCli.Out(), "Username: %v\n", u)
+		}
+		fmt.Fprintf(dockerCli.Out(), "Registry: %v\n", info.IndexServerAddress)
+	}
+
+	// Only output these warnings if the server does not support these features
+	if info.OSType != "windows" {
+		if !info.MemoryLimit {
+			fmt.Fprintln(dockerCli.Err(), "WARNING: No memory limit support")
+		}
+		if !info.SwapLimit {
+			fmt.Fprintln(dockerCli.Err(), "WARNING: No swap limit support")
+		}
+		if !info.KernelMemory {
+			fmt.Fprintln(dockerCli.Err(), "WARNING: No kernel memory limit support")
+		}
+		if !info.OomKillDisable {
+			fmt.Fprintln(dockerCli.Err(), "WARNING: No oom kill disable support")
+		}
+		if !info.CPUCfsQuota {
+			fmt.Fprintln(dockerCli.Err(), "WARNING: No cpu cfs quota support")
+		}
+		if !info.CPUCfsPeriod {
+			fmt.Fprintln(dockerCli.Err(), "WARNING: No cpu cfs period support")
+		}
+		if !info.CPUShares {
+			fmt.Fprintln(dockerCli.Err(), "WARNING: No cpu shares support")
+		}
+		if !info.CPUSet {
+			fmt.Fprintln(dockerCli.Err(), "WARNING: No cpuset support")
+		}
+		if !info.IPv4Forwarding {
+			fmt.Fprintln(dockerCli.Err(), "WARNING: IPv4 forwarding is disabled")
+		}
+		if !info.BridgeNfIptables {
+			fmt.Fprintln(dockerCli.Err(), "WARNING: bridge-nf-call-iptables is disabled")
+		}
+		if !info.BridgeNfIP6tables {
+			fmt.Fprintln(dockerCli.Err(), "WARNING: bridge-nf-call-ip6tables is disabled")
+		}
+	}
+
+	if info.Labels != nil {
+		fmt.Fprintln(dockerCli.Out(), "Labels:")
+		for _, attribute := range info.Labels {
+			fmt.Fprintf(dockerCli.Out(), " %s\n", attribute)
+		}
+	}
+
+	ioutils.FprintfIfTrue(dockerCli.Out(), "Experimental: %v\n", info.ExperimentalBuild)
+	if info.ClusterStore != "" {
+		fmt.Fprintf(dockerCli.Out(), "Cluster Store: %s\n", info.ClusterStore)
+	}
+
+	if info.ClusterAdvertise != "" {
+		fmt.Fprintf(dockerCli.Out(), "Cluster Advertise: %s\n", info.ClusterAdvertise)
+	}
+
+	if info.RegistryConfig != nil && (len(info.RegistryConfig.InsecureRegistryCIDRs) > 0 || len(info.RegistryConfig.IndexConfigs) > 0) {
+		fmt.Fprintln(dockerCli.Out(), "Insecure Registries:")
+		for _, registry := range info.RegistryConfig.IndexConfigs {
+			if registry.Secure == false {
+				fmt.Fprintf(dockerCli.Out(), " %s\n", registry.Name)
+			}
+		}
+
+		for _, registry := range info.RegistryConfig.InsecureRegistryCIDRs {
+			mask, _ := registry.Mask.Size()
+			fmt.Fprintf(dockerCli.Out(), " %s/%d\n", registry.IP.String(), mask)
+		}
+	}
+	return nil
+}

+ 27 - 6
api/client/task/print.go

@@ -16,7 +16,8 @@ import (
 )
 )
 
 
 const (
 const (
-	psTaskItemFmt = "%s\t%s\t%s\t%s\t%s %s ago\t%s\t%s\n"
+	psTaskItemFmt = "%s\t%s\t%s\t%s\t%s\t%s %s ago\t%s\n"
+	maxErrLength  = 30
 )
 )
 
 
 type tasksBySlot []swarm.Task
 type tasksBySlot []swarm.Task
@@ -47,7 +48,9 @@ func Print(dockerCli *client.DockerCli, ctx context.Context, tasks []swarm.Task,
 
 
 	// Ignore flushing errors
 	// Ignore flushing errors
 	defer writer.Flush()
 	defer writer.Flush()
-	fmt.Fprintln(writer, strings.Join([]string{"ID", "NAME", "SERVICE", "IMAGE", "LAST STATE", "DESIRED STATE", "NODE"}, "\t"))
+	fmt.Fprintln(writer, strings.Join([]string{"ID", "NAME", "IMAGE", "NODE", "DESIRED STATE", "CURRENT STATE", "ERROR"}, "\t"))
+
+	prevName := ""
 	for _, task := range tasks {
 	for _, task := range tasks {
 		serviceValue, err := resolver.Resolve(ctx, swarm.Service{}, task.ServiceID)
 		serviceValue, err := resolver.Resolve(ctx, swarm.Service{}, task.ServiceID)
 		if err != nil {
 		if err != nil {
@@ -57,21 +60,39 @@ func Print(dockerCli *client.DockerCli, ctx context.Context, tasks []swarm.Task,
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
+
 		name := serviceValue
 		name := serviceValue
 		if task.Slot > 0 {
 		if task.Slot > 0 {
 			name = fmt.Sprintf("%s.%d", name, task.Slot)
 			name = fmt.Sprintf("%s.%d", name, task.Slot)
 		}
 		}
+
+		// Indent the name if necessary
+		indentedName := name
+		if prevName == name {
+			indentedName = fmt.Sprintf(" \\_ %s", indentedName)
+		}
+		prevName = name
+
+		// Trim and quote the error message.
+		taskErr := task.Status.Err
+		if len(taskErr) > maxErrLength {
+			taskErr = fmt.Sprintf("%s…", taskErr[:maxErrLength-1])
+		}
+		if len(taskErr) > 0 {
+			taskErr = fmt.Sprintf("\"%s\"", taskErr)
+		}
+
 		fmt.Fprintf(
 		fmt.Fprintf(
 			writer,
 			writer,
 			psTaskItemFmt,
 			psTaskItemFmt,
 			task.ID,
 			task.ID,
-			name,
-			serviceValue,
+			indentedName,
 			task.Spec.ContainerSpec.Image,
 			task.Spec.ContainerSpec.Image,
+			nodeValue,
+			client.PrettyPrint(task.DesiredState),
 			client.PrettyPrint(task.Status.State),
 			client.PrettyPrint(task.Status.State),
 			strings.ToLower(units.HumanDuration(time.Since(task.Status.Timestamp))),
 			strings.ToLower(units.HumanDuration(time.Since(task.Status.Timestamp))),
-			client.PrettyPrint(task.DesiredState),
-			nodeValue,
+			taskErr,
 		)
 		)
 	}
 	}
 
 

+ 20 - 1
api/client/volume/cmd.go

@@ -12,8 +12,9 @@ import (
 // NewVolumeCommand returns a cobra command for `volume` subcommands
 // NewVolumeCommand returns a cobra command for `volume` subcommands
 func NewVolumeCommand(dockerCli *client.DockerCli) *cobra.Command {
 func NewVolumeCommand(dockerCli *client.DockerCli) *cobra.Command {
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
-		Use:   "volume",
+		Use:   "volume COMMAND",
 		Short: "Manage Docker volumes",
 		Short: "Manage Docker volumes",
+		Long:  volumeDescription,
 		Args:  cli.NoArgs,
 		Args:  cli.NoArgs,
 		Run: func(cmd *cobra.Command, args []string) {
 		Run: func(cmd *cobra.Command, args []string) {
 			fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
 			fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
@@ -27,3 +28,21 @@ func NewVolumeCommand(dockerCli *client.DockerCli) *cobra.Command {
 	)
 	)
 	return cmd
 	return cmd
 }
 }
+
+var volumeDescription = `
+The **docker volume** command has subcommands for managing data volumes. A data
+volume is a specially-designated directory that by-passes storage driver
+management.
+
+Data volumes persist data independent of a container's life cycle. When you
+delete a container, the Engine daemon does not delete any data volumes. You can
+share volumes across multiple containers. Moreover, you can share data volumes
+with other computing resources in your system.
+
+To see help for a subcommand, use:
+
+    docker volume CMD help
+
+For full details on using docker volume visit Docker's online documentation.
+
+`

+ 41 - 1
api/client/volume/create.go

@@ -26,8 +26,9 @@ func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
 	}
 	}
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
-		Use:   "create",
+		Use:   "create [OPTIONS]",
 		Short: "Create a volume",
 		Short: "Create a volume",
+		Long:  createDescription,
 		Args:  cli.NoArgs,
 		Args:  cli.NoArgs,
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runCreate(dockerCli, opts)
 			return runCreate(dockerCli, opts)
@@ -60,3 +61,42 @@ func runCreate(dockerCli *client.DockerCli, opts createOptions) error {
 	fmt.Fprintf(dockerCli.Out(), "%s\n", vol.Name)
 	fmt.Fprintf(dockerCli.Out(), "%s\n", vol.Name)
 	return nil
 	return nil
 }
 }
+
+var createDescription = `
+Creates a new volume that containers can consume and store data in. If a name
+is not specified, Docker generates a random name. You create a volume and then
+configure the container to use it, for example:
+
+    $ docker volume create --name hello
+    hello
+    $ docker run -d -v hello:/world busybox ls /world
+
+The mount is created inside the container's **/src** directory. Docker doesn't
+not support relative paths for mount points inside the container.
+
+Multiple containers can use the same volume in the same time period. This is
+useful if two containers need access to shared data. For example, if one
+container writes and the other reads the data.
+
+## Driver specific options
+
+Some volume drivers may take options to customize the volume creation. Use the
+**-o** or **--opt** flags to pass driver options:
+
+    $ docker volume create --driver fake --opt tardis=blue --opt timey=wimey
+
+These options are passed directly to the volume driver. Options for different
+volume drivers may do different things (or nothing at all).
+
+The built-in **local** driver on Windows does not support any options.
+
+The built-in **local** driver on Linux accepts options similar to the linux
+**mount** command:
+
+    $ docker volume create --driver local --opt type=tmpfs --opt device=tmpfs --opt o=size=100m,uid=1000
+
+Another example:
+
+    $ docker volume create --driver local --opt type=btrfs --opt device=/dev/sda2
+
+`

+ 9 - 0
api/client/volume/inspect.go

@@ -20,6 +20,7 @@ func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
 		Use:   "inspect [OPTIONS] VOLUME [VOLUME...]",
 		Use:   "inspect [OPTIONS] VOLUME [VOLUME...]",
 		Short: "Display detailed information on one or more volumes",
 		Short: "Display detailed information on one or more volumes",
+		Long:  inspectDescription,
 		Args:  cli.RequiresMinArgs(1),
 		Args:  cli.RequiresMinArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			opts.names = args
 			opts.names = args
@@ -44,3 +45,11 @@ func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
 
 
 	return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getVolFunc)
 	return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getVolFunc)
 }
 }
+
+var inspectDescription = `
+Returns information about one or more volumes. By default, this command renders
+all results in a JSON array. You can specify an alternate format to execute a
+given template is executed for each result. Go's https://golang.org/pkg/text/template/
+package describes all the details of the format.
+
+`

+ 14 - 1
api/client/volume/list.go

@@ -31,9 +31,10 @@ func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
 	var opts listOptions
 	var opts listOptions
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
-		Use:     "ls",
+		Use:     "ls [OPTIONS]",
 		Aliases: []string{"list"},
 		Aliases: []string{"list"},
 		Short:   "List volumes",
 		Short:   "List volumes",
+		Long:    listDescription,
 		Args:    cli.NoArgs,
 		Args:    cli.NoArgs,
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runList(dockerCli, opts)
 			return runList(dockerCli, opts)
@@ -84,3 +85,15 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error {
 	w.Flush()
 	w.Flush()
 	return nil
 	return nil
 }
 }
+
+var listDescription = `
+
+Lists all the volumes Docker knows about. You can filter using the **-f** or
+**--filter** flag. The filtering format is a **key=value** pair. To specify
+more than one filter,  pass multiple flags (for example,
+**--filter "foo=bar" --filter "bif=baz"**)
+
+There is a single supported filter **dangling=value** which takes a boolean of
+**true** or **false**.
+
+`

+ 11 - 0
api/client/volume/remove.go

@@ -15,6 +15,8 @@ func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
 		Use:     "rm VOLUME [VOLUME]...",
 		Use:     "rm VOLUME [VOLUME]...",
 		Aliases: []string{"remove"},
 		Aliases: []string{"remove"},
 		Short:   "Remove a volume",
 		Short:   "Remove a volume",
+		Long:    removeDescription,
+		Example: removeExample,
 		Args:    cli.RequiresMinArgs(1),
 		Args:    cli.RequiresMinArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			return runRemove(dockerCli, args)
 			return runRemove(dockerCli, args)
@@ -41,3 +43,12 @@ func runRemove(dockerCli *client.DockerCli, volumes []string) error {
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+var removeDescription = `
+Removes one or more volumes. You cannot remove a volume that is in use by a container.
+`
+
+var removeExample = `
+$ docker volume rm hello
+hello
+`

+ 12 - 0
api/server/router/network/network_routes.go

@@ -2,11 +2,13 @@ package network
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"net/http"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
+	"github.com/docker/docker/errors"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types/filters"
 	"github.com/docker/engine-api/types/filters"
 	"github.com/docker/engine-api/types/network"
 	"github.com/docker/engine-api/types/network"
@@ -119,6 +121,11 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
 		return err
 		return err
 	}
 	}
 
 
+	if nw.Info().Dynamic() {
+		err := fmt.Errorf("operation not supported for swarm scoped networks")
+		return errors.NewRequestForbiddenError(err)
+	}
+
 	return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name(), connect.EndpointConfig)
 	return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name(), connect.EndpointConfig)
 }
 }
 
 
@@ -141,6 +148,11 @@ func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.Respon
 		return err
 		return err
 	}
 	}
 
 
+	if nw.Info().Dynamic() {
+		err := fmt.Errorf("operation not supported for swarm scoped networks")
+		return errors.NewRequestForbiddenError(err)
+	}
+
 	return n.backend.DisconnectContainerFromNetwork(disconnect.Container, nw, disconnect.Force)
 	return n.backend.DisconnectContainerFromNetwork(disconnect.Container, nw, disconnect.Force)
 }
 }
 
 

+ 1 - 1
api/server/router/swarm/backend.go

@@ -11,7 +11,7 @@ type Backend interface {
 	Join(req types.JoinRequest) error
 	Join(req types.JoinRequest) error
 	Leave(force bool) error
 	Leave(force bool) error
 	Inspect() (types.Swarm, error)
 	Inspect() (types.Swarm, error)
-	Update(uint64, types.Spec) error
+	Update(uint64, types.Spec, types.UpdateFlags) error
 	GetServices(basictypes.ServiceListOptions) ([]types.Service, error)
 	GetServices(basictypes.ServiceListOptions) ([]types.Service, error)
 	GetService(string) (types.Service, error)
 	GetService(string) (types.Service, error)
 	CreateService(types.ServiceSpec, string) (string, error)
 	CreateService(types.ServiceSpec, string) (string, error)

+ 21 - 1
api/server/router/swarm/cluster_routes.go

@@ -66,7 +66,27 @@ func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter,
 		return fmt.Errorf("Invalid swarm version '%s': %s", rawVersion, err.Error())
 		return fmt.Errorf("Invalid swarm version '%s': %s", rawVersion, err.Error())
 	}
 	}
 
 
-	if err := sr.backend.Update(version, swarm); err != nil {
+	var flags types.UpdateFlags
+
+	if value := r.URL.Query().Get("rotateWorkerToken"); value != "" {
+		rot, err := strconv.ParseBool(value)
+		if err != nil {
+			return fmt.Errorf("invalid value for rotateWorkerToken: %s", value)
+		}
+
+		flags.RotateWorkerToken = rot
+	}
+
+	if value := r.URL.Query().Get("rotateManagerToken"); value != "" {
+		rot, err := strconv.ParseBool(value)
+		if err != nil {
+			return fmt.Errorf("invalid value for rotateManagerToken: %s", value)
+		}
+
+		flags.RotateManagerToken = rot
+	}
+
+	if err := sr.backend.Update(version, swarm, flags); err != nil {
 		logrus.Errorf("Error configuring swarm: %v", err)
 		logrus.Errorf("Error configuring swarm: %v", err)
 		return err
 		return err
 	}
 	}

+ 1 - 6
cli/cli.go

@@ -155,11 +155,6 @@ func Subcmd(name string, synopses []string, description string, exitOnError bool
 	}
 	}
 
 
 	flags.ShortUsage = func() {
 	flags.ShortUsage = func() {
-		options := ""
-		if flags.FlagCountUndeprecated() > 0 {
-			options = " [OPTIONS]"
-		}
-
 		if len(synopses) == 0 {
 		if len(synopses) == 0 {
 			synopses = []string{""}
 			synopses = []string{""}
 		}
 		}
@@ -176,7 +171,7 @@ func Subcmd(name string, synopses []string, description string, exitOnError bool
 				synopsis = " " + synopsis
 				synopsis = " " + synopsis
 			}
 			}
 
 
-			fmt.Fprintf(flags.Out(), "\n%sdocker %s%s%s", lead, name, options, synopsis)
+			fmt.Fprintf(flags.Out(), "\n%sdocker %s%s", lead, name, synopsis)
 		}
 		}
 
 
 		fmt.Fprintf(flags.Out(), "\n\n%s\n", description)
 		fmt.Fprintf(flags.Out(), "\n\n%s\n", description)

+ 10 - 3
cli/cobraadaptor/adaptor.go

@@ -32,7 +32,8 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
 	dockerCli := client.NewDockerCli(stdin, stdout, stderr, clientFlags)
 	dockerCli := client.NewDockerCli(stdin, stdout, stderr, clientFlags)
 
 
 	var rootCmd = &cobra.Command{
 	var rootCmd = &cobra.Command{
-		Use:           "docker",
+		Use:           "docker [OPTIONS]",
+		Short:         "A self-sufficient runtime for containers",
 		SilenceUsage:  true,
 		SilenceUsage:  true,
 		SilenceErrors: true,
 		SilenceErrors: true,
 	}
 	}
@@ -129,9 +130,15 @@ func (c CobraAdaptor) Command(name string) func(...string) error {
 	return nil
 	return nil
 }
 }
 
 
-var usageTemplate = `Usage:	{{if not .HasSubCommands}}{{if .HasLocalFlags}}{{appendIfNotPresent .UseLine "[OPTIONS]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasSubCommands}}{{ .CommandPath}} COMMAND{{end}}
+// GetRootCommand returns the root command. Required to generate the man pages
+// and reference docs from a script outside this package.
+func (c CobraAdaptor) GetRootCommand() *cobra.Command {
+	return c.rootCmd
+}
+
+var usageTemplate = `Usage:	{{if not .HasSubCommands}}{{.UseLine}}{{end}}{{if .HasSubCommands}}{{ .CommandPath}} COMMAND{{end}}
 
 
-{{with or .Long .Short }}{{. | trim}}{{end}}{{if gt .Aliases 0}}
+{{ .Short | trim }}{{if gt .Aliases 0}}
 
 
 Aliases:
 Aliases:
   {{.NameAndAliases}}{{end}}{{if .HasExample}}
   {{.NameAndAliases}}{{end}}{{if .HasExample}}

+ 5 - 3
cmd/dockerd/daemon.go

@@ -274,9 +274,11 @@ func (cli *DaemonCli) start() (err error) {
 	name, _ := os.Hostname()
 	name, _ := os.Hostname()
 
 
 	c, err := cluster.New(cluster.Config{
 	c, err := cluster.New(cluster.Config{
-		Root:    cli.Config.Root,
-		Name:    name,
-		Backend: d,
+		Root:                   cli.Config.Root,
+		Name:                   name,
+		Backend:                d,
+		NetworkSubnetsProvider: d,
+		DefaultAdvertiseAddr:   cli.Config.SwarmDefaultAdvertiseAddr,
 	})
 	})
 	if err != nil {
 	if err != nil {
 		logrus.Fatalf("Error creating cluster component: %v", err)
 		logrus.Fatalf("Error creating cluster component: %v", err)

+ 1 - 1
cmd/dockerd/daemon_plugin_support.go

@@ -10,5 +10,5 @@ import (
 )
 )
 
 
 func pluginInit(config *daemon.Config, remote libcontainerd.Remote, rs registry.Service) error {
 func pluginInit(config *daemon.Config, remote libcontainerd.Remote, rs registry.Service) error {
-	return plugin.Init(config.Root, config.ExecRoot, remote, rs, config.LiveRestore)
+	return plugin.Init(config.Root, remote, rs, config.LiveRestore)
 }
 }

+ 1 - 0
cmd/dockerd/daemon_unix.go

@@ -61,6 +61,7 @@ func (cli *DaemonCli) setupConfigReloadTrap() {
 func (cli *DaemonCli) getPlatformRemoteOptions() []libcontainerd.RemoteOption {
 func (cli *DaemonCli) getPlatformRemoteOptions() []libcontainerd.RemoteOption {
 	opts := []libcontainerd.RemoteOption{
 	opts := []libcontainerd.RemoteOption{
 		libcontainerd.WithDebugLog(cli.Config.Debug),
 		libcontainerd.WithDebugLog(cli.Config.Debug),
+		libcontainerd.WithOOMScore(cli.Config.OOMScoreAdjust),
 	}
 	}
 	if cli.Config.ContainerdAddr != "" {
 	if cli.Config.ContainerdAddr != "" {
 		opts = append(opts, libcontainerd.WithRemoteAddr(cli.Config.ContainerdAddr))
 		opts = append(opts, libcontainerd.WithRemoteAddr(cli.Config.ContainerdAddr))

+ 1 - 1
cmd/dockerd/docker.go

@@ -31,7 +31,7 @@ func main() {
 	flag.Merge(flag.CommandLine, daemonCli.commonFlags.FlagSet)
 	flag.Merge(flag.CommandLine, daemonCli.commonFlags.FlagSet)
 
 
 	flag.Usage = func() {
 	flag.Usage = func() {
-		fmt.Fprint(stdout, "Usage: dockerd [ --help | -v | --version ]\n\n")
+		fmt.Fprint(stdout, "Usage: dockerd [OPTIONS]\n\n")
 		fmt.Fprint(stdout, "A self-sufficient runtime for containers.\n\nOptions:\n")
 		fmt.Fprint(stdout, "A self-sufficient runtime for containers.\n\nOptions:\n")
 
 
 		flag.CommandLine.SetOutput(stdout)
 		flag.CommandLine.SetOutput(stdout)

+ 4 - 1
container/health.go

@@ -13,9 +13,12 @@ type Health struct {
 
 
 // String returns a human-readable description of the health-check state
 // String returns a human-readable description of the health-check state
 func (s *Health) String() string {
 func (s *Health) String() string {
+	// This happens when the container is being shutdown and the monitor has stopped
+	// or the monitor has yet to be setup.
 	if s.stop == nil {
 	if s.stop == nil {
-		return "no healthcheck"
+		return types.Unhealthy
 	}
 	}
+
 	switch s.Status {
 	switch s.Status {
 	case types.Starting:
 	case types.Starting:
 		return "health: starting"
 		return "health: starting"

+ 1 - 1
contrib/builder/deb/amd64/debian-jessie/Dockerfile

@@ -10,7 +10,7 @@ RUN sed -i s/httpredir.debian.org/$APT_MIRROR/g /etc/apt/sources.list
 
 
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev  libsqlite3-dev pkg-config libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev  libsqlite3-dev pkg-config libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/deb/amd64/debian-stretch/Dockerfile

@@ -10,7 +10,7 @@ RUN sed -i s/httpredir.debian.org/$APT_MIRROR/g /etc/apt/sources.list
 
 
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev libseccomp-dev libsqlite3-dev pkg-config libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev libseccomp-dev libsqlite3-dev pkg-config libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/deb/amd64/debian-wheezy/Dockerfile

@@ -12,7 +12,7 @@ RUN sed -i s/httpredir.debian.org/$APT_MIRROR/g /etc/apt/sources.list.d/backport
 RUN apt-get update && apt-get install -y -t wheezy-backports btrfs-tools --no-install-recommends && rm -rf /var/lib/apt/lists/*
 RUN apt-get update && apt-get install -y -t wheezy-backports btrfs-tools --no-install-recommends && rm -rf /var/lib/apt/lists/*
 RUN apt-get update && apt-get install -y apparmor bash-completion  build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev  libsqlite3-dev pkg-config --no-install-recommends && rm -rf /var/lib/apt/lists/*
 RUN apt-get update && apt-get install -y apparmor bash-completion  build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev  libsqlite3-dev pkg-config --no-install-recommends && rm -rf /var/lib/apt/lists/*
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/deb/amd64/ubuntu-precise/Dockerfile

@@ -6,7 +6,7 @@ FROM ubuntu:precise
 
 
 RUN apt-get update && apt-get install -y apparmor bash-completion  build-essential curl ca-certificates debhelper dh-apparmor  git libapparmor-dev  libltdl-dev  libsqlite3-dev pkg-config --no-install-recommends && rm -rf /var/lib/apt/lists/*
 RUN apt-get update && apt-get install -y apparmor bash-completion  build-essential curl ca-certificates debhelper dh-apparmor  git libapparmor-dev  libltdl-dev  libsqlite3-dev pkg-config --no-install-recommends && rm -rf /var/lib/apt/lists/*
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/deb/amd64/ubuntu-trusty/Dockerfile

@@ -6,7 +6,7 @@ FROM ubuntu:trusty
 
 
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev  libsqlite3-dev pkg-config libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev  libsqlite3-dev pkg-config libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/deb/amd64/ubuntu-wily/Dockerfile

@@ -6,7 +6,7 @@ FROM ubuntu:wily
 
 
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev libseccomp-dev libsqlite3-dev pkg-config libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev libseccomp-dev libsqlite3-dev pkg-config libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/deb/amd64/ubuntu-xenial/Dockerfile

@@ -6,7 +6,7 @@ FROM ubuntu:xenial
 
 
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev libseccomp-dev libsqlite3-dev pkg-config libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev libseccomp-dev libsqlite3-dev pkg-config libsystemd-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/deb/armhf/debian-jessie/Dockerfile

@@ -6,7 +6,7 @@ RUN sed -i s/httpredir.debian.org/$APT_MIRROR/g /etc/apt/sources.list
 
 
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev libsqlite3-dev libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev libsqlite3-dev libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-armv6l.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-armv6l.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/deb/armhf/raspbian-jessie/Dockerfile

@@ -6,7 +6,7 @@ RUN sed -i s/httpredir.debian.org/$APT_MIRROR/g /etc/apt/sources.list
 
 
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev libsqlite3-dev libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev libsqlite3-dev libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-armv6l.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-armv6l.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/deb/armhf/ubuntu-trusty/Dockerfile

@@ -2,7 +2,7 @@ FROM armhf/ubuntu:trusty
 
 
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev libsqlite3-dev libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 RUN apt-get update && apt-get install -y apparmor bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-apparmor dh-systemd git libapparmor-dev libdevmapper-dev libltdl-dev libsqlite3-dev libsystemd-journal-dev --no-install-recommends && rm -rf /var/lib/apt/lists/*
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-armv6l.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-armv6l.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/rpm/amd64/centos-7/Dockerfile

@@ -8,7 +8,7 @@ RUN yum groupinstall -y "Development Tools"
 RUN yum -y swap -- remove systemd-container systemd-container-libs -- install systemd systemd-libs
 RUN yum -y swap -- remove systemd-container systemd-container-libs -- install systemd systemd-libs
 RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git
 RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/rpm/amd64/fedora-22/Dockerfile

@@ -7,7 +7,7 @@ FROM fedora:22
 RUN dnf install -y @development-tools fedora-packager
 RUN dnf install -y @development-tools fedora-packager
 RUN dnf install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git
 RUN dnf install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/rpm/amd64/fedora-23/Dockerfile

@@ -7,7 +7,7 @@ FROM fedora:23
 RUN dnf install -y @development-tools fedora-packager
 RUN dnf install -y @development-tools fedora-packager
 RUN dnf install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git
 RUN dnf install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/rpm/amd64/fedora-24/Dockerfile

@@ -7,7 +7,7 @@ FROM fedora:24
 RUN dnf install -y @development-tools fedora-packager
 RUN dnf install -y @development-tools fedora-packager
 RUN dnf install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git
 RUN dnf install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/rpm/amd64/opensuse-13.2/Dockerfile

@@ -7,7 +7,7 @@ FROM opensuse:13.2
 RUN zypper --non-interactive install ca-certificates* curl gzip rpm-build
 RUN zypper --non-interactive install ca-certificates* curl gzip rpm-build
 RUN zypper --non-interactive install libbtrfs-devel device-mapper-devel glibc-static  libselinux-devel libtool-ltdl-devel pkg-config selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git systemd-rpm-macros
 RUN zypper --non-interactive install libbtrfs-devel device-mapper-devel glibc-static  libselinux-devel libtool-ltdl-devel pkg-config selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git systemd-rpm-macros
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/rpm/amd64/oraclelinux-6/Dockerfile

@@ -10,7 +10,7 @@ RUN yum install -y kernel-uek-devel-4.1.12-32.el6uek
 RUN yum groupinstall -y "Development Tools"
 RUN yum groupinstall -y "Development Tools"
 RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static  libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel  tar git
 RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static  libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel  tar git
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 1 - 1
contrib/builder/rpm/amd64/oraclelinux-7/Dockerfile

@@ -7,7 +7,7 @@ FROM oraclelinux:7
 RUN yum groupinstall -y "Development Tools"
 RUN yum groupinstall -y "Development Tools"
 RUN yum install -y --enablerepo=ol7_optional_latest btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git
 RUN yum install -y --enablerepo=ol7_optional_latest btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git
 
 
-ENV GO_VERSION 1.6.2
+ENV GO_VERSION 1.6.3
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
 ENV PATH $PATH:/usr/local/go/bin
 ENV PATH $PATH:/usr/local/go/bin
 
 

+ 5 - 1
contrib/check-config.sh

@@ -183,7 +183,7 @@ flags=(
 	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
 	KEYS
-	MACVLAN VETH BRIDGE BRIDGE_NETFILTER
+	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}
 	NF_NAT NF_NAT_NEEDED
 	NF_NAT NF_NAT_NEEDED
@@ -252,6 +252,10 @@ echo '- Network Drivers:'
 	check_flags VXLAN | sed 's/^/  /'
 	check_flags VXLAN | sed 's/^/  /'
 	echo '  Optional (for secure networks):'
 	echo '  Optional (for secure networks):'
 	check_flags XFRM_ALGO XFRM_USER | sed 's/^/  /'
 	check_flags XFRM_ALGO XFRM_USER | sed 's/^/  /'
+	echo '- "'$(wrap_color 'ipvlan' blue)'":'
+	check_flags IPVLAN | sed 's/^/  /'
+	echo '- "'$(wrap_color 'macvlan' blue)'":'
+	check_flags MACVLAN DUMMY | sed 's/^/  /'
 } | sed 's/^/  /'
 } | sed 's/^/  /'
 
 
 echo '- Storage Drivers:'
 echo '- Storage Drivers:'

+ 70 - 45
contrib/completion/bash/docker

@@ -505,7 +505,7 @@ __docker_complete_log_options() {
 	local gelf_options="env gelf-address gelf-compression-level gelf-compression-type labels tag"
 	local gelf_options="env gelf-address gelf-compression-level gelf-compression-type 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-format syslog-tls-ca-cert syslog-tls-cert syslog-tls-key syslog-tls-skip-verify syslog-facility tag"
+	local syslog_options="env labels syslog-address syslog-facility syslog-format syslog-tls-ca-cert syslog-tls-cert syslog-tls-key syslog-tls-skip-verify 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 $gcplogs_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"
@@ -912,6 +912,7 @@ _docker_daemon() {
 		--max-concurrent-downloads
 		--max-concurrent-downloads
 		--max-concurrent-uploads
 		--max-concurrent-uploads
 		--mtu
 		--mtu
+		--oom-score-adjust
 		--pidfile -p
 		--pidfile -p
 		--registry-mirror
 		--registry-mirror
 		--storage-driver -s
 		--storage-driver -s
@@ -1444,7 +1445,7 @@ _docker_network_create() {
 			return
 			return
 			;;
 			;;
 		--driver|-d)
 		--driver|-d)
-			local plugins=" $(__docker_plugins Network) "
+			local plugins="$(__docker_plugins Network) macvlan"
 			# remove drivers that allow one instance only
 			# remove drivers that allow one instance only
 			plugins=${plugins/ host / }
 			plugins=${plugins/ host / }
 			plugins=${plugins/ null / }
 			plugins=${plugins/ null / }
@@ -1600,7 +1601,7 @@ _docker_service_inspect() {
 
 
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
-			COMPREPLY=( $( compgen -W "--format -f --help --pretty -p" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--format -f --help --pretty" -- "$cur" ) )
 			;;
 			;;
 		*)
 		*)
 			__docker_complete_services
 			__docker_complete_services
@@ -1689,7 +1690,7 @@ _docker_service_tasks() {
 
 
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
-			COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-resolve -n" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-resolve" -- "$cur" ) )
 			;;
 			;;
 		*)
 		*)
 			local counter=$(__docker_pos_first_nonflag '--filter|-f')
 			local counter=$(__docker_pos_first_nonflag '--filter|-f')
@@ -1710,8 +1711,9 @@ _docker_service_update() {
 		--label -l
 		--label -l
 		--limit-cpu
 		--limit-cpu
 		--limit-memory
 		--limit-memory
-		--mode
-		--mount -m
+		--log-driver
+		--log-opt
+		--mount
 		--name
 		--name
 		--network
 		--network
 		--publish -p
 		--publish -p
@@ -1724,6 +1726,7 @@ _docker_service_update() {
 		--restart-window
 		--restart-window
 		--stop-grace-period
 		--stop-grace-period
 		--update-delay
 		--update-delay
+		--update-failure-action
 		--update-parallelism
 		--update-parallelism
 		--user -u
 		--user -u
 		--workdir -w
 		--workdir -w
@@ -1731,12 +1734,26 @@ _docker_service_update() {
 
 
 	local boolean_options="
 	local boolean_options="
 		--help
 		--help
+		--with-registry-auth
 	"
 	"
 
 
+	__docker_complete_log_driver_options && return
+
+	if [ "$subcommand" = "create" ] ; then
+		options_with_args="$options_with_args
+			--mode
+		"
+
+		case "$prev" in
+			--mode)
+				COMPREPLY=( $( compgen -W "global replicated" -- "$cur" ) )
+				return
+				;;
+		esac
+	fi
 	if [ "$subcommand" = "update" ] ; then
 	if [ "$subcommand" = "update" ] ; then
 		options_with_args="$options_with_args
 		options_with_args="$options_with_args
 			--arg
 			--arg
-			--command
 			--image
 			--image
 		"
 		"
 
 
@@ -1750,7 +1767,7 @@ _docker_service_update() {
 
 
 	case "$prev" in
 	case "$prev" in
 		--endpoint-mode)
 		--endpoint-mode)
-			COMPREPLY=( $( compgen -W "DNSRR VIP" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "dnsrr vip" -- "$cur" ) )
 			return
 			return
 			;;
 			;;
 		--env|-e)
 		--env|-e)
@@ -1758,8 +1775,12 @@ _docker_service_update() {
 			__docker_nospace
 			__docker_nospace
 			return
 			return
 			;;
 			;;
-		--mode)
-			COMPREPLY=( $( compgen -W "global replicated" -- "$cur" ) )
+		--log-driver)
+			__docker_complete_log_drivers
+			return
+			;;
+		--log-opt)
+			__docker_complete_log_options
 			return
 			return
 			;;
 			;;
 		--network)
 		--network)
@@ -1795,6 +1816,7 @@ _docker_swarm() {
 		init
 		init
 		inspect
 		inspect
 		join
 		join
+		join-token
 		leave
 		leave
 		update
 		update
 	"
 	"
@@ -1812,24 +1834,23 @@ _docker_swarm() {
 
 
 _docker_swarm_init() {
 _docker_swarm_init() {
 	case "$prev" in
 	case "$prev" in
-		--auto-accept)
-			COMPREPLY=( $( compgen -W "manager none worker" -- "$cur" ) )
-			return
-			;;
 		--listen-addr)
 		--listen-addr)
 			if [[ $cur == *: ]] ; then
 			if [[ $cur == *: ]] ; then
 				COMPREPLY=( $( compgen -W "2377" -- "${cur##*:}" ) )
 				COMPREPLY=( $( compgen -W "2377" -- "${cur##*:}" ) )
 			fi
 			fi
 			return
 			return
 			;;
 			;;
-		--secret)
+		--advertise-addr)
+			if [[ $cur == *: ]] ; then
+				COMPREPLY=( $( compgen -W "2377" -- "${cur##*:}" ) )
+			fi
 			return
 			return
 			;;
 			;;
 	esac
 	esac
 
 
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
-			COMPREPLY=( $( compgen -W "--auto-accept --force-new-cluster --help --listen-addr --secret" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--advertise-addr --force-new-cluster --help --listen-addr" -- "$cur" ) )
 			;;
 			;;
 	esac
 	esac
 }
 }
@@ -1850,7 +1871,7 @@ _docker_swarm_inspect() {
 
 
 _docker_swarm_join() {
 _docker_swarm_join() {
 	case "$prev" in
 	case "$prev" in
-		--ca-hash|--secret)
+		--token)
 			return
 			return
 			;;
 			;;
 		--listen-addr)
 		--listen-addr)
@@ -1859,11 +1880,17 @@ _docker_swarm_join() {
 			fi
 			fi
 			return
 			return
 			;;
 			;;
+		--advertise-addr)
+			if [[ $cur == *: ]] ; then
+				COMPREPLY=( $( compgen -W "2377" -- "${cur##*:}" ) )
+			fi
+			return
+			;;
 	esac
 	esac
 
 
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
-			COMPREPLY=( $( compgen -W "--ca-hash --help --listen-addr --manager --secret" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--adveritse-addr --help --listen-addr --token" -- "$cur" ) )
 			;;
 			;;
 		*:)
 		*:)
 			COMPREPLY=( $( compgen -W "2377" -- "${cur##*:}" ) )
 			COMPREPLY=( $( compgen -W "2377" -- "${cur##*:}" ) )
@@ -1871,6 +1898,20 @@ _docker_swarm_join() {
 	esac
 	esac
 }
 }
 
 
+_docker_swarm_join-token() {
+	case "$cur" in
+		-*)
+			COMPREPLY=( $( compgen -W "--help --quiet -q --rotate" -- "$cur" ) )
+			;;
+		*)
+			local counter=$( __docker_pos_first_nonflag )
+			if [ $cword -eq $counter ]; then
+				COMPREPLY=( $( compgen -W "manager worker" -- "$cur" ) )
+			fi
+			;;
+	esac
+}
+
 _docker_swarm_leave() {
 _docker_swarm_leave() {
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
@@ -1881,25 +1922,20 @@ _docker_swarm_leave() {
 
 
 _docker_swarm_update() {
 _docker_swarm_update() {
 	case "$prev" in
 	case "$prev" in
-		--auto-accept)
-			COMPREPLY=( $( compgen -W "manager none worker" -- "$cur" ) )
-			return
-			;;
-		--cert-expiry|--dispatcher-heartbeat|--secret|--task-history-limit)
+		--cert-expiry|--dispatcher-heartbeat|--task-history-limit)
 			return
 			return
 			;;
 			;;
 	esac
 	esac
 
 
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
-			COMPREPLY=( $( compgen -W "--auto-accept --cert-expiry --dispatcher-heartbeat --help --secret --task-history-limit" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--cert-expiry --dispatcher-heartbeat --help --task-history-limit" -- "$cur" ) )
 			;;
 			;;
 	esac
 	esac
 }
 }
 
 
 _docker_node() {
 _docker_node() {
 	local subcommands="
 	local subcommands="
-		accept
 		demote
 		demote
 		inspect
 		inspect
 		ls list
 		ls list
@@ -1920,16 +1956,6 @@ _docker_node() {
 	esac
 	esac
 }
 }
 
 
-_docker_node_accept() {
-	case "$cur" in
-		-*)
-			COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
-			;;
-		*)
-			__docker_complete_nodes --id --filter membership=pending
-	esac
-}
-
 _docker_node_demote() {
 _docker_node_demote() {
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
@@ -1949,7 +1975,7 @@ _docker_node_inspect() {
 
 
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
-			COMPREPLY=( $( compgen -W "--format -f --help --pretty -p" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--format -f --help --pretty" -- "$cur" ) )
 			;;
 			;;
 		*)
 		*)
 			__docker_complete_nodes
 			__docker_complete_nodes
@@ -2035,7 +2061,7 @@ _docker_node_tasks() {
 
 
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
-			COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-resolve -n" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-resolve" -- "$cur" ) )
 			;;
 			;;
 		*)
 		*)
 			local counter=$(__docker_pos_first_nonflag '--filter|-f')
 			local counter=$(__docker_pos_first_nonflag '--filter|-f')
@@ -2052,19 +2078,18 @@ _docker_node_update() {
 			COMPREPLY=( $( compgen -W "active drain pause" -- "$cur" ) )
 			COMPREPLY=( $( compgen -W "active drain pause" -- "$cur" ) )
 			return
 			return
 			;;
 			;;
-		--membership)
-			COMPREPLY=( $( compgen -W "accepted rejected" -- "$cur" ) )
-			return
-			;;
 		--role)
 		--role)
 			COMPREPLY=( $( compgen -W "manager worker" -- "$cur" ) )
 			COMPREPLY=( $( compgen -W "manager worker" -- "$cur" ) )
 			return
 			return
 			;;
 			;;
+		--label-add|--label-rm)
+			return
+			;;
 	esac
 	esac
 
 
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
-			COMPREPLY=( $( compgen -W "--availability --help --membership --role" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--availability --help --label-add --label-rm --role" -- "$cur" ) )
 			;;
 			;;
 		*)
 		*)
 			__docker_complete_nodes
 			__docker_complete_nodes
@@ -2304,8 +2329,8 @@ _docker_run() {
 		--memory-swappiness
 		--memory-swappiness
 		--memory-reservation
 		--memory-reservation
 		--name
 		--name
-		--net
-		--net-alias
+		--network
+		--network-alias
 		--oom-score-adj
 		--oom-score-adj
 		--pid
 		--pid
 		--pids-limit
 		--pids-limit
@@ -2461,7 +2486,7 @@ _docker_run() {
 			__docker_complete_log_options
 			__docker_complete_log_options
 			return
 			return
 			;;
 			;;
-		--net)
+		--network)
 			case "$cur" in
 			case "$cur" in
 				container:*)
 				container:*)
 					local cur=${cur#*:}
 					local cur=${cur#*:}

+ 53 - 37
contrib/completion/zsh/_docker

@@ -227,7 +227,7 @@ __docker_get_log_options() {
     gelf_options=("env" "gelf-address" "gelf-compression-level" "gelf-compression-type" "labels" "tag")
     gelf_options=("env" "gelf-address" "gelf-compression-level" "gelf-compression-type" "labels" "tag")
     journald_options=("env" "labels" "tag")
     journald_options=("env" "labels" "tag")
     json_file_options=("env" "labels" "max-file" "max-size")
     json_file_options=("env" "labels" "max-file" "max-size")
-    syslog_options=("syslog-address" "syslog-format" "syslog-tls-ca-cert" "syslog-tls-cert" "syslog-tls-key" "syslog-tls-skip-verify" "syslog-facility" "tag")
+    syslog_options=("env" "labels" "syslog-address" "syslog-facility" "syslog-format" "syslog-tls-ca-cert" "syslog-tls-cert" "syslog-tls-key" "syslog-tls-skip-verify" "tag")
     splunk_options=("env" "labels" "splunk-caname" "splunk-capath" "splunk-index" "splunk-insecureskipverify" "splunk-source" "splunk-sourcetype" "splunk-token" "splunk-url" "tag")
     splunk_options=("env" "labels" "splunk-caname" "splunk-capath" "splunk-index" "splunk-insecureskipverify" "splunk-source" "splunk-sourcetype" "splunk-token" "splunk-url" "tag")
 
 
     [[ $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
@@ -242,6 +242,14 @@ __docker_get_log_options() {
     return ret
     return ret
 }
 }
 
 
+__docker_log_drivers() {
+    [[ $PREFIX = -*  ]] && return 1
+    integer ret=1
+    drivers=(awslogs etwlogs fluentd gcplogs gelf journald json-file none splunk syslog)
+    _describe -t log-drivers "log drivers" drivers && ret=0
+    return ret
+}
+
 __docker_log_options() {
 __docker_log_options() {
     [[ $PREFIX = -* ]] && return 1
     [[ $PREFIX = -* ]] && return 1
     integer ret=1
     integer ret=1
@@ -775,7 +783,6 @@ __docker_complete_worker_nodes() {
 __docker_node_commands() {
 __docker_node_commands() {
     local -a _docker_node_subcommands
     local -a _docker_node_subcommands
     _docker_node_subcommands=(
     _docker_node_subcommands=(
-        "accept:Accept a node in the swarm"
         "demote:Demote a node as manager in the swarm"
         "demote:Demote a node as manager in the swarm"
         "inspect:Display detailed information on one or more nodes"
         "inspect:Display detailed information on one or more nodes"
         "ls:List nodes in the swarm"
         "ls:List nodes in the swarm"
@@ -795,7 +802,7 @@ __docker_node_subcommand() {
     opts_help=("(: -)--help[Print usage]")
     opts_help=("(: -)--help[Print usage]")
 
 
     case "$words[1]" in
     case "$words[1]" in
-        (accept|rm|remove)
+        (rm|remove)
              _arguments $(__docker_arguments) \
              _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
                 "($help -)*:node:__docker_complete_pending_nodes" && ret=0
                 "($help -)*:node:__docker_complete_pending_nodes" && ret=0
@@ -809,7 +816,7 @@ __docker_node_subcommand() {
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
                 "($help -f --format)"{-f=,--format=}"[Format the output using the given go template]:template: " \
                 "($help -f --format)"{-f=,--format=}"[Format the output using the given go template]:template: " \
-                "($help -p --pretty)"{-p,--pretty}"[Print the information in a human friendly format]" \
+                "($help)--pretty[Print the information in a human friendly format]" \
                 "($help -)*:node:__docker_complete_nodes" && ret=0
                 "($help -)*:node:__docker_complete_nodes" && ret=0
             ;;
             ;;
         (ls|list)
         (ls|list)
@@ -833,7 +840,7 @@ __docker_node_subcommand() {
                 $opts_help \
                 $opts_help \
                 "($help -a --all)"{-a,--all}"[Display all instances]" \
                 "($help -a --all)"{-a,--all}"[Display all instances]" \
                 "($help)*"{-f=,--filter=}"[Provide filter values]:filter:->filter-options" \
                 "($help)*"{-f=,--filter=}"[Provide filter values]:filter:->filter-options" \
-                "($help -n --no-resolve)"{-n,--no-resolve}"[Do not map IDs to Names]" \
+                "($help)--no-resolve[Do not map IDs to Names]" \
                 "($help -)1:node:__docker_complete_nodes" && ret=0
                 "($help -)1:node:__docker_complete_nodes" && ret=0
             case $state in
             case $state in
                 (filter-options)
                 (filter-options)
@@ -845,7 +852,8 @@ __docker_node_subcommand() {
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
                 "($help)--availability=[Availability of the node]:availability:(active pause drain)" \
                 "($help)--availability=[Availability of the node]:availability:(active pause drain)" \
-                "($help)--membership=[Membership of the node]:membership:(accepted rejected)" \
+                "($help)*--label-add=[Add or update a node label]:key=value: " \
+                "($help)*--label-rm=[Remove a node label if exists]:label: " \
                 "($help)--role=[Role of the node]:role:(manager worker)" \
                 "($help)--role=[Role of the node]:role:(manager worker)" \
                 "($help -)1:node:__docker_complete_nodes" && ret=0
                 "($help -)1:node:__docker_complete_nodes" && ret=0
             ;;
             ;;
@@ -1067,13 +1075,14 @@ __docker_service_subcommand() {
     opts_help=("(: -)--help[Print usage]")
     opts_help=("(: -)--help[Print usage]")
     opts_create_update=(
     opts_create_update=(
         "($help)*--constraint=[Placement constraints]:constraint: "
         "($help)*--constraint=[Placement constraints]:constraint: "
-        "($help)--endpoint-mode=[Placement constraints]:mode:(VIP DNSRR)"
+        "($help)--endpoint-mode=[Placement constraints]:mode:(dnsrr vip)"
         "($help)*"{-e=,--env=}"[Set environment variables]:env: "
         "($help)*"{-e=,--env=}"[Set environment variables]:env: "
         "($help)*--label=[Service labels]:label: "
         "($help)*--label=[Service labels]:label: "
         "($help)--limit-cpu=[Limit CPUs]:value: "
         "($help)--limit-cpu=[Limit CPUs]:value: "
         "($help)--limit-memory=[Limit Memory]:value: "
         "($help)--limit-memory=[Limit Memory]:value: "
-        "($help)--mode=[Limit Memory]:mode:(global replicated)"
-        "($help)*"{-m=,--mount=}"[Attach a mount to the service]:mount: "
+        "($help)--log-driver=[Logging driver for service]:logging driver:__docker_log_drivers"
+        "($help)*--log-opt=[Logging driver options]:log driver options:__docker_log_options"
+        "($help)*--mount=[Attach a mount to the service]:mount: "
         "($help)--name=[Service name]:name: "
         "($help)--name=[Service name]:name: "
         "($help)*--network=[Network attachments]:network: "
         "($help)*--network=[Network attachments]:network: "
         "($help)*"{-p=,--publish=}"[Publish a port as a node port]:port: "
         "($help)*"{-p=,--publish=}"[Publish a port as a node port]:port: "
@@ -1086,8 +1095,10 @@ __docker_service_subcommand() {
         "($help)--restart-window=[Window used to evaluate the restart policy]:window: "
         "($help)--restart-window=[Window used to evaluate the restart policy]:window: "
         "($help)--stop-grace-period=[Time to wait before force killing a container]:grace period: "
         "($help)--stop-grace-period=[Time to wait before force killing a container]:grace period: "
         "($help)--update-delay=[Delay between updates]:delay: "
         "($help)--update-delay=[Delay between updates]:delay: "
+        "($help)--update-failure-action=[Action on update failure]:mode:(pause continue)"
         "($help)--update-parallelism=[Maximum number of tasks updated simultaneously]:number: "
         "($help)--update-parallelism=[Maximum number of tasks updated simultaneously]:number: "
         "($help -u --user)"{-u=,--user=}"[Username or UID]:user:_users"
         "($help -u --user)"{-u=,--user=}"[Username or UID]:user:_users"
+        "($help)--with-registry-auth[Send registry authentication details to swarm agents]"
         "($help -w --workdir)"{-w=,--workdir=}"[Working directory inside the container]:directory:_directories"
         "($help -w --workdir)"{-w=,--workdir=}"[Working directory inside the container]:directory:_directories"
     )
     )
 
 
@@ -1096,6 +1107,7 @@ __docker_service_subcommand() {
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
                 $opts_create_update \
                 $opts_create_update \
+                "($help)--mode=[Service Mode]:mode:(global replicated)" \
                 "($help -): :__docker_images" \
                 "($help -): :__docker_images" \
                 "($help -):command: _command_names -e" \
                 "($help -):command: _command_names -e" \
                 "($help -)*::arguments: _normal" && ret=0
                 "($help -)*::arguments: _normal" && ret=0
@@ -1104,7 +1116,7 @@ __docker_service_subcommand() {
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
                 "($help -f --format)"{-f=,--format=}"[Format the output using the given go template]:template: " \
                 "($help -f --format)"{-f=,--format=}"[Format the output using the given go template]:template: " \
-                "($help -p --pretty)"{-p,--pretty}"[Print the information in a human friendly format]" \
+                "($help)--pretty[Print the information in a human friendly format]" \
                 "($help -)*:service:__docker_complete_services" && ret=0
                 "($help -)*:service:__docker_complete_services" && ret=0
             ;;
             ;;
         (ls|list)
         (ls|list)
@@ -1142,7 +1154,7 @@ __docker_service_subcommand() {
                 $opts_help \
                 $opts_help \
                 "($help -a --all)"{-a,--all}"[Display all tasks]" \
                 "($help -a --all)"{-a,--all}"[Display all tasks]" \
                 "($help)*"{-f=,--filter=}"[Provide filter values]:filter:->filter-options" \
                 "($help)*"{-f=,--filter=}"[Provide filter values]:filter:->filter-options" \
-                "($help -n --no-resolve)"{-n,--no-resolve}"[Do not map IDs to Names]" \
+                "($help)--no-resolve[Do not map IDs to Names]" \
                 "($help -)1:service:__docker_complete_services" && ret=0
                 "($help -)1:service:__docker_complete_services" && ret=0
             case $state in
             case $state in
                 (filter-options)
                 (filter-options)
@@ -1155,7 +1167,6 @@ __docker_service_subcommand() {
                 $opts_help \
                 $opts_help \
                 $opts_create_update \
                 $opts_create_update \
                 "($help)--arg=[Service command args]:arguments: _normal" \
                 "($help)--arg=[Service command args]:arguments: _normal" \
-                "($help)--command=[Service command]:command: _command_names -e" \
                 "($help)--image=[Service image tag]:image:__docker_repositories" \
                 "($help)--image=[Service image tag]:image:__docker_repositories" \
                 "($help -)1:service:__docker_complete_services" && ret=0
                 "($help -)1:service:__docker_complete_services" && ret=0
             ;;
             ;;
@@ -1174,11 +1185,12 @@ __docker_service_subcommand() {
 __docker_swarm_commands() {
 __docker_swarm_commands() {
     local -a _docker_swarm_subcommands
     local -a _docker_swarm_subcommands
     _docker_swarm_subcommands=(
     _docker_swarm_subcommands=(
-        "init:Initialize a Swarm"
-        "inspect:Inspect the Swarm"
-        "join:Join a Swarm as a node and/or manager"
-        "leave:Leave a Swarm"
-        "update:Update the Swarm"
+        "init:Initialize a swarm"
+        "inspect:Inspect the swarm"
+        "join:Join a swarm as a node and/or manager"
+        "join-token:Manage join tokens"
+        "leave:Leave a swarm"
+        "update:Update the swarm"
     )
     )
     _describe -t docker-swarm-commands "docker swarm command" _docker_swarm_subcommands
     _describe -t docker-swarm-commands "docker swarm command" _docker_swarm_subcommands
 }
 }
@@ -1194,11 +1206,10 @@ __docker_swarm_subcommand() {
         (init)
         (init)
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
-                "($help)--auto-accept=[Acceptance policy]:policy:(manager none worker)" \
+                "($help)--advertise-addr[Advertised address]:ip\:port: " \
                 "($help)*--external-ca=[Specifications of one or more certificate signing endpoints]:endpoint: " \
                 "($help)*--external-ca=[Specifications of one or more certificate signing endpoints]:endpoint: " \
                 "($help)--force-new-cluster[Force create a new cluster from current state]" \
                 "($help)--force-new-cluster[Force create a new cluster from current state]" \
-                "($help)--listen-addr[Listen address]:ip\:port: " \
-                "($help)--secret[Set secret value needed to accept nodes into cluster]:secret: " && ret=0
+                "($help)--listen-addr=[Listen address]:ip\:port: " && ret=0
             ;;
             ;;
         (inspect)
         (inspect)
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
@@ -1208,12 +1219,18 @@ __docker_swarm_subcommand() {
         (join)
         (join)
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
-                "($help)--ca-hash=[Hash of the Root Certificate Authority certificate used for trusted join]:hash: " \
-                "($help)--listen-addr[Listen address]:ip\:port: " \
-                "($help)--manager[Try joining as a manager]" \
-                "($help)--secret[Secret for node acceptance]:secret: " \
+                "($help)--advertise-addr[Advertised address]:ip\:port: " \
+                "($help)--listen-addr=[Listen address]:ip\:port: " \
+                "($help)--token=[Token for entry into the swarm]:secret: " \
                 "($help -):host\:port: " && ret=0
                 "($help -):host\:port: " && ret=0
             ;;
             ;;
+        (join-token)
+            _arguments $(__docker_arguments) \
+                $opts_help \
+                "($help -q --quiet)"{-q,--quiet}"[Only display token]" \
+                "($help)--rotate[Rotate join token]" \
+                "($help -):role:(manager worker)" && ret=0
+            ;;
         (leave)
         (leave)
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help && ret=0
                 $opts_help && ret=0
@@ -1221,11 +1238,9 @@ __docker_swarm_subcommand() {
         (update)
         (update)
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
-                "($help)--auto-accept=[Acceptance policy]:policy:(manager none worker)" \
                 "($help)--cert-expiry=[Validity period for node certificates]:duration: " \
                 "($help)--cert-expiry=[Validity period for node certificates]:duration: " \
                 "($help)--dispatcher-heartbeat=[Dispatcher heartbeat period]:duration: " \
                 "($help)--dispatcher-heartbeat=[Dispatcher heartbeat period]:duration: " \
-                "($help)--secret[Set secret value needed to accept nodes into cluster]:secret: " \
-                "($help)--task-history-limit[Task history retention limit]:limit: " && ret=0
+                "($help)--task-history-limit=[Task history retention limit]:limit: " && ret=0
             ;;
             ;;
         (help)
         (help)
             _arguments $(__docker_arguments) ":subcommand:__docker_network_commands" && ret=0
             _arguments $(__docker_arguments) ":subcommand:__docker_network_commands" && ret=0
@@ -1430,12 +1445,12 @@ __docker_subcommand() {
         "($help)*--link=[Add link to another container]:link:->link"
         "($help)*--link=[Add link to another container]:link:->link"
         "($help)*--link-local-ip=[Add a link-local address for the container]:IPv4/IPv6: "
         "($help)*--link-local-ip=[Add a link-local address for the container]:IPv4/IPv6: "
         "($help)*"{-l=,--label=}"[Container metadata]:label: "
         "($help)*"{-l=,--label=}"[Container metadata]:label: "
-        "($help)--log-driver=[Default driver for container logs]:Logging driver:(awslogs etwlogs fluentd gcplogs gelf journald json-file none splunk syslog)"
+        "($help)--log-driver=[Default driver for container logs]:logging driver:__docker_log_drivers"
         "($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: "
         "($help)--name=[Container name]:name: "
         "($help)--name=[Container name]:name: "
-        "($help)--net=[Connect a container to a network]:network mode:(bridge none container host)"
-        "($help)*--net-alias=[Add network-scoped alias for the container]:alias: "
+        "($help)--network=[Connect a container to a network]:network mode:(bridge none container host)"
+        "($help)*--network-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)--pids-limit[Tune container pids limit (set -1 for unlimited)]"
@@ -1552,13 +1567,13 @@ __docker_subcommand() {
                 "($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-store-opt=[Cluster options]:Cluster options:->cluster-store-options" \
+                "($help)--cluster-advertise=[Address or interface name to advertise]:Instance to advertise (host\:port): " \
+                "($help)*--cluster-store-opt=[Cluster store 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=[Default ulimit settings for containers]:ulimit: " \
-                "($help)--disable-legacy-registry[Do not contact legacy registries]" \
+                "($help)*--default-ulimit=[Default ulimits for containers]:ulimit: " \
+                "($help)--disable-legacy-registry[Disable contacting legacy registries]" \
                 "($help)*--exec-opt=[Runtime execution options]:runtime execution options: " \
                 "($help)*--exec-opt=[Runtime execution options]:runtime execution options: " \
                 "($help)--exec-root=[Root directory for execution state files]:path:_directories" \
                 "($help)--exec-root=[Root directory for execution state files]:path:_directories" \
                 "($help)--fixed-cidr=[IPv4 subnet for fixed IPs]:IPv4 subnet: " \
                 "($help)--fixed-cidr=[IPv4 subnet for fixed IPs]:IPv4 subnet: " \
@@ -1576,11 +1591,12 @@ __docker_subcommand() {
                 "($help -l --log-level)"{-l=,--log-level=}"[Logging level]:level:(debug info warn error fatal)" \
                 "($help -l --log-level)"{-l=,--log-level=}"[Logging level]:level:(debug info warn error fatal)" \
                 "($help)*--label=[Key=value labels]:label: " \
                 "($help)*--label=[Key=value labels]:label: " \
                 "($help)--live-restore[Enable live restore of docker when containers are still running]" \
                 "($help)--live-restore[Enable live restore of docker when containers are still running]" \
-                "($help)--log-driver=[Default driver for container logs]:Logging driver:(awslogs etwlogs fluentd gcplogs gelf journald json-file none splunk syslog)" \
-                "($help)*--log-opt=[Log driver specific options]:log driver options:__docker_log_options" \
+                "($help)--log-driver=[Default driver for container logs]:logging driver:__docker_log_drivers" \
+                "($help)*--log-opt=[Default log driver options for containers]:log driver options:__docker_log_options" \
                 "($help)--max-concurrent-downloads[Set the max concurrent downloads for each pull]" \
                 "($help)--max-concurrent-downloads[Set the max concurrent downloads for each pull]" \
                 "($help)--max-concurrent-uploads[Set the max concurrent uploads for each push]" \
                 "($help)--max-concurrent-uploads[Set the max concurrent uploads for each push]" \
                 "($help)--mtu=[Network MTU]:mtu:(0 576 1420 1500 9000)" \
                 "($help)--mtu=[Network MTU]:mtu:(0 576 1420 1500 9000)" \
+                "($help)--oom-score-adjust=[Set the oom_score_adj for the daemon]:oom-score:(-500)" \
                 "($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: " \
@@ -1879,7 +1895,7 @@ __docker_subcommand() {
                 "($help)--runtime=[Name of the runtime to be used for that container]:runtime:__docker_complete_runtimes" \
                 "($help)--runtime=[Name of the runtime to be used for that container]:runtime:__docker_complete_runtimes" \
                 "($help)--sig-proxy[Proxy all received signals to the process (non-TTY mode only)]" \
                 "($help)--sig-proxy[Proxy all received signals to the process (non-TTY mode only)]" \
                 "($help)--stop-signal=[Signal to kill a container]:signal:_signals" \
                 "($help)--stop-signal=[Signal to kill a container]:signal:_signals" \
-                "($help)--storage-opt=[Set storage driver options per container]:storage options:->storage-opt" \
+                "($help)--storage-opt=[Storage driver options for the container]:storage options:->storage-opt" \
                 "($help -): :__docker_images" \
                 "($help -): :__docker_images" \
                 "($help -):command: _command_names -e" \
                 "($help -):command: _command_names -e" \
                 "($help -)*::arguments: _normal" && ret=0
                 "($help -)*::arguments: _normal" && ret=0

+ 29 - 0
contrib/init/systemd/docker.service.rpm

@@ -0,0 +1,29 @@
+[Unit]
+Description=Docker Application Container Engine
+Documentation=https://docs.docker.com
+After=network.target
+Requires=docker.socket
+
+[Service]
+Type=notify
+# the default is not to use systemd for cgroups because the delegate issues still
+# exists and systemd currently does not support the cgroup feature set required
+# for containers run by docker
+ExecStart=/usr/bin/dockerd
+ExecReload=/bin/kill -s HUP $MAINPID
+# Having non-zero Limit*s causes performance problems due to accounting overhead
+# in the kernel. We recommend using cgroups to do container-local accounting.
+LimitNOFILE=infinity
+LimitNPROC=infinity
+LimitCORE=infinity
+# Uncomment TasksMax if your systemd version supports it.
+# Only systemd 226 and above support this version.
+#TasksMax=infinity
+TimeoutStartSec=0
+# set delegate yes so that systemd does not reset the cgroups of docker containers
+Delegate=yes
+# kill only the docker process, not all processes in the cgroup
+KillMode=process
+
+[Install]
+WantedBy=multi-user.target

+ 2 - 2
contrib/init/sysvinit-redhat/docker

@@ -22,9 +22,9 @@
 # Source function library.
 # Source function library.
 . /etc/rc.d/init.d/functions
 . /etc/rc.d/init.d/functions
 
 
-prog="dockerd"
+prog="docker"
 unshare=/usr/bin/unshare
 unshare=/usr/bin/unshare
-exec="/usr/bin/$prog"
+exec="/usr/bin/dockerd"
 pidfile="/var/run/$prog.pid"
 pidfile="/var/run/$prog.pid"
 lockfile="/var/lock/subsys/$prog"
 lockfile="/var/lock/subsys/$prog"
 logfile="/var/log/$prog"
 logfile="/var/log/$prog"

+ 1 - 1
contrib/syscall-test/ns.c

@@ -48,7 +48,7 @@ int main(int argc, char **argv)
 	child_stack = stack + STACK_SIZE;	/* Assume stack grows downward */
 	child_stack = stack + STACK_SIZE;	/* Assume stack grows downward */
 
 
 	// the result of this call is that our child_exec will be run in another
 	// the result of this call is that our child_exec will be run in another
-	// process returning it's pid
+	// process returning its pid
 	pid_t pid = clone(child_exec, child_stack, clone_flags, &args);
 	pid_t pid = clone(child_exec, child_stack, clone_flags, &args);
 	if (pid < 0) {
 	if (pid < 0) {
 		fprintf(stderr, "clone failed: %s\n", strerror(errno));
 		fprintf(stderr, "clone failed: %s\n", strerror(errno));

+ 1 - 1
contrib/syscall-test/userns.c

@@ -48,7 +48,7 @@ int main(int argc, char **argv)
 	child_stack = stack + STACK_SIZE;	/* Assume stack grows downward */
 	child_stack = stack + STACK_SIZE;	/* Assume stack grows downward */
 
 
 	// the result of this call is that our child_exec will be run in another
 	// the result of this call is that our child_exec will be run in another
-	// process returning it's pid
+	// process returning its pid
 	pid_t pid = clone(child_exec, child_stack, clone_flags, &args);
 	pid_t pid = clone(child_exec, child_stack, clone_flags, &args);
 	if (pid < 0) {
 	if (pid < 0) {
 		fprintf(stderr, "clone failed: %s\n", strerror(errno));
 		fprintf(stderr, "clone failed: %s\n", strerror(errno));

+ 280 - 114
daemon/cluster/cluster.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
+	"net"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
 	"strings"
 	"strings"
@@ -13,7 +14,6 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
-	"github.com/docker/distribution/digest"
 	"github.com/docker/docker/daemon/cluster/convert"
 	"github.com/docker/docker/daemon/cluster/convert"
 	executorpkg "github.com/docker/docker/daemon/cluster/executor"
 	executorpkg "github.com/docker/docker/daemon/cluster/executor"
 	"github.com/docker/docker/daemon/cluster/executor/container"
 	"github.com/docker/docker/daemon/cluster/executor/container"
@@ -32,6 +32,7 @@ import (
 const swarmDirName = "swarm"
 const swarmDirName = "swarm"
 const controlSocket = "control.sock"
 const controlSocket = "control.sock"
 const swarmConnectTimeout = 20 * time.Second
 const swarmConnectTimeout = 20 * time.Second
+const swarmRequestTimeout = 20 * time.Second
 const stateFile = "docker-state.json"
 const stateFile = "docker-state.json"
 const defaultAddr = "0.0.0.0:2377"
 const defaultAddr = "0.0.0.0:2377"
 
 
@@ -41,16 +42,16 @@ const (
 )
 )
 
 
 // ErrNoSwarm is returned on leaving a cluster that was never initialized
 // ErrNoSwarm is returned on leaving a cluster that was never initialized
-var ErrNoSwarm = fmt.Errorf("This node is not part of Swarm")
+var ErrNoSwarm = fmt.Errorf("This node is not part of swarm")
 
 
 // ErrSwarmExists is returned on initialize or join request for a cluster that has already been activated
 // ErrSwarmExists is returned on initialize or join request for a cluster that has already been activated
-var ErrSwarmExists = fmt.Errorf("This node is already part of a Swarm cluster. Use \"docker swarm leave\" to leave this cluster and join another one.")
+var ErrSwarmExists = fmt.Errorf("This node is already part of a swarm cluster. Use \"docker swarm leave\" to leave this cluster and join another one.")
 
 
 // ErrPendingSwarmExists is returned on initialize or join request for a cluster that is already processing a similar request but has not succeeded yet.
 // ErrPendingSwarmExists is returned on initialize or join request for a cluster that is already processing a similar request but has not succeeded yet.
 var ErrPendingSwarmExists = fmt.Errorf("This node is processing an existing join request that has not succeeded yet. Use \"docker swarm leave\" to cancel the current request.")
 var ErrPendingSwarmExists = fmt.Errorf("This node is processing an existing join request that has not succeeded yet. Use \"docker swarm leave\" to cancel the current request.")
 
 
 // ErrSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached.
 // ErrSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached.
-var ErrSwarmJoinTimeoutReached = fmt.Errorf("Timeout was reached before node was joined. Attempt to join the cluster will continue in the background. Use \"docker info\" command to see the current Swarm status of your node.")
+var ErrSwarmJoinTimeoutReached = fmt.Errorf("Timeout was reached before node was joined. Attempt to join the cluster will continue in the background. Use \"docker info\" command to see the current swarm status of your node.")
 
 
 // defaultSpec contains some sane defaults if cluster options are missing on init
 // defaultSpec contains some sane defaults if cluster options are missing on init
 var defaultSpec = types.Spec{
 var defaultSpec = types.Spec{
@@ -73,14 +74,35 @@ var defaultSpec = types.Spec{
 }
 }
 
 
 type state struct {
 type state struct {
+	// LocalAddr is this machine's local IP or hostname, if specified.
+	LocalAddr string
+	// RemoteAddr is the address that was given to "swarm join. It is used
+	// to find LocalAddr if necessary.
+	RemoteAddr string
+	// ListenAddr is the address we bind to, including a port.
 	ListenAddr string
 	ListenAddr string
+	// AdvertiseAddr is the address other nodes should connect to,
+	// including a port.
+	AdvertiseAddr string
+}
+
+// NetworkSubnetsProvider exposes functions for retrieving the subnets
+// of networks managed by Docker, so they can be filtered.
+type NetworkSubnetsProvider interface {
+	V4Subnets() []net.IPNet
+	V6Subnets() []net.IPNet
 }
 }
 
 
 // Config provides values for Cluster.
 // Config provides values for Cluster.
 type Config struct {
 type Config struct {
-	Root    string
-	Name    string
-	Backend executorpkg.Backend
+	Root                   string
+	Name                   string
+	Backend                executorpkg.Backend
+	NetworkSubnetsProvider NetworkSubnetsProvider
+
+	// DefaultAdvertiseAddr is the default host/IP or network interface to use
+	// if no AdvertiseAddr value is specified.
+	DefaultAdvertiseAddr string
 }
 }
 
 
 // Cluster provides capabilities to participate in a cluster as a worker or a
 // Cluster provides capabilities to participate in a cluster as a worker or a
@@ -88,13 +110,17 @@ type Config struct {
 type Cluster struct {
 type Cluster struct {
 	sync.RWMutex
 	sync.RWMutex
 	*node
 	*node
-	root        string
-	config      Config
-	configEvent chan struct{} // todo: make this array and goroutine safe
-	listenAddr  string
-	stop        bool
-	err         error
-	cancelDelay func()
+	root            string
+	config          Config
+	configEvent     chan struct{} // todo: make this array and goroutine safe
+	localAddr       string
+	actualLocalAddr string // after resolution, not persisted
+	remoteAddr      string
+	listenAddr      string
+	advertiseAddr   string
+	stop            bool
+	err             error
+	cancelDelay     func()
 }
 }
 
 
 type node struct {
 type node struct {
@@ -126,7 +152,7 @@ func New(config Config) (*Cluster, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	n, err := c.startNewNode(false, st.ListenAddr, "", "", "", false)
+	n, err := c.startNewNode(false, st.LocalAddr, st.RemoteAddr, st.ListenAddr, st.AdvertiseAddr, "", "")
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -162,7 +188,12 @@ func (c *Cluster) loadState() (*state, error) {
 }
 }
 
 
 func (c *Cluster) saveState() error {
 func (c *Cluster) saveState() error {
-	dt, err := json.Marshal(state{ListenAddr: c.listenAddr})
+	dt, err := json.Marshal(state{
+		LocalAddr:     c.localAddr,
+		RemoteAddr:    c.remoteAddr,
+		ListenAddr:    c.listenAddr,
+		AdvertiseAddr: c.advertiseAddr,
+	})
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -195,7 +226,7 @@ func (c *Cluster) reconnectOnFailure(n *node) {
 			return
 			return
 		}
 		}
 		var err error
 		var err error
-		n, err = c.startNewNode(false, c.listenAddr, c.getRemoteAddress(), "", "", false)
+		n, err = c.startNewNode(false, c.localAddr, c.getRemoteAddress(), c.listenAddr, c.advertiseAddr, c.getRemoteAddress(), "")
 		if err != nil {
 		if err != nil {
 			c.err = err
 			c.err = err
 			close(n.done)
 			close(n.done)
@@ -204,26 +235,55 @@ func (c *Cluster) reconnectOnFailure(n *node) {
 	}
 	}
 }
 }
 
 
-func (c *Cluster) startNewNode(forceNewCluster bool, listenAddr, joinAddr, secret, cahash string, ismanager bool) (*node, error) {
+func (c *Cluster) startNewNode(forceNewCluster bool, localAddr, remoteAddr, listenAddr, advertiseAddr, joinAddr, joinToken string) (*node, error) {
 	if err := c.config.Backend.IsSwarmCompatible(); err != nil {
 	if err := c.config.Backend.IsSwarmCompatible(); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+
+	actualLocalAddr := localAddr
+	if actualLocalAddr == "" {
+		// If localAddr was not specified, resolve it automatically
+		// based on the route to joinAddr. localAddr can only be left
+		// empty on "join".
+		listenHost, _, err := net.SplitHostPort(listenAddr)
+		if err != nil {
+			return nil, fmt.Errorf("could not parse listen address: %v", err)
+		}
+
+		listenAddrIP := net.ParseIP(listenHost)
+		if listenAddrIP == nil || !listenAddrIP.IsUnspecified() {
+			actualLocalAddr = listenHost
+		} else {
+			if remoteAddr == "" {
+				// Should never happen except using swarms created by
+				// old versions that didn't save remoteAddr.
+				remoteAddr = "8.8.8.8:53"
+			}
+			conn, err := net.Dial("udp", remoteAddr)
+			if err != nil {
+				return nil, fmt.Errorf("could not find local IP address: %v", err)
+			}
+			localHostPort := conn.LocalAddr().String()
+			actualLocalAddr, _, _ = net.SplitHostPort(localHostPort)
+			conn.Close()
+		}
+	}
+
 	c.node = nil
 	c.node = nil
 	c.cancelDelay = nil
 	c.cancelDelay = nil
 	c.stop = false
 	c.stop = false
 	n, err := swarmagent.NewNode(&swarmagent.NodeConfig{
 	n, err := swarmagent.NewNode(&swarmagent.NodeConfig{
-		Hostname:         c.config.Name,
-		ForceNewCluster:  forceNewCluster,
-		ListenControlAPI: filepath.Join(c.root, controlSocket),
-		ListenRemoteAPI:  listenAddr,
-		JoinAddr:         joinAddr,
-		StateDir:         c.root,
-		CAHash:           cahash,
-		Secret:           secret,
-		Executor:         container.NewExecutor(c.config.Backend),
-		HeartbeatTick:    1,
-		ElectionTick:     3,
-		IsManager:        ismanager,
+		Hostname:           c.config.Name,
+		ForceNewCluster:    forceNewCluster,
+		ListenControlAPI:   filepath.Join(c.root, controlSocket),
+		ListenRemoteAPI:    listenAddr,
+		AdvertiseRemoteAPI: advertiseAddr,
+		JoinAddr:           joinAddr,
+		StateDir:           c.root,
+		JoinToken:          joinToken,
+		Executor:           container.NewExecutor(c.config.Backend),
+		HeartbeatTick:      1,
+		ElectionTick:       3,
 	})
 	})
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -238,8 +298,13 @@ func (c *Cluster) startNewNode(forceNewCluster bool, listenAddr, joinAddr, secre
 		reconnectDelay: initialReconnectDelay,
 		reconnectDelay: initialReconnectDelay,
 	}
 	}
 	c.node = node
 	c.node = node
+	c.localAddr = localAddr
+	c.actualLocalAddr = actualLocalAddr // not saved
+	c.remoteAddr = remoteAddr
 	c.listenAddr = listenAddr
 	c.listenAddr = listenAddr
+	c.advertiseAddr = advertiseAddr
 	c.saveState()
 	c.saveState()
+
 	c.config.Backend.SetClusterProvider(c)
 	c.config.Backend.SetClusterProvider(c)
 	go func() {
 	go func() {
 		err := n.Err(ctx)
 		err := n.Err(ctx)
@@ -290,7 +355,7 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) {
 	if node := c.node; node != nil {
 	if node := c.node; node != nil {
 		if !req.ForceNewCluster {
 		if !req.ForceNewCluster {
 			c.Unlock()
 			c.Unlock()
-			return "", errSwarmExists(node)
+			return "", ErrSwarmExists
 		}
 		}
 		if err := c.stopNode(); err != nil {
 		if err := c.stopNode(); err != nil {
 			c.Unlock()
 			c.Unlock()
@@ -303,8 +368,49 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) {
 		return "", err
 		return "", err
 	}
 	}
 
 
+	listenHost, listenPort, err := resolveListenAddr(req.ListenAddr)
+	if err != nil {
+		c.Unlock()
+		return "", err
+	}
+
+	advertiseHost, advertisePort, err := c.resolveAdvertiseAddr(req.AdvertiseAddr, listenPort)
+	if err != nil {
+		c.Unlock()
+		return "", err
+	}
+
+	localAddr := listenHost
+
+	// If the advertise address is not one of the system's
+	// addresses, we also require a listen address.
+	listenAddrIP := net.ParseIP(listenHost)
+	if listenAddrIP != nil && listenAddrIP.IsUnspecified() {
+		advertiseIP := net.ParseIP(advertiseHost)
+		if advertiseIP == nil {
+			// not an IP
+			c.Unlock()
+			return "", errMustSpecifyListenAddr
+		}
+
+		systemIPs := listSystemIPs()
+
+		found := false
+		for _, systemIP := range systemIPs {
+			if systemIP.Equal(advertiseIP) {
+				found = true
+				break
+			}
+		}
+		if !found {
+			c.Unlock()
+			return "", errMustSpecifyListenAddr
+		}
+		localAddr = advertiseIP.String()
+	}
+
 	// todo: check current state existing
 	// todo: check current state existing
-	n, err := c.startNewNode(req.ForceNewCluster, req.ListenAddr, "", "", "", false)
+	n, err := c.startNewNode(req.ForceNewCluster, localAddr, "", net.JoinHostPort(listenHost, listenPort), net.JoinHostPort(advertiseHost, advertisePort), "", "")
 	if err != nil {
 	if err != nil {
 		c.Unlock()
 		c.Unlock()
 		return "", err
 		return "", err
@@ -335,40 +441,47 @@ func (c *Cluster) Join(req types.JoinRequest) error {
 	c.Lock()
 	c.Lock()
 	if node := c.node; node != nil {
 	if node := c.node; node != nil {
 		c.Unlock()
 		c.Unlock()
-		return errSwarmExists(node)
+		return ErrSwarmExists
 	}
 	}
 	if err := validateAndSanitizeJoinRequest(&req); err != nil {
 	if err := validateAndSanitizeJoinRequest(&req); err != nil {
 		c.Unlock()
 		c.Unlock()
 		return err
 		return err
 	}
 	}
+
+	listenHost, listenPort, err := resolveListenAddr(req.ListenAddr)
+	if err != nil {
+		c.Unlock()
+		return err
+	}
+
+	var advertiseAddr string
+	advertiseHost, advertisePort, err := c.resolveAdvertiseAddr(req.AdvertiseAddr, listenPort)
+	// For joining, we don't need to provide an advertise address,
+	// since the remote side can detect it.
+	if err == nil {
+		advertiseAddr = net.JoinHostPort(advertiseHost, advertisePort)
+	}
+
 	// todo: check current state existing
 	// todo: check current state existing
-	n, err := c.startNewNode(false, req.ListenAddr, req.RemoteAddrs[0], req.Secret, req.CACertHash, req.Manager)
+	n, err := c.startNewNode(false, "", req.RemoteAddrs[0], net.JoinHostPort(listenHost, listenPort), advertiseAddr, req.RemoteAddrs[0], req.JoinToken)
 	if err != nil {
 	if err != nil {
 		c.Unlock()
 		c.Unlock()
 		return err
 		return err
 	}
 	}
 	c.Unlock()
 	c.Unlock()
 
 
-	certificateRequested := n.CertificateRequested()
-	for {
-		select {
-		case <-certificateRequested:
-			if n.NodeMembership() == swarmapi.NodeMembershipPending {
-				return fmt.Errorf("Your node is in the process of joining the cluster but needs to be accepted by existing cluster member.\nTo accept this node into cluster run \"docker node accept %v\" in an existing cluster manager. Use \"docker info\" command to see the current Swarm status of your node.", n.NodeID())
-			}
-			certificateRequested = nil
-		case <-time.After(swarmConnectTimeout):
-			// attempt to connect will continue in background, also reconnecting
-			go c.reconnectOnFailure(n)
-			return ErrSwarmJoinTimeoutReached
-		case <-n.Ready():
-			go c.reconnectOnFailure(n)
-			return nil
-		case <-n.done:
-			c.RLock()
-			defer c.RUnlock()
-			return c.err
-		}
+	select {
+	case <-time.After(swarmConnectTimeout):
+		// attempt to connect will continue in background, also reconnecting
+		go c.reconnectOnFailure(n)
+		return ErrSwarmJoinTimeoutReached
+	case <-n.Ready():
+		go c.reconnectOnFailure(n)
+		return nil
+	case <-n.done:
+		c.RLock()
+		defer c.RUnlock()
+		return c.err
 	}
 	}
 }
 }
 
 
@@ -459,9 +572,8 @@ func (c *Cluster) clearState() error {
 	return nil
 	return nil
 }
 }
 
 
-func (c *Cluster) getRequestContext() context.Context { // TODO: not needed when requests don't block on qourum lost
-	ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
-	return ctx
+func (c *Cluster) getRequestContext() (context.Context, func()) { // TODO: not needed when requests don't block on qourum lost
+	return context.WithTimeout(context.Background(), swarmRequestTimeout)
 }
 }
 
 
 // Inspect retrieves the configuration properties of a managed swarm cluster.
 // Inspect retrieves the configuration properties of a managed swarm cluster.
@@ -473,7 +585,10 @@ func (c *Cluster) Inspect() (types.Swarm, error) {
 		return types.Swarm{}, c.errNoManager()
 		return types.Swarm{}, c.errNoManager()
 	}
 	}
 
 
-	swarm, err := getSwarm(c.getRequestContext(), c.client)
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
+	swarm, err := getSwarm(ctx, c.client)
 	if err != nil {
 	if err != nil {
 		return types.Swarm{}, err
 		return types.Swarm{}, err
 	}
 	}
@@ -486,7 +601,7 @@ func (c *Cluster) Inspect() (types.Swarm, error) {
 }
 }
 
 
 // Update updates configuration of a managed swarm cluster.
 // Update updates configuration of a managed swarm cluster.
-func (c *Cluster) Update(version uint64, spec types.Spec) error {
+func (c *Cluster) Update(version uint64, spec types.Spec, flags types.UpdateFlags) error {
 	c.RLock()
 	c.RLock()
 	defer c.RUnlock()
 	defer c.RUnlock()
 
 
@@ -494,24 +609,31 @@ func (c *Cluster) Update(version uint64, spec types.Spec) error {
 		return c.errNoManager()
 		return c.errNoManager()
 	}
 	}
 
 
-	swarm, err := getSwarm(c.getRequestContext(), c.client)
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
+	swarm, err := getSwarm(ctx, c.client)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	swarmSpec, err := convert.SwarmSpecToGRPCandMerge(spec, &swarm.Spec)
+	swarmSpec, err := convert.SwarmSpecToGRPC(spec)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
 	_, err = c.client.UpdateCluster(
 	_, err = c.client.UpdateCluster(
-		c.getRequestContext(),
+		ctx,
 		&swarmapi.UpdateClusterRequest{
 		&swarmapi.UpdateClusterRequest{
 			ClusterID: swarm.ID,
 			ClusterID: swarm.ID,
 			Spec:      &swarmSpec,
 			Spec:      &swarmSpec,
 			ClusterVersion: &swarmapi.Version{
 			ClusterVersion: &swarmapi.Version{
 				Index: version,
 				Index: version,
 			},
 			},
+			Rotation: swarmapi.JoinTokenRotation{
+				RotateWorkerToken:  flags.RotateWorkerToken,
+				RotateManagerToken: flags.RotateManagerToken,
+			},
 		},
 		},
 	)
 	)
 	return err
 	return err
@@ -531,15 +653,22 @@ func (c *Cluster) IsAgent() bool {
 	return c.node != nil && c.ready
 	return c.node != nil && c.ready
 }
 }
 
 
-// GetListenAddress returns the listening address for current manager's
-// consensus and dispatcher APIs.
-func (c *Cluster) GetListenAddress() string {
+// GetLocalAddress returns the local address.
+func (c *Cluster) GetLocalAddress() string {
 	c.RLock()
 	c.RLock()
 	defer c.RUnlock()
 	defer c.RUnlock()
-	if c.isActiveManager() {
-		return c.listenAddr
+	return c.actualLocalAddr
+}
+
+// GetAdvertiseAddress returns the remotely reachable address of this node.
+func (c *Cluster) GetAdvertiseAddress() string {
+	c.RLock()
+	defer c.RUnlock()
+	if c.advertiseAddr != "" {
+		advertiseHost, _, _ := net.SplitHostPort(c.advertiseAddr)
+		return advertiseHost
 	}
 	}
-	return ""
+	return c.actualLocalAddr
 }
 }
 
 
 // GetRemoteAddress returns a known advertise address of a remote manager if
 // GetRemoteAddress returns a known advertise address of a remote manager if
@@ -573,7 +702,10 @@ func (c *Cluster) ListenClusterEvents() <-chan struct{} {
 
 
 // Info returns information about the current cluster state.
 // Info returns information about the current cluster state.
 func (c *Cluster) Info() types.Info {
 func (c *Cluster) Info() types.Info {
-	var info types.Info
+	info := types.Info{
+		NodeAddr: c.GetAdvertiseAddress(),
+	}
+
 	c.RLock()
 	c.RLock()
 	defer c.RUnlock()
 	defer c.RUnlock()
 
 
@@ -591,9 +723,13 @@ func (c *Cluster) Info() types.Info {
 	if c.err != nil {
 	if c.err != nil {
 		info.Error = c.err.Error()
 		info.Error = c.err.Error()
 	}
 	}
+
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
 	if c.isActiveManager() {
 	if c.isActiveManager() {
 		info.ControlAvailable = true
 		info.ControlAvailable = true
-		if r, err := c.client.ListNodes(c.getRequestContext(), &swarmapi.ListNodesRequest{}); err == nil {
+		if r, err := c.client.ListNodes(ctx, &swarmapi.ListNodesRequest{}); err == nil {
 			info.Nodes = len(r.Nodes)
 			info.Nodes = len(r.Nodes)
 			for _, n := range r.Nodes {
 			for _, n := range r.Nodes {
 				if n.ManagerStatus != nil {
 				if n.ManagerStatus != nil {
@@ -601,10 +737,6 @@ func (c *Cluster) Info() types.Info {
 				}
 				}
 			}
 			}
 		}
 		}
-
-		if swarm, err := getSwarm(c.getRequestContext(), c.client); err == nil && swarm != nil {
-			info.CACertHash = swarm.RootCA.CACertHash
-		}
 	}
 	}
 
 
 	if c.node != nil {
 	if c.node != nil {
@@ -626,12 +758,12 @@ func (c *Cluster) isActiveManager() bool {
 // Call with read lock.
 // Call with read lock.
 func (c *Cluster) errNoManager() error {
 func (c *Cluster) errNoManager() error {
 	if c.node == nil {
 	if c.node == nil {
-		return fmt.Errorf("This node is not a Swarm manager. Use \"docker swarm init\" or \"docker swarm join --manager\" to connect this node to Swarm and try again.")
+		return fmt.Errorf("This node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again.")
 	}
 	}
 	if c.node.Manager() != nil {
 	if c.node.Manager() != nil {
-		return fmt.Errorf("This node is not a Swarm manager. Manager is being prepared or has trouble connecting to the cluster.")
+		return fmt.Errorf("This node is not a swarm manager. Manager is being prepared or has trouble connecting to the cluster.")
 	}
 	}
-	return fmt.Errorf("This node is not a Swarm manager. Worker nodes can't be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager.")
+	return fmt.Errorf("This node is not a swarm manager. Worker nodes can't be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager.")
 }
 }
 
 
 // GetServices returns all services of a managed swarm cluster.
 // GetServices returns all services of a managed swarm cluster.
@@ -647,8 +779,11 @@ func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Serv
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
 	r, err := c.client.ListServices(
 	r, err := c.client.ListServices(
-		c.getRequestContext(),
+		ctx,
 		&swarmapi.ListServicesRequest{Filters: filters})
 		&swarmapi.ListServicesRequest{Filters: filters})
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -672,9 +807,10 @@ func (c *Cluster) CreateService(s types.ServiceSpec, encodedAuth string) (string
 		return "", c.errNoManager()
 		return "", c.errNoManager()
 	}
 	}
 
 
-	ctx := c.getRequestContext()
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
 
 
-	err := populateNetworkID(ctx, c.client, &s)
+	err := c.populateNetworkID(ctx, c.client, &s)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
@@ -709,7 +845,10 @@ func (c *Cluster) GetService(input string) (types.Service, error) {
 		return types.Service{}, c.errNoManager()
 		return types.Service{}, c.errNoManager()
 	}
 	}
 
 
-	service, err := getService(c.getRequestContext(), c.client, input)
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
+	service, err := getService(ctx, c.client, input)
 	if err != nil {
 	if err != nil {
 		return types.Service{}, err
 		return types.Service{}, err
 	}
 	}
@@ -725,9 +864,10 @@ func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.Ser
 		return c.errNoManager()
 		return c.errNoManager()
 	}
 	}
 
 
-	ctx := c.getRequestContext()
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
 
 
-	err := populateNetworkID(ctx, c.client, &spec)
+	err := c.populateNetworkID(ctx, c.client, &spec)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -746,7 +886,7 @@ func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.Ser
 	} else {
 	} else {
 		// this is needed because if the encodedAuth isn't being updated then we
 		// this is needed because if the encodedAuth isn't being updated then we
 		// shouldn't lose it, and continue to use the one that was already present
 		// shouldn't lose it, and continue to use the one that was already present
-		currentService, err := getService(c.getRequestContext(), c.client, serviceID)
+		currentService, err := getService(ctx, c.client, serviceID)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -758,7 +898,7 @@ func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.Ser
 	}
 	}
 
 
 	_, err = c.client.UpdateService(
 	_, err = c.client.UpdateService(
-		c.getRequestContext(),
+		ctx,
 		&swarmapi.UpdateServiceRequest{
 		&swarmapi.UpdateServiceRequest{
 			ServiceID: serviceID,
 			ServiceID: serviceID,
 			Spec:      &serviceSpec,
 			Spec:      &serviceSpec,
@@ -779,12 +919,15 @@ func (c *Cluster) RemoveService(input string) error {
 		return c.errNoManager()
 		return c.errNoManager()
 	}
 	}
 
 
-	service, err := getService(c.getRequestContext(), c.client, input)
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
+	service, err := getService(ctx, c.client, input)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	if _, err := c.client.RemoveService(c.getRequestContext(), &swarmapi.RemoveServiceRequest{ServiceID: service.ID}); err != nil {
+	if _, err := c.client.RemoveService(ctx, &swarmapi.RemoveServiceRequest{ServiceID: service.ID}); err != nil {
 		return err
 		return err
 	}
 	}
 	return nil
 	return nil
@@ -803,8 +946,12 @@ func (c *Cluster) GetNodes(options apitypes.NodeListOptions) ([]types.Node, erro
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
 	r, err := c.client.ListNodes(
 	r, err := c.client.ListNodes(
-		c.getRequestContext(),
+		ctx,
 		&swarmapi.ListNodesRequest{Filters: filters})
 		&swarmapi.ListNodesRequest{Filters: filters})
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -827,7 +974,10 @@ func (c *Cluster) GetNode(input string) (types.Node, error) {
 		return types.Node{}, c.errNoManager()
 		return types.Node{}, c.errNoManager()
 	}
 	}
 
 
-	node, err := getNode(c.getRequestContext(), c.client, input)
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
+	node, err := getNode(ctx, c.client, input)
 	if err != nil {
 	if err != nil {
 		return types.Node{}, err
 		return types.Node{}, err
 	}
 	}
@@ -848,8 +998,11 @@ func (c *Cluster) UpdateNode(nodeID string, version uint64, spec types.NodeSpec)
 		return err
 		return err
 	}
 	}
 
 
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
 	_, err = c.client.UpdateNode(
 	_, err = c.client.UpdateNode(
-		c.getRequestContext(),
+		ctx,
 		&swarmapi.UpdateNodeRequest{
 		&swarmapi.UpdateNodeRequest{
 			NodeID: nodeID,
 			NodeID: nodeID,
 			Spec:   &nodeSpec,
 			Spec:   &nodeSpec,
@@ -870,7 +1023,8 @@ func (c *Cluster) RemoveNode(input string) error {
 		return c.errNoManager()
 		return c.errNoManager()
 	}
 	}
 
 
-	ctx := c.getRequestContext()
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
 
 
 	node, err := getNode(ctx, c.client, input)
 	node, err := getNode(ctx, c.client, input)
 	if err != nil {
 	if err != nil {
@@ -922,8 +1076,12 @@ func (c *Cluster) GetTasks(options apitypes.TaskListOptions) ([]types.Task, erro
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
 	r, err := c.client.ListTasks(
 	r, err := c.client.ListTasks(
-		c.getRequestContext(),
+		ctx,
 		&swarmapi.ListTasksRequest{Filters: filters})
 		&swarmapi.ListTasksRequest{Filters: filters})
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -946,7 +1104,10 @@ func (c *Cluster) GetTask(input string) (types.Task, error) {
 		return types.Task{}, c.errNoManager()
 		return types.Task{}, c.errNoManager()
 	}
 	}
 
 
-	task, err := getTask(c.getRequestContext(), c.client, input)
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
+	task, err := getTask(ctx, c.client, input)
 	if err != nil {
 	if err != nil {
 		return types.Task{}, err
 		return types.Task{}, err
 	}
 	}
@@ -962,7 +1123,10 @@ func (c *Cluster) GetNetwork(input string) (apitypes.NetworkResource, error) {
 		return apitypes.NetworkResource{}, c.errNoManager()
 		return apitypes.NetworkResource{}, c.errNoManager()
 	}
 	}
 
 
-	network, err := getNetwork(c.getRequestContext(), c.client, input)
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
+	network, err := getNetwork(ctx, c.client, input)
 	if err != nil {
 	if err != nil {
 		return apitypes.NetworkResource{}, err
 		return apitypes.NetworkResource{}, err
 	}
 	}
@@ -978,7 +1142,10 @@ func (c *Cluster) GetNetworks() ([]apitypes.NetworkResource, error) {
 		return nil, c.errNoManager()
 		return nil, c.errNoManager()
 	}
 	}
 
 
-	r, err := c.client.ListNetworks(c.getRequestContext(), &swarmapi.ListNetworksRequest{})
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
+	r, err := c.client.ListNetworks(ctx, &swarmapi.ListNetworksRequest{})
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -1006,8 +1173,11 @@ func (c *Cluster) CreateNetwork(s apitypes.NetworkCreateRequest) (string, error)
 		return "", errors.NewRequestForbiddenError(err)
 		return "", errors.NewRequestForbiddenError(err)
 	}
 	}
 
 
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
 	networkSpec := convert.BasicNetworkCreateToGRPC(s)
 	networkSpec := convert.BasicNetworkCreateToGRPC(s)
-	r, err := c.client.CreateNetwork(c.getRequestContext(), &swarmapi.CreateNetworkRequest{Spec: &networkSpec})
+	r, err := c.client.CreateNetwork(ctx, &swarmapi.CreateNetworkRequest{Spec: &networkSpec})
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
@@ -1024,21 +1194,28 @@ func (c *Cluster) RemoveNetwork(input string) error {
 		return c.errNoManager()
 		return c.errNoManager()
 	}
 	}
 
 
-	network, err := getNetwork(c.getRequestContext(), c.client, input)
+	ctx, cancel := c.getRequestContext()
+	defer cancel()
+
+	network, err := getNetwork(ctx, c.client, input)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	if _, err := c.client.RemoveNetwork(c.getRequestContext(), &swarmapi.RemoveNetworkRequest{NetworkID: network.ID}); err != nil {
+	if _, err := c.client.RemoveNetwork(ctx, &swarmapi.RemoveNetworkRequest{NetworkID: network.ID}); err != nil {
 		return err
 		return err
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-func populateNetworkID(ctx context.Context, c swarmapi.ControlClient, s *types.ServiceSpec) error {
+func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.ControlClient, s *types.ServiceSpec) error {
 	for i, n := range s.Networks {
 	for i, n := range s.Networks {
-		apiNetwork, err := getNetwork(ctx, c, n.Target)
+		apiNetwork, err := getNetwork(ctx, client, n.Target)
 		if err != nil {
 		if err != nil {
+			if ln, _ := c.config.Backend.FindNetwork(n.Target); ln != nil && !ln.Info().Dynamic() {
+				err = fmt.Errorf("network %s is not eligible for docker services", ln.Name())
+				return errors.NewRequestForbiddenError(err)
+			}
 			return err
 			return err
 		}
 		}
 		s.Networks[i].Target = apiNetwork.ID
 		s.Networks[i].Target = apiNetwork.ID
@@ -1095,7 +1272,8 @@ func (c *Cluster) Cleanup() {
 }
 }
 
 
 func (c *Cluster) managerStats() (current bool, reachable int, unreachable int, err error) {
 func (c *Cluster) managerStats() (current bool, reachable int, unreachable int, err error) {
-	ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
 	nodes, err := c.client.ListNodes(ctx, &swarmapi.ListNodesRequest{})
 	nodes, err := c.client.ListNodes(ctx, &swarmapi.ListNodesRequest{})
 	if err != nil {
 	if err != nil {
 		return false, 0, 0, err
 		return false, 0, 0, err
@@ -1167,11 +1345,6 @@ func validateAndSanitizeJoinRequest(req *types.JoinRequest) error {
 			return fmt.Errorf("invalid remoteAddr %q: %v", req.RemoteAddrs[i], err)
 			return fmt.Errorf("invalid remoteAddr %q: %v", req.RemoteAddrs[i], err)
 		}
 		}
 	}
 	}
-	if req.CACertHash != "" {
-		if _, err := digest.ParseDigest(req.CACertHash); err != nil {
-			return fmt.Errorf("invalid CACertHash %q, %v", req.CACertHash, err)
-		}
-	}
 	return nil
 	return nil
 }
 }
 
 
@@ -1186,13 +1359,6 @@ func validateAddr(addr string) (string, error) {
 	return strings.TrimPrefix(newaddr, "tcp://"), nil
 	return strings.TrimPrefix(newaddr, "tcp://"), nil
 }
 }
 
 
-func errSwarmExists(node *node) error {
-	if node.NodeMembership() != swarmapi.NodeMembershipAccepted {
-		return ErrPendingSwarmExists
-	}
-	return ErrSwarmExists
-}
-
 func initClusterSpec(node *node, spec types.Spec) error {
 func initClusterSpec(node *node, spec types.Spec) error {
 	ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
 	ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
 	for conn := range node.ListenControlSocket(ctx) {
 	for conn := range node.ListenControlSocket(ctx) {
@@ -1217,7 +1383,7 @@ func initClusterSpec(node *node, spec types.Spec) error {
 				cluster = lcr.Clusters[0]
 				cluster = lcr.Clusters[0]
 				break
 				break
 			}
 			}
-			newspec, err := convert.SwarmSpecToGRPCandMerge(spec, &cluster.Spec)
+			newspec, err := convert.SwarmSpecToGRPC(spec)
 			if err != nil {
 			if err != nil {
 				return fmt.Errorf("error updating cluster settings: %v", err)
 				return fmt.Errorf("error updating cluster settings: %v", err)
 			}
 			}

+ 0 - 7
daemon/cluster/convert/node.go

@@ -15,7 +15,6 @@ func NodeFromGRPC(n swarmapi.Node) types.Node {
 		ID: n.ID,
 		ID: n.ID,
 		Spec: types.NodeSpec{
 		Spec: types.NodeSpec{
 			Role:         types.NodeRole(strings.ToLower(n.Spec.Role.String())),
 			Role:         types.NodeRole(strings.ToLower(n.Spec.Role.String())),
-			Membership:   types.NodeMembership(strings.ToLower(n.Spec.Membership.String())),
 			Availability: types.NodeAvailability(strings.ToLower(n.Spec.Availability.String())),
 			Availability: types.NodeAvailability(strings.ToLower(n.Spec.Availability.String())),
 		},
 		},
 		Status: types.NodeStatus{
 		Status: types.NodeStatus{
@@ -79,12 +78,6 @@ func NodeSpecToGRPC(s types.NodeSpec) (swarmapi.NodeSpec, error) {
 		return swarmapi.NodeSpec{}, fmt.Errorf("invalid Role: %q", s.Role)
 		return swarmapi.NodeSpec{}, fmt.Errorf("invalid Role: %q", s.Role)
 	}
 	}
 
 
-	if membership, ok := swarmapi.NodeSpec_Membership_value[strings.ToUpper(string(s.Membership))]; ok {
-		spec.Membership = swarmapi.NodeSpec_Membership(membership)
-	} else {
-		return swarmapi.NodeSpec{}, fmt.Errorf("invalid Membership: %q", s.Membership)
-	}
-
 	if availability, ok := swarmapi.NodeSpec_Availability_value[strings.ToUpper(string(s.Availability))]; ok {
 	if availability, ok := swarmapi.NodeSpec_Availability_value[strings.ToUpper(string(s.Availability))]; ok {
 		spec.Availability = swarmapi.NodeSpec_Availability(availability)
 		spec.Availability = swarmapi.NodeSpec_Availability(availability)
 	} else {
 	} else {

+ 61 - 3
daemon/cluster/convert/service.go

@@ -28,6 +28,7 @@ func ServiceFromGRPC(s swarmapi.Service) types.Service {
 				Resources:     resourcesFromGRPC(s.Spec.Task.Resources),
 				Resources:     resourcesFromGRPC(s.Spec.Task.Resources),
 				RestartPolicy: restartPolicyFromGRPC(s.Spec.Task.Restart),
 				RestartPolicy: restartPolicyFromGRPC(s.Spec.Task.Restart),
 				Placement:     placementFromGRPC(s.Spec.Task.Placement),
 				Placement:     placementFromGRPC(s.Spec.Task.Placement),
+				LogDriver:     driverFromGRPC(s.Spec.Task.LogDriver),
 			},
 			},
 
 
 			Networks:     networks,
 			Networks:     networks,
@@ -52,9 +53,16 @@ func ServiceFromGRPC(s swarmapi.Service) types.Service {
 		}
 		}
 
 
 		service.Spec.UpdateConfig.Delay, _ = ptypes.Duration(&s.Spec.Update.Delay)
 		service.Spec.UpdateConfig.Delay, _ = ptypes.Duration(&s.Spec.Update.Delay)
+
+		switch s.Spec.Update.FailureAction {
+		case swarmapi.UpdateConfig_PAUSE:
+			service.Spec.UpdateConfig.FailureAction = types.UpdateFailureActionPause
+		case swarmapi.UpdateConfig_CONTINUE:
+			service.Spec.UpdateConfig.FailureAction = types.UpdateFailureActionContinue
+		}
 	}
 	}
 
 
-	//Mode
+	// Mode
 	switch t := s.Spec.GetMode().(type) {
 	switch t := s.Spec.GetMode().(type) {
 	case *swarmapi.ServiceSpec_Global:
 	case *swarmapi.ServiceSpec_Global:
 		service.Spec.Mode.Global = &types.GlobalService{}
 		service.Spec.Mode.Global = &types.GlobalService{}
@@ -64,6 +72,23 @@ func ServiceFromGRPC(s swarmapi.Service) types.Service {
 		}
 		}
 	}
 	}
 
 
+	// UpdateStatus
+	service.UpdateStatus = types.UpdateStatus{}
+	if s.UpdateStatus != nil {
+		switch s.UpdateStatus.State {
+		case swarmapi.UpdateStatus_UPDATING:
+			service.UpdateStatus.State = types.UpdateStateUpdating
+		case swarmapi.UpdateStatus_PAUSED:
+			service.UpdateStatus.State = types.UpdateStatePaused
+		case swarmapi.UpdateStatus_COMPLETED:
+			service.UpdateStatus.State = types.UpdateStateCompleted
+		}
+
+		service.UpdateStatus.StartedAt, _ = ptypes.Timestamp(s.UpdateStatus.StartedAt)
+		service.UpdateStatus.CompletedAt, _ = ptypes.Timestamp(s.UpdateStatus.CompletedAt)
+		service.UpdateStatus.Message = s.UpdateStatus.Message
+	}
+
 	return service
 	return service
 }
 }
 
 
@@ -86,6 +111,7 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
 		},
 		},
 		Task: swarmapi.TaskSpec{
 		Task: swarmapi.TaskSpec{
 			Resources: resourcesToGRPC(s.TaskTemplate.Resources),
 			Resources: resourcesToGRPC(s.TaskTemplate.Resources),
+			LogDriver: driverToGRPC(s.TaskTemplate.LogDriver),
 		},
 		},
 		Networks: networks,
 		Networks: networks,
 	}
 	}
@@ -109,9 +135,19 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
 	}
 	}
 
 
 	if s.UpdateConfig != nil {
 	if s.UpdateConfig != nil {
+		var failureAction swarmapi.UpdateConfig_FailureAction
+		switch s.UpdateConfig.FailureAction {
+		case types.UpdateFailureActionPause, "":
+			failureAction = swarmapi.UpdateConfig_PAUSE
+		case types.UpdateFailureActionContinue:
+			failureAction = swarmapi.UpdateConfig_CONTINUE
+		default:
+			return swarmapi.ServiceSpec{}, fmt.Errorf("unrecongized update failure action %s", s.UpdateConfig.FailureAction)
+		}
 		spec.Update = &swarmapi.UpdateConfig{
 		spec.Update = &swarmapi.UpdateConfig{
-			Parallelism: s.UpdateConfig.Parallelism,
-			Delay:       *ptypes.DurationProto(s.UpdateConfig.Delay),
+			Parallelism:   s.UpdateConfig.Parallelism,
+			Delay:         *ptypes.DurationProto(s.UpdateConfig.Delay),
+			FailureAction: failureAction,
 		}
 		}
 	}
 	}
 
 
@@ -251,3 +287,25 @@ func placementFromGRPC(p *swarmapi.Placement) *types.Placement {
 
 
 	return r
 	return r
 }
 }
+
+func driverFromGRPC(p *swarmapi.Driver) *types.Driver {
+	if p == nil {
+		return nil
+	}
+
+	return &types.Driver{
+		Name:    p.Name,
+		Options: p.Options,
+	}
+}
+
+func driverToGRPC(p *types.Driver) *swarmapi.Driver {
+	if p == nil {
+		return nil
+	}
+
+	return &swarmapi.Driver{
+		Name:    p.Name,
+		Options: p.Options,
+	}
+}

+ 6 - 74
daemon/cluster/convert/swarm.go

@@ -5,8 +5,6 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
-	"golang.org/x/crypto/bcrypt"
-
 	types "github.com/docker/engine-api/types/swarm"
 	types "github.com/docker/engine-api/types/swarm"
 	swarmapi "github.com/docker/swarmkit/api"
 	swarmapi "github.com/docker/swarmkit/api"
 	"github.com/docker/swarmkit/protobuf/ptypes"
 	"github.com/docker/swarmkit/protobuf/ptypes"
@@ -28,6 +26,10 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
 				ElectionTick:               c.Spec.Raft.ElectionTick,
 				ElectionTick:               c.Spec.Raft.ElectionTick,
 			},
 			},
 		},
 		},
+		JoinTokens: types.JoinTokens{
+			Worker:  c.RootCA.JoinTokens.Worker,
+			Manager: c.RootCA.JoinTokens.Manager,
+		},
 	}
 	}
 
 
 	heartbeatPeriod, _ := ptypes.Duration(c.Spec.Dispatcher.HeartbeatPeriod)
 	heartbeatPeriod, _ := ptypes.Duration(c.Spec.Dispatcher.HeartbeatPeriod)
@@ -52,23 +54,11 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
 	swarm.Spec.Name = c.Spec.Annotations.Name
 	swarm.Spec.Name = c.Spec.Annotations.Name
 	swarm.Spec.Labels = c.Spec.Annotations.Labels
 	swarm.Spec.Labels = c.Spec.Annotations.Labels
 
 
-	for _, policy := range c.Spec.AcceptancePolicy.Policies {
-		p := types.Policy{
-			Role:       types.NodeRole(strings.ToLower(policy.Role.String())),
-			Autoaccept: policy.Autoaccept,
-		}
-		if policy.Secret != nil {
-			secret := string(policy.Secret.Data)
-			p.Secret = &secret
-		}
-		swarm.Spec.AcceptancePolicy.Policies = append(swarm.Spec.AcceptancePolicy.Policies, p)
-	}
-
 	return swarm
 	return swarm
 }
 }
 
 
-// SwarmSpecToGRPCandMerge converts a Spec to a grpc ClusterSpec and merge AcceptancePolicy from an existing grpc ClusterSpec if provided.
-func SwarmSpecToGRPCandMerge(s types.Spec, existingSpec *swarmapi.ClusterSpec) (swarmapi.ClusterSpec, error) {
+// SwarmSpecToGRPC converts a Spec to a grpc ClusterSpec.
+func SwarmSpecToGRPC(s types.Spec) (swarmapi.ClusterSpec, error) {
 	spec := swarmapi.ClusterSpec{
 	spec := swarmapi.ClusterSpec{
 		Annotations: swarmapi.Annotations{
 		Annotations: swarmapi.Annotations{
 			Name:   s.Name,
 			Name:   s.Name,
@@ -104,63 +94,5 @@ func SwarmSpecToGRPCandMerge(s types.Spec, existingSpec *swarmapi.ClusterSpec) (
 		})
 		})
 	}
 	}
 
 
-	if err := SwarmSpecUpdateAcceptancePolicy(&spec, s.AcceptancePolicy, existingSpec); err != nil {
-		return swarmapi.ClusterSpec{}, err
-	}
-
 	return spec, nil
 	return spec, nil
 }
 }
-
-// SwarmSpecUpdateAcceptancePolicy updates a grpc ClusterSpec using AcceptancePolicy.
-func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolicy types.AcceptancePolicy, oldSpec *swarmapi.ClusterSpec) error {
-	spec.AcceptancePolicy.Policies = nil
-	hashs := make(map[string][]byte)
-
-	for _, p := range acceptancePolicy.Policies {
-		role, ok := swarmapi.NodeRole_value[strings.ToUpper(string(p.Role))]
-		if !ok {
-			return fmt.Errorf("invalid Role: %q", p.Role)
-		}
-
-		policy := &swarmapi.AcceptancePolicy_RoleAdmissionPolicy{
-			Role:       swarmapi.NodeRole(role),
-			Autoaccept: p.Autoaccept,
-		}
-
-		if p.Secret != nil {
-			if *p.Secret == "" { // if provided secret is empty, it means erase previous secret.
-				policy.Secret = nil
-			} else { // if provided secret is not empty, we generate a new one.
-				hashPwd, ok := hashs[*p.Secret]
-				if !ok {
-					hashPwd, _ = bcrypt.GenerateFromPassword([]byte(*p.Secret), 0)
-					hashs[*p.Secret] = hashPwd
-				}
-				policy.Secret = &swarmapi.AcceptancePolicy_RoleAdmissionPolicy_Secret{
-					Data: hashPwd,
-					Alg:  "bcrypt",
-				}
-			}
-		} else if oldSecret := getOldSecret(oldSpec, policy.Role); oldSecret != nil { // else use the old one.
-			policy.Secret = &swarmapi.AcceptancePolicy_RoleAdmissionPolicy_Secret{
-				Data: oldSecret.Data,
-				Alg:  oldSecret.Alg,
-			}
-		}
-
-		spec.AcceptancePolicy.Policies = append(spec.AcceptancePolicy.Policies, policy)
-	}
-	return nil
-}
-
-func getOldSecret(oldSpec *swarmapi.ClusterSpec, role swarmapi.NodeRole) *swarmapi.AcceptancePolicy_RoleAdmissionPolicy_Secret {
-	if oldSpec == nil {
-		return nil
-	}
-	for _, p := range oldSpec.AcceptancePolicy.Policies {
-		if p.Role == role {
-			return p.Secret
-		}
-	}
-	return nil
-}

+ 1 - 0
daemon/cluster/convert/task.go

@@ -22,6 +22,7 @@ func TaskFromGRPC(t swarmapi.Task) types.Task {
 			Resources:     resourcesFromGRPC(t.Spec.Resources),
 			Resources:     resourcesFromGRPC(t.Spec.Resources),
 			RestartPolicy: restartPolicyFromGRPC(t.Spec.Restart),
 			RestartPolicy: restartPolicyFromGRPC(t.Spec.Restart),
 			Placement:     placementFromGRPC(t.Spec.Placement),
 			Placement:     placementFromGRPC(t.Spec.Placement),
+			LogDriver:     driverFromGRPC(t.Spec.LogDriver),
 		},
 		},
 		Status: types.TaskStatus{
 		Status: types.TaskStatus{
 			State:   types.TaskState(strings.ToLower(t.Status.State.String())),
 			State:   types.TaskState(strings.ToLower(t.Status.State.String())),

+ 2 - 0
daemon/cluster/executor/backend.go

@@ -10,6 +10,7 @@ import (
 	"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/engine-api/types/network"
 	"github.com/docker/engine-api/types/network"
+	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork/cluster"
 	"github.com/docker/libnetwork/cluster"
 	networktypes "github.com/docker/libnetwork/types"
 	networktypes "github.com/docker/libnetwork/types"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -19,6 +20,7 @@ import (
 type Backend interface {
 type Backend interface {
 	CreateManagedNetwork(clustertypes.NetworkCreateRequest) error
 	CreateManagedNetwork(clustertypes.NetworkCreateRequest) error
 	DeleteManagedNetwork(name string) error
 	DeleteManagedNetwork(name string) error
+	FindNetwork(idName string) (libnetwork.Network, error)
 	SetupIngress(req clustertypes.NetworkCreateRequest, nodeIP string) error
 	SetupIngress(req clustertypes.NetworkCreateRequest, nodeIP string) error
 	PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
 	PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
 	CreateManagedContainer(config types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error)
 	CreateManagedContainer(config types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error)

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません