Browse code

builder: fix copy —from conflict with force pull

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

Tonis Tiigi authored on 2017/06/20 09:15:23
Showing 6 changed files
... ...
@@ -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 {