Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
Tonis Tiigi authored on 2017/06/20 09:15:23... | ... |
@@ -7,6 +7,18 @@ import ( |
7 | 7 |
"github.com/docker/docker/pkg/streamformatter" |
8 | 8 |
) |
9 | 9 |
|
10 |
+// PullOption defines different modes for accessing images |
|
11 |
+type PullOption int |
|
12 |
+ |
|
13 |
+const ( |
|
14 |
+ // PullOptionNoPull only returns local images |
|
15 |
+ PullOptionNoPull PullOption = iota |
|
16 |
+ // PullOptionForcePull always tries to pull a ref from the registry first |
|
17 |
+ PullOptionForcePull |
|
18 |
+ // PullOptionPreferLocal uses local image if it exists, otherwise pulls |
|
19 |
+ PullOptionPreferLocal |
|
20 |
+) |
|
21 |
+ |
|
10 | 22 |
// ProgressWriter is a data object to transport progress streams to the client |
11 | 23 |
type ProgressWriter struct { |
12 | 24 |
Output io.Writer |
... | ... |
@@ -25,7 +37,7 @@ type BuildConfig struct { |
25 | 25 |
|
26 | 26 |
// GetImageAndLayerOptions are the options supported by GetImageAndReleasableLayer |
27 | 27 |
type GetImageAndLayerOptions struct { |
28 |
- ForcePull bool |
|
28 |
+ PullOption PullOption |
|
29 | 29 |
AuthConfig map[string]types.AuthConfig |
30 | 30 |
Output io.Writer |
31 | 31 |
} |
... | ... |
@@ -196,6 +196,7 @@ func (b *Builder) getImageMount(fromFlag *Flag) (*imageMount, error) { |
196 | 196 |
return nil, nil |
197 | 197 |
} |
198 | 198 |
|
199 |
+ var localOnly bool |
|
199 | 200 |
imageRefOrID := fromFlag.Value |
200 | 201 |
stage, err := b.buildStages.get(fromFlag.Value) |
201 | 202 |
if err != nil { |
... | ... |
@@ -203,8 +204,9 @@ func (b *Builder) getImageMount(fromFlag *Flag) (*imageMount, error) { |
203 | 203 |
} |
204 | 204 |
if stage != nil { |
205 | 205 |
imageRefOrID = stage.ImageID() |
206 |
+ localOnly = true |
|
206 | 207 |
} |
207 |
- return b.imageSources.Get(imageRefOrID) |
|
208 |
+ return b.imageSources.Get(imageRefOrID, localOnly) |
|
208 | 209 |
} |
209 | 210 |
|
210 | 211 |
// FROM imagename[:tag | @digest] [AS build-stage-name] |
... | ... |
@@ -266,8 +268,10 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, err |
266 | 266 |
return nil, err |
267 | 267 |
} |
268 | 268 |
|
269 |
+ var localOnly bool |
|
269 | 270 |
if stage, ok := b.buildStages.getByName(name); ok { |
270 | 271 |
name = stage.ImageID() |
272 |
+ localOnly = true |
|
271 | 273 |
} |
272 | 274 |
|
273 | 275 |
// Windows cannot support a container with no base image. |
... | ... |
@@ -277,7 +281,7 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, err |
277 | 277 |
} |
278 | 278 |
return scratchImage, nil |
279 | 279 |
} |
280 |
- imageMount, err := b.imageSources.Get(name) |
|
280 |
+ imageMount, err := b.imageSources.Get(name, localOnly) |
|
281 | 281 |
if err != nil { |
282 | 282 |
return nil, err |
283 | 283 |
} |
... | ... |
@@ -86,21 +86,29 @@ func (s *buildStages) update(imageID string) { |
86 | 86 |
s.sequence[len(s.sequence)-1].update(imageID) |
87 | 87 |
} |
88 | 88 |
|
89 |
-type getAndMountFunc func(string) (builder.Image, builder.ReleaseableLayer, error) |
|
89 |
+type getAndMountFunc func(string, bool) (builder.Image, builder.ReleaseableLayer, error) |
|
90 | 90 |
|
91 | 91 |
// imageSources mounts images and provides a cache for mounted images. It tracks |
92 | 92 |
// all images so they can be unmounted at the end of the build. |
93 | 93 |
type imageSources struct { |
94 | 94 |
byImageID map[string]*imageMount |
95 |
- withoutID []*imageMount |
|
95 |
+ mounts []*imageMount |
|
96 | 96 |
getImage getAndMountFunc |
97 | 97 |
cache pathCache // TODO: remove |
98 | 98 |
} |
99 | 99 |
|
100 | 100 |
func newImageSources(ctx context.Context, options builderOptions) *imageSources { |
101 |
- getAndMount := func(idOrRef string) (builder.Image, builder.ReleaseableLayer, error) { |
|
101 |
+ getAndMount := func(idOrRef string, localOnly bool) (builder.Image, builder.ReleaseableLayer, error) { |
|
102 |
+ pullOption := backend.PullOptionNoPull |
|
103 |
+ if !localOnly { |
|
104 |
+ if options.Options.PullParent { |
|
105 |
+ pullOption = backend.PullOptionForcePull |
|
106 |
+ } else { |
|
107 |
+ pullOption = backend.PullOptionPreferLocal |
|
108 |
+ } |
|
109 |
+ } |
|
102 | 110 |
return options.Backend.GetImageAndReleasableLayer(ctx, idOrRef, backend.GetImageAndLayerOptions{ |
103 |
- ForcePull: options.Options.PullParent, |
|
111 |
+ PullOption: pullOption, |
|
104 | 112 |
AuthConfig: options.Options.AuthConfigs, |
105 | 113 |
Output: options.ProgressWriter.Output, |
106 | 114 |
}) |
... | ... |
@@ -112,12 +120,12 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources |
112 | 112 |
} |
113 | 113 |
} |
114 | 114 |
|
115 |
-func (m *imageSources) Get(idOrRef string) (*imageMount, error) { |
|
115 |
+func (m *imageSources) Get(idOrRef string, localOnly bool) (*imageMount, error) { |
|
116 | 116 |
if im, ok := m.byImageID[idOrRef]; ok { |
117 | 117 |
return im, nil |
118 | 118 |
} |
119 | 119 |
|
120 |
- image, layer, err := m.getImage(idOrRef) |
|
120 |
+ image, layer, err := m.getImage(idOrRef, localOnly) |
|
121 | 121 |
if err != nil { |
122 | 122 |
return nil, err |
123 | 123 |
} |
... | ... |
@@ -127,13 +135,7 @@ func (m *imageSources) Get(idOrRef string) (*imageMount, error) { |
127 | 127 |
} |
128 | 128 |
|
129 | 129 |
func (m *imageSources) Unmount() (retErr error) { |
130 |
- for _, im := range m.byImageID { |
|
131 |
- if err := im.unmount(); err != nil { |
|
132 |
- logrus.Error(err) |
|
133 |
- retErr = err |
|
134 |
- } |
|
135 |
- } |
|
136 |
- for _, im := range m.withoutID { |
|
130 |
+ for _, im := range m.mounts { |
|
137 | 131 |
if err := im.unmount(); err != nil { |
138 | 132 |
logrus.Error(err) |
139 | 133 |
retErr = err |
... | ... |
@@ -146,10 +148,10 @@ func (m *imageSources) Add(im *imageMount) { |
146 | 146 |
switch im.image { |
147 | 147 |
case nil: |
148 | 148 |
im.image = &dockerimage.Image{} |
149 |
- m.withoutID = append(m.withoutID, im) |
|
150 | 149 |
default: |
151 | 150 |
m.byImageID[im.image.ImageID()] = im |
152 | 151 |
} |
152 |
+ m.mounts = append(m.mounts, im) |
|
153 | 153 |
} |
154 | 154 |
|
155 | 155 |
// imageMount is a reference to an image that can be used as a builder.Source |
... | ... |
@@ -116,7 +116,7 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error |
116 | 116 |
return err |
117 | 117 |
} |
118 | 118 |
|
119 |
- imageMount, err := b.imageSources.Get(state.imageID) |
|
119 |
+ imageMount, err := b.imageSources.Get(state.imageID, true) |
|
120 | 120 |
if err != nil { |
121 | 121 |
return errors.Wrapf(err, "failed to get destination image %q", state.imageID) |
122 | 122 |
} |
... | ... |
@@ -18,6 +18,7 @@ import ( |
18 | 18 |
) |
19 | 19 |
|
20 | 20 |
type releaseableLayer struct { |
21 |
+ released bool |
|
21 | 22 |
layerStore layer.Store |
22 | 23 |
roLayer layer.Layer |
23 | 24 |
rwLayer layer.RWLayer |
... | ... |
@@ -70,6 +71,10 @@ func (rl *releaseableLayer) DiffID() layer.DiffID { |
70 | 70 |
} |
71 | 71 |
|
72 | 72 |
func (rl *releaseableLayer) Release() error { |
73 |
+ if rl.released { |
|
74 |
+ return nil |
|
75 |
+ } |
|
76 |
+ rl.released = true |
|
73 | 77 |
rl.releaseRWLayer() |
74 | 78 |
return rl.releaseROLayer() |
75 | 79 |
} |
... | ... |
@@ -143,8 +148,11 @@ func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID st |
143 | 143 |
return nil, layer, err |
144 | 144 |
} |
145 | 145 |
|
146 |
- if !opts.ForcePull { |
|
147 |
- image, _ := daemon.GetImage(refOrID) |
|
146 |
+ if opts.PullOption != backend.PullOptionForcePull { |
|
147 |
+ image, err := daemon.GetImage(refOrID) |
|
148 |
+ if err != nil && opts.PullOption == backend.PullOptionNoPull { |
|
149 |
+ return nil, nil, err |
|
150 |
+ } |
|
148 | 151 |
// TODO: shouldn't we error out if error is different from "not found" ? |
149 | 152 |
if image != nil { |
150 | 153 |
layer, err := newReleasableLayerForImage(image, daemon.layerStore) |
... | ... |
@@ -4,11 +4,14 @@ import ( |
4 | 4 |
"archive/tar" |
5 | 5 |
"bytes" |
6 | 6 |
"encoding/json" |
7 |
+ "fmt" |
|
8 |
+ "io" |
|
7 | 9 |
"io/ioutil" |
8 | 10 |
"net/http" |
9 | 11 |
"regexp" |
10 | 12 |
"strings" |
11 | 13 |
|
14 |
+ "github.com/docker/docker/api/types" |
|
12 | 15 |
"github.com/docker/docker/integration-cli/checker" |
13 | 16 |
"github.com/docker/docker/integration-cli/cli/build/fakecontext" |
14 | 17 |
"github.com/docker/docker/integration-cli/cli/build/fakegit" |
... | ... |
@@ -322,6 +325,44 @@ func (s *DockerSuite) TestBuildOnBuildCache(c *check.C) { |
322 | 322 |
assert.Equal(c, parentID, image.Parent) |
323 | 323 |
} |
324 | 324 |
|
325 |
+func (s *DockerRegistrySuite) TestBuildCopyFromForcePull(c *check.C) { |
|
326 |
+ client, err := request.NewClient() |
|
327 |
+ require.NoError(c, err) |
|
328 |
+ |
|
329 |
+ repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) |
|
330 |
+ // tag the image to upload it to the private registry |
|
331 |
+ err = client.ImageTag(context.TODO(), "busybox", repoName) |
|
332 |
+ assert.Nil(c, err) |
|
333 |
+ // push the image to the registry |
|
334 |
+ rc, err := client.ImagePush(context.TODO(), repoName, types.ImagePushOptions{RegistryAuth: "{}"}) |
|
335 |
+ assert.Nil(c, err) |
|
336 |
+ _, err = io.Copy(ioutil.Discard, rc) |
|
337 |
+ assert.Nil(c, err) |
|
338 |
+ |
|
339 |
+ dockerfile := fmt.Sprintf(` |
|
340 |
+ FROM %s AS foo |
|
341 |
+ RUN touch abc |
|
342 |
+ FROM %s |
|
343 |
+ COPY --from=foo /abc / |
|
344 |
+ `, repoName, repoName) |
|
345 |
+ |
|
346 |
+ ctx := fakecontext.New(c, "", |
|
347 |
+ fakecontext.WithDockerfile(dockerfile), |
|
348 |
+ ) |
|
349 |
+ defer ctx.Close() |
|
350 |
+ |
|
351 |
+ res, body, err := request.Post( |
|
352 |
+ "/build?pull=1", |
|
353 |
+ request.RawContent(ctx.AsTarReader(c)), |
|
354 |
+ request.ContentType("application/x-tar")) |
|
355 |
+ require.NoError(c, err) |
|
356 |
+ assert.Equal(c, http.StatusOK, res.StatusCode) |
|
357 |
+ |
|
358 |
+ out, err := testutil.ReadBody(body) |
|
359 |
+ require.NoError(c, err) |
|
360 |
+ assert.Contains(c, string(out), "Successfully built") |
|
361 |
+} |
|
362 |
+ |
|
325 | 363 |
type buildLine struct { |
326 | 364 |
Stream string |
327 | 365 |
Aux struct { |