Explorar o código

Merge pull request #29866 from vieux/1.13.0-rc5-cherrpicks

1.13.0 rc5 cherrypicks
Tibor Vass %!s(int64=8) %!d(string=hai) anos
pai
achega
1afd41e4f7
Modificáronse 44 ficheiros con 630 adicións e 360 borrados
  1. 29 98
      api/swagger.yaml
  2. 5 5
      builder/dockerfile/dispatchers.go
  3. 1 1
      cli/command/plugin/inspect.go
  4. 21 4
      cli/command/secret/create.go
  5. 0 7
      cli/command/stack/ps.go
  6. 8 4
      cli/flags/common.go
  7. 42 0
      cli/flags/common_test.go
  8. 5 0
      client/events.go
  9. 17 6
      contrib/completion/bash/docker
  10. 3 4
      daemon/commit.go
  11. 3 1
      daemon/update.go
  12. 41 44
      docs/extend/config.md
  13. 2 2
      docs/extend/index.md
  14. 12 10
      docs/reference/commandline/dockerd.md
  15. 2 2
      docs/reference/commandline/events.md
  16. 1 0
      docs/reference/commandline/index.md
  17. 0 1
      docs/reference/commandline/node_ps.md
  18. 7 7
      docs/reference/commandline/plugin_disable.md
  19. 7 7
      docs/reference/commandline/plugin_enable.md
  20. 5 5
      docs/reference/commandline/plugin_inspect.md
  21. 8 8
      docs/reference/commandline/plugin_install.md
  22. 2 2
      docs/reference/commandline/plugin_ls.md
  23. 2 3
      docs/reference/commandline/plugin_push.md
  24. 5 5
      docs/reference/commandline/plugin_rm.md
  25. 4 4
      docs/reference/commandline/plugin_set.md
  26. 20 7
      docs/reference/commandline/secret_create.md
  27. 4 4
      docs/reference/commandline/stack_ps.md
  28. 20 59
      experimental/README.md
  29. 1 1
      hack/make.ps1
  30. 40 0
      integration-cli/docker_cli_build_test.go
  31. 13 0
      integration-cli/docker_cli_commit_test.go
  32. 4 0
      integration-cli/docker_cli_external_graphdriver_unix_test.go
  33. 8 0
      integration-cli/docker_cli_inspect_test.go
  34. 34 0
      integration-cli/docker_cli_secret_create_test.go
  35. 31 0
      integration-cli/docker_cli_update_unix_test.go
  36. 2 2
      man/docker-events.1.md
  37. 33 1
      man/dockerd.8.md
  38. 37 0
      opts/quotedstring.go
  39. 28 0
      opts/quotedstring_test.go
  40. 37 0
      pkg/plugins/plugin_test.go
  41. 51 23
      pkg/plugins/plugins.go
  42. 2 2
      plugin/store.go
  43. 19 9
      volume/store/db.go
  44. 14 22
      volume/store/restore.go

+ 29 - 98
api/swagger.yaml

@@ -1497,74 +1497,39 @@ definitions:
                   type: "string"
     example:
       Id: "5724e2c8652da337ab2eedd19fc6fc0ec908e4bd907c7421bf6a8dfc70c4c078"
-      Name: "tiborvass/no-remove"
+      Name: "tiborvass/sample-volume-plugin"
       Tag: "latest"
       Active: true
-      Config:
-        Mounts:
-          - Name: ""
-            Description: ""
-            Settable: null
-            Source: "/data"
-            Destination: "/data"
-            Type: "bind"
-            Options:
-              - "shared"
-              - "rbind"
-          - Name: ""
-            Description: ""
-            Settable: null
-            Source: null
-            Destination: "/foobar"
-            Type: "tmpfs"
-            Options: null
+      Settings:
         Env:
-          - "DEBUG=1"
+          - "DEBUG=0"
         Args: null
         Devices: null
-      Manifest:
-        ManifestVersion: "v0"
-        Description: "A test plugin for Docker"
+      Config:
+        Description: "A sample volume plugin for Docker"
         Documentation: "https://docs.docker.com/engine/extend/plugins/"
         Interface:
           Types:
             - "docker.volumedriver/1.0"
           Socket: "plugins.sock"
         Entrypoint:
-          - "plugin-no-remove"
+          - "/usr/bin/sample-volume-plugin"
           - "/data"
         WorkDir: ""
         User: {}
         Network:
-          Type: "host"
-        Capabilities: null
-        Mounts:
-          - Name: ""
-            Description: ""
-            Settable: null
-            Source: "/data"
-            Destination: "/data"
-            Type: "bind"
-            Options:
-              - "shared"
-              - "rbind"
-          - Name: ""
-            Description: ""
-            Settable: null
-            Source: null
-            Destination: "/foobar"
-            Type: "tmpfs"
-            Options: null
-        Devices:
-          - Name: "device"
-            Description: "a host device to mount"
-            Settable: null
-            Path: "/dev/cpu_dma_latency"
+          Type: ""
+        Linux:
+          Capabilities: null
+          DeviceCreation: false
+          Devices: null
+        Mounts: null
+        PropagatedMount: "/data"
         Env:
           - Name: "DEBUG"
             Description: "If set, prints debug messages"
             Settable: null
-            Value: "1"
+            Value: "0"
         Args:
           Name: "args"
           Description: "command line arguments"
@@ -6366,74 +6331,39 @@ paths:
               $ref: "#/definitions/Plugin"
             example:
               - Id: "5724e2c8652da337ab2eedd19fc6fc0ec908e4bd907c7421bf6a8dfc70c4c078"
-                Name: "tiborvass/no-remove"
+                Name: "tiborvass/sample-volume-plugin"
                 Tag: "latest"
                 Active: true
-                Config:
-                  Mounts:
-                    - Name: ""
-                      Description: ""
-                      Settable: null
-                      Source: "/data"
-                      Destination: "/data"
-                      Type: "bind"
-                      Options:
-                        - "shared"
-                        - "rbind"
-                    - Name: ""
-                      Description: ""
-                      Settable: null
-                      Source: null
-                      Destination: "/foobar"
-                      Type: "tmpfs"
-                      Options: null
+                Settings:
                   Env:
-                    - "DEBUG=1"
+                    - "DEBUG=0"
                   Args: null
                   Devices: null
-                Manifest:
-                  ManifestVersion: "v0"
-                  Description: "A test plugin for Docker"
+                Config:
+                  Description: "A sample volume plugin for Docker"
                   Documentation: "https://docs.docker.com/engine/extend/plugins/"
                   Interface:
                     Types:
                       - "docker.volumedriver/1.0"
                     Socket: "plugins.sock"
                   Entrypoint:
-                    - "plugin-no-remove"
+                    - "/usr/bin/sample-volume-plugin"
                     - "/data"
                   WorkDir: ""
                   User: {}
                   Network:
-                    Type: "host"
-                  Capabilities: null
-                  Mounts:
-                    - Name: ""
-                      Description: ""
-                      Settable: null
-                      Source: "/data"
-                      Destination: "/data"
-                      Type: "bind"
-                      Options:
-                        - "shared"
-                        - "rbind"
-                    - Name: ""
-                      Description: ""
-                      Settable: null
-                      Source: null
-                      Destination: "/foobar"
-                      Type: "tmpfs"
-                      Options: null
-                  Devices:
-                    - Name: "device"
-                      Description: "a host device to mount"
-                      Settable: null
-                      Path: "/dev/cpu_dma_latency"
+                    Type: ""
+                  Linux:
+                    Capabilities: null
+                    DeviceCreation: false
+                    Devices: null
+                  Mounts: null
+                  PropagatedMount: "/data"
                   Env:
                     - Name: "DEBUG"
                       Description: "If set, prints debug messages"
                       Settable: null
-                      Value: "1"
+                      Value: "0"
                   Args:
                     Name: "args"
                     Description: "command line arguments"
@@ -6582,6 +6512,7 @@ paths:
           required: true
           type: "string"
       tags: ["Plugin"]
