diff --git a/cli/command/container/create.go b/cli/command/container/create.go index a055121b27..69ebd8ce29 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -66,6 +66,10 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *createOptions, reportError(dockerCli.Err(), "create", err.Error(), true) return cli.StatusError{StatusCode: 125} } + if err = validateAPIVersion(containerConfig, dockerCli.Client().ClientVersion()); err != nil { + reportError(dockerCli.Err(), "create", err.Error(), true) + return cli.StatusError{StatusCode: 125} + } response, err := createContainer(context.Background(), dockerCli, containerConfig, opts) if err != nil { return err diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index 7cd9ce998c..bce5c231e0 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -16,6 +16,7 @@ import ( "github.com/docker/docker/api/types/container" networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/strslice" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/pkg/signal" "github.com/docker/go-connections/nat" "github.com/pkg/errors" @@ -869,3 +870,12 @@ func validateAttach(val string) (string, error) { } return val, errors.Errorf("valid streams are STDIN, STDOUT and STDERR") } + +func validateAPIVersion(c *containerConfig, serverAPIVersion string) error { + for _, m := range c.HostConfig.Mounts { + if m.BindOptions != nil && m.BindOptions.NonRecursive && versions.LessThan(serverAPIVersion, "1.40") { + return errors.Errorf("bind-nonrecursive requires API v1.40 or later") + } + } + return nil +} diff --git a/cli/command/container/run.go b/cli/command/container/run.go index 7013a7167e..e3f36e0af7 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -84,6 +84,10 @@ func runRun(dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copt reportError(dockerCli.Err(), "run", err.Error(), true) return cli.StatusError{StatusCode: 125} } + if err = validateAPIVersion(containerConfig, dockerCli.Client().ClientVersion()); err != nil { + reportError(dockerCli.Err(), "run", err.Error(), true) + return cli.StatusError{StatusCode: 125} + } return runContainer(dockerCli, ropts, copts, containerConfig) } diff --git a/cli/command/service/create.go b/cli/command/service/create.go index ec74eb43c3..c045682e03 100644 --- a/cli/command/service/create.go +++ b/cli/command/service/create.go @@ -79,6 +79,10 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *serviceOptions return err } + if err = validateAPIVersion(service, dockerCli.Client().ClientVersion()); err != nil { + return err + } + specifiedSecrets := opts.secrets.Value() if len(specifiedSecrets) > 0 { // parse and validate secrets diff --git a/cli/command/service/opts.go b/cli/command/service/opts.go index 834b537048..2478ca8e89 100644 --- a/cli/command/service/opts.go +++ b/cli/command/service/opts.go @@ -12,6 +12,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/client" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/api/defaults" @@ -909,3 +910,12 @@ const ( flagConfigRemove = "config-rm" flagIsolation = "isolation" ) + +func validateAPIVersion(c swarm.ServiceSpec, serverAPIVersion string) error { + for _, m := range c.TaskTemplate.ContainerSpec.Mounts { + if m.BindOptions != nil && m.BindOptions.NonRecursive && versions.LessThan(serverAPIVersion, "1.40") { + return errors.Errorf("bind-nonrecursive requires API v1.40 or later") + } + } + return nil +} diff --git a/docs/reference/commandline/service_create.md b/docs/reference/commandline/service_create.md index 0f80384018..1840b767da 100644 --- a/docs/reference/commandline/service_create.md +++ b/docs/reference/commandline/service_create.md @@ -363,16 +363,34 @@ volumes in a service:

The Engine mounts binds and volumes read-write unless readonly option - is given when mounting the bind or volume. + is given when mounting the bind or volume. Note that setting readonly for a + bind-mount does not make its submounts readonly on the current Linux implementation. See also bind-nonrecursive.

+ + +#### Options for Bind Mounts + +The following options can only be used for bind mounts (`type=bind`): + + + + + + + + + + + - + + + +
OptionDescription
bind-propagation +

See the bind propagation section.

+
consistency

The consistency requirements for the mount; one of

    @@ -384,9 +402,24 @@ volumes in a service:

bind-nonrecursive + By default, submounts are recursively bind-mounted as well. However, this behavior can be confusing when a + bind mount is configured with readonly option, because submounts are not mounted as read-only. + Set bind-nonrecursive to disable recursive bind-mount.
+
+ A value is optional:
+
+
    +
  • true or 1: Disables recursive bind-mount.
  • +
  • false or 0: Default if you do not provide a value. Enables recursive bind-mount.
  • +
+
-#### Bind Propagation +##### Bind propagation Bind propagation refers to whether or not mounts created within a given bind mount or named volume can be propagated to replicas of that mount. Consider @@ -423,7 +456,7 @@ volumes do not support bind propagation. For more information about bind propagation, see the [Linux kernel documentation for shared subtree](https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt). -#### Options for Named Volumes +#### Options for named volumes The following options can only be used for named volumes (`type=volume`): @@ -459,9 +492,9 @@ The following options can only be used for named volumes (`type=volume`): the Engine copies those files and directories into the volume, allowing the host to access them. Set volume-nocopy to disable copying files from the container's filesystem to the volume and mount the empty volume.
- - A value is optional: - +
+ A value is optional:
+