فهرست منبع

Merge pull request #28774 from vieux/1.13.0-rc3-cherrypicks

1.13.0-rc3 cherry-picks
Vincent Demeester 8 سال پیش
والد
کامیت
005a5428ee
84فایلهای تغییر یافته به همراه866 افزوده شده و 295 حذف شده
  1. 1 1
      CHANGELOG.md
  2. 1 1
      api/server/router/network/network_routes.go
  3. 3 0
      api/types/filters/parse.go
  4. 2 0
      builder/builder.go
  5. 31 5
      builder/dockerfile/dispatchers.go
  6. 19 0
      cli/cobra.go
  7. 1 1
      cli/command/network/list.go
  8. 1 1
      cli/command/plugin/create.go
  9. 1 1
      cli/command/plugin/enable.go
  10. 1 1
      cli/command/plugin/push.go
  11. 1 1
      cli/command/secret/create.go
  12. 2 2
      cli/command/secret/inspect.go
  13. 1 1
      cli/command/secret/ls.go
  14. 11 3
      cli/command/secret/remove.go
  15. 0 1
      cli/command/service/create.go
  16. 2 1
      cli/command/service/opts.go
  17. 1 0
      cli/command/service/update.go
  18. 26 5
      cli/command/stack/deploy.go
  19. 5 3
      cli/command/stack/deploy_bundlefile.go
  20. 1 1
      cli/command/stack/opts.go
  21. 2 2
      cli/command/swarm/opts.go
  22. 7 8
      cli/command/system/version.go
  23. 11 2
      client/image_list.go
  24. 48 0
      client/image_list_test.go
  25. 14 11
      cmd/docker/docker_test.go
  26. 17 0
      contrib/check-config.sh
  27. 212 13
      contrib/completion/bash/docker
  28. 1 1
      contrib/completion/zsh/_docker
  29. 10 0
      daemon/checkpoint.go
  30. 7 1
      daemon/cluster/cluster.go
  31. 1 0
      daemon/cluster/executor/container/container.go
  32. 4 4
      daemon/cluster/listen_addr.go
  33. 2 2
      daemon/container_operations.go
  34. 0 7
      daemon/daemon_unix.go
  35. 2 1
      daemon/names.go
  36. 1 0
      daemon/network.go
  37. 21 0
      daemon/workdir.go
  38. 3 3
      docs/api/v1.24.md
  39. 44 50
      docs/extend/config.md
  40. 0 2
      docs/extend/index.md
  41. 2 1
      docs/extend/legacy_plugins.md
  42. 0 18
      docs/extend/menu.md
  43. 2 1
      docs/extend/plugins_authorization.md
  44. 1 1
      docs/extend/plugins_network.md
  45. 2 3
      docs/reference/commandline/cli.md
  46. 0 1
      docs/reference/commandline/dockerd.md
  47. 9 2
      docs/reference/commandline/info.md
  48. 1 4
      docs/reference/commandline/network_connect.md
  49. 5 5
      docs/reference/commandline/network_ls.md
  50. 2 2
      docs/reference/commandline/plugin_create.md
  51. 3 2
      docs/reference/commandline/plugin_enable.md
  52. 1 1
      docs/reference/commandline/plugin_push.md
  53. 20 0
      docs/reference/commandline/run.md
  54. 1 1
      docs/reference/commandline/search.md
  55. 1 3
      docs/reference/commandline/secret_create.md
  56. 30 1
      docs/reference/commandline/service_create.md
  57. 1 0
      docs/reference/commandline/service_update.md
  58. 0 37
      docs/reference/commandline/stack_config.md
  59. 2 3
      docs/reference/commandline/stack_deploy.md
  60. 7 2
      docs/reference/commandline/stack_ls.md
  61. 1 2
      docs/reference/commandline/stack_ps.md
  62. 1 2
      docs/reference/commandline/stack_rm.md
  63. 7 7
      docs/reference/commandline/swarm_init.md
  64. 1 1
      docs/reference/commandline/swarm_join.md
  65. 15 15
      docs/reference/run.md
  66. 30 2
      integration-cli/docker_cli_build_test.go
  67. 3 2
      integration-cli/docker_cli_commit_test.go
  68. 1 1
      integration-cli/docker_cli_events_test.go
  69. 33 1
      integration-cli/docker_cli_plugins_test.go
  70. 1 1
      integration-cli/docker_cli_run_test.go
  71. 1 1
      integration-cli/docker_cli_run_unix_test.go
  72. 35 0
      integration-cli/docker_cli_swarm_test.go
  73. 2 2
      integration-cli/docker_cli_version_test.go
  74. 37 0
      man/docker-images.1.md
  75. 6 2
      man/docker-info.1.md
  76. 1 4
      man/docker-network-connect.1.md
  77. 46 19
      plugin/backend_linux.go
  78. 1 1
      plugin/manager.go
  79. 1 1
      plugin/manager_linux.go
  80. 21 2
      plugin/store/store.go
  81. 7 0
      runconfig/opts/opts.go
  82. 5 0
      runconfig/opts/opts_test.go
  83. 1 4
      utils/names.go
  84. 1 1
      volume/local/local.go

+ 1 - 1
CHANGELOG.md

