Browse code

Expose a smaller interface for the Builder retrieving images from daemon

Removes 3 methods from the builder.Backend interface
Remove the coupling between imageContexts, imageMounts and the builder.

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2017/03/28 10:36:28
Showing 12 changed files
... ...
@@ -22,3 +22,10 @@ type BuildConfig struct {
22 22
 	ProgressWriter ProgressWriter
23 23
 	Options        *types.ImageBuildOptions
24 24
 }
25
+
26
+// GetImageAndLayerOptions are the options supported by GetImageAndLayer
27
+type GetImageAndLayerOptions struct {
28
+	ForcePull  bool
29
+	AuthConfig map[string]types.AuthConfig
30
+	Output     io.Writer
31
+}
... ...
@@ -34,12 +34,8 @@ type Source interface {
34 34
 
35 35
 // Backend abstracts calls to a Docker Daemon.
36 36
 type Backend interface {
37
-	// TODO: use digest reference instead of name
37
+	GetImageAndLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (Image, ReleaseableLayer, error)
38 38
 
39
-	// GetImageOnBuild looks up a Docker image referenced by `name`.
40
-	GetImageOnBuild(name string) (Image, error)
41
-	// PullOnBuild tells Docker to pull image referenced by `name`.
42
-	PullOnBuild(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer) (Image, error)
43 39
 	// ContainerAttachRaw attaches to container.
44 40
 	ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool, attached chan struct{}) error
45 41
 	// ContainerCreate creates a new Docker container and returns potential warnings
... ...
@@ -62,15 +58,6 @@ type Backend interface {
62 62
 	// TODO: extract in the builder instead of passing `decompress`
63 63
 	// TODO: use containerd/fs.changestream instead as a source
64 64
 	CopyOnBuild(containerID string, destPath string, srcRoot string, srcPath string, decompress bool) error
65
-
66
-	// MountImage returns mounted path with rootfs of an image.
67
-	MountImage(name string) (string, func() error, error)
68
-}
69
-
70
-// Image represents a Docker image used by the builder.
71
-type Image interface {
72
-	ImageID() string
73
-	RunConfig() *container.Config
74 65
 }
75 66
 
76 67
 // Result is the output produced by a Builder
... ...
@@ -92,3 +79,15 @@ type ImageCache interface {
92 92
 	// and runconfig equals `cfg`. A cache miss is expected to return an empty ID and a nil error.
93 93
 	GetCache(parentID string, cfg *container.Config) (imageID string, err error)
94 94
 }
95
+
96
+// Image represents a Docker image used by the builder.
97
+type Image interface {
98
+	ImageID() string
99
+	RunConfig() *container.Config
100
+}
101
+
102
+// ReleaseableLayer is an image layer that can be mounted and released
103
+type ReleaseableLayer interface {
104
+	Release() error
105
+	Mount() (string, error)
106
+}
... ...
@@ -45,7 +45,10 @@ type BuildManager struct {
45 45
 
46 46
 // NewBuildManager creates a BuildManager
47 47
 func NewBuildManager(b builder.Backend) *BuildManager {
48
-	return &BuildManager{backend: b, pathCache: &syncmap.Map{}}
48
+	return &BuildManager{
49
+		backend:   b,
50
+		pathCache: &syncmap.Map{},
51
+	}
49 52
 }
50 53
 
51 54
 // Build starts a new build from a BuildConfig
... ...
@@ -122,8 +125,8 @@ func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
122 122
 		docker:        options.Backend,
123 123
 		tmpContainers: map[string]struct{}{},
124 124
 		buildArgs:     newBuildArgs(config.BuildArgs),
125
+		imageContexts: &imageContexts{},
125 126
 	}
126
-	b.imageContexts = &imageContexts{b: b, cache: options.PathCache}
127 127
 	return b
128 128
 }
129 129
 
