diff --git a/components/engine/builder/dockerfile/dispatchers.go b/components/engine/builder/dockerfile/dispatchers.go index e59b2ce506..8f82dcc88f 100644 --- a/components/engine/builder/dockerfile/dispatchers.go +++ b/components/engine/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/components/engine/builder/dockerfile/imagecontext.go b/components/engine/builder/dockerfile/imagecontext.go index de611fb3ce..60fa1d2d0d 100644 --- a/components/engine/builder/dockerfile/imagecontext.go +++ b/components/engine/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/components/engine/builder/dockerfile/internals.go b/components/engine/builder/dockerfile/internals.go index 15b459be0b..80385c4f66 100644 --- a/components/engine/builder/dockerfile/internals.go +++ b/components/engine/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/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 4a8bc03e97..7585d706f9 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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