Browse code

Implicit copy-from with reference

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2017/03/23 09:49:39
Showing 4 changed files
... ...
@@ -187,25 +187,16 @@ func dispatchCopy(b *Builder, args []string, attributes map[string]bool, origina
187 187
 		return err
188 188
 	}
189 189
 
190
-	var contextID *int
190
+	var im *imageMount
191 191
 	if flFrom.IsUsed() {
192
-		flFrom.Value = strings.ToLower(flFrom.Value)
193
-		if context, ok := b.imageContexts.byName[flFrom.Value]; ok {
194
-			contextID = &context
195
-		} else {
196
-			var err error
197
-			context, err := strconv.Atoi(flFrom.Value)
198
-			if err != nil {
199
-				return errors.Wrap(err, "from expects an integer value corresponding to the context number")
200
-			}
201
-			if err := b.imageContexts.validate(context); err != nil {
202
-				return err
203
-			}
204
-			contextID = &context
192
+		var err error
193
+		im, err = b.imageContexts.get(flFrom.Value)
194
+		if err != nil {
195
+			return err
205 196
 		}
206 197
 	}
207 198
 
208
-	return b.runContextCommand(args, false, false, "COPY", contextID)
199
+	return b.runContextCommand(args, false, false, "COPY", im)
209 200
 }
210 201
 
211 202
 // FROM imagename
... ...
@@ -232,7 +223,7 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
232 232
 	var image builder.Image
233 233
 
234 234
 	b.resetImageCache()
235
-	if err := b.imageContexts.new(ctxName); err != nil {
235
+	if _, err := b.imageContexts.new(ctxName, true); err != nil {
236 236
 		return err
237 237
 	}
238 238
 
... ...
@@ -844,3 +835,24 @@ func getShell(c *container.Config) []string {
844 844
 	}
845 845
 	return append([]string{}, c.Shell[:]...)
846 846
 }
847
+
848
+// mountByRef creates an imageMount from a reference. pulling the image if needed.
849
+func mountByRef(b *Builder, name string) (*imageMount, error) {
850
+	var image builder.Image
851
+	if !b.options.PullParent {
852
+		image, _ = b.docker.GetImageOnBuild(name)
853
+	}
854
+	if image == nil {
855
+		var err error
856
+		image, err = b.docker.PullOnBuild(b.clientCtx, name, b.options.AuthConfigs, b.Output)
857
+		if err != nil {
858
+			return nil, err
859
+		}
860
+	}
861
+	im, err := b.imageContexts.new("", false)
862
+	if err != nil {
863
+		return nil, err
864
+	}
865
+	im.id = image.ImageID()
866
+	return im, nil
867
+}
... ...
@@ -1,6 +1,8 @@
1 1
 package dockerfile
2 2
 
