mirror of
https://github.com/moby/moby.git
synced 2025-08-07 02:02:57 +03:00
Implicit copy-from with reference
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
@@ -187,25 +187,16 @@ func dispatchCopy(b *Builder, args []string, attributes map[string]bool, origina
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var contextID *int
|
var im *imageMount
|
||||||
if flFrom.IsUsed() {
|
if flFrom.IsUsed() {
|
||||||
flFrom.Value = strings.ToLower(flFrom.Value)
|
|
||||||
if context, ok := b.imageContexts.byName[flFrom.Value]; ok {
|
|
||||||
contextID = &context
|
|
||||||
} else {
|
|
||||||
var err error
|
var err error
|
||||||
context, err := strconv.Atoi(flFrom.Value)
|
im, err = b.imageContexts.get(flFrom.Value)
|
||||||
if err != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
contextID = &context
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.runContextCommand(args, false, false, "COPY", contextID)
|
return b.runContextCommand(args, false, false, "COPY", im)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FROM imagename
|
// FROM imagename
|
||||||
@@ -232,7 +223,7 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
|
|||||||
var image builder.Image
|
var image builder.Image
|
||||||
|
|
||||||
b.resetImageCache()
|
b.resetImageCache()
|
||||||
if err := b.imageContexts.new(ctxName); err != nil {
|
if _, err := b.imageContexts.new(ctxName, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -844,3 +835,24 @@ func getShell(c *container.Config) []string {
|
|||||||
}
|
}
|
||||||
return append([]string{}, c.Shell[:]...)
|
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
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package dockerfile
|
package dockerfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
@@ -14,28 +16,25 @@ import (
|
|||||||
type imageContexts struct {
|
type imageContexts struct {
|
||||||
b *Builder
|
b *Builder
|
||||||
list []*imageMount
|
list []*imageMount
|
||||||
byName map[string]int
|
byName map[string]*imageMount
|
||||||
cache *pathCache
|
cache *pathCache
|
||||||
}
|
}
|
||||||
|
|
||||||
type imageMount struct {
|
func (ic *imageContexts) new(name string, increment bool) (*imageMount, error) {
|
||||||
id string
|
im := &imageMount{ic: ic}
|
||||||
ctx builder.Context
|
|
||||||
release func() error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ic *imageContexts) new(name string) error {
|
|
||||||
if len(name) > 0 {
|
if len(name) > 0 {
|
||||||
if ic.byName == nil {
|
if ic.byName == nil {
|
||||||
ic.byName = make(map[string]int)
|
ic.byName = make(map[string]*imageMount)
|
||||||
}
|
}
|
||||||
if _, ok := ic.byName[name]; ok {
|
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{})
|
if increment {
|
||||||
return nil
|
ic.list = append(ic.list, im)
|
||||||
|
}
|
||||||
|
return im, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *imageContexts) update(imageID string) {
|
func (ic *imageContexts) update(imageID string) {
|
||||||
@@ -53,16 +52,71 @@ func (ic *imageContexts) validate(i int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *imageContexts) context(i int) (builder.Context, error) {
|
func (ic *imageContexts) get(indexOrName string) (*imageMount, error) {
|
||||||
if err := ic.validate(i); err != nil {
|
index, err := strconv.Atoi(indexOrName)
|
||||||
|
if err == nil {
|
||||||
|
if err := ic.validate(index); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
im := ic.list[i]
|
return ic.list[index], nil
|
||||||
|
}
|
||||||
|
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.ctx == nil {
|
||||||
if im.id == "" {
|
if im.id == "" {
|
||||||
return nil, errors.Errorf("could not copy from empty context")
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to mount %s", im.id)
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to create lazycontext for %s", p)
|
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.release = release
|
||||||
im.ctx = ctx
|
im.ctx = ctx
|
||||||
}
|
}
|
||||||
return im.ctx, nil
|
return im.ctx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *imageContexts) unmount() (retErr error) {
|
func (im *imageMount) unmount() error {
|
||||||
for _, im := range ic.list {
|
|
||||||
if im.release != nil {
|
if im.release != nil {
|
||||||
if err := im.release(); err != nil {
|
if err := im.release(); err != nil {
|
||||||
logrus.Error(errors.Wrapf(err, "failed to unmount previous build image"))
|
return errors.Wrapf(err, "failed to unmount previous build image %s", im.id)
|
||||||
retErr = err
|
|
||||||
}
|
}
|
||||||
|
im.release = nil
|
||||||
}
|
}
|
||||||
}
|
return 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type pathCache struct {
|
type pathCache struct {
|
||||||
|
@@ -94,7 +94,7 @@ type copyInfo struct {
|
|||||||
decompress bool
|
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 {
|
if len(args) < 2 {
|
||||||
return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
|
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
|
continue
|
||||||
}
|
}
|
||||||
// not a URL
|
// not a URL
|
||||||
subInfos, err := b.calcCopyInfo(cmdName, orig, allowLocalDecompression, true, contextID)
|
subInfos, err := b.calcCopyInfo(cmdName, orig, allowLocalDecompression, true, imageSource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
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
|
// Work in daemon-specific OS filepath semantics
|
||||||
origPath = filepath.FromSlash(origPath)
|
origPath = filepath.FromSlash(origPath)
|
||||||
|
|
||||||
// validate windows paths from other images
|
// 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) + ".+)?)?$")
|
forbid := regexp.MustCompile("(?i)^" + string(os.PathSeparator) + "?(windows(" + string(os.PathSeparator) + ".+)?)?$")
|
||||||
if p := filepath.Clean(origPath); p == "." || forbid.MatchString(p) {
|
if p := filepath.Clean(origPath); p == "." || forbid.MatchString(p) {
|
||||||
return nil, errors.Errorf("copy from %s is not allowed on windows", origPath)
|
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
|
context := b.context
|
||||||
var err error
|
var err error
|
||||||
if contextID != nil {
|
if imageSource != nil {
|
||||||
context, err = b.imageContexts.context(*contextID)
|
context, err = imageSource.context()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Note we set allowWildcards to false in case the name has
|
||||||
// a * in it
|
// a * in it
|
||||||
subInfos, err := b.calcCopyInfo(cmdName, path, allowLocalDecompression, false, contextID)
|
subInfos, err := b.calcCopyInfo(cmdName, path, allowLocalDecompression, false, imageSource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -370,9 +370,9 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
|
|||||||
if !handleHash {
|
if !handleHash {
|
||||||
return copyInfos, nil
|
return copyInfos, nil
|
||||||
}
|
}
|
||||||
if contextID != nil {
|
if imageSource != nil {
|
||||||
// fast-cache based on imageID
|
// 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))
|
hfi.SetHash(h.(string))
|
||||||
return copyInfos, nil
|
return copyInfos, nil
|
||||||
}
|
}
|
||||||
@@ -401,8 +401,8 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
|
|||||||
hasher := sha256.New()
|
hasher := sha256.New()
|
||||||
hasher.Write([]byte(strings.Join(subfiles, ",")))
|
hasher.Write([]byte(strings.Join(subfiles, ",")))
|
||||||
hfi.SetHash("dir:" + hex.EncodeToString(hasher.Sum(nil)))
|
hfi.SetHash("dir:" + hex.EncodeToString(hasher.Sum(nil)))
|
||||||
if contextID != nil {
|
if imageSource != nil {
|
||||||
b.imageContexts.setCache(*contextID, origPath, hfi.Hash())
|
b.imageContexts.setCache(imageSource.id, origPath, hfi.Hash())
|
||||||
}
|
}
|
||||||
|
|
||||||
return copyInfos, nil
|
return copyInfos, nil
|
||||||
|
@@ -5831,7 +5831,7 @@ func (s *DockerSuite) TestBuildCopyFromPreviousRootFSErrors(c *check.C) {
|
|||||||
|
|
||||||
buildImage("build1", withExternalBuildContext(ctx)).Assert(c, icmd.Expected{
|
buildImage("build1", withExternalBuildContext(ctx)).Assert(c, icmd.Expected{
|
||||||
ExitCode: 1,
|
ExitCode: 1,
|
||||||
Err: "from expects an integer value corresponding to the context number",
|
Err: "invalid from flag value foo",
|
||||||
})
|
})
|
||||||
|
|
||||||
dockerfile = `
|
dockerfile = `
|
||||||
@@ -5861,7 +5861,7 @@ func (s *DockerSuite) TestBuildCopyFromPreviousRootFSErrors(c *check.C) {
|
|||||||
|
|
||||||
buildImage("build1", withExternalBuildContext(ctx)).Assert(c, icmd.Expected{
|
buildImage("build1", withExternalBuildContext(ctx)).Assert(c, icmd.Expected{
|
||||||
ExitCode: 1,
|
ExitCode: 1,
|
||||||
Err: "invalid context value bar",
|
Err: "invalid from flag value bar",
|
||||||
})
|
})
|
||||||
|
|
||||||
dockerfile = `
|
dockerfile = `
|
||||||
@@ -5913,6 +5913,34 @@ func (s *DockerSuite) TestBuildCopyFromPreviousFrom(c *check.C) {
|
|||||||
c.Assert(strings.TrimSpace(out), check.Equals, "def")
|
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
|
// TestBuildOpaqueDirectory tests that a build succeeds which
|
||||||
// creates opaque directories.
|
// creates opaque directories.
|
||||||
// See https://github.com/docker/docker/issues/25244
|
// See https://github.com/docker/docker/issues/25244
|
||||||
|
Reference in New Issue
Block a user