From 87512bbc8490aca261133a7f3a3ea6518d703c34 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 20 Mar 2017 10:28:21 -0700 Subject: [PATCH 1/4] Add named context support Signed-off-by: Tonis Tiigi --- builder/dockerfile/dispatchers.go | 33 ++++++++++++++------ builder/dockerfile/imagecontext.go | 19 +++++++++--- builder/dockerfile/parser/parser.go | 2 +- integration-cli/docker_cli_build_test.go | 39 +++++++++++++++++++++--- 4 files changed, 74 insertions(+), 19 deletions(-) diff --git a/builder/dockerfile/dispatchers.go b/builder/dockerfile/dispatchers.go index 1b875b9dc9..c6c34cac66 100644 --- a/builder/dockerfile/dispatchers.go +++ b/builder/dockerfile/dispatchers.go @@ -189,15 +189,20 @@ func dispatchCopy(b *Builder, args []string, attributes map[string]bool, origina var contextID *int if flFrom.IsUsed() { - var err error - context, err := strconv.Atoi(flFrom.Value) - if err != nil { - return errors.Wrap(err, "from expects an integer value corresponding to the context number") + flFrom.Value = strings.ToLower(flFrom.Value) + if context, ok := b.imageContexts.byName[flFrom.Value]; ok { + contextID = &context + } else { + var err error + context, err := strconv.Atoi(flFrom.Value) + if err != nil { + return errors.Wrap(err, "from expects an integer value corresponding to the context number") + } + if err := b.imageContexts.validate(context); err != nil { + return err + } + contextID = &context } - if err := b.imageContexts.validate(context); err != nil { - return err - } - contextID = &context } return b.runContextCommand(args, false, false, "COPY", contextID) @@ -208,7 +213,13 @@ func dispatchCopy(b *Builder, args []string, attributes map[string]bool, origina // This sets the image the dockerfile will build on top of. // func from(b *Builder, args []string, attributes map[string]bool, original string) error { - if len(args) != 1 { + ctxName := "" + if len(args) == 3 && strings.EqualFold(args[1], "as") { + ctxName = strings.ToLower(args[2]) + if ok, _ := regexp.MatchString("^[a-z][a-z0-9-_\\.]*$", ctxName); !ok { + return errors.Errorf("invalid name for build stage: %q, name can't start with a number or contain symbols", ctxName) + } + } else if len(args) != 1 { return errExactlyOneArgument("FROM") } @@ -221,7 +232,9 @@ func from(b *Builder, args []string, attributes map[string]bool, original string var image builder.Image b.resetImageCache() - b.imageContexts.new() + if err := b.imageContexts.new(ctxName); err != nil { + return err + } // Windows cannot support a container with no base image. if name == api.NoBaseImageSpecifier { diff --git a/builder/dockerfile/imagecontext.go b/builder/dockerfile/imagecontext.go index 32ce2aacbc..803c96d7d6 100644 --- a/builder/dockerfile/imagecontext.go +++ b/builder/dockerfile/imagecontext.go @@ -12,9 +12,10 @@ import ( // imageContexts is a helper for stacking up built image rootfs and reusing // them as contexts type imageContexts struct { - b *Builder - list []*imageMount - cache *pathCache + b *Builder + list []*imageMount + byName map[string]int + cache *pathCache } type imageMount struct { @@ -23,8 +24,18 @@ type imageMount struct { release func() error } -func (ic *imageContexts) new() { +func (ic *imageContexts) new(name string) error { + if len(name) > 0 { + if ic.byName == nil { + ic.byName = make(map[string]int) + } + if _, ok := ic.byName[name]; ok { + return errors.Errorf("duplicate name %s", name) + } + ic.byName[name] = len(ic.list) + } ic.list = append(ic.list, &imageMount{}) + return nil } func (ic *imageContexts) update(imageID string) { diff --git a/builder/dockerfile/parser/parser.go b/builder/dockerfile/parser/parser.go index 1b2dc8f3a0..b0d4d9c2d0 100644 --- a/builder/dockerfile/parser/parser.go +++ b/builder/dockerfile/parser/parser.go @@ -80,7 +80,7 @@ func init() { command.Entrypoint: parseMaybeJSON, command.Env: parseEnv, command.Expose: parseStringsWhitespaceDelimited, - command.From: parseString, + command.From: parseStringsWhitespaceDelimited, command.Healthcheck: parseHealthConfig, command.Label: parseLabel, command.Maintainer: parseString, diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index bd712615ab..95a5b7bc7d 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5752,7 +5752,7 @@ func (s *DockerSuite) TestBuildContChar(c *check.C) { func (s *DockerSuite) TestBuildCopyFromPreviousRootFS(c *check.C) { dockerfile := ` - FROM busybox + FROM busybox AS first COPY foo bar FROM busybox %s @@ -5762,7 +5762,8 @@ func (s *DockerSuite) TestBuildCopyFromPreviousRootFS(c *check.C) { COPY bar / COPY --from=1 baz sub/ COPY --from=0 bar baz - COPY --from=0 bar bay` + COPY --from=first bar bay` + ctx := fakeContext(c, fmt.Sprintf(dockerfile, ""), map[string]string{ "Dockerfile": dockerfile, "foo": "abc", @@ -5847,6 +5848,36 @@ func (s *DockerSuite) TestBuildCopyFromPreviousRootFSErrors(c *check.C) { ExitCode: 1, Err: "invalid from flag value 0 refers current build block", }) + + dockerfile = ` + FROM busybox AS foo + COPY --from=bar foo bar` + + ctx = fakeContext(c, dockerfile, map[string]string{ + "Dockerfile": dockerfile, + "foo": "abc", + }) + defer ctx.Close() + + buildImage("build1", withExternalBuildContext(ctx)).Assert(c, icmd.Expected{ + ExitCode: 1, + Err: "invalid context value bar", + }) + + dockerfile = ` + FROM busybox AS 1 + COPY --from=1 foo bar` + + ctx = fakeContext(c, dockerfile, map[string]string{ + "Dockerfile": dockerfile, + "foo": "abc", + }) + defer ctx.Close() + + buildImage("build1", withExternalBuildContext(ctx)).Assert(c, icmd.Expected{ + ExitCode: 1, + Err: "invalid name for build stage", + }) } func (s *DockerSuite) TestBuildCopyFromPreviousFrom(c *check.C) { @@ -5863,9 +5894,9 @@ func (s *DockerSuite) TestBuildCopyFromPreviousFrom(c *check.C) { result.Assert(c, icmd.Success) dockerfile = ` - FROM build1:latest + FROM build1:latest AS foo FROM busybox - COPY --from=0 bar / + COPY --from=foo bar / COPY foo /` ctx = fakeContext(c, dockerfile, map[string]string{ "Dockerfile": dockerfile, From 73b4b8ed7f86b8df70d7028c5ac7c2ba61ee0a8c Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Wed, 22 Mar 2017 17:49:39 -0700 Subject: [PATCH 2/4] Implicit copy-from with reference Signed-off-by: Tonis Tiigi --- builder/dockerfile/dispatchers.go | 44 +++++--- builder/dockerfile/imagecontext.go | 122 +++++++++++++++-------- builder/dockerfile/internals.go | 22 ++-- integration-cli/docker_cli_build_test.go | 32 +++++- 4 files changed, 147 insertions(+), 73 deletions(-) diff --git a/builder/dockerfile/dispatchers.go b/builder/dockerfile/dispatchers.go index c6c34cac66..e59b2ce506 100644 --- a/builder/dockerfile/dispatchers.go +++ b/builder/dockerfile/dispatchers.go @@ -187,25 +187,16 @@ func dispatchCopy(b *Builder, args []string, attributes map[string]bool, origina return err } - var contextID *int + var im *imageMount if flFrom.IsUsed() { - flFrom.Value = strings.ToLower(flFrom.Value) - if context, ok := b.imageContexts.byName[flFrom.Value]; ok { - contextID = &context - } else { - var err error - context, err := strconv.Atoi(flFrom.Value) - if err != nil { - return errors.Wrap(err, "from expects an integer value corresponding to the context number") - } - if err := b.imageContexts.validate(context); err != nil { - return err - } - contextID = &context + var err error + im, err = b.imageContexts.get(flFrom.Value) + if err != nil { + return err } } - return b.runContextCommand(args, false, false, "COPY", contextID) + return b.runContextCommand(args, false, false, "COPY", im) } // FROM imagename @@ -232,7 +223,7 @@ func from(b *Builder, args []string, attributes map[string]bool, original string var image builder.Image b.resetImageCache() - if err := b.imageContexts.new(ctxName); err != nil { + if _, err := b.imageContexts.new(ctxName, true); err != nil { return err } @@ -844,3 +835,24 @@ func getShell(c *container.Config) []string { } return append([]string{}, c.Shell[:]...) } + +// mountByRef creates an imageMount from a reference. pulling the image if needed. +func mountByRef(b *Builder, name string) (*imageMount, error) { + var image builder.Image + if !b.options.PullParent { + image, _ = b.docker.GetImageOnBuild(name) + } + if image == nil { + var err error + image, err = b.docker.PullOnBuild(b.clientCtx, name, b.options.AuthConfigs, b.Output) + if err != nil { + return nil, err + } + } + im, err := b.imageContexts.new("", false) + if err != nil { + return nil, err + } + im.id = image.ImageID() + return im, nil +} diff --git a/builder/dockerfile/imagecontext.go b/builder/dockerfile/imagecontext.go index 803c96d7d6..de611fb3ce 100644 --- a/builder/dockerfile/imagecontext.go +++ b/builder/dockerfile/imagecontext.go @@ -1,6 +1,8 @@ package dockerfile import ( + "strconv" + "strings" "sync" "github.com/Sirupsen/logrus" @@ -14,28 +16,25 @@ import ( type imageContexts struct { b *Builder list []*imageMount - byName map[string]int + byName map[string]*imageMount cache *pathCache } -type imageMount struct { - id string - ctx builder.Context - release func() error -} - -func (ic *imageContexts) new(name string) error { +func (ic *imageContexts) new(name string, increment bool) (*imageMount, error) { + im := &imageMount{ic: ic} if len(name) > 0 { if ic.byName == nil { - ic.byName = make(map[string]int) + ic.byName = make(map[string]*imageMount) } if _, ok := ic.byName[name]; ok { - return errors.Errorf("duplicate name %s", name) + return nil, errors.Errorf("duplicate name %s", name) } - ic.byName[name] = len(ic.list) + ic.byName[name] = im } - ic.list = append(ic.list, &imageMount{}) - return nil + if increment { + ic.list = append(ic.list, im) + } + return im, nil } func (ic *imageContexts) update(imageID string) { @@ -53,16 +52,71 @@ func (ic *imageContexts) validate(i int) error { return nil } -func (ic *imageContexts) context(i int) (builder.Context, error) { - if err := ic.validate(i); err != nil { - return nil, err +func (ic *imageContexts) get(indexOrName string) (*imageMount, error) { + index, err := strconv.Atoi(indexOrName) + if err == nil { + if err := ic.validate(index); err != nil { + return nil, err + } + return ic.list[index], nil } - im := ic.list[i] + if im, ok := ic.byName[strings.ToLower(indexOrName)]; ok { + return im, nil + } + im, err := mountByRef(ic.b, indexOrName) + if err != nil { + return nil, errors.Wrapf(err, "invalid from flag value %s", indexOrName) + } + return im, nil +} + +func (ic *imageContexts) unmount() (retErr error) { + for _, im := range ic.list { + if err := im.unmount(); err != nil { + logrus.Error(err) + retErr = err + } + } + for _, im := range ic.byName { + if err := im.unmount(); err != nil { + logrus.Error(err) + retErr = err + } + } + return +} + +func (ic *imageContexts) getCache(id, path string) (interface{}, bool) { + if ic.cache != nil { + if id == "" { + return nil, false + } + return ic.cache.get(id + path) + } + return nil, false +} + +func (ic *imageContexts) setCache(id, path string, v interface{}) { + if ic.cache != nil { + ic.cache.set(id+path, v) + } +} + +// imageMount is a reference for getting access to a buildcontext that is backed +// by an existing image +type imageMount struct { + id string + ctx builder.Context + release func() error + ic *imageContexts +} + +func (im *imageMount) context() (builder.Context, error) { if im.ctx == nil { if im.id == "" { return nil, errors.Errorf("could not copy from empty context") } - p, release, err := ic.b.docker.MountImage(im.id) + p, release, err := im.ic.b.docker.MountImage(im.id) if err != nil { return nil, errors.Wrapf(err, "failed to mount %s", im.id) } @@ -70,40 +124,20 @@ func (ic *imageContexts) context(i int) (builder.Context, error) { if err != nil { return nil, errors.Wrapf(err, "failed to create lazycontext for %s", p) } - logrus.Debugf("mounted image: %s %s", im.id, p) im.release = release im.ctx = ctx } return im.ctx, nil } -func (ic *imageContexts) unmount() (retErr error) { - for _, im := range ic.list { - if im.release != nil { - if err := im.release(); err != nil { - logrus.Error(errors.Wrapf(err, "failed to unmount previous build image")) - retErr = err - } +func (im *imageMount) unmount() error { + if im.release != nil { + if err := im.release(); err != nil { + return errors.Wrapf(err, "failed to unmount previous build image %s", im.id) } + im.release = nil } - return -} - -func (ic *imageContexts) getCache(i int, path string) (interface{}, bool) { - if ic.cache != nil { - im := ic.list[i] - if im.id == "" { - return nil, false - } - return ic.cache.get(im.id + path) - } - return nil, false -} - -func (ic *imageContexts) setCache(i int, path string, v interface{}) { - if ic.cache != nil { - ic.cache.set(ic.list[i].id+path, v) - } + return nil } type pathCache struct { diff --git a/builder/dockerfile/internals.go b/builder/dockerfile/internals.go index 1a47c5d1ca..15b459be0b 100644 --- a/builder/dockerfile/internals.go +++ b/builder/dockerfile/internals.go @@ -94,7 +94,7 @@ type copyInfo struct { decompress bool } -func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalDecompression bool, cmdName string, contextID *int) error { +func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalDecompression bool, cmdName string, imageSource *imageMount) error { if len(args) < 2 { return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName) } @@ -128,7 +128,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD continue } // not a URL - subInfos, err := b.calcCopyInfo(cmdName, orig, allowLocalDecompression, true, contextID) + subInfos, err := b.calcCopyInfo(cmdName, orig, allowLocalDecompression, true, imageSource) if err != nil { return err } @@ -298,13 +298,13 @@ func (b *Builder) download(srcURL string) (fi builder.FileInfo, err error) { return &builder.HashedFileInfo{FileInfo: builder.PathFileInfo{FileInfo: tmpFileSt, FilePath: tmpFileName}, FileHash: hash}, nil } -func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression, allowWildcards bool, contextID *int) ([]copyInfo, error) { +func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression, allowWildcards bool, imageSource *imageMount) ([]copyInfo, error) { // Work in daemon-specific OS filepath semantics origPath = filepath.FromSlash(origPath) // validate windows paths from other images - if contextID != nil && runtime.GOOS == "windows" { + if imageSource != nil && runtime.GOOS == "windows" { forbid := regexp.MustCompile("(?i)^" + string(os.PathSeparator) + "?(windows(" + string(os.PathSeparator) + ".+)?)?$") if p := filepath.Clean(origPath); p == "." || forbid.MatchString(p) { return nil, errors.Errorf("copy from %s is not allowed on windows", origPath) @@ -318,8 +318,8 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression context := b.context var err error - if contextID != nil { - context, err = b.imageContexts.context(*contextID) + if imageSource != nil { + context, err = imageSource.context() if err != nil { return nil, err } @@ -346,7 +346,7 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression // Note we set allowWildcards to false in case the name has // a * in it - subInfos, err := b.calcCopyInfo(cmdName, path, allowLocalDecompression, false, contextID) + subInfos, err := b.calcCopyInfo(cmdName, path, allowLocalDecompression, false, imageSource) if err != nil { return err } @@ -370,9 +370,9 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression if !handleHash { return copyInfos, nil } - if contextID != nil { + if imageSource != nil { // fast-cache based on imageID - if h, ok := b.imageContexts.getCache(*contextID, origPath); ok { + if h, ok := b.imageContexts.getCache(imageSource.id, origPath); ok { hfi.SetHash(h.(string)) return copyInfos, nil } @@ -401,8 +401,8 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression hasher := sha256.New() hasher.Write([]byte(strings.Join(subfiles, ","))) hfi.SetHash("dir:" + hex.EncodeToString(hasher.Sum(nil))) - if contextID != nil { - b.imageContexts.setCache(*contextID, origPath, hfi.Hash()) + if imageSource != nil { + b.imageContexts.setCache(imageSource.id, origPath, hfi.Hash()) } return copyInfos, nil diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 95a5b7bc7d..4a8bc03e97 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5831,7 +5831,7 @@ func (s *DockerSuite) TestBuildCopyFromPreviousRootFSErrors(c *check.C) { buildImage("build1", withExternalBuildContext(ctx)).Assert(c, icmd.Expected{ ExitCode: 1, - Err: "from expects an integer value corresponding to the context number", + Err: "invalid from flag value foo", }) dockerfile = ` @@ -5861,7 +5861,7 @@ func (s *DockerSuite) TestBuildCopyFromPreviousRootFSErrors(c *check.C) { buildImage("build1", withExternalBuildContext(ctx)).Assert(c, icmd.Expected{ ExitCode: 1, - Err: "invalid context value bar", + Err: "invalid from flag value bar", }) dockerfile = ` @@ -5913,6 +5913,34 @@ func (s *DockerSuite) TestBuildCopyFromPreviousFrom(c *check.C) { c.Assert(strings.TrimSpace(out), check.Equals, "def") } +func (s *DockerSuite) TestBuildCopyFromImplicitFrom(c *check.C) { + dockerfile := ` + FROM busybox + COPY --from=busybox /etc/passwd /mypasswd + RUN cmp /etc/passwd /mypasswd` + + if DaemonIsWindows() { + dockerfile = ` + FROM busybox + COPY --from=busybox License.txt foo` + } + + ctx := fakeContext(c, dockerfile, map[string]string{ + "Dockerfile": dockerfile, + }) + defer ctx.Close() + + result := buildImage("build1", withExternalBuildContext(ctx)) + result.Assert(c, icmd.Success) + + if DaemonIsWindows() { + out, _ := dockerCmd(c, "run", "build1", "cat", "License.txt") + c.Assert(len(out), checker.GreaterThan, 10) + out2, _ := dockerCmd(c, "run", "build1", "cat", "foo") + c.Assert(out, check.Equals, out2) + } +} + // TestBuildOpaqueDirectory tests that a build succeeds which // creates opaque directories. // See https://github.com/docker/docker/issues/25244 From 672ea30a94933eec228d71217b033da5d6572b8d Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Wed, 22 Mar 2017 18:36:08 -0700 Subject: [PATCH 3/4] Add support for `FROM` using named block Signed-off-by: Tonis Tiigi --- builder/dockerfile/dispatchers.go | 58 ++++++++++++---------- builder/dockerfile/imagecontext.go | 20 ++++++-- builder/dockerfile/internals.go | 4 +- integration-cli/docker_cli_build_test.go | 61 ++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 31 deletions(-) diff --git a/builder/dockerfile/dispatchers.go b/builder/dockerfile/dispatchers.go index e59b2ce506..8f82dcc88f 100644 --- a/builder/dockerfile/dispatchers.go +++ b/builder/dockerfile/dispatchers.go @@ -227,27 +227,28 @@ func from(b *Builder, args []string, attributes map[string]bool, original string return err } - // Windows cannot support a container with no base image. - if name == api.NoBaseImageSpecifier { - if runtime.GOOS == "windows" { - return errors.New("Windows does not support FROM scratch") + if im, ok := b.imageContexts.byName[name]; ok { + if len(im.ImageID()) > 0 { + image = im } - b.image = "" - b.noBaseImage = true } else { - // TODO: don't use `name`, instead resolve it to a digest - if !b.options.PullParent { - image, _ = b.docker.GetImageOnBuild(name) - // TODO: shouldn't we error out if error is different from "not found" ? - } - if image == nil { + // Windows cannot support a container with no base image. + if name == api.NoBaseImageSpecifier { + if runtime.GOOS == "windows" { + return errors.New("Windows does not support FROM scratch") + } + b.image = "" + b.noBaseImage = true + } else { var err error - image, err = b.docker.PullOnBuild(b.clientCtx, name, b.options.AuthConfigs, b.Output) + image, err = pullOrGetImage(b, name) if err != nil { return err } } - b.imageContexts.update(image.ImageID()) + } + if image != nil { + b.imageContexts.update(image.ImageID(), image.RunConfig()) } b.from = image @@ -838,16 +839,9 @@ func getShell(c *container.Config) []string { // mountByRef creates an imageMount from a reference. pulling the image if needed. func mountByRef(b *Builder, name string) (*imageMount, error) { - var image builder.Image - if !b.options.PullParent { - image, _ = b.docker.GetImageOnBuild(name) - } - if image == nil { - var err error - image, err = b.docker.PullOnBuild(b.clientCtx, name, b.options.AuthConfigs, b.Output) - if err != nil { - return nil, err - } + image, err := pullOrGetImage(b, name) + if err != nil { + return nil, err } im, err := b.imageContexts.new("", false) if err != nil { @@ -856,3 +850,19 @@ func mountByRef(b *Builder, name string) (*imageMount, error) { im.id = image.ImageID() return im, nil } + +func pullOrGetImage(b *Builder, name string) (builder.Image, error) { + var image builder.Image + if !b.options.PullParent { + image, _ = b.docker.GetImageOnBuild(name) + // TODO: shouldn't we error out if error is different from "not found" ? + } + if image == nil { + var err error + image, err = b.docker.PullOnBuild(b.clientCtx, name, b.options.AuthConfigs, b.Output) + if err != nil { + return nil, err + } + } + return image, nil +} diff --git a/builder/dockerfile/imagecontext.go b/builder/dockerfile/imagecontext.go index de611fb3ce..60fa1d2d0d 100644 --- a/builder/dockerfile/imagecontext.go +++ b/builder/dockerfile/imagecontext.go @@ -6,6 +6,7 @@ import ( "sync" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/builder" "github.com/docker/docker/builder/remotecontext" "github.com/pkg/errors" @@ -37,8 +38,9 @@ func (ic *imageContexts) new(name string, increment bool) (*imageMount, error) { return im, nil } -func (ic *imageContexts) update(imageID string) { +func (ic *imageContexts) update(imageID string, runConfig *container.Config) { ic.list[len(ic.list)-1].id = imageID + ic.list[len(ic.list)-1].runConfig = runConfig } func (ic *imageContexts) validate(i int) error { @@ -105,10 +107,11 @@ func (ic *imageContexts) setCache(id, path string, v interface{}) { // imageMount is a reference for getting access to a buildcontext that is backed // by an existing image type imageMount struct { - id string - ctx builder.Context - release func() error - ic *imageContexts + id string + ctx builder.Context + release func() error + ic *imageContexts + runConfig *container.Config } func (im *imageMount) context() (builder.Context, error) { @@ -140,6 +143,13 @@ func (im *imageMount) unmount() error { return nil } +func (im *imageMount) ImageID() string { + return im.id +} +func (im *imageMount) RunConfig() *container.Config { + return im.runConfig +} + type pathCache struct { mu sync.Mutex items map[string]interface{} diff --git a/builder/dockerfile/internals.go b/builder/dockerfile/internals.go index 15b459be0b..80385c4f66 100644 --- a/builder/dockerfile/internals.go +++ b/builder/dockerfile/internals.go @@ -85,7 +85,7 @@ func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) e } b.image = imageID - b.imageContexts.update(imageID) + b.imageContexts.update(imageID, &autoConfig) return nil } @@ -497,7 +497,7 @@ func (b *Builder) probeCache() (bool, error) { fmt.Fprint(b.Stdout, " ---> Using cache\n") logrus.Debugf("[BUILDER] Use cached version: %s", b.runConfig.Cmd) b.image = string(cache) - b.imageContexts.update(b.image) + b.imageContexts.update(b.image, b.runConfig) return true, nil } diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 4a8bc03e97..7585d706f9 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5941,6 +5941,67 @@ func (s *DockerSuite) TestBuildCopyFromImplicitFrom(c *check.C) { } } +func (s *DockerRegistrySuite) TestBuildCopyFromImplicitPullingFrom(c *check.C) { + repoName := fmt.Sprintf("%v/dockercli/testf", privateRegistryURL) + + dockerfile := ` + FROM busybox + COPY foo bar` + ctx := fakeContext(c, dockerfile, map[string]string{ + "Dockerfile": dockerfile, + "foo": "abc", + }) + defer ctx.Close() + + result := buildImage(repoName, withExternalBuildContext(ctx)) + result.Assert(c, icmd.Success) + + dockerCmd(c, "push", repoName) + dockerCmd(c, "rmi", repoName) + + dockerfile = ` + FROM busybox + COPY --from=%s bar baz` + + ctx = fakeContext(c, fmt.Sprintf(dockerfile, repoName), map[string]string{ + "Dockerfile": dockerfile, + }) + defer ctx.Close() + + result = buildImage("build1", withExternalBuildContext(ctx)) + result.Assert(c, icmd.Success) + + dockerCmdWithResult("run", "build1", "cat", "baz").Assert(c, icmd.Expected{Out: "abc"}) +} + +func (s *DockerSuite) TestBuildFromPreviousBlock(c *check.C) { + dockerfile := ` + FROM busybox as foo + COPY foo / + FROM foo as foo1 + RUN echo 1 >> foo + FROM foo as foO2 + RUN echo 2 >> foo + FROM foo + COPY --from=foo1 foo f1 + COPY --from=FOo2 foo f2 + ` // foo2 case also tests that names are canse insensitive + ctx := fakeContext(c, dockerfile, map[string]string{ + "Dockerfile": dockerfile, + "foo": "bar", + }) + defer ctx.Close() + + result := buildImage("build1", withExternalBuildContext(ctx)) + result.Assert(c, icmd.Success) + + dockerCmdWithResult("run", "build1", "cat", "foo").Assert(c, icmd.Expected{Out: "bar"}) + + dockerCmdWithResult("run", "build1", "cat", "f1").Assert(c, icmd.Expected{Out: "bar1"}) + + dockerCmdWithResult("run", "build1", "cat", "f2").Assert(c, icmd.Expected{Out: "bar2"}) +} + // TestBuildOpaqueDirectory tests that a build succeeds which // creates opaque directories. // See https://github.com/docker/docker/issues/25244 From 79ccb3478f2d1c541d8689a8b5c60ab51d95ca79 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 4 Apr 2017 09:54:21 -0700 Subject: [PATCH 4/4] Add a multi stage trusted build testcase Signed-off-by: Tonis Tiigi --- integration-cli/docker_cli_build_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 7585d706f9..81d08e5419 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -6002,6 +6002,28 @@ func (s *DockerSuite) TestBuildFromPreviousBlock(c *check.C) { dockerCmdWithResult("run", "build1", "cat", "f2").Assert(c, icmd.Expected{Out: "bar2"}) } +func (s *DockerTrustSuite) TestCopyFromTrustedBuild(c *check.C) { + img1 := s.setupTrustedImage(c, "trusted-build1") + img2 := s.setupTrustedImage(c, "trusted-build2") + dockerFile := fmt.Sprintf(` + FROM %s AS build-base + RUN echo ok > /foo + FROM %s + COPY --from=build-base foo bar`, img1, img2) + + name := "testcopyfromtrustedbuild" + + r := buildImage(name, trustedBuild, build.WithDockerfile(dockerFile)) + r.Assert(c, icmd.Expected{ + Out: fmt.Sprintf("FROM %s@sha", img1[:len(img1)-7]), + }) + r.Assert(c, icmd.Expected{ + Out: fmt.Sprintf("FROM %s@sha", img2[:len(img2)-7]), + }) + + dockerCmdWithResult("run", name, "cat", "bar").Assert(c, icmd.Expected{Out: "ok"}) +} + // TestBuildOpaqueDirectory tests that a build succeeds which // creates opaque directories. // See https://github.com/docker/docker/issues/25244