3 3
 import (
4
+	"strconv"
5
+	"strings"
4 6
 	"sync"
5 7
 
6 8
 	"github.com/Sirupsen/logrus"
... ...
@@ -14,28 +16,25 @@ import (
14 14
 type imageContexts struct {
15 15
 	b      *Builder
16 16
 	list   []*imageMount
17
-	byName map[string]int
17
+	byName map[string]*imageMount
18 18
 	cache  *pathCache
19 19
 }
20 20
 
21
-type imageMount struct {
22
-	id      string
23
-	ctx     builder.Context
24
-	release func() error
25
-}
26
-
27
-func (ic *imageContexts) new(name string) error {
21
+func (ic *imageContexts) new(name string, increment bool) (*imageMount, error) {
22
+	im := &imageMount{ic: ic}
28 23
 	if len(name) > 0 {
29 24
 		if ic.byName == nil {
30
-			ic.byName = make(map[string]int)
25
+			ic.byName = make(map[string]*imageMount)
31 26
 		}
32 27
 		if _, ok := ic.byName[name]; ok {
33
-			return errors.Errorf("duplicate name %s", name)
28
+			return nil, errors.Errorf("duplicate name %s", name)
34 29
 		}
35
-		ic.byName[name] = len(ic.list)
30
+		ic.byName[name] = im
36 31
 	}
37
-	ic.list = append(ic.list, &imageMount{})
38
-	return nil
32
+	if increment {
33
+		ic.list = append(ic.list, im)
34
+	}
35
+	return im, nil
39 36
 }
40 37
 
41 38
 func (ic *imageContexts) update(imageID string) {
... ...
@@ -53,59 +52,94 @@ func (ic *imageContexts) validate(i int) error {
53 53
 	return nil
54 54
 }
55 55
 
56
-func (ic *imageContexts) context(i int) (builder.Context, error) {
57
-	if err := ic.validate(i); err != nil {
58
-		return nil, err
59
-	}
60
-	im := ic.list[i]
61
-	if im.ctx == nil {
62
-		if im.id == "" {
63
-			return nil, errors.Errorf("could not copy from empty context")
64
-		}
65
-		p, release, err := ic.b.docker.MountImage(im.id)
66
-		if err != nil {
67
-			return nil, errors.Wrapf(err, "failed to mount %s", im.id)
56
+func (ic *imageContexts) get(indexOrName string) (*imageMount, error) {
57
+	index, err := strconv.Atoi(indexOrName)
58
+	if err == nil {
59
+		if err := ic.validate(index); err != nil {
60
+			return nil, err
68 61
 		}
69
-		ctx, err := remotecontext.NewLazyContext(p)
70
-		if err != nil {
71
-			return nil, errors.Wrapf(err, "failed to create lazycontext for %s", p)
72
-		}
73
-		logrus.Debugf("mounted image: %s %s", im.id, p)
74
-		im.release = release
75
-		im.ctx = ctx
62
+		return ic.list[index], nil
76 63
 	}
77
-	return im.ctx, nil
64
+	if im, ok := ic.byName[strings.ToLower(indexOrName)]; ok {
65
+		return im, nil
66
+	}
67
+	im, err := mountByRef(ic.b, indexOrName)
68
+	if err != nil {
69
+		return nil, errors.Wrapf(err, "invalid from flag value %s", indexOrName)
70
+	}
71
+	return im, nil
78 72
 }
79 73
 
80 74
 func (ic *imageContexts) unmount() (retErr error) {
81 75
 	for _, im := range ic.list {
82
-		if im.release != nil {
83
-			if err := im.release(); err != nil {
84
-				logrus.Error(errors.Wrapf(err, "failed to unmount previous build image"))
85
-				retErr = err
86
-			}
76
+		if err := im.unmount(); err != nil {
77
+			logrus.Error(err)
78
+			retErr = err
79
+		}
80
+	}
81
+	for _, im := range ic.byName {
82
+		if err := im.unmount(); err != nil {
83
+			logrus.Error(err)
84
+			retErr = err
87 85
 		}
88 86
 	}
89 87
 	return
90 88
 }
91 89
 
92
-func (ic *imageContexts) getCache(i int, path string) (interface{}, bool) {
90
+func (ic *imageContexts) getCache(id, path string) (interface{}, bool) {
93 91
 	if ic.cache != nil {
94
-		im := ic.list[i]
95
-		if im.id == "" {
92
+		if id == "" {
96 93
 			return nil, false
97 94
 		}
98
-		return ic.cache.get(im.id + path)
95
+		return ic.cache.get(id + path)
99 96
 	}
100 97
 	return nil, false
101 98
 }
102 99
 
103
-func (ic *imageContexts) setCache(i int, path string, v interface{}) {
100
+func (ic *imageContexts) setCache(id, path string, v interface{}) {
104 101
 	if ic.cache != nil {
105
-		ic.cache.set(ic.list[i].id+path, v)
102
+		ic.cache.set(id+path, v)
106 103
 	}
107 104
 }
108 105
 
106
+// imageMount is a reference for getting access to a buildcontext that is backed
107
+// by an existing image
108
+type imageMount struct {
109
+	id      string
110
+	ctx     builder.Context
111
+	release func() error
112
+	ic      *imageContexts
113
+}
114
+
115
+func (im *imageMount) context() (builder.Context, error) {
116
+	if im.ctx == nil {
117
+		if im.id == "" {
118
+			return nil, errors.Errorf("could not copy from empty context")
119
+		}
120
+		p, release, err := im.ic.b.docker.MountImage(im.id)
121
+		if err != nil {
122
+			return nil, errors.Wrapf(err, "failed to mount %s", im.id)
123
+		}
124
+		ctx, err := remotecontext.NewLazyContext(p)
125
+		if err != nil {
126
+			return nil, errors.Wrapf(err, "failed to create lazycontext for %s", p)
127
+		}
128
+		im.release = release
129
+		im.ctx = ctx
130
+	}
131
+	return im.ctx, nil
132
+}
133
+
134
+func (im *imageMount) unmount() error {
135
+	if im.release != nil {
136
+		if err := im.release(); err != nil {
137
+			return errors.Wrapf(err, "failed to unmount previous build image %s", im.id)
138
+		}
139
+		im.release = nil
140
+	}
141
+	return nil
142
+}
143
+
109 144
 type pathCache struct {
110 145
 	mu    sync.Mutex
111 146
 	items map[string]interface{}
... ...
@@ -94,7 +94,7 @@ type copyInfo struct {
94 94
 	decompress bool
95 95
 }
96 96
 
97
-func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalDecompression bool, cmdName string, contextID *int) error {
97
+func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalDecompression bool, cmdName string, imageSource *imageMount) error {
98 98
 	if len(args) < 2 {
99 99
 		return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
100 100
 	}
... ...
@@ -128,7 +128,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
128 128
 			continue
129 129
 		}
130 130
 		// not a URL
131
-		subInfos, err := b.calcCopyInfo(cmdName, orig, allowLocalDecompression, true, contextID)
131
+		subInfos, err := b.calcCopyInfo(cmdName, orig, allowLocalDecompression, true, imageSource)
132 132
 		if err != nil {
133 133
 			return err
134 134
 		}
... ...
@@ -298,13 +298,13 @@ func (b *Builder) download(srcURL string) (fi builder.FileInfo, err error) {
298 298
 	return &builder.HashedFileInfo{FileInfo: builder.PathFileInfo{FileInfo: tmpFileSt, FilePath: tmpFileName}, FileHash: hash}, nil
299 299
 }
300 300
 
301
-func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression, allowWildcards bool, contextID *int) ([]copyInfo, error) {
301
+func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression, allowWildcards bool, imageSource *imageMount) ([]copyInfo, error) {
302 302
 
303 303
 	// Work in daemon-specific OS filepath semantics
304 304
 	origPath = filepath.FromSlash(origPath)
305 305
 
306 306
 	// validate windows paths from other images
307
-	if contextID != nil && runtime.GOOS == "windows" {
307
+	if imageSource != nil && runtime.GOOS == "windows" {
308 308
 		forbid := regexp.MustCompile("(?i)^" + string(os.PathSeparator) + "?(windows(" + string(os.PathSeparator) + ".+)?)?$")
309 309
 		if p := filepath.Clean(origPath); p == "." || forbid.MatchString(p) {
310 310
 			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
318 318
 
319 319
 	context := b.context
320 320
 	var err error
321
-	if contextID != nil {
322
-		context, err = b.imageContexts.context(*contextID)
321
+	if imageSource != nil {
322
+		context, err = imageSource.context()
323 323
 		if err != nil {
324 324
 			return nil, err
325 325
 		}
... ...
@@ -346,7 +346,7 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
346 346
 
347 347
 			// Note we set allowWildcards to false in case the name has
348 348
 			// a * in it
349
-			subInfos, err := b.calcCopyInfo(cmdName, path, allowLocalDecompression, false, contextID)
349
+			subInfos, err := b.calcCopyInfo(cmdName, path, allowLocalDecompression, false, imageSource)
350 350
 			if err != nil {
351 351
 				return err
352 352
 			}
... ...
@@ -370,9 +370,9 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
370 370
 	if !handleHash {
371 371
 		return copyInfos, nil
372 372
 	}
373
-	if contextID != nil {
373
+	if imageSource != nil {
374 374
 		// fast-cache based on imageID
375
-		if h, ok := b.imageContexts.getCache(*contextID, origPath); ok {
375
+		if h, ok := b.imageContexts.getCache(imageSource.id, origPath); ok {
376 376
 			hfi.SetHash(h.(string))
377 377
 			return copyInfos, nil
378 378
 		}
... ...
@@ -401,8 +401,8 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
401 401
 	hasher := sha256.New()
402 402
 	hasher.Write([]byte(strings.Join(subfiles, ",")))
403 403
 	hfi.SetHash("dir:" + hex.EncodeToString(hasher.Sum(nil)))
404
-	if contextID != nil {
405
-		b.imageContexts.setCache(*contextID, origPath, hfi.Hash())
404
+	if imageSource != nil {
405
+		b.imageContexts.setCache(imageSource.id, origPath, hfi.Hash())
406 406
 	}
407 407
 
408 408
 	return copyInfos, nil
... ...
@@ -5831,7 +5831,7 @@ func (s *DockerSuite) TestBuildCopyFromPreviousRootFSErrors(c *check.C) {
5831 5831
 
5832 5832
 	buildImage("build1", withExternalBuildContext(ctx)).Assert(c, icmd.Expected{
5833 5833
 		ExitCode: 1,
5834
-		Err:      "from expects an integer value corresponding to the context number",
5834
+		Err:      "invalid from flag value foo",
5835 5835
 	})
5836 5836
 
5837 5837
 	dockerfile = `
... ...
@@ -5861,7 +5861,7 @@ func (s *DockerSuite) TestBuildCopyFromPreviousRootFSErrors(c *check.C) {
5861 5861
 
5862 5862
 	buildImage("build1", withExternalBuildContext(ctx)).Assert(c, icmd.Expected{
5863 5863
 		ExitCode: 1,
5864
-		Err:      "invalid context value bar",
5864
+		Err:      "invalid from flag value bar",
5865 5865
 	})
5866 5866
 
5867 5867
 	dockerfile = `
... ...
@@ -5913,6 +5913,34 @@ func (s *DockerSuite) TestBuildCopyFromPreviousFrom(c *check.C) {
5913 5913
 	c.Assert(strings.TrimSpace(out), check.Equals, "def")
5914 5914
 }
5915 5915
 
5916
+func (s *DockerSuite) TestBuildCopyFromImplicitFrom(c *check.C) {
5917
+	dockerfile := `
5918
+		FROM busybox
5919
+		COPY --from=busybox /etc/passwd /mypasswd
5920
+		RUN cmp /etc/passwd /mypasswd`
5921
+
5922
+	if DaemonIsWindows() {
5923
+		dockerfile = `
5924
+			FROM busybox
5925
+			COPY --from=busybox License.txt foo`
5926
+	}
5927
+
5928
+	ctx := fakeContext(c, dockerfile, map[string]string{
5929
+		"Dockerfile": dockerfile,
5930
+	})
5931
+	defer ctx.Close()
5932
+
5933
+	result := buildImage("build1", withExternalBuildContext(ctx))
5934
+	result.Assert(c, icmd.Success)
5935
+
5936
+	if DaemonIsWindows() {
5937
+		out, _ := dockerCmd(c, "run", "build1", "cat", "License.txt")
5938
+		c.Assert(len(out), checker.GreaterThan, 10)
5939
+		out2, _ := dockerCmd(c, "run", "build1", "cat", "foo")
5940
+		c.Assert(out, check.Equals, out2)
5941
+	}
5942
+}
5943
+
5916 5944
 // TestBuildOpaqueDirectory tests that a build succeeds which
5917 5945
 // creates opaque directories.
5918 5946
 // See https://github.com/docker/docker/issues/25244