Signed-off-by: John Howard <jhoward@microsoft.com>

This fixes an issue which was reported internally. I think the cause
of it is more my mis-understanding of the VOLUME statement in a
dockerfile from some 3+ years ago. While it mostly worked, there was
a bug (obviously).

The issue was something such as the following was causing an error
starting a Windows container using this image:

```
FROM microsoft/windowsservercore
RUN mkdir c:\source
VOLUME c:/source
```

The error was `"docker: Error response from daemon: Unrecognised volume spec: file 'c:/source' cannot be mapped. Only directories can be mapped on this platform."`

It was root caused that on the Windows path, it was erroneously attempting
to parse the spec as a volume spec, not as a destination. In that scenario,
the parse looked at the spec as it if were local (host) rather than a
container path. Now in the case (such as above) that c:/source (c:\\source)
exists, and is a file rather than a directory, the above error would be seen.

The fix is to treat (as Linux does) the spec as a destination.

And manual verification: First WCOW:

```
PS E:\docker\build\volume> docker build -t wcowvolume .
Sending build context to Docker daemon  3.584kB
Step 1/4 : FROM microsoft/windowsservercore
 ---> e523d49b4b94
Step 2/4 : RUN mkdir c:\source
 ---> Using cache
 ---> ba84419e9d41
Step 3/4 : RUN copy c:\windows\system32\ntdll.dll c:\source
 ---> Using cache
 ---> 409ed22dd31a
Step 4/4 : VOLUME c:/source
 ---> Using cache
 ---> 8c56416c697f
Successfully built 8c56416c697f
Successfully tagged wcowvolume:latest
PS E:\docker\build\volume> dir c:\source

    Directory: C:\

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       11/20/2018   8:45 AM            125 source

PS E:\docker\build\volume> docker run volume
Microsoft Windows [Version 10.0.17763.1]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\>
PS E:\docker\build\volume>

```

And an LCOW equivalent:
```
PS E:\docker\build\volume\Linux> type Dockerfile
FROM alpine
RUN mkdir /source
RUN cp /bin/ls /source
VOLUME /source

PS E:\docker\build\volume\Linux> docker build -t lcowvolume .
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM alpine
 ---> 196d12cf6ab1
Step 2/4 : RUN mkdir /source
 ---> Using cache
 ---> 8f2f87ba69b7
Step 3/4 : RUN cp /bin/ls /source
 ---> Using cache
 ---> 9d2cf70ecb0e
Step 4/4 : VOLUME /source
 ---> Using cache
 ---> a0ee39598ee2
Successfully built a0ee39598ee2
Successfully tagged lcowvolume:latest
PS E:\docker\build\volume\Linux> docker run lcowvolume
PS E:\docker\build\volume\Linux>
```
This commit is contained in:
John Howard 2018-11-20 12:11:03 -08:00
Родитель 68cbc3712c
Коммит 41766e95c0
1 изменённых файлов: 60 добавлений и 50 удалений

Просмотреть файл

