diff --git a/container/container.go b/container/container.go index 2c407d19de..c9991629be 100644 --- a/container/container.go +++ b/container/container.go @@ -552,6 +552,7 @@ func (container *Container) AddMountPointWithVolume(destination string, vol volu Destination: destination, RW: rw, Volume: vol, + CopyData: volume.DefaultCopyMode, } } diff --git a/container/container_unix.go b/container/container_unix.go index 4f86a45581..754090f967 100644 --- a/container/container_unix.go +++ b/container/container_unix.go @@ -185,12 +185,8 @@ func (container *Container) CopyImagePathContent(v volume.Volume, destination st if err != nil { return err } - - if err := copyExistingContents(rootfs, path); err != nil { - return err - } - - return v.Unmount() + defer v.Unmount() + return copyExistingContents(rootfs, path) } // ShmResourcePath returns path to shm diff --git a/daemon/create_unix.go b/daemon/create_unix.go index 583ca13b76..aa9b31896c 100644 --- a/daemon/create_unix.go +++ b/daemon/create_unix.go @@ -63,8 +63,7 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain // this is only called when the container is created. func (daemon *Daemon) populateVolumes(c *container.Container) error { for _, mnt := range c.MountPoints { - // skip binds and volumes referenced by other containers (ie, volumes-from) - if mnt.Driver == "" || mnt.Volume == nil || len(daemon.volumes.Refs(mnt.Volume)) > 1 { + if !mnt.CopyData || mnt.Volume == nil { continue } diff --git a/daemon/volumes.go b/daemon/volumes.go index d1b220cd91..80e6cdfa42 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -131,6 +131,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo bind = setBindModeIfNull(bind) } } + if label.RelabelNeeded(bind.Mode) { if err := label.Relabel(bind.Source, container.MountLabel, label.IsShared(bind.Mode)); err != nil { return err diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index c40bd0bd16..34914c4ebd 100644 --- a/docs/reference/api/docker_remote_api.md +++ b/docs/reference/api/docker_remote_api.md @@ -126,6 +126,7 @@ This section lists each version from latest to oldest. Each listing includes a * `POST /containers/create` now takes `PidsLimit` field, if the kernel is >= 4.3 and the pids cgroup is supported. * `GET /containers/(id or name)/stats` now returns `pids_stats`, if the kernel is >= 4.3 and the pids cgroup is supported. * `POST /containers/create` now allows you to override usernamespaces remapping and use privileged options for the container. +* `POST /containers/create` now allows specifying `nocopy` for named volumes, which disables automatic copying from the container path to the volume. * `POST /auth` now returns an `IdentityToken` when supported by a registry. * `POST /containers/create` with both `Hostname` and `Domainname` fields specified will result in the container's hostname being set to `Hostname`, rather than `Hostname.Domainname`. diff --git a/docs/reference/commandline/create.md b/docs/reference/commandline/create.md index 6128411c5f..70c0e4c35a 100644 --- a/docs/reference/commandline/create.md +++ b/docs/reference/commandline/create.md @@ -90,10 +90,10 @@ Creates a new container. --uts="" UTS namespace to use -v, --volume=[host-src:]container-dest[:] Bind mount a volume. The comma-delimited - `options` are [rw|ro], [z|Z], or - [[r]shared|[r]slave|[r]private]. The - 'host-src' is an absolute path or a name - value. + `options` are [rw|ro], [z|Z], + [[r]shared|[r]slave|[r]private], and + [nocopy]. The 'host-src' is an absolute path + or a name value. --volume-driver="" Container's volume driver --volumes-from=[] Mount volumes from the specified container(s) -w, --workdir="" Working directory inside the container diff --git a/docs/reference/commandline/run.md b/docs/reference/commandline/run.md index a3ef21f79b..97553a67dc 100644 --- a/docs/reference/commandline/run.md +++ b/docs/reference/commandline/run.md @@ -92,10 +92,10 @@ parent = "smn_cli" --uts="" UTS namespace to use -v, --volume=[host-src:]container-dest[:] Bind mount a volume. The comma-delimited - `options` are [rw|ro], [z|Z], or - [[r]shared|[r]slave|[r]private]. The - 'host-src' is an absolute path or a name - value. + `options` are [rw|ro], [z|Z], + [[r]shared|[r]slave|[r]private], and + [nocopy]. The 'host-src' is an absolute path + or a name value. --volume-driver="" Container's volume driver --volumes-from=[] Mount volumes from the specified container(s) -w, --workdir="" Working directory inside the container diff --git a/docs/reference/run.md b/docs/reference/run.md index 65271a285f..7edcd7e16a 100644 --- a/docs/reference/run.md +++ b/docs/reference/run.md @@ -1400,13 +1400,18 @@ The example below mounts an empty tmpfs into the container with the `rw`, ### VOLUME (shared filesystems) -v, --volume=[host-src:]container-dest[:]: Bind mount a volume. - The comma-delimited `options` are [rw|ro], [z|Z], or - [[r]shared|[r]slave|[r]private]. The 'host-src' is an absolute path or a - name value. + The comma-delimited `options` are [rw|ro], [z|Z], + [[r]shared|[r]slave|[r]private], and [nocopy]. + The 'host-src' is an absolute path or a name value. If neither 'rw' or 'ro' is specified then the volume is mounted in read-write mode. + The `nocopy` modes is used to disable automatic copying requested volume + path in the container to the volume storage location. + For named volumes, `copy` is the default mode. Copy modes are not supported + for bind-mounted volumes. + --volumes-from="": Mount all volumes from the given container(s) > **Note**: diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index b7c776f912..b6bb6cae96 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -4249,6 +4249,44 @@ func (s *DockerSuite) TestRunVolumeWithOneCharacter(c *check.C) { testRequires(c, DaemonIsLinux) out, _ := dockerCmd(c, "run", "-v", "/tmp/q:/foo", "busybox", "sh", "-c", "find /foo") - fmt.Printf("OUTPUT: %+v", out) c.Assert(strings.TrimSpace(out), checker.Equals, "/foo") } + +func (s *DockerSuite) TestRunVolumeCopyFlag(c *check.C) { + testRequires(c, DaemonIsLinux) // Windows does not support copying data from image to the volume + _, err := buildImage("volumecopy", + `FROM busybox + RUN mkdir /foo && echo hello > /foo/bar + CMD cat /foo/bar`, + true, + ) + c.Assert(err, checker.IsNil) + + dockerCmd(c, "volume", "create", "--name=test") + + // test with the nocopy flag + out, _, err := dockerCmdWithError("run", "-v", "test:/foo:nocopy", "volumecopy") + c.Assert(err, checker.NotNil, check.Commentf(out)) + // test default behavior which is to copy for non-binds + out, _ = dockerCmd(c, "run", "-v", "test:/foo", "volumecopy") + c.Assert(strings.TrimSpace(out), checker.Equals, "hello") + // error out when the volume is already populated + out, _, err = dockerCmdWithError("run", "-v", "test:/foo:copy", "volumecopy") + c.Assert(err, checker.NotNil, check.Commentf(out)) + // do not error out when copy isn't explicitly set even though it's already populated + out, _ = dockerCmd(c, "run", "-v", "test:/foo", "volumecopy") + c.Assert(strings.TrimSpace(out), checker.Equals, "hello") + + // do not allow copy modes on volumes-from + dockerCmd(c, "run", "--name=test", "-v", "/foo", "busybox", "true") + out, _, err = dockerCmdWithError("run", "--volumes-from=test:copy", "busybox", "true") + c.Assert(err, checker.NotNil, check.Commentf(out)) + out, _, err = dockerCmdWithError("run", "--volumes-from=test:nocopy", "busybox", "true") + c.Assert(err, checker.NotNil, check.Commentf(out)) + + // do not allow copy modes on binds + out, _, err = dockerCmdWithError("run", "-v", "/foo:/bar:copy", "busybox", "true") + c.Assert(err, checker.NotNil, check.Commentf(out)) + out, _, err = dockerCmdWithError("run", "-v", "/foo:/bar:nocopy", "busybox", "true") + c.Assert(err, checker.NotNil, check.Commentf(out)) +} diff --git a/man/docker-create.1.md b/man/docker-create.1.md index 376f8308a5..f12edb5483 100644 --- a/man/docker-create.1.md +++ b/man/docker-create.1.md @@ -434,6 +434,10 @@ change propagation properties of source mount. Say `/` is source mount for > is `slave`, you may not be able to use the `shared` or `rshared` propagation on > a volume. + +To disable automatic copying of data from the container path to the volume, use +the `nocopy` flag. The `nocopy` flag can be set on bind mounts and named volumes. + **--volume-driver**="" Container's volume driver. This driver creates volumes specified either from a Dockerfile's `VOLUME` instruction or from the `docker run -v` flag. diff --git a/man/docker-run.1.md b/man/docker-run.1.md index d63a0b6452..57808f33a0 100644 --- a/man/docker-run.1.md +++ b/man/docker-run.1.md @@ -531,6 +531,7 @@ any options, the systems uses the following options: * [rw|ro] * [z|Z] * [`[r]shared`|`[r]slave`|`[r]private`] + * [nocopy] The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR` can be an absolute path or a `name` value. A `name` value must start with an @@ -603,6 +604,9 @@ change propagation properties of source mount. Say `/` is source mount for > is `slave`, you may not be able to use the `shared` or `rshared` propagation on > a volume. +To disable automatic copying of data from the container path to the volume, use +the `nocopy` flag. The `nocopy` flag can be set on bind mounts and named volumes. + **--volume-driver**="" Container's volume driver. This driver creates volumes specified either from a Dockerfile's `VOLUME` instruction or from the `docker run -v` flag. diff --git a/volume/volume.go b/volume/volume.go index c3f23bbedd..1abd1ccb35 100644 --- a/volume/volume.go +++ b/volume/volume.go @@ -60,6 +60,11 @@ type MountPoint struct { // Note Propagation is not used on Windows Propagation string // Mount propagation string Named bool // specifies if the mountpoint was specified by name + + // Specifies if data should be copied from the container before the first mount + // Use a pointer here so we can tell if the user set this value explicitly + // This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated + CopyData bool `json:"-"` } // Setup sets up a mount point by either mounting the volume if it is @@ -115,6 +120,10 @@ func ParseVolumesFrom(spec string) (string, string, error) { if HasPropagation(mode) { return "", "", errInvalidMode(mode) } + // Do not allow copy modes on volumes-from + if _, isSet := getCopyMode(mode); isSet { + return "", "", errInvalidMode(mode) + } } return id, mode, nil } diff --git a/volume/volume_copy.go b/volume/volume_copy.go new file mode 100644 index 0000000000..067537fb7d --- /dev/null +++ b/volume/volume_copy.go @@ -0,0 +1,28 @@ +package volume + +import "strings" + +const ( + // DefaultCopyMode is the copy mode used by default for normal/named volumes + DefaultCopyMode = true +) + +// {=isEnabled} +var copyModes = map[string]bool{ + "nocopy": false, +} + +func copyModeExists(mode string) bool { + _, exists := copyModes[mode] + return exists +} + +// GetCopyMode gets the copy mode from the mode string for mounts +func getCopyMode(mode string) (bool, bool) { + for _, o := range strings.Split(mode, ",") { + if isEnabled, exists := copyModes[o]; exists { + return isEnabled, true + } + } + return DefaultCopyMode, false +} diff --git a/volume/volume_unix.go b/volume/volume_unix.go index acd0b1bcd0..2520d7c144 100644 --- a/volume/volume_unix.go +++ b/volume/volume_unix.go @@ -110,6 +110,13 @@ func ParseMountSpec(spec, volumeDriver string) (*MountPoint, error) { mp.Source = filepath.Clean(source) } + copyData, isSet := getCopyMode(mp.Mode) + // do not allow copy modes on binds + if len(name) == 0 && isSet { + return nil, errInvalidMode(mp.Mode) + } + + mp.CopyData = copyData mp.Name = name return mp, nil @@ -137,23 +144,25 @@ func ValidMountMode(mode string) bool { rwModeCount := 0 labelModeCount := 0 propagationModeCount := 0 + copyModeCount := 0 for _, o := range strings.Split(mode, ",") { - if rwModes[o] { + switch { + case rwModes[o]: rwModeCount++ - continue - } else if labelModes[o] { + case labelModes[o]: labelModeCount++ - continue - } else if propagationModes[o] { + case propagationModes[o]: propagationModeCount++ - continue + case copyModeExists(o): + copyModeCount++ + default: + return false } - return false } // Only one string for each mode is allowed. - if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 { + if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 { return false } return true