Fix symlink handling in builder ADD/COPY commands

Fixes #17290

Fixes following issues:

- Cache checksums turning off while walking a broken symlink.

- Cache checksums were taken from symlinks while targets were actually copied.

- Copying a symlink pointing to a file to a directory used the basename of the target as a destination basename, instead of basename of the symlink.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit 47da59f7ec4ee0f49d47a9b32abb137bb30b2c48)
This commit is contained in:
Tonis Tiigi
2015-11-09 18:36:29 -05:00
committed by Tibor Vass
parent 6aa14d58ff
commit f4dbb093eb
6 changed files with 187 additions and 45 deletions
+125
View File
@@ -20,6 +20,7 @@ import (
"github.com/docker/docker/builder/dockerfile/command"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/integration/checker"
"github.com/docker/docker/pkg/stringutils"
"github.com/go-check/check"
)
@@ -6223,3 +6224,127 @@ func (s *DockerSuite) TestBuildNoNamedVolume(c *check.C) {
_, err := buildImage("test", dockerFile, false)
c.Assert(err, check.NotNil, check.Commentf("image build should have failed"))
}
// #17290
func (s *DockerSuite) TestBuildCacheBrokenSymlink(c *check.C) {
testRequires(c, DaemonIsLinux)
name := "testbuildbrokensymlink"
ctx, err := fakeContext(`
FROM busybox
COPY . ./`,
map[string]string{
"foo": "bar",
})
c.Assert(err, checker.IsNil)
defer ctx.Close()
err = os.Symlink(filepath.Join(ctx.Dir, "nosuchfile"), filepath.Join(ctx.Dir, "asymlink"))
c.Assert(err, checker.IsNil)
// warm up cache
_, err = buildImageFromContext(name, ctx, true)
c.Assert(err, checker.IsNil)
// add new file to context, should invalidate cache
err = ioutil.WriteFile(filepath.Join(ctx.Dir, "newfile"), []byte("foo"), 0644)
c.Assert(err, checker.IsNil)
_, out, err := buildImageFromContextWithOut(name, ctx, true)
c.Assert(err, checker.IsNil)
c.Assert(out, checker.Not(checker.Contains), "Using cache")
}
func (s *DockerSuite) TestBuildFollowSymlinkToFile(c *check.C) {
testRequires(c, DaemonIsLinux)
name := "testbuildbrokensymlink"
ctx, err := fakeContext(`
FROM busybox
COPY asymlink target`,
map[string]string{
"foo": "bar",
})
c.Assert(err, checker.IsNil)
defer ctx.Close()
err = os.Symlink("foo", filepath.Join(ctx.Dir, "asymlink"))
c.Assert(err, checker.IsNil)
id, err := buildImageFromContext(name, ctx, true)
c.Assert(err, checker.IsNil)
out, _ := dockerCmd(c, "run", "--rm", id, "cat", "target")
c.Assert(out, checker.Matches, "bar")
// change target file should invalidate cache
err = ioutil.WriteFile(filepath.Join(ctx.Dir, "foo"), []byte("baz"), 0644)
c.Assert(err, checker.IsNil)
id, out, err = buildImageFromContextWithOut(name, ctx, true)
c.Assert(err, checker.IsNil)
c.Assert(out, checker.Not(checker.Contains), "Using cache")
out, _ = dockerCmd(c, "run", "--rm", id, "cat", "target")
c.Assert(out, checker.Matches, "baz")
}
func (s *DockerSuite) TestBuildFollowSymlinkToDir(c *check.C) {
testRequires(c, DaemonIsLinux)
name := "testbuildbrokensymlink"
ctx, err := fakeContext(`
FROM busybox
COPY asymlink /`,
map[string]string{
"foo/abc": "bar",
"foo/def": "baz",
})
c.Assert(err, checker.IsNil)
defer ctx.Close()
err = os.Symlink("foo", filepath.Join(ctx.Dir, "asymlink"))
c.Assert(err, checker.IsNil)
id, err := buildImageFromContext(name, ctx, true)
c.Assert(err, checker.IsNil)
out, _ := dockerCmd(c, "run", "--rm", id, "cat", "abc", "def")
c.Assert(out, checker.Matches, "barbaz")
// change target file should invalidate cache
err = ioutil.WriteFile(filepath.Join(ctx.Dir, "foo/def"), []byte("bax"), 0644)
c.Assert(err, checker.IsNil)
id, out, err = buildImageFromContextWithOut(name, ctx, true)
c.Assert(err, checker.IsNil)
c.Assert(out, checker.Not(checker.Contains), "Using cache")
out, _ = dockerCmd(c, "run", "--rm", id, "cat", "abc", "def")
c.Assert(out, checker.Matches, "barbax")
}
// TestBuildSymlinkBasename tests that target file gets basename from symlink,
// not from the target file.
func (s *DockerSuite) TestBuildSymlinkBasename(c *check.C) {
testRequires(c, DaemonIsLinux)
name := "testbuildbrokensymlink"
ctx, err := fakeContext(`
FROM busybox
COPY asymlink /`,
map[string]string{
"foo": "bar",
})
c.Assert(err, checker.IsNil)
defer ctx.Close()
err = os.Symlink("foo", filepath.Join(ctx.Dir, "asymlink"))
c.Assert(err, checker.IsNil)
id, err := buildImageFromContext(name, ctx, true)
c.Assert(err, checker.IsNil)
out, _ := dockerCmd(c, "run", "--rm", id, "cat", "asymlink")
c.Assert(out, checker.Matches, "bar")
}
+14 -2
View File
@@ -1214,6 +1214,14 @@ func buildImage(name, dockerfile string, useCache bool, buildFlags ...string) (s
}
func buildImageFromContext(name string, ctx *FakeContext, useCache bool, buildFlags ...string) (string, error) {
id, _, err := buildImageFromContextWithOut(name, ctx, useCache, buildFlags...)
if err != nil {
return "", err
}
return id, nil
}
func buildImageFromContextWithOut(name string, ctx *FakeContext, useCache bool, buildFlags ...string) (string, string, error) {
args := []string{"build", "-t", name}
if !useCache {
args = append(args, "--no-cache")
@@ -1224,9 +1232,13 @@ func buildImageFromContext(name string, ctx *FakeContext, useCache bool, buildFl
buildCmd.Dir = ctx.Dir
out, exitCode, err := runCommandWithOutput(buildCmd)
if err != nil || exitCode != 0 {
return "", fmt.Errorf("failed to build the image: %s", out)
return "", "", fmt.Errorf("failed to build the image: %s", out)
}
return getIDByName(name)
id, err := getIDByName(name)
if err != nil {
return "", "", err
}
return id, out, nil
}
func buildImageFromPath(name, path string, useCache bool, buildFlags ...string) (string, error) {