@ -3,18 +3,18 @@ package daemon // import "github.com/docker/docker/daemon"
import ( import (
"context" "context"
"fmt" "fmt"
"path"
"path/filepath"
"runtime" "runtime"
containertypes "github.com/docker/docker/api/types/container" containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/container" "github.com/docker/docker/container"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
volumemounts "github.com/docker/docker/volume/mounts"
volumeopts "github.com/docker/docker/volume/service/opts" volumeopts "github.com/docker/docker/volume/service/opts"
) )
// createContainerOSSpecificSettings performs host-OS specific container create functionality // createContainerOSSpecificSettings performs host-OS specific container create functionality
func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error { func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
if container.OS == runtime.GOOS { if container.OS == runtime.GOOS {
// Make sure the host config has the default daemon isolation if not specified by caller. // Make sure the host config has the default daemon isolation if not specified by caller.
if containertypes.Isolation.IsDefault(containertypes.Isolation(hostConfig.Isolation)) { if containertypes.Isolation.IsDefault(containertypes.Isolation(hostConfig.Isolation)) {
@ -28,66 +28,76 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con
} }
hostConfig.Isolation = "hyperv" hostConfig.Isolation = "hyperv"
} }
parser := volumemounts.NewParser(container.OS)
for spec := range config.Volumes { for spec := range config.Volumes {
var destination string
mp, err := parser.ParseMountRaw(spec, hostConfig.VolumeDriver) if container.OS == runtime.GOOS {
if err != nil { // We do a filepath.FromSlash here to not break "legacy" Windows
return fmt.Errorf("Unrecognised volume spec: %v", err) // dockerfiles use Unix-style paths such as "VOLUME c:/somevolume".
} // If we don't do this, HCS will balk.
destination = filepath.Clean(filepath.FromSlash(spec))
// If the mountpoint doesn't have a name, generate one. } else {
if len(mp.Name) == 0 { destination = path.Clean(spec)
mp.Name = stringid.GenerateNonCryptoID()
} }
// Skip volumes for which we already have something mounted on that // Skip volumes for which we already have something mounted on that
// destination because of a --volume-from. // destination because of a --volume-from.
if container.IsDestinationMounted(mp.Destination) { if container.IsDestinationMounted(destination) {
continue continue
} }
volumeDriver := hostConfig.VolumeDriver // Create the volume in the volume driver.
v, err := daemon.volumes.Create(
// Create the volume in the volume driver. If it doesn't exist, context.TODO(),
// a new one will be created. stringid.GenerateNonCryptoID(),
v, err := daemon.volumes.Create(context.TODO(), mp.Name, volumeDriver, volumeopts.WithCreateReference(container.ID)) hostConfig.VolumeDriver,
volumeopts.WithCreateReference(container.ID))
if err != nil { if err != nil {
return err return err
} }
// FIXME Windows: This code block is present in the Linux version and // Add it to container.MountPoints. Note that the last parameter is true - as in read-write
// allows the contents to be copied to the container FS prior to it container.AddMountPointWithVolume(destination, &volumeWrapper{v: v, s: daemon.volumes}, true)
// being started. However, the function utilizes the FollowSymLinkInScope
// path which does not cope with Windows volume-style file paths. There
// is a separate effort to resolve this (@swernli), so this processing
// is deferred for now. A case where this would be useful is when
// a dockerfile includes a VOLUME statement, but something is created
// in that directory during the dockerfile processing. What this means
// on Windows for TP5 is that in that scenario, the contents will not
// copied, but that's (somewhat) OK as HCS will bomb out soon after
// at it doesn't support mapped directories which have contents in the
// destination path anyway.
//
// Example for repro later:
// FROM windowsservercore
// RUN mkdir c:\myvol
// RUN copy c:\windows\system32\ntdll.dll c:\myvol
// VOLUME "c:\myvol"
//
// Then
// docker build -t vol .
// docker run -it --rm vol cmd <-- This is where HCS will error out.
//
// // never attempt to copy existing content in a container FS to a shared volume
// if v.DriverName() == volume.DefaultDriverName {
// if err := container.CopyImagePathContent(v, mp.Destination); err != nil {
// return err
// }
// }
// Add it to container.MountPoints
container.AddMountPointWithVolume(mp.Destination, &volumeWrapper{v: v, s: daemon.volumes}, mp.RW)
} }
// NOTE: On Linux, this function returns daemon.populateVolumes(container).
// This allows the contents of a volume to be copied to the containers
// filesystem prior to it be in started.
//
// There are many issues with solving this problem, and the reality is
// that it will likely be one thing that can't be done on Windows.
//
// FollowSymLinkInScope doesn't cope with Windows volume-style file paths.
// To avoid break outs we need to do scoped access. This is surmountable.
//
// Argons might be possible, but at this execution point on Windows,
// the container filesystem isn't mounted as it's done much later than
// on Linux.
//
// Xenons (both WCOW and LCOW) are difficult as we don't want to mount
// the filesystem on the host, for both security and perf reasons.
// Further, obviously the LCOW filesystem can't be mounted on the host
// directly.
//
// What this means is that on Windows, the contents of a VOLUME created
// in a Dockerfile with contents will NOT be copied.
//
// Pre ~RS3 had limitations in mapped directories which preclude actually
// doing this in the platform anyway.
//
// Example for repro later:
// FROM windowsservercore
// RUN mkdir c:\myvol
// RUN copy c:\windows\system32\ntdll.dll c:\myvol
// VOLUME "c:\myvol"
//
// Then
// docker build -t vol .
// docker run -it --rm vol cmd
//
// Result
// RS1 to ~RS3 HCS would error out
// RS4+ Succeeds, container starts, but c:\source will be empty
return nil return nil
} }