... ...
@@ -20,9 +20,9 @@ import (
20 20
 	"github.com/Sirupsen/logrus"
21 21
 	"github.com/docker/docker/api"
22 22
 	"github.com/docker/docker/api/types"
23
+	"github.com/docker/docker/api/types/backend"
23 24
 	"github.com/docker/docker/api/types/container"
24 25
 	"github.com/docker/docker/api/types/strslice"
25
-	"github.com/docker/docker/builder"
26 26
 	"github.com/docker/docker/pkg/signal"
27 27
 	"github.com/docker/go-connections/nat"
28 28
 	"github.com/pkg/errors"
... ...
@@ -160,23 +160,29 @@ func dispatchCopy(req dispatchRequest) error {
160 160
 	}
161 161
 
162 162
 	flFrom := req.flags.AddString("from", "")
163
-
164 163
 	if err := req.flags.Parse(); err != nil {
165 164
 		return err
166 165
 	}
167 166
 
168
-	var im *imageMount
169
-	if flFrom.IsUsed() {
170
-		var err error
171
-		im, err = req.builder.imageContexts.get(flFrom.Value)
172
-		if err != nil {
173
-			return err
174
-		}
167
+	im, err := req.builder.getImageMount(flFrom)
168
+	if err != nil {
169
+		return errors.Wrapf(err, "invalid from flag value %s", flFrom.Value)
175 170
 	}
176
-
177 171
 	return req.builder.runContextCommand(req, false, false, "COPY", im)
178 172
 }
179 173
 
174
+func (b *Builder) getImageMount(fromFlag *Flag) (*imageMount, error) {
175
+	if !fromFlag.IsUsed() {
176
+		// TODO: this could return the mount in the default case as well
177
+		return nil, nil
178
+	}
179
+	im, err := b.imageContexts.getMount(fromFlag.Value)
180
+	if err != nil || im != nil {
181
+		return im, err
182
+	}
183
+	return b.getImage(fromFlag.Value)
184
+}
185
+
180 186
 // FROM imagename[:tag | @digest] [AS build-stage-name]
181 187
 //
182 188
 func from(req dispatchRequest) error {
... ...
@@ -192,20 +198,17 @@ func from(req dispatchRequest) error {
192 192
 	req.builder.resetImageCache()
193 193
 	req.state.noBaseImage = false
194 194
 	req.state.stageName = stageName
195
-	if _, err := req.builder.imageContexts.add(stageName); err != nil {
196
-		return err
197
-	}
198
-
199
-	image, err := req.builder.getFromImage(req.state, req.shlex, req.args[0])
195
+	image, err := req.builder.getFromImage(req.shlex, req.args[0])
200 196
 	if err != nil {
201 197
 		return err
202 198
 	}
203
-	switch image {
204
-	case nil:
199
+	if image == nil {
205 200
 		req.state.imageID = ""
206 201
 		req.state.noBaseImage = true
207
-	default:
208
-		req.builder.imageContexts.update(image.ImageID(), image.RunConfig())
202
+		image = newImageMount(nil, nil)
203
+	}
204
+	if err := req.builder.imageContexts.add(stageName, image); err != nil {
205
+		return err
209 206
 	}
210 207
 	req.state.baseImage = image
211 208
 
... ...
@@ -228,7 +231,7 @@ func parseBuildStageName(args []string) (string, error) {
228 228
 	return stageName, nil
229 229
 }
230 230
 
231
-func (b *Builder) getFromImage(dispatchState *dispatchState, shlex *ShellLex, name string) (builder.Image, error) {
231
+func (b *Builder) getFromImage(shlex *ShellLex, name string) (*imageMount, error) {
232 232
 	substitutionArgs := []string{}
233 233
 	for key, value := range b.buildArgs.GetAllMeta() {
234 234
 		substitutionArgs = append(substitutionArgs, key+"="+value)
... ...
@@ -254,7 +257,19 @@ func (b *Builder) getFromImage(dispatchState *dispatchState, shlex *ShellLex, na
254 254
 		}
255 255
 		return nil, nil
256 256
 	}
257
-	return pullOrGetImage(b, name)
257
+	return b.getImage(name)
258
+}
259
+
260
+func (b *Builder) getImage(name string) (*imageMount, error) {
261
+	image, layer, err := b.docker.GetImageAndLayer(b.clientCtx, name, backend.GetImageAndLayerOptions{
262
+		ForcePull:  b.options.PullParent,
263
+		AuthConfig: b.options.AuthConfigs,
264
+		Output:     b.Output,
265
+	})
266
+	if err != nil {
267
+		return nil, err
268
+	}
269
+	return newImageMount(image, layer), nil
258 270
 }
259 271
 
260 272
 // ONBUILD RUN echo yo
... ...
@@ -801,29 +816,3 @@ func errBlankCommandNames(command string) error {
801 801
 func errTooManyArguments(command string) error {
802 802
 	return fmt.Errorf("Bad input to %s, too many arguments", command)
803 803
 }
804
-
805
-// mountByRef creates an imageMount from a reference. pulling the image if needed.
806
-func mountByRef(b *Builder, name string) (*imageMount, error) {
807
-	image, err := pullOrGetImage(b, name)
808
-	if err != nil {
809
-		return nil, err
810
-	}
811
-	im := b.imageContexts.newImageMount(image.ImageID())
812
-	return im, nil
813
-}
814
-
815
-func pullOrGetImage(b *Builder, name string) (builder.Image, error) {
816
-	var image builder.Image
817
-	if !b.options.PullParent {
818
-		image, _ = b.docker.GetImageOnBuild(name)
819
-		// TODO: shouldn't we error out if error is different from "not found" ?
820
-	}
821
-	if image == nil {
822
-		var err error
823
-		image, err = b.docker.PullOnBuild(b.clientCtx, name, b.options.AuthConfigs, b.Output)
824
-		if err != nil {
825
-			return nil, err
826
-		}
827
-	}
828
-	return image, nil
829
-}
... ...
@@ -47,16 +47,17 @@ func defaultDispatchReq(builder *Builder, args ...string) dispatchRequest {
47 47
 }
48 48
 
49 49
 func newBuilderWithMockBackend() *Builder {
50
+	mockBackend := &MockBackend{}
50 51
 	b := &Builder{
51 52
 		options:       &types.ImageBuildOptions{},
52
-		docker:        &MockBackend{},
53
+		docker:        mockBackend,
53 54
 		buildArgs:     newBuildArgs(make(map[string]*string)),
54 55
 		tmpContainers: make(map[string]struct{}),
55 56
 		Stdout:        new(bytes.Buffer),
56 57
 		clientCtx:     context.Background(),
57 58
 		disableCommit: true,
59
+		imageContexts: &imageContexts{},
58 60
 	}
59
-	b.imageContexts = &imageContexts{b: b}
60 61
 	return b
61 62
 }
62 63
 
... ...
@@ -198,12 +199,12 @@ func TestFromScratch(t *testing.T) {
198 198
 func TestFromWithArg(t *testing.T) {
199 199
 	tag, expected := ":sometag", "expectedthisid"
200 200
 
201
-	getImage := func(name string) (builder.Image, error) {
201
+	getImage := func(name string) (builder.Image, builder.ReleaseableLayer, error) {
202 202
 		assert.Equal(t, "alpine"+tag, name)
203
-		return &mockImage{id: "expectedthisid"}, nil
203
+		return &mockImage{id: "expectedthisid"}, nil, nil
204 204
 	}
205 205
 	b := newBuilderWithMockBackend()
206
-	b.docker.(*MockBackend).getImageOnBuildFunc = getImage
206
+	b.docker.(*MockBackend).getImageFunc = getImage
207 207
 
208 208
 	require.NoError(t, arg(defaultDispatchReq(b, "THETAG="+tag)))
209 209
 	req := defaultDispatchReq(b, "alpine${THETAG}")
... ...
@@ -219,12 +220,12 @@ func TestFromWithArg(t *testing.T) {
219 219
 func TestFromWithUndefinedArg(t *testing.T) {
220 220
 	tag, expected := "sometag", "expectedthisid"
221 221
 
222
-	getImage := func(name string) (builder.Image, error) {
222
+	getImage := func(name string) (builder.Image, builder.ReleaseableLayer, error) {
223 223
 		assert.Equal(t, "alpine", name)
224
-		return &mockImage{id: "expectedthisid"}, nil
224
+		return &mockImage{id: "expectedthisid"}, nil, nil
225 225
 	}
226 226
 	b := newBuilderWithMockBackend()
227
-	b.docker.(*MockBackend).getImageOnBuildFunc = getImage
227
+	b.docker.(*MockBackend).getImageFunc = getImage
228 228
 	b.options.BuildArgs = map[string]*string{"THETAG": &tag}
229 229
 
230 230
 	req := defaultDispatchReq(b, "alpine${THETAG}")
... ...
@@ -474,9 +475,11 @@ func TestRunWithBuildArgs(t *testing.T) {
474 474
 	b.imageCache = imageCache
475 475
 
476 476
 	mockBackend := b.docker.(*MockBackend)
477
-	mockBackend.getImageOnBuildImage = &mockImage{
478
-		id:     "abcdef",
479
-		config: &container.Config{Cmd: origCmd},
477
+	mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ReleaseableLayer, error) {
478
+		return &mockImage{
479
+			id:     "abcdef",
480
+			config: &container.Config{Cmd: origCmd},
481
+		}, nil, nil
480 482
 	}
481 483
 	mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
482 484
 		// Check the runConfig.Cmd sent to create()
... ...
@@ -19,49 +19,40 @@ type pathCache interface {
19 19
 // imageContexts is a helper for stacking up built image rootfs and reusing
20 20
 // them as contexts
21 21
 type imageContexts struct {
22
-	b              *Builder
23
-	list           []*imageMount // indexed list of stages
24
-	implicitMounts []*imageMount // implicitly mounted images
25
-	byName         map[string]*imageMount
26
-	cache          pathCache
22
+	list   []*imageMount
23
+	byName map[string]*imageMount
24
+	cache  pathCache
27 25
 }
28 26
 
29
-func (ic *imageContexts) newImageMount(id string) *imageMount {
30
-	return &imageMount{ic: ic, id: id}
31
-}
32
-
33
-func (ic *imageContexts) add(name string) (*imageMount, error) {
34
-	im := &imageMount{ic: ic}
27
+func (ic *imageContexts) add(name string, im *imageMount) error {
35 28
 	if len(name) > 0 {
36 29
 		if ic.byName == nil {
37 30
 			ic.byName = make(map[string]*imageMount)
38 31
 		}
39 32
 		if _, ok := ic.byName[name]; ok {
40
-			return nil, errors.Errorf("duplicate name %s", name)
33
+			return errors.Errorf("duplicate name %s", name)
41 34
 		}
42 35
 		ic.byName[name] = im
43 36
 	}
44 37
 	ic.list = append(ic.list, im)
45
-	return im, nil
38
+	return nil
46 39
 }
47 40
 
48 41
 func (ic *imageContexts) update(imageID string, runConfig *container.Config) {
49
-	ic.list[len(ic.list)-1].id = imageID
50
-	ic.list[len(ic.list)-1].runConfig = runConfig
42
+	ic.list[len(ic.list)-1].update(imageID, runConfig)
51 43
 }
52 44
 
53 45
 func (ic *imageContexts) validate(i int) error {
54 46
 	if i < 0 || i >= len(ic.list)-1 {
55
-		var extraMsg string
56 47
 		if i == len(ic.list)-1 {
57
-			extraMsg = " refers current build block"
48
+			return errors.Errorf("%d refers to current build stage", i)
58 49
 		}
59
-		return errors.Errorf("invalid from flag value %d%s", i, extraMsg)
50
+		return errors.Errorf("index out of bounds")
60 51
 	}
61 52
 	return nil
62 53
 }
63 54
 
64
-func (ic *imageContexts) get(indexOrName string) (*imageMount, error) {
55
+func (ic *imageContexts) getMount(indexOrName string) (*imageMount, error) {
65 56
 	index, err := strconv.Atoi(indexOrName)
66 57
 	if err == nil {
67 58
 		if err := ic.validate(index); err != nil {
... ...
@@ -72,12 +63,7 @@ func (ic *imageContexts) get(indexOrName string) (*imageMount, error) {
72 72
 	if im, ok := ic.byName[strings.ToLower(indexOrName)]; ok {
73 73
 		return im, nil
74 74
 	}
75
-	im, err := mountByRef(ic.b, indexOrName)
76
-	if err != nil {
77
-		return nil, errors.Wrapf(err, "invalid from flag value %s", indexOrName)
78
-	}
79
-	ic.implicitMounts = append(ic.implicitMounts, im)
80
-	return im, nil
75
+	return nil, nil
81 76
 }
82 77
 
83 78
 func (ic *imageContexts) unmount() (retErr error) {
... ...
@@ -98,6 +84,7 @@ func (ic *imageContexts) unmount() (retErr error) {
98 98
 	return
99 99
 }
100 100
 
101
+// TODO: remove getCache/setCache from imageContexts
101 102
 func (ic *imageContexts) getCache(id, path string) (interface{}, bool) {
102 103
 	if ic.cache != nil {
103 104
 		if id == "" {
... ...
@@ -114,48 +101,56 @@ func (ic *imageContexts) setCache(id, path string, v interface{}) {
114 114
 	}
115 115
 }
116 116
 
117
-// imageMount is a reference for getting access to a buildcontext that is backed
118
-// by an existing image
117
+// imageMount is a reference to an image that can be used as a builder.Source
119 118
 type imageMount struct {
120 119
 	id        string
121 120
 	source    builder.Source
122
-	release   func() error
123
-	ic        *imageContexts
124 121
 	runConfig *container.Config
122
+	layer     builder.ReleaseableLayer
123
+}
124
+
125
+func newImageMount(image builder.Image, layer builder.ReleaseableLayer) *imageMount {
126
+	im := &imageMount{layer: layer}
127
+	if image != nil {
128
+		im.update(image.ImageID(), image.RunConfig())
129
+	}
130
+	return im
125 131
 }
126 132
 
127 133
 func (im *imageMount) context() (builder.Source, error) {
128 134
 	if im.source == nil {
129
-		if im.id == "" {
130
-			return nil, errors.Errorf("could not copy from empty context")
135
+		if im.id == "" || im.layer == nil {
136
+			return nil, errors.Errorf("empty context")
131 137
 		}
132
-		p, release, err := im.ic.b.docker.MountImage(im.id)
138
+		mountPath, err := im.layer.Mount()
133 139
 		if err != nil {
134 140
 			return nil, errors.Wrapf(err, "failed to mount %s", im.id)
135 141
 		}
136
-		source, err := remotecontext.NewLazyContext(p)
142
+		source, err := remotecontext.NewLazyContext(mountPath)
137 143
 		if err != nil {
138
-			return nil, errors.Wrapf(err, "failed to create lazycontext for %s", p)
144
+			return nil, errors.Wrapf(err, "failed to create lazycontext for %s", mountPath)
139 145
 		}
140
-		im.release = release
141 146
 		im.source = source
142 147
 	}
143 148
 	return im.source, nil
144 149
 }
145 150
 
146 151
 func (im *imageMount) unmount() error {
147
-	if im.release != nil {
148
-		if err := im.release(); err != nil {
149
-			return errors.Wrapf(err, "failed to unmount previous build image %s", im.id)
150
-		}
151
-		im.release = nil
152
+	if err := im.layer.Release(); err != nil {
153
+		return errors.Wrapf(err, "failed to unmount previous build image %s", im.id)
152 154
 	}
153 155
 	return nil
154 156
 }
155 157
 
158
+func (im *imageMount) update(imageID string, runConfig *container.Config) {
159
+	im.id = imageID
160
+	im.runConfig = runConfig
161
+}
162
+
156 163
 func (im *imageMount) ImageID() string {
157 164
 	return im.id
158 165
 }
166
+
159 167
 func (im *imageMount) RunConfig() *container.Config {
160 168
 	return im.runConfig
161 169
 }
... ...
@@ -372,7 +372,7 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
372 372
 	if imageSource != nil {
373 373
 		source, err = imageSource.context()
374 374
 		if err != nil {
375
-			return nil, err
375
+			return nil, errors.Wrapf(err, "failed to copy")
376 376
 		}
377 377
 	}
378 378
 
... ...
@@ -15,30 +15,15 @@ import (
15 15
 
16 16
 // MockBackend implements the builder.Backend interface for unit testing
17 17
 type MockBackend struct {
18
-	getImageOnBuildFunc  func(string) (builder.Image, error)
19
-	getImageOnBuildImage *mockImage
20
-	containerCreateFunc  func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error)
21
-	commitFunc           func(string, *backend.ContainerCommitConfig) (string, error)
22
-}
23
-
24
-func (m *MockBackend) GetImageOnBuild(name string) (builder.Image, error) {
25
-	if m.getImageOnBuildFunc != nil {
26
-		return m.getImageOnBuildFunc(name)
27
-	}
28
-	if m.getImageOnBuildImage != nil {
29
-		return m.getImageOnBuildImage, nil
30
-	}
31
-	return &mockImage{id: "theid"}, nil
18
+	containerCreateFunc func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error)
19
+	commitFunc          func(string, *backend.ContainerCommitConfig) (string, error)
20
+	getImageFunc        func(string) (builder.Image, builder.ReleaseableLayer, error)
32 21
 }
33 22
 
34 23
 func (m *MockBackend) TagImageWithReference(image.ID, reference.Named) error {
35 24
 	return nil
36 25
 }
37 26
 
38
-func (m *MockBackend) PullOnBuild(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer) (builder.Image, error) {
39
-	return nil, nil
40
-}
41
-
42 27
 func (m *MockBackend) ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool, attached chan struct{}) error {
43 28
 	return nil
44 29
 }
... ...
@@ -89,8 +74,12 @@ func (m *MockBackend) SquashImage(from string, to string) (string, error) {
89 89
 	return "", nil
90 90
 }
91 91
 
92
-func (m *MockBackend) MountImage(name string) (string, func() error, error) {
93
-	return "", func() error { return nil }, nil
92
+func (m *MockBackend) GetImageAndLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
93
+	if m.getImageFunc != nil {
94
+		return m.getImageFunc(refOrID)
95
+	}
96
+
97
+	return &mockImage{id: "theid"}, &mockLayer{}, nil
94 98
 }
95 99
 
96 100
 type mockImage struct {
... ...
@@ -116,3 +105,13 @@ func (mic *mockImageCache) GetCache(parentID string, cfg *container.Config) (str
116 116
 	}
117 117
 	return "", nil
118 118
 }
119
+
120
+type mockLayer struct{}
121
+
122
+func (l *mockLayer) Release() error {
123
+	return nil
124
+}
125
+
126
+func (l *mockLayer) Mount() (string, error) {
127
+	return "mountPath", nil
128
+}
... ...
@@ -8,12 +8,10 @@ import (
8 8
 
9 9
 	"github.com/docker/docker/api/types"
10 10
 	"github.com/docker/docker/container"
11
-	"github.com/docker/docker/layer"
12 11
 	"github.com/docker/docker/pkg/archive"
13 12
 	"github.com/docker/docker/pkg/chrootarchive"
14 13
 	"github.com/docker/docker/pkg/idtools"
15 14
 	"github.com/docker/docker/pkg/ioutils"
16
-	"github.com/docker/docker/pkg/stringid"
17 15
 	"github.com/docker/docker/pkg/symlink"
18 16
 	"github.com/docker/docker/pkg/system"
19 17
 	"github.com/pkg/errors"
... ...
@@ -470,34 +468,3 @@ func (daemon *Daemon) CopyOnBuild(cID, destPath, srcRoot, srcPath string, decomp
470 470
 
471 471
 	return fixPermissions(fullSrcPath, destPath, rootUID, rootGID, destExists)
472 472
 }
473
-
474
-// MountImage returns mounted path with rootfs of an image.
475
-func (daemon *Daemon) MountImage(name string) (string, func() error, error) {
476
-	img, err := daemon.GetImage(name)
477
-	if err != nil {
478
-		return "", nil, errors.Wrapf(err, "no such image: %s", name)
479
-	}
480
-
481
-	mountID := stringid.GenerateRandomID()
482
-	rwLayer, err := daemon.layerStore.CreateRWLayer(mountID, img.RootFS.ChainID(), nil)
483
-	if err != nil {
484
-		return "", nil, errors.Wrap(err, "failed to create rwlayer")
485
-	}
486
-
487
-	mountPath, err := rwLayer.Mount("")
488
-	if err != nil {
489
-		metadata, releaseErr := daemon.layerStore.ReleaseRWLayer(rwLayer)
490
-		if releaseErr != nil {
491
-			err = errors.Wrapf(err, "failed to release rwlayer: %s", releaseErr.Error())
492
-		}
493
-		layer.LogReleaseMetadata(metadata)
494
-		return "", nil, errors.Wrap(err, "failed to mount rwlayer")
495
-	}
496
-
497
-	return mountPath, func() error {
498
-		rwLayer.Unmount()
499
-		metadata, err := daemon.layerStore.ReleaseRWLayer(rwLayer)
500
-		layer.LogReleaseMetadata(metadata)
501
-		return err
502
-	}, nil
503
-}
504 473
new file mode 100644
... ...
@@ -0,0 +1,108 @@
0
+package daemon
1
+
2
+import (
3
+	"github.com/docker/distribution/reference"
4
+	"github.com/docker/docker/api/types"
5
+	"github.com/docker/docker/api/types/backend"
6
+	"github.com/docker/docker/builder"
7
+	"github.com/docker/docker/image"
8
+	"github.com/docker/docker/layer"
9
+	"github.com/docker/docker/pkg/stringid"
10
+	"github.com/docker/docker/registry"
11
+	"github.com/pkg/errors"
12
+	"golang.org/x/net/context"
13
+	"io"
14
+)
15
+
16
+type releaseableLayer struct {
17
+	rwLayer layer.RWLayer
18
+	release func(layer.RWLayer) error
19
+	mount   func() (layer.RWLayer, error)
20
+}
21
+
22
+func (rl *releaseableLayer) Release() error {
23
+	if rl.rwLayer == nil {
24
+		return nil
25
+	}
26
+	rl.rwLayer.Unmount()
27
+	return rl.release(rl.rwLayer)
28
+}
29
+
30
+func (rl *releaseableLayer) Mount() (string, error) {
31
+	var err error
32
+	// daemon.layerStore.CreateRWLayer(mountID, img.RootFS.ChainID(), nil)
33
+	rl.rwLayer, err = rl.mount()
34
+	if err != nil {
35
+		return "", errors.Wrap(err, "failed to create rwlayer")
36
+	}
37
+
38
+	mountPath, err := rl.rwLayer.Mount("")
39
+	if err != nil {
40
+		releaseErr := rl.release(rl.rwLayer)
41
+		if releaseErr != nil {
42
+			err = errors.Wrapf(err, "failed to release rwlayer: %s", releaseErr.Error())
43
+		}
44
+		return "", errors.Wrap(err, "failed to mount rwlayer")
45
+	}
46
+	return mountPath, err
47
+}
48
+
49
+func (daemon *Daemon) getReleasableLayerForImage(img *image.Image) (*releaseableLayer, error) {
50
+	mountFunc := func() (layer.RWLayer, error) {
51
+		mountID := stringid.GenerateRandomID()
52
+		return daemon.layerStore.CreateRWLayer(mountID, img.RootFS.ChainID(), nil)
53
+	}
54
+
55
+	releaseFunc := func(rwLayer layer.RWLayer) error {
56
+		metadata, err := daemon.layerStore.ReleaseRWLayer(rwLayer)
57
+		layer.LogReleaseMetadata(metadata)
58
+		return err
59
+	}
60
+
61
+	return &releaseableLayer{mount: mountFunc, release: releaseFunc}, nil
62
+}
63
+
64
+// TODO: could this use the regular daemon PullImage ?
65
+func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer) (*image.Image, error) {
66
+	ref, err := reference.ParseNormalizedNamed(name)
67
+	if err != nil {
68
+		return nil, err
69
+	}
70
+	ref = reference.TagNameOnly(ref)
71
+
72
+	pullRegistryAuth := &types.AuthConfig{}
73
+	if len(authConfigs) > 0 {
74
+		// The request came with a full auth config file, we prefer to use that
75
+		repoInfo, err := daemon.RegistryService.ResolveRepository(ref)
76
+		if err != nil {
77
+			return nil, err
78
+		}
79
+
80
+		resolvedConfig := registry.ResolveAuthConfig(authConfigs, repoInfo.Index)
81
+		pullRegistryAuth = &resolvedConfig
82
+	}
83
+
84
+	if err := daemon.pullImageWithReference(ctx, ref, nil, pullRegistryAuth, output); err != nil {
85
+		return nil, err
86
+	}
87
+	return daemon.GetImage(name)
88
+}
89
+
90
+// GetImageAndLayer returns an image and releaseable layer for a reference or ID
91
+func (daemon *Daemon) GetImageAndLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
92
+	if !opts.ForcePull {
93
+		image, _ := daemon.GetImage(refOrID)
94
+		// TODO: shouldn't we error out if error is different from "not found" ?
95
+		if image != nil {
96
+			layer, err := daemon.getReleasableLayerForImage(image)
97
+			return image, layer, err
98
+		}
99
+	}
100
+
101
+	image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output)
102
+	if err != nil {
103
+		return nil, nil, err
104
+	}
105
+	layer, err := daemon.getReleasableLayerForImage(image)
106
+	return image, layer, err
107
+}
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"fmt"
5 5
 
6 6
 	"github.com/docker/distribution/reference"
7
-	"github.com/docker/docker/builder"
8 7
 	"github.com/docker/docker/image"
9 8
 	"github.com/docker/docker/pkg/stringid"
10 9
 )
... ...
@@ -75,12 +74,3 @@ func (daemon *Daemon) GetImage(refOrID string) (*image.Image, error) {
75 75
 	}
76 76
 	return daemon.imageStore.Get(imgID)
77 77
 }
78
-
79
-// GetImageOnBuild looks up a Docker image referenced by `name`.
80
-func (daemon *Daemon) GetImageOnBuild(name string) (builder.Image, error) {
81
-	img, err := daemon.GetImage(name)
82
-	if err != nil {
83
-		return nil, err
84
-	}
85
-	return img, nil
86
-}
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	dist "github.com/docker/distribution"
8 8
 	"github.com/docker/distribution/reference"
9 9
 	"github.com/docker/docker/api/types"
10
-	"github.com/docker/docker/builder"
11 10
 	"github.com/docker/docker/distribution"
12 11
 	progressutils "github.com/docker/docker/distribution/utils"
13 12
 	"github.com/docker/docker/pkg/progress"
... ...
@@ -46,35 +45,6 @@ func (daemon *Daemon) PullImage(ctx context.Context, image, tag string, metaHead
46 46
 	return daemon.pullImageWithReference(ctx, ref, metaHeaders, authConfig, outStream)
47 47
 }
48 48
 
49
-// PullOnBuild tells Docker to pull image referenced by `name`.
50
-func (daemon *Daemon) PullOnBuild(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer) (builder.Image, error) {
51
-	ref, err := reference.ParseNormalizedNamed(name)
52
-	if err != nil {
53
-		return nil, err
54
-	}
55
-	ref = reference.TagNameOnly(ref)
56
-
57
-	pullRegistryAuth := &types.AuthConfig{}
58
-	if len(authConfigs) > 0 {
59
-		// The request came with a full auth config file, we prefer to use that
60
-		repoInfo, err := daemon.RegistryService.ResolveRepository(ref)
61
-		if err != nil {
62
-			return nil, err
63
-		}
64
-
65
-		resolvedConfig := registry.ResolveAuthConfig(
66
-			authConfigs,
67
-			repoInfo.Index,
68
-		)
69
-		pullRegistryAuth = &resolvedConfig
70
-	}
71
-
72
-	if err := daemon.pullImageWithReference(ctx, ref, nil, pullRegistryAuth, output); err != nil {
73
-		return nil, err
74
-	}
75
-	return daemon.GetImage(name)
76
-}
77
-
78 49
 func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
79 50
 	// Include a buffer so that slow client connections don't affect
80 51
 	// transfer performance.