+  /plugins/{name}:
     delete:
       summary: "Remove a plugin"
       operationId: "PluginDelete"

+ 5 - 5
builder/dockerfile/dispatchers.go

@@ -297,17 +297,17 @@ func workdir(b *Builder, args []string, attributes map[string]bool, original str
 	}
 	b.runConfig.Image = b.image
 
-	cmd := b.runConfig.Cmd
-	b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), fmt.Sprintf("#(nop) WORKDIR %s", b.runConfig.WorkingDir)))
-	defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
-
 	if hit, err := b.probeCache(); err != nil {
 		return err
 	} else if hit {
 		return nil
 	}
 
-	container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{Config: b.runConfig})
+	// Actually copy the struct
+	workdirConfig := *b.runConfig
+	workdirConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), fmt.Sprintf("#(nop) WORKDIR %s", b.runConfig.WorkingDir)))
+
+	container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{Config: &workdirConfig})
 	if err != nil {
 		return err
 	}

+ 1 - 1
cli/command/plugin/inspect.go

@@ -17,7 +17,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
 	var opts inspectOptions
 
 	cmd := &cobra.Command{
-		Use:   "inspect [OPTIONS] PLUGIN|ID [PLUGIN|ID...]",
+		Use:   "inspect [OPTIONS] PLUGIN [PLUGIN...]",
 		Short: "Display detailed information on one or more plugins",
 		Args:  cli.RequiresMinArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {

+ 21 - 4
cli/command/secret/create.go

@@ -2,13 +2,14 @@ package secret
 
 import (
 	"fmt"
+	"io"
 	"io/ioutil"
-	"os"
 
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/opts"
+	"github.com/docker/docker/pkg/system"
 	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/spf13/cobra"
 	"golang.org/x/net/context"
@@ -16,6 +17,7 @@ import (
 
 type createOptions struct {
 	name   string
+	file   string
 	labels opts.ListOpts
 }
 
@@ -26,7 +28,7 @@ func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 
 	cmd := &cobra.Command{
 		Use:   "create [OPTIONS] SECRET",
-		Short: "Create a secret using stdin as content",
+		Short: "Create a secret from a file or STDIN as content",
 		Args:  cli.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			createOpts.name = args[0]
@@ -35,6 +37,7 @@ func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 	}
 	flags := cmd.Flags()
 	flags.VarP(&createOpts.labels, "label", "l", "Secret labels")
+	flags.StringVarP(&createOpts.file, "file", "f", "", "Read from a file or STDIN ('-')")
 
 	return cmd
 }
@@ -43,9 +46,23 @@ func runSecretCreate(dockerCli *command.DockerCli, options createOptions) error
 	client := dockerCli.Client()
 	ctx := context.Background()
 
-	secretData, err := ioutil.ReadAll(os.Stdin)
+	if options.file == "" {
+		return fmt.Errorf("Please specify either a file name or STDIN ('-') with --file")
+	}
+
+	var in io.Reader = dockerCli.In()
+	if options.file != "-" {
+		file, err := system.OpenSequential(options.file)
+		if err != nil {
+			return err
+		}
+		in = file
+		defer file.Close()
+	}
+
+	secretData, err := ioutil.ReadAll(in)
 	if err != nil {
-		return fmt.Errorf("Error reading content from STDIN: %v", err)
+		return fmt.Errorf("Error reading content from %q: %v", options.file, err)
 	}
 
 	spec := swarm.SecretSpec{

+ 0 - 7
cli/command/stack/ps.go

@@ -6,7 +6,6 @@ import (
 	"golang.org/x/net/context"
 
 	"github.com/docker/docker/api/types"
-	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command/idresolver"
@@ -16,7 +15,6 @@ import (
 )
 
 type psOptions struct {
-	all       bool
 	filter    opts.FilterOpt
 	noTrunc   bool
 	namespace string
@@ -36,7 +34,6 @@ func newPsCommand(dockerCli *command.DockerCli) *cobra.Command {
 		},
 	}
 	flags := cmd.Flags()
-	flags.BoolVarP(&opts.all, "all", "a", false, "Display all tasks")
 	flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
 	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")
@@ -51,10 +48,6 @@ func runPS(dockerCli *command.DockerCli, opts psOptions) error {
 
 	filter := opts.filter.Value()
 	filter.Add("label", labelNamespace+"="+opts.namespace)
-	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(ctx, types.TaskListOptions{Filters: filter})
 	if err != nil {

+ 8 - 4
cli/flags/common.go

@@ -59,11 +59,15 @@ func (commonOpts *CommonOptions) InstallFlags(flags *pflag.FlagSet) {
 
 	// TODO use flag flags.String("identity"}, "i", "", "Path to libtrust key file")
 
-	commonOpts.TLSOptions = &tlsconfig.Options{}
+	commonOpts.TLSOptions = &tlsconfig.Options{
+		CAFile:   filepath.Join(dockerCertPath, DefaultCaFile),
+		CertFile: filepath.Join(dockerCertPath, DefaultCertFile),
+		KeyFile:  filepath.Join(dockerCertPath, DefaultKeyFile),
+	}
 	tlsOptions := commonOpts.TLSOptions
-	flags.StringVar(&tlsOptions.CAFile, "tlscacert", filepath.Join(dockerCertPath, DefaultCaFile), "Trust certs signed only by this CA")
-	flags.StringVar(&tlsOptions.CertFile, "tlscert", filepath.Join(dockerCertPath, DefaultCertFile), "Path to TLS certificate file")
-	flags.StringVar(&tlsOptions.KeyFile, "tlskey", filepath.Join(dockerCertPath, DefaultKeyFile), "Path to TLS key file")
+	flags.Var(opts.NewQuotedString(&tlsOptions.CAFile), "tlscacert", "Trust certs signed only by this CA")
+	flags.Var(opts.NewQuotedString(&tlsOptions.CertFile), "tlscert", "Path to TLS certificate file")
+	flags.Var(opts.NewQuotedString(&tlsOptions.KeyFile), "tlskey", "Path to TLS key file")
 
 	hostOpt := opts.NewNamedListOptsRef("hosts", &commonOpts.Hosts, opts.ValidateHost)
 	flags.VarP(hostOpt, "host", "H", "Daemon socket(s) to connect to")

+ 42 - 0
cli/flags/common_test.go

@@ -0,0 +1,42 @@
+package flags
+
+import (
+	"path/filepath"
+	"testing"
+
+	"github.com/docker/docker/cliconfig"
+	"github.com/docker/docker/pkg/testutil/assert"
+	"github.com/spf13/pflag"
+)
+
+func TestCommonOptionsInstallFlags(t *testing.T) {
+	flags := pflag.NewFlagSet("testing", pflag.ContinueOnError)
+	opts := NewCommonOptions()
+	opts.InstallFlags(flags)
+
+	err := flags.Parse([]string{
+		"--tlscacert=\"/foo/cafile\"",
+		"--tlscert=\"/foo/cert\"",
+		"--tlskey=\"/foo/key\"",
+	})
+	assert.NilError(t, err)
+	assert.Equal(t, opts.TLSOptions.CAFile, "/foo/cafile")
+	assert.Equal(t, opts.TLSOptions.CertFile, "/foo/cert")
+	assert.Equal(t, opts.TLSOptions.KeyFile, "/foo/key")
+}
+
+func defaultPath(filename string) string {
+	return filepath.Join(cliconfig.ConfigDir(), filename)
+}
+
+func TestCommonOptionsInstallFlagsWithDefaults(t *testing.T) {
+	flags := pflag.NewFlagSet("testing", pflag.ContinueOnError)
+	opts := NewCommonOptions()
+	opts.InstallFlags(flags)
+
+	err := flags.Parse([]string{})
+	assert.NilError(t, err)
+	assert.Equal(t, opts.TLSOptions.CAFile, defaultPath("ca.pem"))
+	assert.Equal(t, opts.TLSOptions.CertFile, defaultPath("cert.pem"))
+	assert.Equal(t, opts.TLSOptions.KeyFile, defaultPath("key.pem"))
+}

+ 5 - 0
client/events.go

@@ -22,17 +22,20 @@ func (cli *Client) Events(ctx context.Context, options types.EventsOptions) (<-c
 	messages := make(chan events.Message)
 	errs := make(chan error, 1)
 
+	started := make(chan struct{})
 	go func() {
 		defer close(errs)
 
 		query, err := buildEventsQueryParams(cli.version, options)
 		if err != nil {
+			close(started)
 			errs <- err
 			return
 		}
 
 		resp, err := cli.get(ctx, "/events", query, nil)
 		if err != nil {
+			close(started)
 			errs <- err
 			return
 		}
@@ -40,6 +43,7 @@ func (cli *Client) Events(ctx context.Context, options types.EventsOptions) (<-c
 
 		decoder := json.NewDecoder(resp.body)
 
+		close(started)
 		for {
 			select {
 			case <-ctx.Done():
@@ -61,6 +65,7 @@ func (cli *Client) Events(ctx context.Context, options types.EventsOptions) (<-c
 			}
 		}
 	}()
+	<-started
 
 	return messages, errs
 }

+ 17 - 6
contrib/completion/bash/docker

@@ -3139,7 +3139,7 @@ _docker_node_ps() {
 
 	case "$cur" in
 		-*)
-			COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-resolve --no-trunc" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--filter -f --help --no-resolve --no-trunc" -- "$cur" ) )
 			;;
 		*)
 			__docker_complete_nodes_plus_self
@@ -3223,10 +3223,13 @@ _docker_plugin_create() {
 _docker_plugin_disable() {
 	case "$cur" in
 		-*)
-			COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--force -f --help" -- "$cur" ) )
 			;;
 		*)
-			__docker_complete_plugins_installed
+			local counter=$(__docker_pos_first_nonflag)
+			if [ $cword -eq $counter ]; then
+				__docker_complete_plugins_installed
+			fi
 			;;
 	esac
 }
@@ -3243,7 +3246,10 @@ _docker_plugin_enable() {
 			COMPREPLY=( $( compgen -W "--help --timeout" -- "$cur" ) )
 			;;
 		*)
-			__docker_complete_plugins_installed
+			local counter=$(__docker_pos_first_nonflag '--timeout')
+			if [ $cword -eq $counter ]; then
+				__docker_complete_plugins_installed
+			fi
 			;;
 	esac
 }
@@ -3266,9 +3272,15 @@ _docker_plugin_inspect() {
 }
 
 _docker_plugin_install() {
+	case "$prev" in
+		--alias)
+			return
+			;;
+	esac
+
 	case "$cur" in
 		-*)
-			COMPREPLY=( $( compgen -W "--disable --grant-all-permissions--help" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--alias --disable --grant-all-permissions --help" -- "$cur" ) )
 			;;
 	esac
 }
@@ -3955,7 +3967,6 @@ _docker() {
 		container
 		cp
 		create
-		daemon
 		diff
 		events
 		exec

+ 3 - 4
daemon/commit.go

@@ -65,11 +65,10 @@ func merge(userConf, imageConf *containertypes.Config) error {
 	if userConf.Labels == nil {
 		userConf.Labels = map[string]string{}
 	}
-	if imageConf.Labels != nil {
-		for l := range userConf.Labels {
-			imageConf.Labels[l] = userConf.Labels[l]
+	for l, v := range imageConf.Labels {
+		if _, ok := userConf.Labels[l]; !ok {
+			userConf.Labels[l] = v
 		}
-		userConf.Labels = imageConf.Labels
 	}
 
 	if len(userConf.Entrypoint) == 0 {

+ 3 - 1
daemon/update.go

@@ -67,7 +67,9 @@ func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) erro
 	}
 
 	// if Restart Policy changed, we need to update container monitor
-	container.UpdateMonitor(hostConfig.RestartPolicy)
+	if hostConfig.RestartPolicy.Name != "" {
+		container.UpdateMonitor(hostConfig.RestartPolicy)
+	}
 
 	// If container is not running, update hostConfig struct is enough,
 	// resources will be updated when the container is started again.

+ 41 - 44
docs/extend/config.md

@@ -171,52 +171,49 @@ Config provides the base accessible fields for working with V0 plugin format
 
 ## Example Config
 
-*Example showing the 'tiborvass/no-remove' plugin config.*
+*Example showing the 'tiborvass/sample-volume-plugin' plugin config.*
 
 ```json
 {
-  "description": "A test plugin for Docker",
-  "documentation": "https://docs.docker.com/engine/extend/plugins/",
-  "entrypoint": ["plugin-no-remove", "/data"],
-  "interface": {
-    "types": ["docker.volumedriver/1.0"],
-    "socket": "plugins.sock"
-  },
-  "network": {
-    "type": "host"
-  },
-  "mounts": [
-    {
-      "source": "/data",
-      "destination": "/data",
-      "type": "bind",
-      "options": ["shared", "rbind"]
-    },
-    {
-      "destination": "/foobar",
-      "type": "tmpfs"
-    }
-  ],
-  "args": {
-    "name": "args",
-    "description": "command line arguments",
-    "value": []
-  },
-  "env": [
-    {
-      "name": "DEBUG",
-      "description": "If set, prints debug messages",
-      "value": "1"
-    }
-  ],
-  "linux": {
-    "devices": [
-      {
-        "name": "device",
-        "description": "a host device to mount",
-        "path": "/dev/cpu_dma_latency"
-      }
-    ]
-  }
+            "Args": {
+                "Description": "",
+                "Name": "",
+                "Settable": null,
+                "Value": null
+            },
+            "Description": "A sample volume plugin for Docker",
+            "Documentation": "https://docs.docker.com/engine/extend/plugins/",
+            "Entrypoint": [
+                "/usr/bin/sample-volume-plugin",
+                "/data"
+            ],
+            "Env": [
+                {
+                    "Description": "",
+                    "Name": "DEBUG",
+                    "Settable": [
+                        "value"
+                    ],
+                    "Value": "0"
+                }
+            ],
+            "Interface": {
+                "Socket": "plugin.sock",
+                "Types": [
+                    "docker.volumedriver/1.0"
+                ]
+            },
+            "Linux": {
+                "Capabilities": null,
+                "DeviceCreation": false,
+                "Devices": null
+            },
+            "Mounts": null,
+            "Network": {
+                "Type": ""
+            },
+            "PropagatedMount": "/data",
+            "User": {},
+            "Workdir": ""
 }
 ```

+ 2 - 2
docs/extend/index.md

@@ -69,8 +69,8 @@ enabled, and use it to create a volume.
     ```bash
     $ docker plugin ls
 
-    NAME                TAG                 ENABLED
-    vieux/sshfs         latest              true
+    ID                    NAME                  TAG                 DESCRIPTION                   ENABLED
+    69553ca1d789          vieux/sshfs           latest              the `sshfs` plugin            true
     ```
 
 3.  Create a volume using the plugin.

+ 12 - 10
docs/reference/commandline/dockerd.md

@@ -684,16 +684,18 @@ configuration file or using the `--add-runtime` command line argument.
 The following is an example adding 2 runtimes via the configuration:
 
 ```json
-"default-runtime": "runc",
-"runtimes": {
-	"runc": {
-		"path": "runc"
-	},
-	"custom": {
-		"path": "/usr/local/bin/my-runc-replacement",
-		"runtimeArgs": [
-			"--debug"
-		]
+{
+	"default-runtime": "runc",
+	"runtimes": {
+		"runc": {
+			"path": "runc"
+		},
+		"custom": {
+			"path": "/usr/local/bin/my-runc-replacement",
+			"runtimeArgs": [
+				"--debug"
+			]
+		}
 	}
 }
 ```

+ 2 - 2
docs/reference/commandline/events.md

@@ -194,8 +194,8 @@ relative to the current time on the client machine:
     2015-12-23T21:38:25.119625123Z network connect 8b111217944ba0ba844a65b13efcd57dc494932ee2527577758f939315ba2c5b (name=test-event-network-local, container=b4be644031a3d90b400f88ab3d4bdf4dc23adb250e696b6328b85441abe2c54e, type=bridge)
 
     $ docker events --filter 'type=plugin' (experimental)
-    2016-07-25T17:30:14.825557616Z plugin pull ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
-    2016-07-25T17:30:14.888127370Z plugin enable ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
+    2016-07-25T17:30:14.825557616Z plugin pull ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/sample-volume-plugin:latest)
+    2016-07-25T17:30:14.888127370Z plugin enable ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/sample-volume-plugin:latest)
 
 **Format:**
 

+ 1 - 0
docs/reference/commandline/index.md

@@ -106,6 +106,7 @@ read the [`dockerd`](dockerd.md) reference page.
 | [volume create](volume_create.md) | Creates a new volume where containers can consume and store data |
 | [volume inspect](volume_inspect.md) | Display information about a volume     |
 | [volume ls](volume_ls.md) | Lists all the volumes Docker knows about         |
+| [volume prune](volume_prune.md) | Remove all unused volumes                  |
 | [volume rm](volume_rm.md) | Remove one or more volumes                       |
 
 

+ 0 - 1
docs/reference/commandline/node_ps.md

@@ -22,7 +22,6 @@ Usage:  docker node ps [OPTIONS] [NODE...]
 List tasks running on one or more nodes, defaults to current node.
 
 Options:
-  -a, --all            Display all instances
   -f, --filter value   Filter output based on conditions provided
       --help           Print usage
       --no-resolve     Do not map IDs to Names

+ 7 - 7
docs/reference/commandline/plugin_disable.md

@@ -30,27 +30,27 @@ see [`docker plugin install`](plugin_install.md). Without the `-f` option,
 a plugin that has references (eg, volumes, networks) cannot be disabled.
 
 
-The following example shows that the `no-remove` plugin is installed
+The following example shows that the `sample-volume-plugin` plugin is installed
 and enabled:
 
 ```bash
 $ docker plugin ls
 
-NAME                  TAG                 DESCRIPTION                ENABLED
-tiborvass/no-remove   latest              A test plugin for Docker   true
+ID                  NAME                             TAG                 DESCRIPTION                ENABLED
+69553ca1d123        tiborvass/sample-volume-plugin   latest              A test plugin for Docker   true
 ```
 
 To disable the plugin, use the following command:
 
 ```bash
-$ docker plugin disable tiborvass/no-remove
+$ docker plugin disable tiborvass/sample-volume-plugin
 
-tiborvass/no-remove
+tiborvass/sample-volume-plugin
 
 $ docker plugin ls
 
-NAME                  TAG                 DESCRIPTION                ENABLED
-tiborvass/no-remove   latest              A test plugin for Docker   false
+ID                  NAME                             TAG                 DESCRIPTION                ENABLED
+69553ca1d123        tiborvass/sample-volume-plugin   latest              A test plugin for Docker   false
 ```
 
 ## Related information

+ 7 - 7
docs/reference/commandline/plugin_enable.md

@@ -29,27 +29,27 @@ Enables a plugin. The plugin must be installed before it can be enabled,
 see [`docker plugin install`](plugin_install.md).
 
 
-The following example shows that the `no-remove` plugin is installed,
+The following example shows that the `sample-volume-plugin` plugin is installed,
 but disabled:
 
 ```bash
 $ docker plugin ls
 
-NAME                  TAG                 DESCRIPTION                ENABLED
-tiborvass/no-remove   latest              A test plugin for Docker   false
+ID                  NAME                             TAG                 DESCRIPTION                ENABLED
+69553ca1d123        tiborvass/sample-volume-plugin   latest              A test plugin for Docker   false
 ```
 
 To enable the plugin, use the following command:
 
 ```bash
-$ docker plugin enable tiborvass/no-remove
+$ docker plugin enable tiborvass/sample-volume-plugin
 
-tiborvass/no-remove
+tiborvass/sample-volume-plugin
 
 $ docker plugin ls
 
-NAME                  TAG                 DESCRIPTION                ENABLED
-tiborvass/no-remove   latest              A test plugin for Docker   true
+ID                  NAME                             TAG                 DESCRIPTION                ENABLED
+69553ca1d123        tiborvass/sample-volume-plugin   latest              A test plugin for Docker   true
 ```
 
 ## Related information

+ 5 - 5
docs/reference/commandline/plugin_inspect.md

@@ -16,7 +16,7 @@ keywords: "plugin, inspect"
 # plugin inspect
 
 ```markdown
-Usage:	docker plugin inspect [OPTIONS] PLUGIN|ID [PLUGIN|ID...]
+Usage:	docker plugin inspect [OPTIONS] PLUGIN [PLUGIN...]
 
 Display detailed information on one or more plugins
 
@@ -31,12 +31,12 @@ in a JSON array.
 Example output:
 
 ```bash
-$ docker plugin inspect tiborvass/no-remove:latest
+$ docker plugin inspect tiborvass/sample-volume-plugin:latest
 ```
 ```JSON
 {
   "Id": "8c74c978c434745c3ade82f1bc0acf38d04990eaf494fa507c16d9f1daa99c21",
-  "Name": "tiborvass/no-remove:latest",
+  "Name": "tiborvass/sample-volume-plugin:latest",
   "Enabled": true,
   "Config": {
     "Mounts": [
@@ -79,7 +79,7 @@ $ docker plugin inspect tiborvass/no-remove:latest
       "Socket": "plugins.sock"
     },
     "Entrypoint": [
-      "plugin-no-remove",
+      "plugin-sample-volume-plugin",
       "/data"
     ],
     "Workdir": "",
@@ -143,7 +143,7 @@ $ docker plugin inspect tiborvass/no-remove:latest
 
 
 ```bash
-$ docker plugin inspect -f '{{.Id}}' tiborvass/no-remove:latest
+$ docker plugin inspect -f '{{.Id}}' tiborvass/sample-volume-plugin:latest
 ```
 ```
 8c74c978c434745c3ade82f1bc0acf38d04990eaf494fa507c16d9f1daa99c21

+ 8 - 8
docs/reference/commandline/plugin_install.md

@@ -33,20 +33,20 @@ the registry. Note that the minimum required registry version to distribute
 plugins is 2.3.0
 
 
-The following example installs `no-remove` plugin and [set](plugin_set.md) it's env variable
+The following example installs `vieus/sshfs` plugin and [set](plugin_set.md) it's env variable
 `DEBUG` to 1. Install consists of pulling the plugin from Docker Hub, prompting
 the user to accept the list of privileges that the plugin needs, settings parameters
  and enabling the plugin.
 
 ```bash
-$ docker plugin install tiborvass/no-remove DEBUG=1
+$ docker plugin install vieux/sshfs DEBUG=1
 
-Plugin "tiborvass/no-remove" is requesting the following privileges:
+Plugin "vieux/sshfs" is requesting the following privileges:
  - network: [host]
- - mount: [/data]
- - device: [/dev/cpu_dma_latency]
+ - device: [/dev/fuse]
+ - capabilities: [CAP_SYS_ADMIN]
 Do you grant the above permissions? [y/N] y
-tiborvass/no-remove
+vieux/sshfs
 ```
 
 After the plugin is installed, it appears in the list of plugins:
@@ -54,8 +54,8 @@ After the plugin is installed, it appears in the list of plugins:
 ```bash
 $ docker plugin ls
 
-NAME                  TAG                 DESCRIPTION                ENABLED
-tiborvass/no-remove   latest              A test plugin for Docker   true
+ID                  NAME                  TAG                 DESCRIPTION                ENABLED
+69553ca1d123        vieux/sshfs           latest              sshFS plugin for Docker    true
 ```
 
 ## Related information

+ 2 - 2
docs/reference/commandline/plugin_ls.md

@@ -36,8 +36,8 @@ Example output:
 ```bash
 $ docker plugin ls
 
-ID                  NAME                  TAG                 DESCRIPTION                ENABLED
-69553ca1d123        tiborvass/no-remove   latest              A test plugin for Docker   true
+ID                  NAME                             TAG                 DESCRIPTION                ENABLED
+69553ca1d123        tiborvass/sample-volume-plugin   latest              A test plugin for Docker   true
 ```
 
 ## Related information

+ 2 - 3
docs/reference/commandline/plugin_push.md

@@ -32,9 +32,8 @@ The following example shows how to push a sample `user/plugin`.
 ```bash
 
 $ docker plugin ls
-NAME                  	TAG                 DESCRIPTION                  ENABLED
-user/plugin             latest              A sample plugin for Docker   false
-
+ID                  NAME                  TAG                 DESCRIPTION                ENABLED
+69553ca1d456        user/plugin           latest              A sample plugin for Docker false
 $ docker plugin push user/plugin
 ```
 

+ 5 - 5
docs/reference/commandline/plugin_rm.md

@@ -33,14 +33,14 @@ a plugin using the [`docker plugin disable`](plugin_disable.md) before removing
 it (or use --force, use of force is not recommended, since it can affect
 functioning of running containers using the plugin).
 
-The following example disables and removes the `no-remove:latest` plugin;
+The following example disables and removes the `sample-volume-plugin:latest` plugin;
 
 ```bash
-$ docker plugin disable tiborvass/no-remove
-tiborvass/no-remove
+$ docker plugin disable tiborvass/sample-volume-plugin
+tiborvass/sample-volume-plugin
 
-$ docker plugin rm tiborvass/no-remove:latest
-tiborvass/no-remove
+$ docker plugin rm tiborvass/sample-volume-plugin:latest
+tiborvass/sample-volume-plugin
 ```
 
 ## Related information

+ 4 - 4
docs/reference/commandline/plugin_set.md

@@ -33,15 +33,15 @@ The settings currently supported are:
  * args
 
 The following example change the env variable `DEBUG` on the
-`no-remove` plugin.
+`sample-volume-plugin` plugin.
 
 ```bash
-$ docker plugin inspect -f {{.Settings.Env}} tiborvass/no-remove
+$ docker plugin inspect -f {{.Settings.Env}} tiborvass/sample-volume-plugin
 [DEBUG=0]
 
-$ docker plugin set tiborvass/no-remove DEBUG=1
+$ docker plugin set tiborvass/sample-volume-plugin DEBUG=1
 
-$ docker plugin inspect -f {{.Settings.Env}} tiborvass/no-remove
+$ docker plugin inspect -f {{.Settings.Env}} tiborvass/sample-volume-plugin
 [DEBUG=1]
 ```
 

+ 20 - 7
docs/reference/commandline/secret_create.md

@@ -16,15 +16,17 @@ keywords: ["secret, create"]
 # secret create
 
 ```Markdown
-Usage:  docker secret create [OPTIONS] SECRET
+Usage:	docker secret create [OPTIONS] SECRET
+
+Create a secret from a file or STDIN as content
 
-Create a secret using stdin as content
 Options:
-      --help         Print usage
-  -l, --label list   Secret labels (default [])
+  -f, --file string   Read from a file or STDIN ('-')
+      --help          Print usage
+  -l, --label list    Secret labels (default [])
 ```
 
-Creates a secret using standard input for the secret content. You must run this
+Creates a secret using standard input or from a file for the secret content. You must run this
 command on a manager node.
 
 ## Examples
@@ -32,7 +34,18 @@ command on a manager node.
 ### Create a secret
 
 ```bash
-$ cat secret.json | docker secret create secret.json
+$ cat secret.json | docker secret create -f - secret.json
+mhv17xfe3gh6xc4rij5orpfds
+
+$ docker secret ls
+ID                          NAME                    CREATED                                   UPDATED                                   SIZE
+mhv17xfe3gh6xc4rij5orpfds   secret.json             2016-10-27 23:25:43.909181089 +0000 UTC   2016-10-27 23:25:43.909181089 +0000 UTC   1679
+```
+
+### Create a secret with a file
+
+```bash
+$ docker secret create --file secret.in secret.json
 mhv17xfe3gh6xc4rij5orpfds
 
 $ docker secret ls
@@ -43,7 +56,7 @@ mhv17xfe3gh6xc4rij5orpfds   secret.json             2016-10-27 23:25:43.90918108
 ### Create a secret with labels
 
 ```bash
-$ cat secret.json | docker secret create secret.json --label env=dev --label rev=20161102
+$ cat secret.json | docker secret create secret.json -f - --label env=dev --label rev=20161102
 jtn7g6aukl5ky7nr9gvwafoxh
 
 $ docker secret inspect secret.json

+ 4 - 4
docs/reference/commandline/stack_ps.md

@@ -21,10 +21,10 @@ Usage:  docker stack ps [OPTIONS] STACK
 List the tasks in the stack
 
 Options:
-  -a, --all            Display all tasks
-  -f, --filter value   Filter output based on conditions provided
-      --no-resolve     Do not map IDs to Names
-      --no-trunc       Do not truncate output
+  -f, --filter filter   Filter output based on conditions provided
+      --help            Print usage
+      --no-resolve      Do not map IDs to Names
+      --no-trunc        Do not truncate output
 ```
 
 Lists the tasks that are running as part of the specified stack. This

+ 20 - 59
experimental/README.md

@@ -9,65 +9,26 @@ issues associated with it. If necessary, links are provided to additional
 documentation on an issue.  As an active Docker user and community member,
 please feel free to provide any feedback on these features you wish.
 
-## Install Docker experimental
-
-Unlike the regular Docker binary, the experimental channels is built and
-updated nightly on https://experimental.docker.com. From one day to the
-next, new features may appear, while existing experimental features may be
-refined or entirely removed.
-
-1. Verify that you have `curl` installed.
-
-        $ which curl
-
-    If `curl` isn't installed, install it after updating your manager:
-
-        $ sudo apt-get update
-        $ sudo apt-get install curl
-
-2. Get the latest Docker package.
-
-        $ curl -sSL https://experimental.docker.com/ | sh
-
-    The system prompts you for your `sudo` password. Then, it downloads and
-    installs Docker and its dependencies.
-
-	>**Note**: If your company is behind a filtering proxy, you may find that the
-	>`apt-key`
-	>command fails for the Docker repo during installation. To work around this,
-	>add the key directly using the following:
-	>
-	>       $ curl -sSL https://experimental.docker.com/gpg | sudo apt-key add -
-
-3. Verify `docker` is installed correctly.
-
-        $ sudo docker run hello-world
-
-    This command downloads a test image and runs it in a container.
-
-### Get the Linux binary
-To download the latest experimental `docker` binary for Linux,
-use the following URLs:
-
-    https://experimental.docker.com/builds/Linux/i386/docker-latest.tgz
-
-    https://experimental.docker.com/builds/Linux/x86_64/docker-latest.tgz
-
-After downloading the appropriate binary, you can follow the instructions
-[here](https://docs.docker.com/engine/installation/binaries/#/get-the-docker-engine-binaries) to run the `docker` daemon.
-
-> **Note**
->
-> 1) You can get the MD5 and SHA256 hashes by appending .md5 and .sha256 to the URLs respectively
->
-> 2) You can get the compressed binaries by appending .tgz to the URLs
-
-### Build an experimental binary
-You can also build the experimental binary from the standard development environment by adding
-`DOCKER_EXPERIMENTAL=1` to the environment where you run `make` to build Docker binaries. For example,
-to build a Docker binary with the experimental features enabled:
-
-        $ DOCKER_EXPERIMENTAL=1 make binary
+## Use Docker experimental
+
+Experimental features are now included in the standard Docker binaries as of
+version 1.13.0.
+For enabling experimental features, you need to start the Docker daemon with
+`--experimental` flag.
+You can also enable the daemon flag via `/etc/docker/daemon.json`. e.g.
+
+```json
+{
+    "experimental": true
+}
+```
+
+Then make sure the experimental flag is enabled:
+
+```bash
+$ docker version -f '{{.Server.Experimental}}'
+true
+```
 
 ## Current experimental features
 

+ 1 - 1
hack/make.ps1

@@ -184,7 +184,7 @@ Function Validate-DCO($headCommit, $upstreamCommit) {
     $usernameRegex='[a-zA-Z0-9][a-zA-Z0-9-]+'
 
     $dcoPrefix="Signed-off-by:"
-    $dcoRegex="^(Docker-DCO-1.1-)?$dcoPrefix ([^<]+) <([^<>@]+@[^<>]+)>( \\(github: ($githubUsernameRegex)\\))?$"
+    $dcoRegex="^(Docker-DCO-1.1-)?$dcoPrefix ([^<]+) <([^<>@]+@[^<>]+)>( \\(github: ($usernameRegex)\\))?$"
 
     $counts = Invoke-Expression "git diff --numstat $upstreamCommit...$headCommit"
     if ($LASTEXITCODE -ne 0) { Throw "Failed git diff --numstat" }

+ 40 - 0
integration-cli/docker_cli_build_test.go

@@ -7357,3 +7357,43 @@ func (s *DockerSuite) TestBuildWindowsEnvCaseInsensitive(c *check.C) {
 		c.Fatalf("Case insensitive environment variables on Windows failed. Got %s", res)
 	}
 }
+
+// Test case for 29667
+func (s *DockerSuite) TestBuildWorkdirImageCmd(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+
+	image := "testworkdirimagecmd"
+	dockerfile := `
+FROM busybox
+WORKDIR /foo/bar
+`
+	out, err := buildImage(image, dockerfile, true)
+	c.Assert(err, checker.IsNil, check.Commentf("Output: %s", out))
+
+	out, _ = dockerCmd(c, "inspect", "--format", "{{ json .Config.Cmd }}", image)
+	c.Assert(strings.TrimSpace(out), checker.Equals, `["sh"]`)
+
+	image = "testworkdirlabelimagecmd"
+	dockerfile = `
+FROM busybox
+WORKDIR /foo/bar
+LABEL a=b
+`
+	out, err = buildImage(image, dockerfile, true)
+	c.Assert(err, checker.IsNil, check.Commentf("Output: %s", out))
+
+	out, _ = dockerCmd(c, "inspect", "--format", "{{ json .Config.Cmd }}", image)
+	c.Assert(strings.TrimSpace(out), checker.Equals, `["sh"]`)
+}
+
+// Test case for 28902/28090
+func (s *DockerSuite) TestBuildWorkdirCmd(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+
+	dockerFile := `
+                FROM golang:1.7-alpine
+                WORKDIR /
+                `
+	_, err := buildImage("testbuildworkdircmd", dockerFile, false)
+	c.Assert(err, checker.IsNil)
+}

+ 13 - 0
integration-cli/docker_cli_commit_test.go

@@ -142,3 +142,16 @@ func (s *DockerSuite) TestCommitChange(c *check.C) {
 		}
 	}
 }
+
+func (s *DockerSuite) TestCommitChangeLabels(c *check.C) {
+	dockerCmd(c, "run", "--name", "test", "--label", "some=label", "busybox", "true")
+
+	imageID, _ := dockerCmd(c, "commit",
+		"--change", "LABEL some=label2",
+		"test", "test-commit")
+	imageID = strings.TrimSpace(imageID)
+
+	c.Assert(inspectField(c, imageID, "Config.Labels"), checker.Equals, "map[some:label2]")
+	// check that container labels didn't change
+	c.Assert(inspectField(c, "test", "Config.Labels"), checker.Equals, "map[some:label]")
+}

+ 4 - 0
integration-cli/docker_cli_external_graphdriver_unix_test.go

@@ -54,6 +54,10 @@ func (s *DockerExternalGraphdriverSuite) SetUpTest(c *check.C) {
 	s.d = NewDaemon(c)
 }
 
+func (s *DockerExternalGraphdriverSuite) OnTimeout(c *check.C) {
+	s.d.DumpStackAndQuit()
+}
+
 func (s *DockerExternalGraphdriverSuite) TearDownTest(c *check.C) {
 	s.d.Stop()
 	s.ds.TearDownTest(c)

+ 8 - 0
integration-cli/docker_cli_inspect_test.go

@@ -456,3 +456,11 @@ func (s *DockerSuite) TestInspectUnknownObject(c *check.C) {
 	c.Assert(out, checker.Contains, "Error: No such object: foobar")
 	c.Assert(err.Error(), checker.Contains, "Error: No such object: foobar")
 }
+
+func (s *DockerSuite) TestInpectInvalidReference(c *check.C) {
+	// This test should work on both Windows and Linux
+	out, _, err := dockerCmdWithError("inspect", "FooBar")
+	c.Assert(err, checker.NotNil)
+	c.Assert(out, checker.Contains, "Error: No such object: FooBar")
+	c.Assert(err.Error(), checker.Contains, "Error: No such object: FooBar")
+}

+ 34 - 0
integration-cli/docker_cli_secret_create_test.go

@@ -3,6 +3,10 @@
 package main
 
 import (
+	"io/ioutil"
+	"os"
+	"strings"
+
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/go-check/check"
@@ -104,3 +108,33 @@ func (s *DockerSwarmSuite) TestSecretCreateResolve(c *check.C) {
 	c.Assert(out, checker.Not(checker.Contains), id)
 	c.Assert(out, checker.Not(checker.Contains), fake)
 }
+
+func (s *DockerSwarmSuite) TestSecretCreateWithFile(c *check.C) {
+	d := s.AddDaemon(c, true, true)
+
+	testFile, err := ioutil.TempFile("", "secretCreateTest")
+	c.Assert(err, checker.IsNil, check.Commentf("failed to create temporary file"))
+	defer os.Remove(testFile.Name())
+
+	testData := "TESTINGDATA"
+	_, err = testFile.Write([]byte(testData))
+	c.Assert(err, checker.IsNil, check.Commentf("failed to write to temporary file"))
+
+	testName := "test_secret"
+	out, err := d.Cmd("secret", "create", "--file", testFile.Name(), testName)
+	c.Assert(err, checker.IsNil)
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "", check.Commentf(out))
+
+	id := strings.TrimSpace(out)
+	secret := d.getSecret(c, id)
+	c.Assert(secret.Spec.Name, checker.Equals, testName)
+
+	testName = "test_secret_2"
+	out, err = d.Cmd("secret", "create", testName, "-f", testFile.Name())
+	c.Assert(err, checker.IsNil)
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "", check.Commentf(out))
+
+	id = strings.TrimSpace(out)
+	secret = d.getSecret(c, id)
+	c.Assert(secret.Spec.Name, checker.Equals, testName)
+}

+ 31 - 0
integration-cli/docker_cli_update_unix_test.go

@@ -5,7 +5,10 @@ package main
 import (
 	"encoding/json"
 	"fmt"
+	"github.com/kr/pty"
+	"os/exec"
 	"strings"
+	"time"
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/pkg/integration/checker"
@@ -250,3 +253,31 @@ func (s *DockerSuite) TestUpdateMemoryWithSwapMemory(c *check.C) {
 
 	dockerCmd(c, "update", "--memory", "800M", "--memory-swap", "1000M", name)
 }
+
+func (s *DockerSuite) TestUpdateNotAffectMonitorRestartPolicy(c *check.C) {
+	testRequires(c, DaemonIsLinux, cpuShare)
+
+	out, _ := dockerCmd(c, "run", "-tid", "--restart=always", "busybox", "sh")
+	id := strings.TrimSpace(string(out))
+	dockerCmd(c, "update", "--cpu-shares", "512", id)
+
+	cpty, tty, err := pty.Open()
+	c.Assert(err, checker.IsNil)
+	defer cpty.Close()
+
+	cmd := exec.Command(dockerBinary, "attach", id)
+	cmd.Stdin = tty
+
+	c.Assert(cmd.Start(), checker.IsNil)
+	defer cmd.Process.Kill()
+
+	_, err = cpty.Write([]byte("exit\n"))
+	c.Assert(err, checker.IsNil)
+
+	c.Assert(cmd.Wait(), checker.IsNil)
+
+	// container should restart again and keep running
+	err = waitInspect(id, "{{.RestartCount}}", "1", 30*time.Second)
+	c.Assert(err, checker.IsNil)
+	c.Assert(waitRun(id), checker.IsNil)
+}

+ 2 - 2
man/docker-events.1.md

@@ -168,8 +168,8 @@ Lines. For information about JSON Lines, please refer to http://jsonlines.org/ .
     2015-12-23T21:38:25.119625123Z network connect 8b111217944ba0ba844a65b13efcd57dc494932ee2527577758f939315ba2c5b (name=test-event-network-local, container=b4be644031a3d90b400f88ab3d4bdf4dc23adb250e696b6328b85441abe2c54e, type=bridge)
 
     $ docker events --filter 'type=plugin' (experimental)
-    2016-07-25T17:30:14.825557616Z plugin pull ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
-    2016-07-25T17:30:14.888127370Z plugin enable ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
+    2016-07-25T17:30:14.825557616Z plugin pull ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/sample-volume-plugin:latest)
+    2016-07-25T17:30:14.888127370Z plugin enable ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/sample-volume-plugin:latest)
 
 
 # HISTORY

+ 33 - 1
man/dockerd.8.md

@@ -20,6 +20,7 @@ dockerd - Enable daemon mode
 [**-D**|**--debug**]
 [**--default-gateway**[=*DEFAULT-GATEWAY*]]
 [**--default-gateway-v6**[=*DEFAULT-GATEWAY-V6*]]
+[**--default-runtime**[=*runc*]]
 [**--default-ulimit**[=*[]*]]
 [**--disable-legacy-registry**]
 [**--dns**[=*[]*]]
@@ -84,7 +85,35 @@ following format.
 # OPTIONS
 
 **--add-runtime**=[]
-  Set additional OCI compatible runtime.
+  Runtimes can be registered with the daemon either via the
+configuration file or using the `--add-runtime` command line argument.
+
+  The following is an example adding 2 runtimes via the configuration:
+
+```json
+{
+	"default-runtime": "runc",
+	"runtimes": {
+		"runc": {
+			"path": "runc"
+		},
+		"custom": {
+			"path": "/usr/local/bin/my-runc-replacement",
+			"runtimeArgs": [
+				"--debug"
+			]
+		}
+	}
+}
+```
+
+  This is the same example via the command line:
+
+```bash
+$ sudo dockerd --add-runtime runc=runc --add-runtime custom=/usr/local/bin/my-runc-replacement
+```
+
+  **Note**: defining runtime arguments via the command line is not supported.
 
 **--api-cors-header**=""
   Set CORS headers in the Engine API. Default is cors disabled. Give urls like
@@ -132,6 +161,9 @@ following format.
 **--default-gateway-v6**=""
   IPv6 address of the container default gateway
 
+**--default-runtime**="runc"
+  Set default runtime if there're more than one specified by `--add-runtime`.
+
 **--default-ulimit**=[]
   Default ulimits for containers.
 

+ 37 - 0
opts/quotedstring.go

@@ -0,0 +1,37 @@
+package opts
+
+// QuotedString is a string that may have extra quotes around the value. The
+// quotes are stripped from the value.
+type QuotedString struct {
+	value *string
+}
+
+// Set sets a new value
+func (s *QuotedString) Set(val string) error {
+	*s.value = trimQuotes(val)
+	return nil
+}
+
+// Type returns the type of the value
+func (s *QuotedString) Type() string {
+	return "string"
+}
+
+func (s *QuotedString) String() string {
+	return string(*s.value)
+}
+
+func trimQuotes(value string) string {
+	lastIndex := len(value) - 1
+	for _, char := range []byte{'\'', '"'} {
+		if value[0] == char && value[lastIndex] == char {
+			return value[1:lastIndex]
+		}
+	}
+	return value
+}
+
+// NewQuotedString returns a new quoted string option
+func NewQuotedString(value *string) *QuotedString {
+	return &QuotedString{value: value}
+}

+ 28 - 0
opts/quotedstring_test.go

@@ -0,0 +1,28 @@
+package opts
+
+import (
+	"github.com/docker/docker/pkg/testutil/assert"
+	"testing"
+)
+
+func TestQuotedStringSetWithQuotes(t *testing.T) {
+	value := ""
+	qs := NewQuotedString(&value)
+	assert.NilError(t, qs.Set("\"something\""))
+	assert.Equal(t, qs.String(), "something")
+	assert.Equal(t, value, "something")
+}
+
+func TestQuotedStringSetWithMismatchedQuotes(t *testing.T) {
+	value := ""
+	qs := NewQuotedString(&value)
+	assert.NilError(t, qs.Set("\"something'"))
+	assert.Equal(t, qs.String(), "\"something'")
+}
+
+func TestQuotedStringSetWithNoQuotes(t *testing.T) {
+	value := ""
+	qs := NewQuotedString(&value)
+	assert.NilError(t, qs.Set("something"))
+	assert.Equal(t, qs.String(), "something")
+}

+ 37 - 0
pkg/plugins/plugin_test.go

@@ -0,0 +1,37 @@
+package plugins
+
+import (
+	"path/filepath"
+	"runtime"
+	"sync"
+	"testing"
+	"time"
+)
+
+// regression test for deadlock in handlers
+func TestPluginAddHandler(t *testing.T) {
+	// make a plugin which is pre-activated
+	p := &Plugin{activateWait: sync.NewCond(&sync.Mutex{})}
+	p.Manifest = &Manifest{Implements: []string{"bananas"}}
+	storage.plugins["qwerty"] = p
+
+	testActive(t, p)
+	Handle("bananas", func(_ string, _ *Client) {})
+	testActive(t, p)
+}
+
+func testActive(t *testing.T, p *Plugin) {
+	done := make(chan struct{})
+	go func() {
+		p.waitActive()
+		close(done)
+	}()
+
+	select {
+	case <-time.After(100 * time.Millisecond):
+		_, f, l, _ := runtime.Caller(1)
+		t.Fatalf("%s:%d: deadlock in waitActive", filepath.Base(f), l)
+	case <-done:
+	}
+
+}

+ 51 - 23
pkg/plugins/plugins.go

@@ -70,12 +70,12 @@ type Plugin struct {
 	// Manifest of the plugin (see above)
 	Manifest *Manifest `json:"-"`
 
-	// error produced by activation
-	activateErr error
-	// specifies if the activation sequence is completed (not if it is successful or not)
-	activated bool
 	// wait for activation to finish
 	activateWait *sync.Cond
+	// error produced by activation
+	activateErr error
+	// keeps track of callback handlers run against this plugin
+	handlersRun bool
 }
 
 // BasePath returns the path to which all paths returned by the plugin are relative to.
@@ -112,19 +112,51 @@ func NewLocalPlugin(name, addr string) *Plugin {
 
 func (p *Plugin) activate() error {
 	p.activateWait.L.Lock()
-	if p.activated {
+
+	if p.activated() {
+		p.runHandlers()
 		p.activateWait.L.Unlock()
 		return p.activateErr
 	}
 
 	p.activateErr = p.activateWithLock()
-	p.activated = true
 
+	p.runHandlers()
 	p.activateWait.L.Unlock()
 	p.activateWait.Broadcast()
 	return p.activateErr
 }
 
+// runHandlers runs the registered handlers for the implemented plugin types
+// This should only be run after activation, and while the activation lock is held.
+func (p *Plugin) runHandlers() {
+	if !p.activated() {
+		return
+	}
+
+	handlers.RLock()
+	if !p.handlersRun {
+		for _, iface := range p.Manifest.Implements {
+			hdlrs, handled := handlers.extpointHandlers[iface]
+			if !handled {
+				continue
+			}
+			for _, handler := range hdlrs {
+				handler(p.name, p.client)
+			}
+		}
+		p.handlersRun = true
+	}
+	handlers.RUnlock()
+
+}
+
+// activated returns if the plugin has already been activated.
+// This should only be called with the activation lock held
+func (p *Plugin) activated() bool {
+	return p.Manifest != nil
+}
+
 func (p *Plugin) activateWithLock() error {
 	c, err := NewClient(p.Addr, p.TLSConfig)
 	if err != nil {
@@ -138,24 +170,12 @@ func (p *Plugin) activateWithLock() error {
 	}
 
 	p.Manifest = m
-
-	handlers.RLock()
-	for _, iface := range m.Implements {
-		hdlrs, handled := handlers.extpointHandlers[iface]
-		if !handled {
-			continue
-		}
-		for _, handler := range hdlrs {
-			handler(p.name, p.client)
-		}
-	}
-	handlers.RUnlock()
 	return nil
 }
 
 func (p *Plugin) waitActive() error {
 	p.activateWait.L.Lock()
-	for !p.activated {
+	for !p.activated() {
 		p.activateWait.Wait()
 	}
 	p.activateWait.L.Unlock()
@@ -163,7 +183,7 @@ func (p *Plugin) waitActive() error {
 }
 
 func (p *Plugin) implements(kind string) bool {
-	if err := p.waitActive(); err != nil {
+	if p.Manifest == nil {
 		return false
 	}
 	for _, driver := range p.Manifest.Implements {
@@ -232,7 +252,7 @@ func Get(name, imp string) (*Plugin, error) {
 	if err != nil {
 		return nil, err
 	}
-	if pl.implements(imp) {
+	if err := pl.waitActive(); err == nil && pl.implements(imp) {
 		logrus.Debugf("%s implements: %s", name, imp)
 		return pl, nil
 	}
@@ -249,9 +269,17 @@ func Handle(iface string, fn func(string, *Client)) {
 
 	hdlrs = append(hdlrs, fn)
 	handlers.extpointHandlers[iface] = hdlrs
+
+	storage.Lock()
 	for _, p := range storage.plugins {
-		p.activated = false
+		p.activateWait.L.Lock()
+		if p.activated() && p.implements(iface) {
+			p.handlersRun = false
+		}
+		p.activateWait.L.Unlock()
 	}
+	storage.Unlock()
+
 	handlers.Unlock()
 }
 
@@ -292,7 +320,7 @@ func GetAll(imp string) ([]*Plugin, error) {
 			logrus.Error(pl.err)
 			continue
 		}
-		if pl.pl.implements(imp) {
+		if err := pl.pl.waitActive(); err == nil && pl.pl.implements(imp) {
 			out = append(out, pl.pl)
 		}
 	}

+ 2 - 2
plugin/store.go

@@ -58,7 +58,7 @@ func (ps *Store) GetV2Plugin(refOrID string) (*v2.Plugin, error) {
 func (ps *Store) validateName(name string) error {
 	for _, p := range ps.plugins {
 		if p.Name() == name {
-			return errors.Errorf("%v already exists", name)
+			return errors.Errorf("plugin %q already exists", name)
 		}
 	}
 	return nil
@@ -232,7 +232,7 @@ func (ps *Store) resolvePluginID(idOrName string) (string, error) {
 
 	ref, err := reference.ParseNamed(idOrName)
 	if err != nil {
-		return "", errors.Wrapf(err, "failed to parse %v", idOrName)
+		return "", errors.WithStack(ErrNotFound(idOrName))
 	}
 	if _, ok := ref.(reference.Canonical); ok {
 		logrus.Warnf("canonical references cannot be resolved: %v", ref.String())

+ 19 - 9
volume/store/db.go

@@ -3,17 +3,13 @@ package store
 import (
 	"encoding/json"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/boltdb/bolt"
 	"github.com/pkg/errors"
 )
 
 var volumeBucketName = []byte("volumes")
 
-type dbEntry struct {
-	Key   []byte
-	Value []byte
-}
-
 type volumeMetadata struct {
 	Name    string
 	Driver  string
@@ -67,12 +63,26 @@ func removeMeta(tx *bolt.Tx, name string) error {
 	return errors.Wrap(b.Delete([]byte(name)), "error removing volume metadata")
 }
 
-func listEntries(tx *bolt.Tx) []*dbEntry {
-	var entries []*dbEntry
+// listMeta is used during restore to get the list of volume metadata
+// from the on-disk database.
+// Any errors that occur are only logged.
+func listMeta(tx *bolt.Tx) []volumeMetadata {
+	var ls []volumeMetadata
 	b := tx.Bucket(volumeBucketName)
 	b.ForEach(func(k, v []byte) error {
-		entries = append(entries, &dbEntry{k, v})
+		if len(v) == 0 {
+			// don't try to unmarshal an empty value
+			return nil
+		}
+
+		var m volumeMetadata
+		if err := json.Unmarshal(v, &m); err != nil {
+			// Just log the error
+			logrus.Errorf("Error while reading volume metadata for volume %q: %v", string(k), err)
+			return nil
+		}
+		ls = append(ls, m)
 		return nil
 	})
-	return entries
+	return ls
 }

+ 14 - 22
volume/store/restore.go

@@ -1,7 +1,6 @@
 package store
 
 import (
-	"encoding/json"
 	"sync"
 
 	"github.com/Sirupsen/logrus"
@@ -17,45 +16,38 @@ import (
 // It does not probe the available drivers to find anything that may have been added
 // out of band.
 func (s *VolumeStore) restore() {
-	var entries []*dbEntry
+	var ls []volumeMetadata
 	s.db.View(func(tx *bolt.Tx) error {
-		entries = listEntries(tx)
+		ls = listMeta(tx)
 		return nil
 	})
 
-	chRemove := make(chan []byte, len(entries))
+	chRemove := make(chan *volumeMetadata, len(ls))
 	var wg sync.WaitGroup
-	for _, entry := range entries {
+	for _, meta := range ls {
 		wg.Add(1)
 		// this is potentially a very slow operation, so do it in a goroutine
-		go func(entry *dbEntry) {
+		go func(meta volumeMetadata) {
 			defer wg.Done()
-			var meta volumeMetadata
-			if len(entry.Value) != 0 {
-				if err := json.Unmarshal(entry.Value, &meta); err != nil {
-					logrus.Errorf("Error while reading volume metadata for volume %q: %v", string(entry.Key), err)
-					// don't return here, we can try with `getVolume` below
-				}
-			}
 
 			var v volume.Volume
 			var err error
 			if meta.Driver != "" {
-				v, err = lookupVolume(meta.Driver, string(entry.Key))
+				v, err = lookupVolume(meta.Driver, meta.Name)
 				if err != nil && err != errNoSuchVolume {
-					logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", string(entry.Key)).Warn("Error restoring volume")
+					logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", meta.Name).Warn("Error restoring volume")
 					return
 				}
 				if v == nil {
 					// doesn't exist in the driver, remove it from the db
-					chRemove <- entry.Key
+					chRemove <- &meta
 					return
 				}
 			} else {
-				v, err = s.getVolume(string(entry.Key))
+				v, err = s.getVolume(meta.Name)
 				if err != nil {
 					if err == errNoSuchVolume {
-						chRemove <- entry.Key
+						chRemove <- &meta
 					}
 					return
 				}
@@ -75,15 +67,15 @@ func (s *VolumeStore) restore() {
 			s.labels[v.Name()] = meta.Labels
 			s.names[v.Name()] = v
 			s.globalLock.Unlock()
-		}(entry)
+		}(meta)
 	}
 
 	wg.Wait()
 	close(chRemove)
 	s.db.Update(func(tx *bolt.Tx) error {
-		for k := range chRemove {
-			if err := removeMeta(tx, string(k)); err != nil {
-				logrus.Warnf("Error removing stale entry from volume db: %v", err)
+		for meta := range chRemove {
+			if err := removeMeta(tx, meta.Name); err != nil {
+				logrus.WithField("volume", meta.Name).Warnf("Error removing stale entry from volume db: %v", err)
 			}
 		}
 		return nil