Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
Tonis Tiigi authored on 2017/03/23 09:49:39... | ... |
@@ -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 |