@@ -92,7 +92,7 @@ be found.
 + Add `--format` on `docker stats` [#24987](https://github.com/docker/docker/pull/24987)
 + Make `docker node ps` default to `self` in swarm node [#25214](https://github.com/docker/docker/pull/25214)
 + Add `--group` in `docker service create` [#25317](https://github.com/docker/docker/pull/25317)
-+ Add `--no-trunc` to service/node/stack ps output [#25337(https://github.com/docker/docker/pull/25337)
++ Add `--no-trunc` to service/node/stack ps output [#25337](https://github.com/docker/docker/pull/25337)
 + Add Logs to `ContainerAttachOptions` so go clients can request to retrieve container logs as part of the attach process [#26718](https://github.com/docker/docker/pull/26718)
 + Allow client to talk to an older server [#27745](https://github.com/docker/docker/pull/27745)
 * Inform user client-side that a container removal is in progress [#26074](https://github.com/docker/docker/pull/26074)

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

@@ -172,10 +172,10 @@ func (n *networkRouter) buildNetworkResource(nw libnetwork.Network) *types.Netwo
 	r.Driver = nw.Type()
 	r.EnableIPv6 = info.IPv6Enabled()
 	r.Internal = info.Internal()
+	r.Attachable = info.Attachable()
 	r.Options = info.DriverOptions()
 	r.Containers = make(map[string]types.EndpointResource)
 	buildIpamResources(r, info)
-	r.Internal = info.Internal()
 	r.Labels = info.Labels()
 
 	peers := info.Peers()

+ 3 - 0
api/types/filters/parse.go

@@ -144,6 +144,9 @@ func (filters Args) Add(name, value string) {
 func (filters Args) Del(name, value string) {
 	if _, ok := filters.fields[name]; ok {
 		delete(filters.fields[name], value)
+		if len(filters.fields[name]) == 0 {
+			delete(filters.fields, name)
+		}
 	}
 }
 

+ 2 - 0
builder/builder.go

@@ -129,6 +129,8 @@ type Backend interface {
 	ContainerWait(containerID string, timeout time.Duration) (int, error)
 	// ContainerUpdateCmdOnBuild updates container.Path and container.Args
 	ContainerUpdateCmdOnBuild(containerID string, cmd []string) error
+	// ContainerCreateWorkdir creates the workdir (currently only used on Windows)
+	ContainerCreateWorkdir(containerID string) error
 
 	// ContainerCopy copies/extracts a source FileInfo to a destination path inside a container
 	// specified by a container object.

+ 31 - 5
builder/dockerfile/dispatchers.go

@@ -18,6 +18,7 @@ import (
 
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/api"
+	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/strslice"
 	"github.com/docker/docker/builder"
@@ -279,12 +280,37 @@ func workdir(b *Builder, args []string, attributes map[string]bool, original str
 		return err
 	}
 
-	// NOTE: You won't find the "mkdir" for the directory in here. Rather we
-	// just set the value in the image's runConfig.WorkingDir property
-	// and container.SetupWorkingDirectory() will create it automatically
-	// for us the next time the image is used to create a container.
+	// For performance reasons, we explicitly do a create/mkdir now
+	// This avoids having an unnecessary expensive mount/unmount calls
+	// (on Windows in particular) during each container create.
+	// Prior to 1.13, the mkdir was deferred and not executed at this step.
+	if b.disableCommit {
+		// Don't call back into the daemon if we're going through docker commit --change "WORKDIR /foo".
+		// We've already updated the runConfig and that's enough.
+		return nil
+	}
+	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}, true)
+	if err != nil {
+		return err
+	}
+	b.tmpContainers[container.ID] = struct{}{}
+	if err := b.docker.ContainerCreateWorkdir(container.ID); err != nil {
+		return err
+	}
 
-	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("WORKDIR %v", b.runConfig.WorkingDir))
+	return b.commit(container.ID, b.runConfig.Cmd, "WORKDIR "+b.runConfig.WorkingDir)
 }
 
 // RUN some command yo

+ 19 - 0
cli/cobra.go

@@ -2,6 +2,7 @@ package cli
 
 import (
 	"fmt"
+	"strings"
 
 	"github.com/spf13/cobra"
 )
@@ -17,6 +18,7 @@ func SetupRootCommand(rootCmd *cobra.Command) {
 	rootCmd.SetUsageTemplate(usageTemplate)
 	rootCmd.SetHelpTemplate(helpTemplate)
 	rootCmd.SetFlagErrorFunc(FlagErrorFunc)
+	rootCmd.SetHelpCommand(helpCommand)
 
 	rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
 	rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")
@@ -39,6 +41,23 @@ func FlagErrorFunc(cmd *cobra.Command, err error) error {
 	}
 }
 
+var helpCommand = &cobra.Command{
+	Use:               "help [command]",
+	Short:             "Help about the command",
+	PersistentPreRun:  func(cmd *cobra.Command, args []string) {},
+	PersistentPostRun: func(cmd *cobra.Command, args []string) {},
+	RunE: func(c *cobra.Command, args []string) error {
+		cmd, args, e := c.Root().Find(args)
+		if cmd == nil || e != nil || len(args) > 0 {
+			return fmt.Errorf("unknown help topic: %v", strings.Join(args, " "))
+		}
+
+		helpFunc := cmd.HelpFunc()
+		helpFunc(cmd, args)
+		return nil
+	},
+}
+
 func hasSubCommands(cmd *cobra.Command) bool {
 	return len(operationSubCommands(cmd)) > 0
 }

+ 1 - 1
cli/command/network/list.go

@@ -43,7 +43,7 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
 	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display network IDs")
 	flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output")
 	flags.StringVar(&opts.format, "format", "", "Pretty-print networks using a Go template")
-	flags.VarP(&opts.filter, "filter", "f", "Provide filter values (i.e. 'dangling=true')")
+	flags.VarP(&opts.filter, "filter", "f", "Provide filter values (e.g. 'driver=bridge')")
 
 	return cmd
 }

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

@@ -64,7 +64,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 	options := pluginCreateOptions{}
 
 	cmd := &cobra.Command{
-		Use:   "create [OPTIONS] reponame[:tag] PATH-TO-ROOTFS (rootfs + config.json)",
+		Use:   "create [OPTIONS] PLUGIN[:tag] PATH-TO-ROOTFS(rootfs + config.json)",
 		Short: "Create a plugin from a rootfs and config",
 		Args:  cli.RequiresMinArgs(2),
 		RunE: func(cmd *cobra.Command, args []string) error {

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

@@ -20,7 +20,7 @@ func newEnableCommand(dockerCli *command.DockerCli) *cobra.Command {
 	var opts enableOpts
 
 	cmd := &cobra.Command{
-		Use:   "enable PLUGIN",
+		Use:   "enable [OPTIONS] PLUGIN",
 		Short: "Enable a plugin",
 		Args:  cli.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {

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

@@ -14,7 +14,7 @@ import (
 
 func newPushCommand(dockerCli *command.DockerCli) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "push NAME[:TAG]",
+		Use:   "push PLUGIN[:TAG]",
 		Short: "Push a plugin to a registry",
 		Args:  cli.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {

+ 1 - 1
cli/command/secret/create.go

@@ -25,7 +25,7 @@ func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 	}
 
 	cmd := &cobra.Command{
-		Use:   "create [name]",
+		Use:   "create [OPTIONS] SECRET [SECRET...]",
 		Short: "Create a secret using stdin as content",
 		Args:  cli.RequiresMinArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {

+ 2 - 2
cli/command/secret/inspect.go

@@ -16,8 +16,8 @@ type inspectOptions struct {
 func newSecretInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
 	opts := inspectOptions{}
 	cmd := &cobra.Command{
-		Use:   "inspect SECRET [SECRET]",
-		Short: "Inspect a secret",
+		Use:   "inspect [OPTIONS] SECRET [SECRET...]",
+		Short: "Display detailed information on one or more secrets",
 		Args:  cli.RequiresMinArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			opts.names = args

+ 1 - 1
cli/command/secret/ls.go

@@ -21,7 +21,7 @@ func newSecretListCommand(dockerCli *command.DockerCli) *cobra.Command {
 	opts := listOptions{}
 
 	cmd := &cobra.Command{
-		Use:   "ls",
+		Use:   "ls [OPTIONS]",
 		Short: "List secrets",
 		Args:  cli.NoArgs,
 		RunE: func(cmd *cobra.Command, args []string) error {

+ 11 - 3
cli/command/secret/remove.go

@@ -2,6 +2,7 @@ package secret
 
 import (
 	"fmt"
+	"strings"
 
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
@@ -15,8 +16,8 @@ type removeOptions struct {
 
 func newSecretRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
 	return &cobra.Command{
-		Use:   "rm SECRET [SECRET]",
-		Short: "Remove a secret",
+		Use:   "rm SECRET [SECRET...]",
+		Short: "Remove one or more secrets",
 		Args:  cli.RequiresMinArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			opts := removeOptions{
@@ -36,13 +37,20 @@ func runSecretRemove(dockerCli *command.DockerCli, opts removeOptions) error {
 		return err
 	}
 
+	var errs []string
+
 	for _, id := range ids {
 		if err := client.SecretRemove(ctx, id); err != nil {
-			fmt.Fprintf(dockerCli.Out(), "WARN: %s\n", err)
+			errs = append(errs, err.Error())
+			continue
 		}
 
 		fmt.Fprintln(dockerCli.Out(), id)
 	}
 
+	if len(errs) > 0 {
+		return fmt.Errorf("%s", strings.Join(errs, "\n"))
+	}
+
 	return nil
 }

+ 0 - 1
cli/command/service/create.go

@@ -33,7 +33,6 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
 
 	flags.VarP(&opts.labels, flagLabel, "l", "Service labels")
 	flags.Var(&opts.containerLabels, flagContainerLabel, "Container labels")
-	flags.StringVar(&opts.hostname, flagHostname, "", "Container hostname")
 	flags.VarP(&opts.env, flagEnv, "e", "Set environment variables")
 	flags.Var(&opts.envFile, flagEnvFile, "Read in a file of environment variables")
 	flags.Var(&opts.mounts, flagMount, "Attach a filesystem mount to the service")

+ 2 - 1
cli/command/service/opts.go

@@ -182,7 +182,7 @@ func (o *SecretOpt) Set(value string) error {
 
 		value := parts[1]
 		switch key {
-		case "source":
+		case "source", "src":
 			spec.source = value
 		case "target":
 			tDir, _ := filepath.Split(value)
@@ -570,6 +570,7 @@ func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
 
 	flags.StringVarP(&opts.workdir, flagWorkdir, "w", "", "Working directory inside the container")
 	flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
+	flags.StringVar(&opts.hostname, flagHostname, "", "Container hostname")
 
 	flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs")
 	flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")

+ 1 - 0
cli/command/service/update.go

@@ -208,6 +208,7 @@ func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
 	updateEnvironment(flags, &cspec.Env)
 	updateString(flagWorkdir, &cspec.Dir)
 	updateString(flagUser, &cspec.User)
+	updateString(flagHostname, &cspec.Hostname)
 	if err := updateMounts(flags, &cspec.Mounts); err != nil {
 		return err
 	}

+ 26 - 5
cli/command/stack/deploy.go

@@ -1,6 +1,7 @@
 package stack
 
 import (
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -59,19 +60,36 @@ func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
 }
 
 func runDeploy(dockerCli *command.DockerCli, opts deployOptions) error {
+	ctx := context.Background()
+
 	switch {
 	case opts.bundlefile == "" && opts.composefile == "":
 		return fmt.Errorf("Please specify either a bundle file (with --bundle-file) or a Compose file (with --compose-file).")
 	case opts.bundlefile != "" && opts.composefile != "":
 		return fmt.Errorf("You cannot specify both a bundle file and a Compose file.")
 	case opts.bundlefile != "":
-		return deployBundle(dockerCli, opts)
+		return deployBundle(ctx, dockerCli, opts)
 	default:
-		return deployCompose(dockerCli, opts)
+		return deployCompose(ctx, dockerCli, opts)
 	}
 }
 
-func deployCompose(dockerCli *command.DockerCli, opts deployOptions) error {
+// checkDaemonIsSwarmManager does an Info API call to verify that the daemon is
+// a swarm manager. This is necessary because we must create networks before we
+// create services, but the API call for creating a network does not return a
+// proper status code when it can't create a network in the "global" scope.
+func checkDaemonIsSwarmManager(ctx context.Context, dockerCli *command.DockerCli) error {
+	info, err := dockerCli.Client().Info(ctx)
+	if err != nil {
+		return err
+	}
+	if !info.Swarm.ControlAvailable {
+		return errors.New("This node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again.")
+	}
+	return nil
+}
+
+func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deployOptions) error {
 	configDetails, err := getConfigDetails(opts)
 	if err != nil {
 		return err
@@ -99,7 +117,10 @@ func deployCompose(dockerCli *command.DockerCli, opts deployOptions) error {
 			propertyWarnings(deprecatedProperties))
 	}
 
-	ctx := context.Background()
+	if err := checkDaemonIsSwarmManager(ctx, dockerCli); err != nil {
+		return err
+	}
+
 	namespace := namespace{name: opts.namespace}
 
 	networks := convertNetworks(namespace, config.Networks)
@@ -326,7 +347,7 @@ func convertVolumeToMount(
 		source = stackVolume.External.Name
 	} else {
 		volumeOptions = &mount.VolumeOptions{
-			Labels: stackVolume.Labels,
+			Labels: getStackLabels(namespace.name, stackVolume.Labels),
 			NoCopy: isNoCopy(mode),
 		}
 

+ 5 - 3
cli/command/stack/deploy_bundlefile.go

@@ -8,12 +8,16 @@ import (
 	"github.com/docker/docker/cli/command"
 )
 
-func deployBundle(dockerCli *command.DockerCli, opts deployOptions) error {
+func deployBundle(ctx context.Context, dockerCli *command.DockerCli, opts deployOptions) error {
 	bundle, err := loadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
 	if err != nil {
 		return err
 	}
 
+	if err := checkDaemonIsSwarmManager(ctx, dockerCli); err != nil {
+		return err
+	}
+
 	namespace := namespace{name: opts.namespace}
 
 	networks := make(map[string]types.NetworkCreate)
@@ -71,8 +75,6 @@ func deployBundle(dockerCli *command.DockerCli, opts deployOptions) error {
 		services[internalName] = serviceSpec
 	}
 
-	ctx := context.Background()
-
 	if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil {
 		return err
 	}

+ 1 - 1
cli/command/stack/opts.go

@@ -10,7 +10,7 @@ import (
 )
 
 func addComposefileFlag(opt *string, flags *pflag.FlagSet) {
-	flags.StringVar(opt, "compose-file", "", "Path to a Compose file")
+	flags.StringVarP(opt, "compose-file", "c", "", "Path to a Compose file")
 }
 
 func addBundlefileFlag(opt *string, flags *pflag.FlagSet) {

+ 2 - 2
cli/command/swarm/opts.go

@@ -171,8 +171,8 @@ func parseExternalCA(caSpec string) (*swarm.ExternalCA, error) {
 
 func addSwarmFlags(flags *pflag.FlagSet, opts *swarmOptions) {
 	flags.Int64Var(&opts.taskHistoryLimit, flagTaskHistoryLimit, 5, "Task history retention limit")
-	flags.DurationVar(&opts.dispatcherHeartbeat, flagDispatcherHeartbeat, time.Duration(5*time.Second), "Dispatcher heartbeat period (ns|us|ms|s|m|h) (default 5s)")
-	flags.DurationVar(&opts.nodeCertExpiry, flagCertExpiry, time.Duration(90*24*time.Hour), "Validity period for node certificates (ns|us|ms|s|m|h) (default 2160h0m0s)")
+	flags.DurationVar(&opts.dispatcherHeartbeat, flagDispatcherHeartbeat, time.Duration(5*time.Second), "Dispatcher heartbeat period (ns|us|ms|s|m|h)")
+	flags.DurationVar(&opts.nodeCertExpiry, flagCertExpiry, time.Duration(90*24*time.Hour), "Validity period for node certificates (ns|us|ms|s|m|h)")
 	flags.Var(&opts.externalCA, flagExternalCA, "Specifications of one or more certificate signing endpoints")
 	flags.Uint64Var(&opts.maxSnapshots, flagMaxSnapshots, 0, "Number of additional Raft snapshots to retain")
 	flags.Uint64Var(&opts.snapshotInterval, flagSnapshotInterval, 10000, "Number of log entries between Raft snapshots")

+ 7 - 8
cli/command/system/version.go

@@ -24,14 +24,13 @@ var versionTemplate = `Client:
  OS/Arch:      {{.Client.Os}}/{{.Client.Arch}}{{if .ServerOK}}
 
 Server:
- Version:             {{.Server.Version}}
- API version:         {{.Server.APIVersion}}
- Minimum API version: {{.Server.MinAPIVersion}}
- Go version:          {{.Server.GoVersion}}
- Git commit:          {{.Server.GitCommit}}
- Built:               {{.Server.BuildTime}}
- OS/Arch:             {{.Server.Os}}/{{.Server.Arch}}
- Experimental:        {{.Server.Experimental}}{{end}}`
+ Version:      {{.Server.Version}}
+ API version:  {{.Server.APIVersion}} (minimum version {{.Server.MinAPIVersion}})
+ Go version:   {{.Server.GoVersion}}
+ Git commit:   {{.Server.GitCommit}}
+ Built:        {{.Server.BuildTime}}
+ OS/Arch:      {{.Server.Os}}/{{.Server.Arch}}
+ Experimental: {{.Server.Experimental}}{{end}}`
 
 type versionOptions struct {
 	format string

+ 11 - 2
client/image_list.go

@@ -6,6 +6,7 @@ import (
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
+	"github.com/docker/docker/api/types/versions"
 	"golang.org/x/net/context"
 )
 
@@ -14,8 +15,16 @@ func (cli *Client) ImageList(ctx context.Context, options types.ImageListOptions
 	var images []types.ImageSummary
 	query := url.Values{}
 
-	if options.Filters.Len() > 0 {
-		filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
+	optionFilters := options.Filters
+	referenceFilters := optionFilters.Get("reference")
+	if versions.LessThan(cli.version, "1.25") && len(referenceFilters) > 0 {
+		query.Set("filter", referenceFilters[0])
+		for _, filterValue := range referenceFilters {
+			optionFilters.Del("reference", filterValue)
+		}
+	}
+	if optionFilters.Len() > 0 {
+		filterJSON, err := filters.ToParamWithVersion(cli.version, optionFilters)
 		if err != nil {
 			return images, err
 		}

+ 48 - 0
client/image_list_test.go

@@ -109,3 +109,51 @@ func TestImageList(t *testing.T) {
 		}
 	}
 }
+
+func TestImageListApiBefore125(t *testing.T) {
+	expectedFilter := "image:tag"
+	client := &Client{
+		client: newMockClient(func(req *http.Request) (*http.Response, error) {
+			query := req.URL.Query()
+			actualFilter := query.Get("filter")
+			if actualFilter != expectedFilter {
+				return nil, fmt.Errorf("filter not set in URL query properly. Expected '%s', got %s", expectedFilter, actualFilter)
+			}
+			actualFilters := query.Get("filters")
+			if actualFilters != "" {
+				return nil, fmt.Errorf("filters should have not been present, were with value: %s", actualFilters)
+			}
+			content, err := json.Marshal([]types.ImageSummary{
+				{
+					ID: "image_id2",
+				},
+				{
+					ID: "image_id2",
+				},
+			})
+			if err != nil {
+				return nil, err
+			}
+			return &http.Response{
+				StatusCode: http.StatusOK,
+				Body:       ioutil.NopCloser(bytes.NewReader(content)),
+			}, nil
+		}),
+		version: "1.24",
+	}
+
+	filters := filters.NewArgs()
+	filters.Add("reference", "image:tag")
+
+	options := types.ImageListOptions{
+		Filters: filters,
+	}
+
+	images, err := client.ImageList(context.Background(), options)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(images) != 2 {
+		t.Fatalf("expected 2 images, got %v", images)
+	}
+}

+ 14 - 11
cmd/docker/docker_test.go

@@ -1,13 +1,14 @@
 package main
 
 import (
+	"io/ioutil"
 	"os"
 	"testing"
 
 	"github.com/Sirupsen/logrus"
-	"github.com/docker/docker/utils"
-
 	"github.com/docker/docker/cli/command"
+	"github.com/docker/docker/pkg/testutil/assert"
+	"github.com/docker/docker/utils"
 )
 
 func TestClientDebugEnabled(t *testing.T) {
@@ -16,14 +17,16 @@ func TestClientDebugEnabled(t *testing.T) {
 	cmd := newDockerCommand(&command.DockerCli{})
 	cmd.Flags().Set("debug", "true")
 
-	if err := cmd.PersistentPreRunE(cmd, []string{}); err != nil {
-		t.Fatalf("Unexpected error: %s", err.Error())
-	}
+	err := cmd.PersistentPreRunE(cmd, []string{})
+	assert.NilError(t, err)
+	assert.Equal(t, os.Getenv("DEBUG"), "1")
+	assert.Equal(t, logrus.GetLevel(), logrus.DebugLevel)
+}
 
-	if os.Getenv("DEBUG") != "1" {
-		t.Fatal("expected debug enabled, got false")
-	}
-	if logrus.GetLevel() != logrus.DebugLevel {
-		t.Fatalf("expected logrus debug level, got %v", logrus.GetLevel())
-	}
+func TestExitStatusForInvalidSubcommandWithHelpFlag(t *testing.T) {
+	discard := ioutil.Discard
+	cmd := newDockerCommand(command.NewDockerCli(os.Stdin, discard, discard))
+	cmd.SetArgs([]string{"help", "invalid"})
+	err := cmd.Execute()
+	assert.Error(t, err, "unknown help topic: invalid")
 }

+ 17 - 0
contrib/check-config.sh

@@ -222,6 +222,23 @@ echo 'Optional Features:'
 		echo "    $(wrap_color '(note that cgroup swap accounting is not enabled in your kernel config, you can enable it by setting boot option "swapaccount=1")' bold black)"
 	fi
 }
+{
+	if is_set LEGACY_VSYSCALL_NATIVE; then
+		echo -n "- "; wrap_good "CONFIG_LEGACY_VSYSCALL_NATIVE" 'enabled'
+	elif is_set LEGACY_VSYSCALL_EMULATE; then
+		echo -n "- "; wrap_good "CONFIG_LEGACY_VSYSCALL_EMULATE" 'enabled'
+	elif is_set LEGACY_VSYSCALL_NONE; then
+		echo -n "- "; wrap_bad "CONFIG_LEGACY_VSYSCALL_NONE" 'enabled'
+		echo "    $(wrap_color '(containers using eglibc <= 2.13 will not work. Switch to' bold black)"
+		echo "    $(wrap_color ' "CONFIG_VSYSCALL_[NATIVE|EMULATE]" or use "vsyscall=[native|emulate]"' bold black)"
+		echo "    $(wrap_color ' on kernel command line. Note that this will disable ASLR for the,' bold black)"
+		echo "    $(wrap_color ' VDSO which may assist in exploiting security vulnerabilities.)' bold black)"
+	# else Older kernels (prior to 3dc33bd30f3e, released in v4.40-rc1) do
+	#      not have these LEGACY_VSYSCALL options and are effectively
+	#      LEGACY_VSYSCALL_EMULATE. Even older kernels are presumably
+	#      effectively LEGACY_VSYSCALL_NATIVE.
+	fi
+}
 
 if [ "$kernelMajor" -lt 4 ] || [ "$kernelMajor" -eq 4 -a "$kernelMinor" -le 5 ]; then
 	check_flags MEMCG_KMEM

+ 212 - 13
contrib/completion/bash/docker

@@ -237,11 +237,13 @@ __docker_complete_volumes() {
 	COMPREPLY=( $(compgen -W "$(__docker_volumes "$@")" -- "$current") )
 }
 
-# __docker_plugins returns a list of all plugins of a given type.
+# __docker_plugins_bundled returns a list of all plugins of a given type.
 # The type has to be specified with the mandatory option `--type`.
 # Valid types are: Network, Volume, Authorization.
 # Completions may be added or removed with `--add` and `--remove`
-__docker_plugins() {
+# This function only deals with plugins that come bundled with Docker.
+# For plugins managed by `docker plugin`, see `__docker_plugins_installed`.
+__docker_plugins_bundled() {
 	local type add=() remove=()
 	while true ; do
 		case "$1" in
@@ -270,16 +272,39 @@ __docker_plugins() {
 	echo "${plugins[@]} ${add[@]}"
 }
 
-# __docker_complete_plugins applies completion of plugins based on the current
+# __docker_complete_plugins_bundled applies completion of plugins based on the current
 # value of `$cur` or the value of the optional first option `--cur`, if given.
 # The plugin type has to be specified with the next option `--type`.
-__docker_complete_plugins() {
+# This function only deals with plugins that come bundled with Docker.
+# For completion of plugins managed by `docker plugin`, see
+# `__docker_complete_plugins_installed`.
+__docker_complete_plugins_bundled() {
 	local current="$cur"
 	if [ "$1" = "--cur" ] ; then
 		current="$2"
 		shift 2
 	fi
-	COMPREPLY=( $(compgen -W "$(__docker_plugins "$@")" -- "$current") )
+	COMPREPLY=( $(compgen -W "$(__docker_plugins_bundled "$@")" -- "$current") )
+}
+
+# __docker_plugins_installed returns a list of all plugins that were installed with
+# the Docker plugin API.
+# For built-in pugins, see `__docker_plugins_bundled`.
+__docker_plugins_installed() {
+	__docker_q plugin ls | awk 'NR>1 {print $1}'
+}
+
+# __docker_complete_plugins_installed applies completion of plugins that were installed
+# with the Docker plugin API, based on the current value of `$cur` or the value of
+# the optional first option `--cur`, if given.
+# For completion of built-in pugins, see `__docker_complete_plugins_bundled`.
+__docker_complete_plugins_installed() {
+	local current="$cur"
+	if [ "$1" = "--cur" ] ; then
+		current="$2"
+		shift 2
+	fi
+	COMPREPLY=( $(compgen -W "$(__docker_plugins_installed "$@")" -- "$current") )
 }
 
 __docker_runtimes() {
@@ -1439,7 +1464,7 @@ _docker_container_run() {
 					__docker_complete_containers_all --cur "${cur#*:}"
 					;;
 				*)
-					COMPREPLY=( $( compgen -W "$(__docker_plugins --type Network) $(__docker_networks) container:" -- "$cur") )
+					COMPREPLY=( $( compgen -W "$(__docker_plugins_bundled --type Network) $(__docker_networks) container:" -- "$cur") )
 					if [ "${COMPREPLY[*]}" = "container:" ] ; then
 						__docker_nospace
 					fi
@@ -1486,7 +1511,7 @@ _docker_container_run() {
 			return
 			;;
 		--volume-driver)
-			__docker_complete_plugins --type Volume
+			__docker_complete_plugins_bundled --type Volume
 			return
 			;;
 		--volumes-from)
@@ -1742,7 +1767,7 @@ _docker_daemon() {
 
 	case "$prev" in
 		--authorization-plugin)
-			__docker_complete_plugins --type Authorization
+			__docker_complete_plugins_bundled --type Authorization
 			return
 			;;
 		--cluster-store)
@@ -2348,7 +2373,7 @@ _docker_network_create() {
 			;;
 		--driver|-d)
 			# remove drivers that allow one instance only, add drivers missing in `docker info`
-			__docker_complete_plugins --type Network --remove host --remove null --add macvlan
+			__docker_complete_plugins_bundled --type Network --remove host --remove null --add macvlan
 			return
 			;;
 		--label)
@@ -2399,7 +2424,7 @@ _docker_network_ls() {
 	local key=$(__docker_map_key_of_current_option '--filter|-f')
 	case "$key" in
 		driver)
-			__docker_complete_plugins --cur "${cur##*=}" --type Network --add macvlan
+			__docker_complete_plugins_bundled --cur "${cur##*=}" --type Network --add macvlan
 			return
 			;;
 		id)
@@ -2621,6 +2646,7 @@ _docker_service_update() {
 		--health-interval
 		--health-retries
 		--health-timeout
+		--hostname
 		--label -l
 		--limit-cpu
 		--limit-memory
@@ -2664,7 +2690,7 @@ _docker_service_update() {
 			--dns-search
 			--env-file
 			--group
-			--hostname
+			--host
 			--mode
 			--name
 			--port
@@ -2675,6 +2701,14 @@ _docker_service_update() {
 				_filedir
 				return
 				;;
+			--host)
+				case "$cur" in
+					*:)
+						__docker_complete_resolved_hostname
+						return
+						;;
+				esac
+				;;
 			--mode)
 				COMPREPLY=( $( compgen -W "global replicated" -- "$cur" ) )
 				return
@@ -2698,6 +2732,8 @@ _docker_service_update() {
 			--dns-search-rm
 			--group-add
 			--group-rm
+			--host-add
+			--host-rm
 			--image
 			--port-add
 			--port-rm
@@ -2712,6 +2748,14 @@ _docker_service_update() {
 				COMPREPLY=( $(compgen -g -- "$cur") )
 				return
 				;;
+			--host-add|--host-rm)
+				case "$cur" in
+					*:)
+						__docker_complete_resolved_hostname
+						return
+						;;
+				esac
+				;;
 			--image)
 				__docker_complete_image_repos_and_tags
 				return
@@ -3049,6 +3093,160 @@ _docker_pause() {
 	_docker_container_pause
 }
 
+_docker_plugin() {
+	local subcommands="
+		create
+		disable
+		enable
+		inspect
+		install
+		ls
+		push
+		rm
+		set
+	"
+	local aliases="
+		list
+		remove
+	"
+	__docker_subcommands "$subcommands $aliases" && return
+
+	case "$cur" in
+		-*)
+			COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
+			;;
+		*)
+			COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) )
+			;;
+	esac
+}
+
+_docker_plugin_create() {
+	case "$cur" in
+		-*)
+			COMPREPLY=( $( compgen -W "--compress --help" -- "$cur" ) )
+			;;
+		*)
+			local counter=$(__docker_pos_first_nonflag)
+			if [ $cword -eq $counter ]; then
+				# reponame
+				return
+			elif [ $cword -eq  $((counter + 1)) ]; then
+				_filedir -d
+			fi
+			;;
+	esac
+}
+
+_docker_plugin_disable() {
+	case "$cur" in
+		-*)
+			COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
+			;;
+		*)
+			__docker_complete_plugins_installed
+			;;
+	esac
+}
+
+_docker_plugin_enable() {
+	case "$prev" in
+		--timeout)
+			return
+			;;
+	esac
+
+	case "$cur" in
+		-*)
+			COMPREPLY=( $( compgen -W "--help --timeout" -- "$cur" ) )
+			;;
+		*)
+			__docker_complete_plugins_installed
+			;;
+	esac
+}
+
+_docker_plugin_inspect() {
+	case "$prev" in
+		--format|f)
+			return
+			;;
+	esac
+
+	case "$cur" in
+		-*)
+			COMPREPLY=( $( compgen -W "--format -f --help" -- "$cur" ) )
+			;;
+		*)
+			__docker_complete_plugins_installed
+			;;
+	esac
+}
+
+_docker_plugin_install() {
+	case "$cur" in
+		-*)
+			COMPREPLY=( $( compgen -W "--disable --grant-all-permissions--help" -- "$cur" ) )
+			;;
+	esac
+}
+
+_docker_plugin_list() {
+	_docker_plugin_ls
+}
+
+_docker_plugin_ls() {
+	case "$cur" in
+		-*)
+			COMPREPLY=( $( compgen -W "--help --no-trunc" -- "$cur" ) )
+			;;
+	esac
+}
+
+_docker_plugin_push() {
+	case "$cur" in
+		-*)
+			COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
+			;;
+		*)
+			local counter=$(__docker_pos_first_nonflag)
+			if [ $cword -eq $counter ]; then
+				__docker_complete_plugins_installed
+			fi
+			;;
+	esac
+}
+
+_docker_plugin_remove() {
+	_docker_plugin_rm
+}
+
+_docker_plugin_rm() {
+	case "$cur" in
+		-*)
+			COMPREPLY=( $( compgen -W "--force -f --help" -- "$cur" ) )
+			;;
+		*)
+			__docker_complete_plugins_installed
+			;;
+	esac
+}
+
+_docker_plugin_set() {
+	case "$cur" in
+		-*)
+			COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
+			;;
+		*)
+			local counter=$(__docker_pos_first_nonflag)
+			if [ $cword -eq $counter ]; then
+				__docker_complete_plugins_installed
+			fi
+			;;
+	esac
+}
+
+
 _docker_port() {
 	_docker_container_port
 }
@@ -3300,7 +3498,7 @@ _docker_version() {
 _docker_volume_create() {
 	case "$prev" in
 		--driver|-d)
-			__docker_complete_plugins --type Volume
+			__docker_complete_plugins_bundled --type Volume
 			return
 			;;
 		--label|--opt|-o)
@@ -3340,7 +3538,7 @@ _docker_volume_ls() {
 			return
 			;;
 		driver)
-			__docker_complete_plugins --cur "${cur##*=}" --type Volume
+			__docker_complete_plugins_bundled --cur "${cur##*=}" --type Volume
 			return
 			;;
 		name)
@@ -3440,6 +3638,7 @@ _docker() {
 		network
 		node
 		pause
+		plugin
 		port
 		ps
 		pull

+ 1 - 1
contrib/completion/zsh/_docker

@@ -1753,6 +1753,7 @@ __docker_service_subcommand() {
         "($help)--health-interval=[Time between running the check]:time: "
         "($help)--health-retries=[Consecutive failures needed to report unhealthy]:retries:(1 2 3 4 5)"
         "($help)--health-timeout=[Maximum time to allow one check to run]:time: "
+        "($help)--hostname=[Service container hostname]:hostname: " \
         "($help)*--label=[Service labels]:label: "
         "($help)--limit-cpu=[Limit CPUs]:value: "
         "($help)--limit-memory=[Limit Memory]:value: "
@@ -1792,7 +1793,6 @@ __docker_service_subcommand() {
                 "($help)*--dns-option=[Set DNS options]:DNS option: " \
                 "($help)*--dns-search=[Set custom DNS search domains]:DNS search: " \
                 "($help)*--env-file=[Read environment variables from a file]:environment file:_files" \
-                "($help)--hostname=[Service containers hostname]:hostname: " \
                 "($help)--mode=[Service Mode]:mode:(global replicated)" \
                 "($help)--name=[Service name]:name: " \
                 "($help)*--port=[Publish a port]:port: " \

+ 10 - 0
daemon/checkpoint.go

@@ -8,6 +8,12 @@ import (
 	"path/filepath"
 
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/utils"
+)
+
+var (
+	validCheckpointNameChars   = utils.RestrictedNameChars
+	validCheckpointNamePattern = utils.RestrictedNamePattern
 )
 
 // CheckpointCreate checkpoints the process running in a container with CRIU
@@ -28,6 +34,10 @@ func (daemon *Daemon) CheckpointCreate(name string, config types.CheckpointCreat
 		checkpointDir = container.CheckpointDir()
 	}
 
+	if !validCheckpointNamePattern.MatchString(config.CheckpointID) {
+		return fmt.Errorf("Invalid checkpoint ID (%s), only %s are allowed", config.CheckpointID, validCheckpointNameChars)
+	}
+
 	err = daemon.containerd.CreateCheckpoint(container.ID, config.CheckpointID, checkpointDir, config.Exit)
 	if err != nil {
 		return fmt.Errorf("Cannot checkpoint container %s: %s", name, err)

+ 7 - 1
daemon/cluster/cluster.go

@@ -1767,7 +1767,7 @@ func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.Control
 		apiNetwork, err := getNetwork(ctx, client, n.Target)
 		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())
+				err = fmt.Errorf("The network %s cannot be used with services. Only networks scoped to the swarm can be used, such as those created with the overlay driver.", ln.Name())
 				return apierrors.NewRequestForbiddenError(err)
 			}
 			return err
@@ -1855,6 +1855,12 @@ func validateAndSanitizeInitRequest(req *types.InitRequest) error {
 		return fmt.Errorf("invalid ListenAddr %q: %v", req.ListenAddr, err)
 	}
 
+	if req.Spec.Annotations.Name == "" {
+		req.Spec.Annotations.Name = "default"
+	} else if req.Spec.Annotations.Name != "default" {
+		return errors.New(`swarm spec must be named "default"`)
+	}
+
 	return nil
 }
 

+ 1 - 0
daemon/cluster/executor/container/container.go

@@ -571,6 +571,7 @@ func (c *containerConfig) networkCreateRequest(name string) (clustertypes.Networ
 		Options:        na.Network.DriverState.Options,
 		Labels:         na.Network.Spec.Annotations.Labels,
 		Internal:       na.Network.Spec.Internal,
+		Attachable:     na.Network.Spec.Attachable,
 		EnableIPv6:     na.Network.Spec.Ipv6Enabled,
 		CheckDuplicate: true,
 	}

+ 4 - 4
daemon/cluster/listen_addr.go

@@ -11,8 +11,8 @@ var (
 	errNoIP                    = errors.New("could not find the system's IP address")
 	errMustSpecifyListenAddr   = errors.New("must specify a listening address because the address to advertise is not recognized as a system address, and a system's IP address to use could not be uniquely identified")
 	errBadListenAddr           = errors.New("listen address must be an IP address or network interface (with optional port number)")
-	errBadAdvertiseAddr        = errors.New("advertise address must be an IP address or network interface (with optional port number)")
-	errBadDefaultAdvertiseAddr = errors.New("default advertise address must be an IP address or network interface (without a port number)")
+	errBadAdvertiseAddr        = errors.New("advertise address must be a non-zero IP address or network interface (with optional port number)")
+	errBadDefaultAdvertiseAddr = errors.New("default advertise address must be a non-zero IP address or network interface (without a port number)")
 )
 
 func resolveListenAddr(specifiedAddr string) (string, string, error) {
@@ -69,7 +69,7 @@ func (c *Cluster) resolveAdvertiseAddr(advertiseAddr, listenAddrPort string) (st
 		}
 
 		// If it's not an interface, it must be an IP (for now)
-		if net.ParseIP(advertiseHost) == nil {
+		if ip := net.ParseIP(advertiseHost); ip == nil || ip.IsUnspecified() {
 			return "", "", errBadAdvertiseAddr
 		}
 
@@ -89,7 +89,7 @@ func (c *Cluster) resolveAdvertiseAddr(advertiseAddr, listenAddrPort string) (st
 		}
 
 		// If it's not an interface, it must be an IP (for now)
-		if net.ParseIP(c.config.DefaultAdvertiseAddr) == nil {
+		if ip := net.ParseIP(c.config.DefaultAdvertiseAddr); ip == nil || ip.IsUnspecified() {
 			return "", "", errBadDefaultAdvertiseAddr
 		}
 

+ 2 - 2
daemon/container_operations.go

@@ -1028,7 +1028,7 @@ func (daemon *Daemon) ActivateContainerServiceBinding(containerName string) erro
 	}
 	sb := daemon.getNetworkSandbox(container)
 	if sb == nil {
-		return fmt.Errorf("network sandbox not exists for container %s", containerName)
+		return fmt.Errorf("network sandbox does not exist for container %s", containerName)
 	}
 	return sb.EnableService()
 }
@@ -1041,7 +1041,7 @@ func (daemon *Daemon) DeactivateContainerServiceBinding(containerName string) er
 	}
 	sb := daemon.getNetworkSandbox(container)
 	if sb == nil {
-		return fmt.Errorf("network sandbox not exists for container %s", containerName)
+		return fmt.Errorf("network sandbox does not exist for container %s", containerName)
 	}
 	return sb.DisableService()
 }

+ 0 - 7
daemon/daemon_unix.go

@@ -4,7 +4,6 @@ package daemon
 
 import (
 	"bytes"
-	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"net"
@@ -1281,12 +1280,6 @@ func (daemon *Daemon) setupSeccompProfile() error {
 			return fmt.Errorf("opening seccomp profile (%s) failed: %v", daemon.configStore.SeccompProfile, err)
 		}
 		daemon.seccompProfile = b
-		p := struct {
-			DefaultAction string `json:"defaultAction"`
-		}{}
-		if err := json.Unmarshal(daemon.seccompProfile, &p); err != nil {
-			return err
-		}
 	}
 	return nil
 }

+ 2 - 1
daemon/names.go

@@ -2,6 +2,7 @@ package daemon
 
 import (
 	"fmt"
+	"strings"
 
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/container"
@@ -58,7 +59,7 @@ func (daemon *Daemon) generateIDAndName(name string) (string, string, error) {
 }
 
 func (daemon *Daemon) reserveName(id, name string) (string, error) {
-	if !validContainerNamePattern.MatchString(name) {
+	if !validContainerNamePattern.MatchString(strings.TrimPrefix(name, "/")) {
 		return "", fmt.Errorf("Invalid container name (%s), only %s are allowed", name, validContainerNameChars)
 	}
 	if name[0] != '/' {

+ 1 - 0
daemon/network.go

@@ -269,6 +269,7 @@ func (daemon *Daemon) createNetwork(create types.NetworkCreateRequest, id string
 		libnetwork.NetworkOptionEnableIPv6(create.EnableIPv6),
 		libnetwork.NetworkOptionDriverOpts(create.Options),
 		libnetwork.NetworkOptionLabels(create.Labels),
+		libnetwork.NetworkOptionAttachable(create.Attachable),
 	}
 
 	if create.IPAM != nil {

+ 21 - 0
daemon/workdir.go

@@ -0,0 +1,21 @@
+package daemon
+
+// ContainerCreateWorkdir creates the working directory. This is solves the
+// issue arising from https://github.com/docker/docker/issues/27545,
+// which was initially fixed by https://github.com/docker/docker/pull/27884. But that fix
+// was too expensive in terms of performance on Windows. Instead,
+// https://github.com/docker/docker/pull/28514 introduces this new functionality
+// where the builder calls into the backend here to create the working directory.
+func (daemon *Daemon) ContainerCreateWorkdir(cID string) error {
+	container, err := daemon.GetContainer(cID)
+	if err != nil {
+		return err
+	}
+	err = daemon.Mount(container)
+	if err != nil {
+		return err
+	}
+	defer daemon.Unmount(container)
+	rootUID, rootGID := daemon.GetRemappedUIDGID()
+	return container.SetupWorkingDirectory(rootUID, rootGID)
+}

+ 3 - 3
docs/api/v1.24.md

@@ -4562,14 +4562,14 @@ image](#create-an-image) section for more details.
         "Placement": {},
         "Resources": {
           "Limits": {
-            "MemoryBytes": 104857600.0
+            "MemoryBytes": 104857600
           },
           "Reservations": {
           }
         },
         "RestartPolicy": {
           "Condition": "on-failure",
-          "Delay": 10000000000.0,
+          "Delay": 10000000000,
           "MaxAttempts": 10
         }
       },
@@ -4579,7 +4579,7 @@ image](#create-an-image) section for more details.
         }
       },
       "UpdateConfig": {
-        "Delay": 30000000000.0,
+        "Delay": 30000000000,
         "Parallelism": 2,
         "FailureAction": "pause"
       },

+ 44 - 50
docs/extend/config.md

@@ -1,7 +1,4 @@
 ---
-aliases: [
-"/engine/extend/"
-]
 title: "Plugin config"
 description: "How develop and use a plugin with the managed plugin system"
 keywords: "API, Usage, plugins, documentation, developer"
@@ -172,53 +169,50 @@ Config provides the base accessible fields for working with V0 plugin format
 
 *Example showing the 'tiborvass/no-remove' 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"
-       		}
-       	],
-
-       	"devices": [
-       		{
-       			"name": "device",
-       			"description": "a host device to mount",
-       			"path": "/dev/cpu_dma_latency"
-       		}
-       	]
+  "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"
+      }
+    ]
+  }
 }
-
 ```

+ 0 - 2
docs/extend/index.md

@@ -1,6 +1,4 @@
 ---
-aliases:
-- /engine/extend/
 description: Develop and use a plugin with the managed plugin system
 keywords: "API, Usage, plugins, documentation, developer"
 title: Managed plugin system

+ 2 - 1
docs/extend/legacy_plugins.md

@@ -1,5 +1,6 @@
 ---
-aliases: "/engine/extend/plugins/"
+redirect_from:
+- "/engine/extend/plugins/"
 title: "Use Docker Engine plugins"
 description: "How to add additional functionality to Docker with plugins extensions"
 keywords: "Examples, Usage, plugins, docker, documentation, user guide"

+ 0 - 18
docs/extend/menu.md

@@ -1,18 +0,0 @@
----
-title: "Implement plugins"
-description: "Develop plugins and use existing plugins for Docker Engine"
-keywords: ["extend, plugins, docker, documentation, developer"]
-type: "menu"
-identifier: "engine_extend"
----
-
-<!-- This file is maintained within the docker/docker Github
-     repository at https://github.com/docker/docker/. Make all
-     pull requests against that repo. If you see this file in
-     another repository, consider it read-only there, as it will
-     periodically be overwritten by the definitive file. Pull
-     requests which include edits to this file in other repositories
-     will be rejected.
--->
-
-<!--menu page not rendered-->

+ 2 - 1
docs/extend/plugins_authorization.md

@@ -2,7 +2,8 @@
 title: "Access authorization plugin"
 description: "How to create authorization plugins to manage access control to your Docker daemon."
 keywords: "security, authorization, authentication, docker, documentation, plugin, extend"
-aliases: ["/engine/extend/authorization/"]
+redirect_from:
+- "/engine/extend/authorization/"
 ---
 
 <!-- This file is maintained within the docker/docker Github

+ 1 - 1
docs/extend/plugins_network.md

@@ -61,7 +61,7 @@ referring to that network will be sent to the plugin,
 ## Write a network plugin
 
 Network plugins implement the [Docker plugin
-API](https://docs.docker.com/extend/plugin_api/) and the network plugin protocol
+API](plugin_api.md) and the network plugin protocol
 
 ## Network plugin protocol
 

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

@@ -26,7 +26,6 @@ Usage: docker [OPTIONS] COMMAND [ARG...]
 A self-sufficient runtime for containers.
 
 Options:
-
       --config string      Location of client config files (default "/root/.docker")
   -D, --debug              Enable debug mode
       --help               Print usage
@@ -72,8 +71,8 @@ by the `docker` command line:
   to the same URL as the registry.
 * `DOCKER_TMPDIR` Location for temporary Docker files.
 
-Because Docker is developed using 'Go', you can also use any environment
-variables used by the 'Go' runtime. In particular, you may find these useful:
+Because Docker is developed using Go, you can also use any environment
+variables used by the Go runtime. In particular, you may find these useful:
 
 * `HTTP_PROXY`
 * `HTTPS_PROXY`

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

@@ -22,7 +22,6 @@ Usage: dockerd [OPTIONS]
 A self-sufficient runtime for containers.
 
 Options:
-
       --add-runtime value                     Register an additional OCI compatible runtime (default [])
       --api-cors-header string                Set CORS headers in the Engine API
       --authorization-plugin value            Authorization plugins to load (default [])

+ 9 - 2
docs/reference/commandline/info.md

@@ -111,7 +111,8 @@ storage driver and a node that is part of a 2-node swarm:
      Goroutines: 123
      System Time: 2016-11-12T17:24:37.955404361-08:00
      EventsListeners: 0
-    Http Proxy: http://proxy.example.com:80/
+    Http Proxy: http://test:test@proxy.example.com:8080
+    Https Proxy: https://test:test@proxy.example.com:8080
     No Proxy: localhost,127.0.0.1,docker-registry.somecorporation.com
     Registry: https://index.docker.io/v1/
     WARNING: No swap limit support
@@ -121,6 +122,9 @@ storage driver and a node that is part of a 2-node swarm:
     Experimental: false
     Insecure Registries:
      127.0.0.0/8
+    Registry Mirrors:
+      http://192.168.1.2/
+      http://registry-mirror.example.com:5000/
     Live Restore Enabled: false
 
 The global `-D` option tells all `docker` commands to output debug information.
@@ -172,7 +176,7 @@ information about the devicemapper storage driver is shown:
     Docker Root Dir: /var/lib/docker
     Debug mode (client): false
     Debug mode (server): false
-    Username: xyz
+    Username: gordontheturtle
     Registry: https://index.docker.io/v1/
     Insecure registries:
      myinsecurehost:5000
@@ -214,4 +218,7 @@ Here is a sample output for a daemon running on Windows Server 2016:
     Registry: https://index.docker.io/v1/
     Insecure Registries:
      127.0.0.0/8
+    Registry Mirrors:
+      http://192.168.1.2/
+      http://registry-mirror.example.com:5000/
     Live Restore Enabled: false

+ 1 - 4
docs/reference/commandline/network_connect.md

@@ -61,11 +61,8 @@ being connected to.
 ```bash
 $ docker network connect --alias db --alias mysql multi-host-network container2
 ```
-
 You can pause, restart, and stop containers that are connected to a network.
-Paused containers remain connected and can be revealed by a `network inspect`.
-When the container is stopped, it does not appear on the network until you restart
-it.
+A container connects to its configured networks when it runs.
 
 If specified, the container's IP address(es) is reapplied when a stopped
 container is restarted. If the IP address is no longer available, the container

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

@@ -24,11 +24,11 @@ Aliases:
   ls, list
 
 Options:
-  -f, --filter value   Provide filter values (i.e. 'dangling=true') (default [])
-      --format string  Pretty-print networks using a Go template
-      --help           Print usage
-      --no-trunc       Do not truncate the output
-  -q, --quiet          Only display network IDs
+  -f, --filter filter   Provide filter values (e.g. 'driver=bridge')
+      --format string   Pretty-print networks using a Go template
+      --help            Print usage
+      --no-trunc        Do not truncate the output
+  -q, --quiet           Only display network IDs
 ```
 
 Lists all the networks the Engine `daemon` knows about. This includes the

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

@@ -16,7 +16,7 @@ keywords: "plugin, create"
 # plugin create
 
 ```markdown
-Usage:  docker plugin create [OPTIONS] reponame[:tag] PATH-TO-ROOTFS
+Usage:  docker plugin create [OPTIONS] PLUGIN[:tag] PATH-TO-ROOTFS(rootfs + config.json)
 
 Create a plugin from a rootfs and configuration
 
@@ -26,7 +26,7 @@ Options:
 ```
 
 Creates a plugin. Before creating the plugin, prepare the plugin's root filesystem as well as
-the config.json (https://github.com/docker/docker/blob/master/docs/extend/config.md)
+[the config.json](../../extend/config.md)
 
 
 The following example shows how to create a sample `plugin`.

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

@@ -16,12 +16,13 @@ keywords: "plugin, enable"
 # plugin enable
 
 ```markdown
-Usage:  docker plugin enable PLUGIN
+Usage:  docker plugin enable [OPTIONS] PLUGIN
 
 Enable a plugin
 
 Options:
-      --help   Print usage
+      --help          Print usage
+      --timeout int   HTTP client timeout (in seconds)
 ```
 
 Enables a plugin. The plugin must be installed before it can be enabled,

+ 1 - 1
docs/reference/commandline/plugin_push.md

@@ -14,7 +14,7 @@ keywords: "plugin, push"
 -->
 
 ```markdown
-Usage:  docker plugin push NAME[:TAG]
+Usage:  docker plugin push PLUGIN[:TAG]
 
 Push a plugin to a registry
 

+ 20 - 0
docs/reference/commandline/run.md

@@ -262,6 +262,26 @@ https://docs.docker.com/engine/installation/binaries/#/get-the-linux-binary)),
 you give the container the full access to create and manipulate the host's
 Docker daemon.
 
+On Windows, the paths must be specified using Windows-style semantics. 
+
+    PS C:\> docker run -v c:\foo:c:\dest microsoft/nanoserver cmd /s /c type c:\dest\somefile.txt
+    Contents of file
+	
+    PS C:\> docker run -v c:\foo:d: microsoft/nanoserver cmd /s /c type d:\somefile.txt
+    Contents of file
+
+The following examples will fail when using Windows-based containers, as the 
+destination of a volume or bind-mount inside the container must be one of: 
+a non-existing or empty directory; or a drive other than C:. Further, the source
+of a bind mount must be a local directory, not a file.
+
+    net use z: \\remotemachine\share
+    docker run -v z:\foo:c:\dest ...
+    docker run -v \\uncpath\to\directory:c:\dest ...
+    docker run -v c:\foo\somefile.txt:c:\dest ...
+    docker run -v c:\foo:c: ...
+    docker run -v c:\foo:c:\existing-directory-with-contents ...
+
 For in-depth information about volumes, refer to [manage data in containers](https://docs.docker.com/engine/tutorials/dockervolumes/)
 
 ### Add bin-mounts or volumes using the --mount flag

+ 1 - 1
docs/reference/commandline/search.md

@@ -128,7 +128,7 @@ and are automated builds:
 This example displays images with a name containing 'busybox', at least
 3 stars and are official builds:
 
-    $ docker search --filter "is-automated=true" --filter "stars=3" busybox
+    $ docker search --filter "is-official=true" --filter "stars=3" busybox
     NAME                 DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
     progrium/busybox                                                     50                   [OK]
     radial/busyboxplus   Full-chain, Internet enabled, busybox made...   8                    [OK]

+ 1 - 3
docs/reference/commandline/secret_create.md

@@ -16,7 +16,7 @@ keywords: ["secret, create"]
 # secret create
 
 ```Markdown
-Usage:  docker secret create [NAME]
+Usage:  docker secret create [OPTIONS] SECRET
 
 Create a secret using stdin as content
 Options:
@@ -76,5 +76,3 @@ $ docker secret inspect secret.json
 * [secret inspect](secret_inspect.md)
 * [secret ls](secret_ls.md)
 * [secret rm](secret_rm.md)
-
-<style>table tr > td:first-child { white-space: nowrap;}</style>

+ 30 - 1
docs/reference/commandline/service_create.md

@@ -137,7 +137,7 @@ Create a service specifying the secret, target, user/group ID and mode:
 ```bash
 $ docker service create --name redis \
     --secret source=ssh-key,target=ssh \
-    --secret source=app-key,target=app,uid=1000,gid=1001,mode=0400 \
+    --secret src=app-key,target=app,uid=1000,gid=1001,mode=0400 \
     redis:3.0.6
 4cdgfyky7ozwh3htjfw0d12qv
 ```
@@ -474,6 +474,35 @@ accessible at the target port on every node regardless if there is a task for
 the service running on the node. For more information refer to
 [Use swarm mode routing mesh](https://docs.docker.com/engine/swarm/ingress/).
 
+### Publish a port for TCP only or UCP only
+
+By default, when you publish a port, it is a TCP port. You can
+specifically publish a UDP port instead of or in addition to a TCP port. When
+you publish both TCP and UDP ports, Docker 1.12.2 and earlier require you to
+add the suffix `/tcp` for TCP ports. Otherwise it is optional.
+
+#### TCP only
+
+The following two commands are equivalent.
+
+```bash
+$ docker service create --name dns-cache -p 53:53 dns-cache
+
+$ docker service create --name dns-cache -p 53:53/tcp dns-cache
+```
+
+#### TCP and UDP
+
+```bash
+$ docker service create --name dns-cache -p 53:53/tcp -p 53:53/udp dns-cache
+```
+
+#### UDP only
+
+```bash
+$ docker service create --name dns-cache -p 53:53/udp dns-cache
+```
+
 ### Create services using templates
 
 You can use templates for some flags of `service create`, using the syntax

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

@@ -45,6 +45,7 @@ Options:
       --help                             Print usage
       --host-add list                    Add or update a custom host-to-IP mapping (host:ip) (default [])
       --host-rm list                     Remove a custom host-to-IP mapping (host:ip) (default [])
+      --hostname string                  Container hostname
       --image string                     Service image tag
       --label-add list                   Add or update a service label (default [])
       --label-rm list                    Remove a label by its key (default [])

+ 0 - 37
docs/reference/commandline/stack_config.md

@@ -1,37 +0,0 @@
----
-title: "stack config"
-description: "The stack config command description and usage"
-keywords: "stack, config"
-advisory: "experimental"
----
-
-<!-- This file is maintained within the docker/docker Github
-     repository at https://github.com/docker/docker/. Make all
-     pull requests against that repo. If you see this file in
-     another repository, consider it read-only there, as it will
-     periodically be overwritten by the definitive file. Pull
-     requests which include edits to this file in other repositories
-     will be rejected.
--->
-
-# stack config (experimental)
-
-```markdown
-Usage:  docker stack config [OPTIONS] STACK
-
-Print the stack configuration
-
-Options:
-      --file   string   Path to a Distributed Application Bundle file (Default: STACK.dab)
-      --help            Print usage
-```
-
-Displays the configuration of a stack.
-
-## Related information
-
-* [stack deploy](stack_deploy.md)
-* [stack rm](stack_rm.md)
-* [stack services](stack_services.md)
-* [stack ps](stack_ps.md)
-* [stack ls](stack_ls.md)

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

@@ -2,7 +2,6 @@
 title: "stack deploy"
 description: "The stack deploy command description and usage"
 keywords: "stack, deploy, up"
-advisory: "experimental"
 ---
 
 <!-- This file is maintained within the docker/docker Github
@@ -14,7 +13,7 @@ advisory: "experimental"
      will be rejected.
 -->
 
-# stack deploy (experimental)
+# stack deploy
 
 ```markdown
 Usage:  docker stack deploy [OPTIONS] STACK
@@ -26,7 +25,7 @@ Aliases:
 
 Options:
       --bundle-file string    Path to a Distributed Application Bundle file
-      --compose-file string   Path to a Compose file
+  -c, --compose-file string   Path to a Compose file
       --help                  Print usage
       --with-registry-auth    Send registry authentication details to Swarm agents
 ```

+ 7 - 2
docs/reference/commandline/stack_ls.md

@@ -2,7 +2,6 @@
 title: "stack ls"
 description: "The stack ls command description and usage"
 keywords: "stack, ls"
-advisory: "experimental"
 ---
 
 <!-- This file is maintained within the docker/docker Github
@@ -14,12 +13,18 @@ advisory: "experimental"
      will be rejected.
 -->
 
-# stack ls (experimental)
+# stack ls
 
 ```markdown
 Usage:	docker stack ls
 
 List stacks
+
+Aliases:
+  ls, list
+
+Options:
+      --help   Print usage
 ```
 
 Lists the stacks.

+ 1 - 2
docs/reference/commandline/stack_ps.md

@@ -2,7 +2,6 @@
 title: "stack ps"
 description: "The stack ps command description and usage"
 keywords: "stack, ps"
-advisory: "experimental"
 ---
 
 <!-- This file is maintained within the docker/docker Github
@@ -14,7 +13,7 @@ advisory: "experimental"
      will be rejected.
 -->
 
-# stack ps (experimental)
+# stack ps
 
 ```markdown
 Usage:  docker stack ps [OPTIONS] STACK

+ 1 - 2
docs/reference/commandline/stack_rm.md

@@ -2,7 +2,6 @@
 title: "stack rm"
 description: "The stack rm command description and usage"
 keywords: "stack, rm, remove, down"
-advisory: "experimental"
 ---
 
 <!-- This file is maintained within the docker/docker Github
@@ -14,7 +13,7 @@ advisory: "experimental"
      will be rejected.
 -->
 
-# stack rm (experimental)
+# stack rm
 
 ```markdown
 Usage:  docker stack rm STACK

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

@@ -21,16 +21,16 @@ Usage:  docker swarm init [OPTIONS]
 Initialize a swarm
 
 Options:
-      --advertise-addr value            Advertised address (format: <ip|interface>[:port])
+      --advertise-addr string           Advertised address (format: <ip|interface>[:port])
       --autolock                        Enable manager autolocking (requiring an unlock key to start a stopped manager)
       --cert-expiry duration            Validity period for node certificates (ns|us|ms|s|m|h) (default 2160h0m0s)
       --dispatcher-heartbeat duration   Dispatcher heartbeat period (ns|us|ms|s|m|h) (default 5s)
-      --external-ca value               Specifications of one or more certificate signing endpoints
+      --external-ca external-ca         Specifications of one or more certificate signing endpoints
       --force-new-cluster               Force create a new cluster from current state
       --help                            Print usage
-      --listen-addr value               Listen address (format: <ip|interface>[:port])
-      --max-snapshots int               Number of additional Raft snapshots to retain
-      --snapshot-interval int           Number of log entries between Raft snapshots
+      --listen-addr node-addr           Listen address (format: <ip|interface>[:port]) (default 0.0.0.0:2377)
+      --max-snapshots uint              Number of additional Raft snapshots to retain
+      --snapshot-interval uint          Number of log entries between Raft snapshots (default 10000)
       --task-history-limit int          Task history retention limit (default 5)
 ```
 
@@ -76,7 +76,7 @@ This flag sets the validity period for node certificates.
 
 ### `--dispatcher-heartbeat`
 
-This flags sets the frequency with which nodes are told to use as a
+This flag sets the frequency with which nodes are told to use as a
 period to report their health.
 
 ### `--external-ca`
@@ -103,7 +103,7 @@ name, the default port 2377 will be used.
 
 This flag specifies the address that will be advertised to other members of the
 swarm for API access and overlay networking. If unspecified, Docker will check
-if the system has a single IP address, and use that IP address with with the
+if the system has a single IP address, and use that IP address with the
 listening port (see `--listen-addr`). If the system has multiple IP addresses,
 `--advertise-addr` must be specified so that the correct address is chosen for
 inter-manager communication and overlay networking.

+ 1 - 1
docs/reference/commandline/swarm_join.md

@@ -77,7 +77,7 @@ This flag is generally not necessary when joining an existing swarm.
 
 This flag specifies the address that will be advertised to other members of the
 swarm for API access. If unspecified, Docker will check if the system has a
-single IP address, and use that IP address with with the listening port (see
+single IP address, and use that IP address with the listening port (see
 `--listen-addr`). If the system has multiple IP addresses, `--advertise-addr`
 must be specified so that the correct address is chosen for inter-manager
 communication and overlay networking.

+ 15 - 15
docs/reference/run.md

@@ -123,8 +123,8 @@ and pass along signals. All of that is configurable:
     --sig-proxy=true: Proxy all received signals to the process (non-TTY mode only)
     -i              : Keep STDIN open even if not attached
 
-If you do not specify `-a` then Docker will [attach all standard
-streams]( https://github.com/docker/docker/blob/75a7f4d90cde0295bcfb7213004abce8d4779b75/commands.go#L1797).
+If you do not specify `-a` then Docker will [attach to both stdout and stderr
+]( https://github.com/docker/docker/blob/4118e0c9eebda2412a09ae66e90c34b85fae3275/runconfig/opts/parse.go#L267).
 You can specify to which of the three standard streams (`STDIN`, `STDOUT`,
 `STDERR`) you'd like to connect instead, as in:
 
@@ -345,13 +345,13 @@ Supported networks :
       <td class="no-wrap"><strong>container</strong>:&lt;name|id&gt;</td>
       <td>
         Use the network stack of another container, specified via
-        its *name* or *id*.
+        its <i>name</i> or <i>id</i>.
       </td>
     </tr>
     <tr>
       <td class="no-wrap"><strong>NETWORK</strong></td>
       <td>
-        Connects the container to a user created network (using `docker network create` command)
+        Connects the container to a user created network (using <code>docker network create</code> command)
       </td>
     </tr>
   </tbody>
@@ -624,15 +624,15 @@ but the volume for `/bar` will not. Volumes inherited via `--volumes-from` will
 with the same logic -- if the original volume was specified with a name it will **not** be removed.
 
 ## Security configuration
-    --security-opt="label=user:USER"   : Set the label user for the container
-    --security-opt="label=role:ROLE"   : Set the label role for the container
-    --security-opt="label=type:TYPE"   : Set the label type for the container
-    --security-opt="label=level:LEVEL" : Set the label level for the container
-    --security-opt="label=disable"     : Turn off label confinement for the container
-    --security-opt="apparmor=PROFILE"  : Set the apparmor profile to be applied to the container
-    --security-opt="no-new-privileges" : Disable container processes from gaining new privileges
-    --security-opt="seccomp=unconfined": Turn off seccomp confinement for the container
-    --security-opt="seccomp=profile.json: White listed syscalls seccomp Json file to be used as a seccomp filter
+    --security-opt="label=user:USER"     : Set the label user for the container
+    --security-opt="label=role:ROLE"     : Set the label role for the container
+    --security-opt="label=type:TYPE"     : Set the label type for the container
+    --security-opt="label=level:LEVEL"   : Set the label level for the container
+    --security-opt="label=disable"       : Turn off label confinement for the container
+    --security-opt="apparmor=PROFILE"    : Set the apparmor profile to be applied to the container
+    --security-opt="no-new-privileges"   : Disable container processes from gaining new privileges
+    --security-opt="seccomp=unconfined"  : Turn off seccomp confinement for the container
+    --security-opt="seccomp=profile.json": White listed syscalls seccomp Json file to be used as a seccomp filter
 
 
 You can override the default labeling scheme for each container by specifying
@@ -737,7 +737,7 @@ We have four ways to set user memory usage:
       <td class="no-wrap"><strong>memory=L&lt;inf, memory-swap=2*L</strong></td>
       <td>
         (specify memory without memory-swap) The container is not allowed to
-        use more than L bytes of memory, swap *plus* memory usage is double
+        use more than L bytes of memory, swap <i>plus</i> memory usage is double
         of that.
       </td>
     </tr>
@@ -747,7 +747,7 @@ We have four ways to set user memory usage:
       </td>
       <td>
         (specify both memory and memory-swap) The container is not allowed to
-        use more than L bytes of memory, swap *plus* memory usage is limited
+        use more than L bytes of memory, swap <i>plus</i> memory usage is limited
         by S.
       </td>
     </tr>

+ 30 - 2
integration-cli/docker_cli_build_test.go

@@ -1878,8 +1878,8 @@ func (s *DockerSuite) TestBuildWindowsAddCopyPathProcessing(c *check.C) {
 			WORKDIR /wc2
 			ADD wc2 c:/wc2
 			WORKDIR c:/
-			RUN sh -c "[ $(cat c:/wc1) = 'hellowc1' ]"
-			RUN sh -c "[ $(cat c:/wc2) = 'worldwc2' ]"
+			RUN sh -c "[ $(cat c:/wc1/wc1) = 'hellowc1' ]"
+			RUN sh -c "[ $(cat c:/wc2/wc2) = 'worldwc2' ]"
 
 			# Trailing slash on COPY/ADD, Windows-style path.
 			WORKDIR /wd1
@@ -7283,3 +7283,31 @@ func (s *DockerSuite) TestBuildWindowsUser(c *check.C) {
 	}
 	c.Assert(strings.ToLower(out), checker.Contains, "username=user")
 }
+
+// Verifies if COPY file . when WORKDIR is set to a non-existing directory,
+// the directory is created and the file is copied into the directory,
+// as opposed to the file being copied as a file with the name of the
+// directory. Fix for 27545 (found on Windows, but regression good for Linux too).
+// Note 27545 was reverted in 28505, but a new fix was added subsequently in 28514.
+func (s *DockerSuite) TestBuildCopyFileDotWithWorkdir(c *check.C) {
+	name := "testbuildcopyfiledotwithworkdir"
+	ctx, err := fakeContext(`FROM busybox 
+WORKDIR /foo 
+COPY file . 
+RUN ["cat", "/foo/file"] 
+`,
+		map[string]string{})
+
+	if err != nil {
+		c.Fatal(err)
+	}
+	defer ctx.Close()
+
+	if err := ctx.Add("file", "content"); err != nil {
+		c.Fatal(err)
+	}
+
+	if _, err = buildImageFromContext(name, ctx, true); err != nil {
+		c.Fatal(err)
+	}
+}

+ 3 - 2
integration-cli/docker_cli_commit_test.go

@@ -104,7 +104,6 @@ func (s *DockerSuite) TestCommitWithHostBindMount(c *check.C) {
 }
 
 func (s *DockerSuite) TestCommitChange(c *check.C) {
-	testRequires(c, DaemonIsLinux)
 	dockerCmd(c, "run", "--name", "test", "busybox", "true")
 
 	imageID, _ := dockerCmd(c, "commit",
@@ -122,12 +121,14 @@ func (s *DockerSuite) TestCommitChange(c *check.C) {
 		"test", "test-commit")
 	imageID = strings.TrimSpace(imageID)
 
+	prefix, slash := getPrefixAndSlashFromDaemonPlatform()
+	prefix = strings.ToUpper(prefix) // Force C: as that's how WORKDIR is normalised on Windows
 	expected := map[string]string{
 		"Config.ExposedPorts": "map[8080/tcp:{}]",
 		"Config.Env":          "[DEBUG=true test=1 PATH=/foo]",
 		"Config.Labels":       "map[foo:bar]",
 		"Config.Cmd":          "[/bin/sh]",
-		"Config.WorkingDir":   "/opt",
+		"Config.WorkingDir":   prefix + slash + "opt",
 		"Config.Entrypoint":   "[/bin/sh]",
 		"Config.User":         "testuser",
 		"Config.Volumes":      "map[/var/lib/docker:{}]",

+ 1 - 1
integration-cli/docker_cli_events_test.go

@@ -276,7 +276,7 @@ func (s *DockerSuite) TestEventsImageLoad(c *check.C) {
 }
 
 func (s *DockerSuite) TestEventsPluginOps(c *check.C) {
-	testRequires(c, DaemonIsLinux)
+	testRequires(c, DaemonIsLinux, Network)
 
 	since := daemonUnixTime(c)
 

+ 33 - 1
integration-cli/docker_cli_plugins_test.go

@@ -4,6 +4,7 @@ import (
 	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/go-check/check"
 
+	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -143,7 +144,7 @@ func (s *DockerSuite) TestPluginInstallArgs(c *check.C) {
 }
 
 func (s *DockerSuite) TestPluginInstallImage(c *check.C) {
-	testRequires(c, DaemonIsLinux)
+	testRequires(c, DaemonIsLinux, Network)
 	out, _, err := dockerCmdWithError("plugin", "install", "redis")
 	c.Assert(err, checker.NotNil)
 	c.Assert(out, checker.Contains, "content is not a plugin")
@@ -169,3 +170,34 @@ func (s *DockerSuite) TestPluginEnableDisableNegative(c *check.C) {
 	_, _, err = dockerCmdWithError("plugin", "remove", pName)
 	c.Assert(err, checker.IsNil)
 }
+
+func (s *DockerSuite) TestPluginCreate(c *check.C) {
+	testRequires(c, DaemonIsLinux, Network)
+
+	name := "foo/bar-driver"
+	temp, err := ioutil.TempDir("", "foo")
+	c.Assert(err, checker.IsNil)
+	defer os.RemoveAll(temp)
+
+	data := `{"description": "foo plugin"}`
+	err = ioutil.WriteFile(filepath.Join(temp, "config.json"), []byte(data), 0644)
+	c.Assert(err, checker.IsNil)
+
+	out, _, err := dockerCmdWithError("plugin", "create", name, temp)
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Contains, name)
+
+	out, _, err = dockerCmdWithError("plugin", "ls")
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Contains, name)
+
+	out, _, err = dockerCmdWithError("plugin", "create", name, temp)
+	c.Assert(err, checker.NotNil)
+	c.Assert(out, checker.Contains, "already exist")
+
+	out, _, err = dockerCmdWithError("plugin", "ls")
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Contains, name)
+	// The output will consists of one HEADER line and one line of foo/bar-driver
+	c.Assert(len(strings.Split(strings.TrimSpace(out), "\n")), checker.Equals, 2)
+}

+ 1 - 1
integration-cli/docker_cli_run_test.go

@@ -4570,7 +4570,7 @@ func (s *DockerSuite) TestRunServicingContainer(c *check.C) {
 }
 
 func (s *DockerSuite) TestRunDuplicateMount(c *check.C) {
-	testRequires(c, SameHostDaemon, DaemonIsLinux)
+	testRequires(c, SameHostDaemon, DaemonIsLinux, NotUserNamespace)
 
 	tmpFile, err := ioutil.TempFile("", "touch-me")
 	c.Assert(err, checker.IsNil)

+ 1 - 1
integration-cli/docker_cli_run_unix_test.go

@@ -1312,7 +1312,7 @@ func (s *DockerSuite) TestUserNoEffectiveCapabilitiesChroot(c *check.C) {
 }
 
 func (s *DockerSuite) TestUserNoEffectiveCapabilitiesMknod(c *check.C) {
-	testRequires(c, DaemonIsLinux)
+	testRequires(c, DaemonIsLinux, NotUserNamespace)
 	ensureSyscallTest(c)
 
 	// test that a root user has default capability CAP_MKNOD

+ 35 - 0
integration-cli/docker_cli_swarm_test.go

@@ -86,6 +86,13 @@ func (s *DockerSwarmSuite) TestSwarmInitIPv6(c *check.C) {
 	c.Assert(out, checker.Contains, "Swarm: active")
 }
 
+func (s *DockerSwarmSuite) TestSwarmInitUnspecifiedAdvertiseAddr(c *check.C) {
+	d := s.AddDaemon(c, false, false)
+	out, err := d.Cmd("swarm", "init", "--advertise-addr", "0.0.0.0")
+	c.Assert(err, checker.NotNil)
+	c.Assert(out, checker.Contains, "advertise address must be a non-zero IP address")
+}
+
 func (s *DockerSwarmSuite) TestSwarmIncompatibleDaemon(c *check.C) {
 	// init swarm mode and stop a daemon
 	d := s.AddDaemon(c, true, true)
@@ -418,6 +425,34 @@ func (s *DockerSwarmSuite) TestSwarmContainerAttachByNetworkId(c *check.C) {
 	waitAndAssert(c, 3*time.Second, checkNetwork, checker.Not(checker.Contains), "testnet")
 }
 
+func (s *DockerSwarmSuite) TestOverlayAttachable(c *check.C) {
+	d1 := s.AddDaemon(c, true, true)
+	d2 := s.AddDaemon(c, true, false)
+
+	out, err := d1.Cmd("network", "create", "-d", "overlay", "--attachable", "ovnet")
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+
+	// validate attachable
+	out, err = d1.Cmd("network", "inspect", "--format", "{{json .Attachable}}", "ovnet")
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+	c.Assert(strings.TrimSpace(out), checker.Equals, "true")
+
+	// validate containers can attache to this overlay network
+	out, err = d1.Cmd("run", "-d", "--network", "ovnet", "--name", "c1", "busybox", "top")
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+	out, err = d2.Cmd("run", "-d", "--network", "ovnet", "--name", "c2", "busybox", "top")
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+
+	// redo validation, there was a bug that the value of attachable changes after
+	// containers attach to the network
+	out, err = d1.Cmd("network", "inspect", "--format", "{{json .Attachable}}", "ovnet")
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+	c.Assert(strings.TrimSpace(out), checker.Equals, "true")
+	out, err = d2.Cmd("network", "inspect", "--format", "{{json .Attachable}}", "ovnet")
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+	c.Assert(strings.TrimSpace(out), checker.Equals, "true")
+}
+
 func (s *DockerSwarmSuite) TestSwarmRemoveInternalNetwork(c *check.C) {
 	d := s.AddDaemon(c, true, true)
 

+ 2 - 2
integration-cli/docker_cli_version_test.go

@@ -14,7 +14,7 @@ func (s *DockerSuite) TestVersionEnsureSucceeds(c *check.C) {
 		"Client:":       1,
 		"Server:":       1,
 		" Version:":     2,
-		" API version:": 3,
+		" API version:": 2,
 		" Go version:":  2,
 		" Git commit:":  2,
 		" OS/Arch:":     2,
@@ -40,7 +40,7 @@ func (s *DockerSuite) TestVersionPlatform_l(c *check.C) {
 
 func testVersionPlatform(c *check.C, platform string) {
 	out, _ := dockerCmd(c, "version")
-	expected := "OS/Arch:             " + platform
+	expected := "OS/Arch:      " + platform
 
 	split := strings.Split(out, "\n")
 	c.Assert(len(split) >= 14, checker.Equals, true, check.Commentf("got %d lines from version", len(split)))

+ 37 - 0
man/docker-images.1.md

@@ -103,6 +103,43 @@ which displayed different visualizations of the image data. Docker core removed
 this functionality in the 1.7 version. If you liked this functionality, you can
 still find it in the third-party dockviz tool: https://github.com/justone/dockviz.
 
+## Listing images in a desired format
+
+When using the --format option, the image command will either output the data 
+exactly as the template declares or, when using the `table` directive, will 
+include column headers as well. You can use special characters like `\t` for
+inserting tab spacing between columns. 
+
+The following example uses a template without headers and outputs the ID and 
+Repository entries separated by a colon for all images:
+
+    docker images --format "{{.ID}}: {{.Repository}}"
+    77af4d6b9913: <none>
+    b6fa739cedf5: committ
+    78a85c484bad: ipbabble
+    30557a29d5ab: docker
+    5ed6274db6ce: <none>
+    746b819f315e: postgres
+    746b819f315e: postgres
+    746b819f315e: postgres
+    746b819f315e: postgres
+
+To list all images with their repository and tag in a table format you can use:
+
+    docker images --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"
+    IMAGE ID            REPOSITORY                TAG
+    77af4d6b9913        <none>                    <none>
+    b6fa739cedf5        committ                   latest
+    78a85c484bad        ipbabble                  <none>
+    30557a29d5ab        docker                    latest
+    5ed6274db6ce        <none>                    <none>
+    746b819f315e        postgres                  9
+    746b819f315e        postgres                  9.3
+    746b819f315e        postgres                  9.3.5
+    746b819f315e        postgres                  latest
+
+Valid template placeholders are listed above.
+
 ## Listing only the shortened image IDs
 
 Listing just the shortened image IDs. This can be useful for some automated

+ 6 - 2
man/docker-info.1.md

@@ -103,7 +103,8 @@ storage driver:
      Goroutines: 123
      System Time: 2016-11-12T17:24:37.955404361-08:00
      EventsListeners: 0
-    Http Proxy: http://proxy.example.com:80/
+    Http Proxy: http://test:test@proxy.example.com:8080
+    Https Proxy: https://test:test@proxy.example.com:8080
     No Proxy: localhost,127.0.0.1,docker-registry.somecorporation.com
     Registry: https://index.docker.io/v1/
     WARNING: No swap limit support
@@ -113,6 +114,9 @@ storage driver:
     Experimental: false
     Insecure Registries:
      127.0.0.0/8
+    Registry Mirrors:
+      http://192.168.1.2/
+      http://registry-mirror.example.com:5000/
     Live Restore Enabled: false
 
 
@@ -166,7 +170,7 @@ information about the devicemapper storage driver is shown:
     Docker Root Dir: /var/lib/docker
     Debug mode (client): false
     Debug mode (server): false
-    Username: xyz
+    Username: gordontheturtle
     Registry: https://index.docker.io/v1/
     Insecure registries:
      myinsecurehost:5000

+ 1 - 4
man/docker-network-connect.1.md

@@ -24,11 +24,8 @@ You can also use the `docker run --net=<network-name>` option to start a contain
 ```bash
 $ docker run -itd --net=multi-host-network --ip 172.20.88.22 --ip6 2001:db8::8822 busybox
 ```
-
 You can pause, restart, and stop containers that are connected to a network.
-Paused containers remain connected and can be revealed by a `network inspect`.
-When the container is stopped, it does not appear on the network until you restart
-it.
+A container connects to its configured networks when it runs.
 
 If specified, the container's IP address(es) is reapplied when a stopped
 container is restarted. If the IP address is no longer available, the container

+ 46 - 19
plugin/backend_linux.go

@@ -19,6 +19,7 @@ import (
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/plugin/distribution"
 	"github.com/docker/docker/plugin/v2"
+	"github.com/docker/docker/reference"
 	"golang.org/x/net/context"
 )
 
@@ -60,6 +61,29 @@ func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) {
 	return p.PluginObj, nil
 }
 
+func (pm *Manager) pull(ref reference.Named, metaHeader http.Header, authConfig *types.AuthConfig, pluginID string) (types.PluginPrivileges, error) {
+	pd, err := distribution.Pull(ref, pm.registryService, metaHeader, authConfig)
+	if err != nil {
+		logrus.Debugf("error in distribution.Pull(): %v", err)
+		return nil, err
+	}
+
+	if err := distribution.WritePullData(pd, filepath.Join(pm.libRoot, pluginID), true); err != nil {
+		logrus.Debugf("error in distribution.WritePullData(): %v", err)
+		return nil, err
+	}
+
+	tag := distribution.GetTag(ref)
+	p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, pm.libRoot, tag)
+	if err := p.InitPlugin(); err != nil {
+		return nil, err
+	}
+	pm.pluginStore.Add(p)
+
+	pm.pluginEventLogger(pluginID, ref.String(), "pull")
+	return p.ComputePrivileges(), nil
+}
+
 // Pull pulls a plugin and computes the privileges required to install it.
 func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
 	ref, err := distribution.GetRef(name)
@@ -75,32 +99,21 @@ func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.A
 	}
 
 	pluginID := stringid.GenerateNonCryptoID()
-
-	if err := os.MkdirAll(filepath.Join(pm.libRoot, pluginID), 0755); err != nil {
+	pluginDir := filepath.Join(pm.libRoot, pluginID)
+	if err := os.MkdirAll(pluginDir, 0755); err != nil {
 		logrus.Debugf("error in MkdirAll: %v", err)
 		return nil, err
 	}
 
-	pd, err := distribution.Pull(ref, pm.registryService, metaHeader, authConfig)
+	priv, err := pm.pull(ref, metaHeader, authConfig, pluginID)
 	if err != nil {
-		logrus.Debugf("error in distribution.Pull(): %v", err)
-		return nil, err
-	}
-
-	if err := distribution.WritePullData(pd, filepath.Join(pm.libRoot, pluginID), true); err != nil {
-		logrus.Debugf("error in distribution.WritePullData(): %v", err)
+		if err := os.RemoveAll(pluginDir); err != nil {
+			logrus.Warnf("unable to remove %q from failed plugin pull: %v", pluginDir, err)
+		}
 		return nil, err
 	}
 
-	tag := distribution.GetTag(ref)
-	p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, pm.libRoot, tag)
-	if err := p.InitPlugin(); err != nil {
-		return nil, err
-	}
-	pm.pluginStore.Add(p)
-
-	pm.pluginEventLogger(pluginID, name, "pull")
-	return p.ComputePrivileges(), nil
+	return priv, nil
 }
 
 // List displays the list of plugins and associated metadata.
@@ -194,6 +207,18 @@ func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.Reader, opti
 		return err
 	}
 
+	// In case an error happens, remove the created directory.
+	if err := pm.createFromContext(ctx, pluginID, pluginDir, tarCtx, options); err != nil {
+		if err := os.RemoveAll(pluginDir); err != nil {
+			logrus.Warnf("unable to remove %q from failed plugin creation: %v", pluginDir, err)
+		}
+		return err
+	}
+
+	return nil
+}
+
+func (pm *Manager) createFromContext(ctx context.Context, pluginID, pluginDir string, tarCtx io.Reader, options *types.PluginCreateOptions) error {
 	if err := chrootarchive.Untar(tarCtx, pluginDir, nil); err != nil {
 		return err
 	}
@@ -211,7 +236,9 @@ func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.Reader, opti
 		return err
 	}
 
-	pm.pluginStore.Add(p)
+	if err := pm.pluginStore.Add(p); err != nil {
+		return err
+	}
 
 	pm.pluginEventLogger(p.GetID(), repoName, "create")
 

+ 1 - 1
plugin/manager.go

@@ -124,7 +124,7 @@ func (pm *Manager) init() error {
 				return
 			}
 
-			pm.pluginStore.Add(p)
+			pm.pluginStore.Update(p)
 			requiresManualRestore := !pm.liveRestore && p.IsEnabled()
 
 			if requiresManualRestore {

+ 1 - 1
plugin/manager_linux.go

@@ -26,6 +26,7 @@ func (pm *Manager) enable(p *v2.Plugin, force bool) error {
 	}
 	p.Lock()
 	p.Restart = true
+	p.ExitChan = make(chan bool)
 	p.Unlock()
 	if err := pm.containerdClient.Create(p.GetID(), "", "", specs.Spec(*spec), attachToLog(p.GetID())); err != nil {
 		return err
@@ -92,7 +93,6 @@ func (pm *Manager) Shutdown() {
 		}
 		if pm.containerdClient != nil && p.IsEnabled() {
 			p.Lock()
-			p.ExitChan = make(chan bool)
 			p.Restart = false
 			p.Unlock()
 			shutdownPlugin(p, pm.containerdClient)

+ 21 - 2
plugin/store/store.go

@@ -98,12 +98,31 @@ func (ps *Store) SetState(p *v2.Plugin, state bool) {
 }
 
 // Add adds a plugin to memory and plugindb.
-func (ps *Store) Add(p *v2.Plugin) {
+// An error will be returned if there is a collision.
+func (ps *Store) Add(p *v2.Plugin) error {
 	ps.Lock()
+	defer ps.Unlock()
+
+	if v, exist := ps.plugins[p.GetID()]; exist {
+		return fmt.Errorf("plugin %q has the same ID %s as %q", p.Name(), p.GetID(), v.Name())
+	}
+	if _, exist := ps.nameToID[p.Name()]; exist {
+		return fmt.Errorf("plugin %q already exists", p.Name())
+	}
+	ps.plugins[p.GetID()] = p
+	ps.nameToID[p.Name()] = p.GetID()
+	ps.updatePluginDB()
+	return nil
+}
+
+// Update updates a plugin to memory and plugindb.
+func (ps *Store) Update(p *v2.Plugin) {
+	ps.Lock()
+	defer ps.Unlock()
+
 	ps.plugins[p.GetID()] = p
 	ps.nameToID[p.Name()] = p.GetID()
 	ps.updatePluginDB()
-	ps.Unlock()
 }
 
 // Remove removes a plugin from memory and plugindb.

+ 7 - 0
runconfig/opts/opts.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"net"
 	"os"
+	"runtime"
 	"strings"
 
 	fopts "github.com/docker/docker/opts"
@@ -45,6 +46,12 @@ func ValidateEnv(val string) (string, error) {
 func doesEnvExist(name string) bool {
 	for _, entry := range os.Environ() {
 		parts := strings.SplitN(entry, "=", 2)
+		if runtime.GOOS == "windows" {
+			// Environment variable are case-insensitive on Windows. PaTh, path and PATH are equivalent.
+			if strings.EqualFold(parts[0], name) {
+				return true
+			}
+		}
 		if parts[0] == name {
 			return true
 		}

+ 5 - 0
runconfig/opts/opts_test.go

@@ -3,6 +3,7 @@ package opts
 import (
 	"fmt"
 	"os"
+	"runtime"
 	"strings"
 	"testing"
 )
@@ -50,6 +51,10 @@ func TestValidateEnv(t *testing.T) {
 		"  some space before": "  some space before",
 		"some space after  ":  "some space after  ",
 	}
+	// Environment variables are case in-sensitive on Windows
+	if runtime.GOOS == "windows" {
+		valids["PaTh"] = fmt.Sprintf("PaTh=%v", os.Getenv("PATH"))
+	}
 	for value, expected := range valids {
 		actual, err := ValidateEnv(value)
 		if err != nil {

+ 1 - 4
utils/names.go

@@ -6,7 +6,4 @@ import "regexp"
 const RestrictedNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.-]`
 
 // RestrictedNamePattern is a regular expression to validate names against the collection of restricted characters.
-var RestrictedNamePattern = regexp.MustCompile(`^/?` + RestrictedNameChars + `+$`)
-
-// RestrictedVolumeNamePattern is a regular expression to validate volume names against the collection of restricted characters.
-var RestrictedVolumeNamePattern = regexp.MustCompile(`^` + RestrictedNameChars + `+$`)
+var RestrictedNamePattern = regexp.MustCompile(`^` + RestrictedNameChars + `+$`)

+ 1 - 1
volume/local/local.go

@@ -36,7 +36,7 @@ var (
 	// volumeNameRegex ensures the name assigned for the volume is valid.
 	// This name is used to create the bind directory, so we need to avoid characters that
 	// would make the path to escape the root directory.
-	volumeNameRegex = utils.RestrictedVolumeNamePattern
+	volumeNameRegex = utils.RestrictedNamePattern
 )
 
 type validationError struct {