mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Builder: fix "COPY --from" to non-existing directory on Windows
This fixes a regression introduced in 6d87f19142
,
causing `COPY --from` to fail if the target directory does not exist:
```
FROM mcr.microsoft.com/windows/servercore:ltsc2019 as s1
RUN echo "Hello World" > /hello
FROM mcr.microsoft.com/windows/servercore:ltsc2019
COPY --from=s1 /hello /hello/another/world
```
Would produce an error:
```
Step 4/4 : COPY --from=s1 /hello /hello/another/world
failed to copy files: mkdir \\?: The filename, directory name, or volume label syntax is incorrect.
```
The cause for this was that Go's `os.MkdirAll()` does not support/detect volume GUID paths
(`\\?\Volume{dae8d3ac-b9a1-11e9-88eb-e8554b2ba1db}\hello\another}`), and as a result
attempted to create the volume as a directory (`\\?`), causing it to fail.
This patch replaces `os.MkdirAll()` with our own `system.MkdirAll()` function, which
is capable of detecting GUID volumes.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
6ae46aeabf
commit
5858a99267
3 changed files with 76 additions and 2 deletions
|
@ -556,13 +556,15 @@ func copyFile(archiver Archiver, source, dest *copyEndpoint, identity *idtools.I
|
||||||
return errors.Wrapf(err, "failed to create new directory")
|
return errors.Wrapf(err, "failed to create new directory")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Normal containers
|
||||||
if identity == nil {
|
if identity == nil {
|
||||||
if err := os.MkdirAll(filepath.Dir(dest.path), 0755); err != nil {
|
// Use system.MkdirAll here, which is a custom version of os.MkdirAll
|
||||||
|
// modified for use on Windows to handle volume GUID paths (\\?\{dae8d3ac-b9a1-11e9-88eb-e8554b2ba1db}\path\)
|
||||||
|
if err := system.MkdirAll(filepath.Dir(dest.path), 0755, ""); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := idtools.MkdirAllAndChownNew(filepath.Dir(dest.path), 0755, *identity); err != nil {
|
if err := idtools.MkdirAllAndChownNew(filepath.Dir(dest.path), 0755, *identity); err != nil {
|
||||||
// Normal containers
|
|
||||||
return errors.Wrapf(err, "failed to create new directory")
|
return errors.Wrapf(err, "failed to create new directory")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,6 +136,58 @@ func buildContainerIdsFilter(buildOutput io.Reader) (filters.Args, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestBuildMultiStageCopy verifies that copying between stages works correctly.
|
||||||
|
//
|
||||||
|
// Regression test for docker/for-win#4349, ENGCORE-935, where creating the target
|
||||||
|
// directory failed on Windows, because `os.MkdirAll()` was called with a volume
|
||||||
|
// GUID path (\\?\Volume{dae8d3ac-b9a1-11e9-88eb-e8554b2ba1db}\newdir\hello}),
|
||||||
|
// which currently isn't supported by Golang.
|
||||||
|
func TestBuildMultiStageCopy(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
dockerfile, err := ioutil.ReadFile("testdata/Dockerfile." + t.Name())
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
source := fakecontext.New(t, "", fakecontext.WithDockerfile(string(dockerfile)))
|
||||||
|
defer source.Close()
|
||||||
|
|
||||||
|
apiclient := testEnv.APIClient()
|
||||||
|
|
||||||
|
for _, target := range []string{"copy_to_root", "copy_to_newdir", "copy_to_newdir_nested", "copy_to_existingdir", "copy_to_newsubdir"} {
|
||||||
|
t.Run(target, func(t *testing.T) {
|
||||||
|
imgName := strings.ToLower(t.Name())
|
||||||
|
|
||||||
|
resp, err := apiclient.ImageBuild(
|
||||||
|
ctx,
|
||||||
|
source.AsTarReader(t),
|
||||||
|
types.ImageBuildOptions{
|
||||||
|
Remove: true,
|
||||||
|
ForceRemove: true,
|
||||||
|
Target: target,
|
||||||
|
Tags: []string{imgName},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
out := bytes.NewBuffer(nil)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
_, err = io.Copy(out, resp.Body)
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Log(out)
|
||||||
|
}
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
// verify the image was successfully built
|
||||||
|
_, _, err = apiclient.ImageInspectWithRaw(ctx, imgName)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(out)
|
||||||
|
}
|
||||||
|
assert.NilError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildMultiStageParentConfig(t *testing.T) {
|
func TestBuildMultiStageParentConfig(t *testing.T) {
|
||||||
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.35"), "broken in earlier versions")
|
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.35"), "broken in earlier versions")
|
||||||
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
|
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME")
|
||||||
|
|
20
integration/build/testdata/Dockerfile.TestBuildMultiStageCopy
vendored
Normal file
20
integration/build/testdata/Dockerfile.TestBuildMultiStageCopy
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
FROM busybox AS base
|
||||||
|
RUN mkdir existingdir
|
||||||
|
|
||||||
|
FROM base AS source
|
||||||
|
RUN echo "Hello World" > /hello
|
||||||
|
|
||||||
|
FROM base AS copy_to_root
|
||||||
|
COPY --from=source /hello /hello
|
||||||
|
|
||||||
|
FROM base AS copy_to_newdir
|
||||||
|
COPY --from=source /hello /newdir/hello
|
||||||
|
|
||||||
|
FROM base AS copy_to_newdir_nested
|
||||||
|
COPY --from=source /hello /newdir/newsubdir/hello
|
||||||
|
|
||||||
|
FROM base AS copy_to_existingdir
|
||||||
|
COPY --from=source /hello /existingdir/hello
|
||||||
|
|
||||||
|
FROM base AS copy_to_newsubdir
|
||||||
|
COPY --from=source /hello /existingdir/newsubdir/hello
|
Loading…
Reference in a new issue