Browse code

Refactor imageContexts into two different structs.

buildStages now tracks the imageID and runConfig for a build stage

imageMounter tracks image mounts so they can released when the build ends.

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

Daniel Nephin authored on 2017/05/06 07:52:11
Showing 9 changed files
... ...
@@ -23,7 +23,7 @@ type BuildConfig struct {
23 23
 	Options        *types.ImageBuildOptions
24 24
 }
25 25
 
26
-// GetImageAndLayerOptions are the options supported by GetImageAndLayer
26
+// GetImageAndLayerOptions are the options supported by GetImageAndReleasableLayer
27 27
 type GetImageAndLayerOptions struct {
28 28
 	ForcePull  bool
29 29
 	AuthConfig map[string]types.AuthConfig
... ...
@@ -34,7 +34,7 @@ type Source interface {
34 34
 
35 35
 // Backend abstracts calls to a Docker Daemon.
36 36
 type Backend interface {
37
-	GetImageAndLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (Image, ReleaseableLayer, error)
37
+	ImageBackend
38 38
 
39 39
 	// ContainerAttachRaw attaches to container.
40 40
 	ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool, attached chan struct{}) error
... ...
@@ -60,6 +60,11 @@ type Backend interface {
60 60
 	CopyOnBuild(containerID string, destPath string, srcRoot string, srcPath string, decompress bool) error
61 61
 }
62 62
 
63
+// ImageBackend are the interface methods required from an image component
64
+type ImageBackend interface {
65
+	GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (Image, ReleaseableLayer, error)
66
+}
67
+
63 68
 // Result is the output produced by a Builder
64 69
 type Result struct {
65 70
 	ImageID   string
... ...
@@ -89,5 +94,5 @@ type Image interface {
89 89
 // ReleaseableLayer is an image layer that can be mounted and released
90 90
 type ReleaseableLayer interface {
91 91
 	Release() error
92
-	Mount(string) (string, error)
92
+	Mount() (string, error)
93 93
 }
... ...
@@ -102,11 +102,12 @@ type Builder struct {
102 102
 	clientCtx context.Context
103 103
 
104 104
 	tmpContainers map[string]struct{}
105
-	imageContexts *imageContexts // helper for storing contexts from builds
105
+	buildStages   *buildStages
106 106
 	disableCommit bool
107 107
 	cacheBusted   bool
108 108
 	buildArgs     *buildArgs
109 109
 	imageCache    builder.ImageCache
110
+	imageSources  *imageSources
110 111
 }
111 112
 
112 113
 // newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
... ...
@@ -125,7 +126,8 @@ func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
125 125
 		docker:        options.Backend,
126 126
 		tmpContainers: map[string]struct{}{},
127 127
 		buildArgs:     newBuildArgs(config.BuildArgs),
128
-		imageContexts: &imageContexts{},
128
+		buildStages:   newBuildStages(),
129
+		imageSources:  newImageSources(clientCtx, options),
129 130
 	}
130 131
 	return b
131 132
 }
... ...
@@ -140,7 +142,7 @@ func (b *Builder) resetImageCache() {
140 140
 // Build runs the Dockerfile builder by parsing the Dockerfile and executing
141 141
 // the instructions from the file.
142 142
 func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*builder.Result, error) {
143
-	defer b.imageContexts.unmount()
143
+	defer b.imageSources.Unmount()
144 144
 
145 145
 	// TODO: Remove source field from Builder
146 146
 	b.source = source
... ...
@@ -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"
24 23
 	"github.com/docker/docker/api/types/container"
25 24
 	"github.com/docker/docker/api/types/strslice"
25
+	"github.com/docker/docker/builder"
26 26
 	"github.com/docker/docker/builder/dockerfile/parser"
27 27
 	"github.com/docker/docker/pkg/signal"
28 28
 	"github.com/docker/go-connections/nat"
... ...
@@ -174,14 +174,19 @@ func dispatchCopy(req dispatchRequest) error {
174 174
 
175 175
 func (b *Builder) getImageMount(fromFlag *Flag) (*imageMount, error) {
176 176
 	if !fromFlag.IsUsed() {
177
-		// TODO: this could return the mount in the default case as well
177
+		// TODO: this could return the source in the default case as well?
178 178
 		return nil, nil
179 179
 	}
180
-	im, err := b.imageContexts.getMount(fromFlag.Value)
181
-	if err != nil || im != nil {
182
-		return im, err
180
+
181
+	imageRefOrID := fromFlag.Value
182
+	stage, err := b.buildStages.get(fromFlag.Value)
183
+	if err != nil {
184
+		return nil, err
185
+	}
186
+	if stage != nil {
187
+		imageRefOrID = stage.ImageID()
183 188
 	}
184
-	return b.getImage(fromFlag.Value)
189
+	return b.imageSources.Get(imageRefOrID)
185 190
 }
186 191
 
187 192
 // FROM imagename[:tag | @digest] [AS build-stage-name]
... ...
@@ -201,7 +206,7 @@ func from(req dispatchRequest) error {
201 201
 	if err != nil {
202 202
 		return err
203 203
 	}
204
-	if err := req.builder.imageContexts.add(stageName, image); err != nil {
204
+	if err := req.builder.buildStages.add(stageName, image); err != nil {
205 205
 		return err
206 206
 	}
207 207
 	req.state.beginStage(stageName, image)
... ...
@@ -229,7 +234,12 @@ func parseBuildStageName(args []string) (string, error) {
229 229
 	return stageName, nil
230 230
 }
231 231
 
232
-func (b *Builder) getFromImage(shlex *ShellLex, name string) (*imageMount, error) {
232
+// scratchImage is used as a token for the empty base image. It uses buildStage
233
+// as a convenient implementation of builder.Image, but is not actually a
234
+// buildStage.
235
+var scratchImage builder.Image = &buildStage{}
236
+
237
+func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, error) {
233 238
 	substitutionArgs := []string{}
234 239
 	for key, value := range b.buildArgs.GetAllMeta() {
235 240
 		substitutionArgs = append(substitutionArgs, key+"="+value)
... ...
@@ -240,7 +250,7 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (*imageMount, error
240 240
 		return nil, err
241 241
 	}
242 242
 
243
-	if im, ok := b.imageContexts.byName[name]; ok {
243
+	if im, ok := b.buildStages.getByName(name); ok {
244 244
 		return im, nil
245 245
 	}
246 246
 
... ...
@@ -249,21 +259,13 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (*imageMount, error
249 249
 		if runtime.GOOS == "windows" {
250 250
 			return nil, errors.New("Windows does not support FROM scratch")
251 251
 		}
252
-		return newImageMount(nil, nil), nil
252
+		return scratchImage, nil
253 253
 	}
254
-	return b.getImage(name)
255
-}
256
-
257
-func (b *Builder) getImage(name string) (*imageMount, error) {
258
-	image, layer, err := b.docker.GetImageAndLayer(b.clientCtx, name, backend.GetImageAndLayerOptions{
259
-		ForcePull:  b.options.PullParent,
260
-		AuthConfig: b.options.AuthConfigs,
261
-		Output:     b.Output,
262
-	})
254
+	imageMount, err := b.imageSources.Get(name)
263 255
 	if err != nil {
264 256
 		return nil, err
265 257
 	}
266
-	return newImageMount(image, layer), nil
258
+	return imageMount.Image(), nil
267 259
 }
268 260
 
269 261
 func processOnBuild(req dispatchRequest) error {
... ...
@@ -49,15 +49,20 @@ func defaultDispatchReq(builder *Builder, args ...string) dispatchRequest {
49 49
 
50 50
 func newBuilderWithMockBackend() *Builder {
51 51
 	mockBackend := &MockBackend{}
52
+	ctx := context.Background()
52 53
 	b := &Builder{
53 54
 		options:       &types.ImageBuildOptions{},
54 55
 		docker:        mockBackend,
55 56
 		buildArgs:     newBuildArgs(make(map[string]*string)),
56 57
 		tmpContainers: make(map[string]struct{}),
57 58
 		Stdout:        new(bytes.Buffer),
58
-		clientCtx:     context.Background(),
59
+		clientCtx:     ctx,
59 60
 		disableCommit: true,
60
-		imageContexts: &imageContexts{},
61
+		imageSources: newImageSources(ctx, builderOptions{
62
+			Options: &types.ImageBuildOptions{},
63
+			Backend: mockBackend,
64
+		}),
65
+		buildStages: newBuildStages(),
61 66
 	}
62 67
 	return b
63 68
 }
... ...
@@ -5,10 +5,12 @@ import (
5 5
 	"strings"
6 6
 
7 7
 	"github.com/Sirupsen/logrus"
8
+	"github.com/docker/docker/api/types/backend"
8 9
 	"github.com/docker/docker/api/types/container"
9 10
 	"github.com/docker/docker/builder"
10 11
 	"github.com/docker/docker/builder/remotecontext"
11 12
 	"github.com/pkg/errors"
13
+	"golang.org/x/net/context"
12 14
 )
13 15
 
14 16
 type pathCache interface {
... ...
@@ -16,66 +18,128 @@ type pathCache interface {
16 16
 	Store(key, value interface{})
17 17
 }
18 18
 
19
-// imageContexts is a helper for stacking up built image rootfs and reusing
20
-// them as contexts
21
-type imageContexts struct {
22
-	list   []*imageMount
23
-	byName map[string]*imageMount
24
-	cache  pathCache
19
+type buildStage struct {
20
+	id     string
21
+	config *container.Config
25 22
 }
26 23
 
27
-func (ic *imageContexts) add(name string, im *imageMount) error {
28
-	if len(name) > 0 {
29
-		if ic.byName == nil {
30
-			ic.byName = make(map[string]*imageMount)
31
-		}
32
-		if _, ok := ic.byName[name]; ok {
33
-			return errors.Errorf("duplicate name %s", name)
34
-		}
35
-		ic.byName[name] = im
36
-	}
37
-	ic.list = append(ic.list, im)
38
-	return nil
24
+func newBuildStageFromImage(image builder.Image) *buildStage {
25
+	return &buildStage{id: image.ImageID(), config: image.RunConfig()}
39 26
 }
40 27
 
41
-func (ic *imageContexts) update(imageID string, runConfig *container.Config) {
42
-	ic.list[len(ic.list)-1].update(imageID, runConfig)
28
+func (b *buildStage) ImageID() string {
29
+	return b.id
43 30
 }
44 31
 
45
-func (ic *imageContexts) validate(i int) error {
46
-	if i < 0 || i >= len(ic.list)-1 {
47
-		if i == len(ic.list)-1 {
48
-			return errors.New("refers to current build stage")
49
-		}
50
-		return errors.New("index out of bounds")
51
-	}
52
-	return nil
32
+func (b *buildStage) RunConfig() *container.Config {
33
+	return b.config
53 34
 }
54 35
 
55
-func (ic *imageContexts) getMount(indexOrName string) (*imageMount, error) {
36
+func (b *buildStage) update(imageID string, runConfig *container.Config) {
37
+	b.id = imageID
38
+	b.config = runConfig
39
+}
40
+
41
+var _ builder.Image = &buildStage{}
42
+
43
+// buildStages tracks each stage of a build so they can be retrieved by index
44
+// or by name.
45
+type buildStages struct {
46
+	sequence []*buildStage
47
+	byName   map[string]*buildStage
48
+}
49
+
50
+func newBuildStages() *buildStages {
51
+	return &buildStages{byName: make(map[string]*buildStage)}
52
+}
53
+
54
+func (s *buildStages) getByName(name string) (builder.Image, bool) {
55
+	stage, ok := s.byName[strings.ToLower(name)]
56
+	return stage, ok
57
+}
58
+
59
+func (s *buildStages) get(indexOrName string) (builder.Image, error) {
56 60
 	index, err := strconv.Atoi(indexOrName)
57 61
 	if err == nil {
58
-		if err := ic.validate(index); err != nil {
62
+		if err := s.validateIndex(index); err != nil {
59 63
 			return nil, err
60 64
 		}
61
-		return ic.list[index], nil
65
+		return s.sequence[index], nil
62 66
 	}
63
-	if im, ok := ic.byName[strings.ToLower(indexOrName)]; ok {
67
+	if im, ok := s.byName[strings.ToLower(indexOrName)]; ok {
64 68
 		return im, nil
65 69
 	}
66 70
 	return nil, nil
67 71
 }
68 72
 
69
-func (ic *imageContexts) unmount() (retErr error) {
70
-	for _, iml := range append([][]*imageMount{}, ic.list, ic.implicitMounts) {
71
-		for _, im := range iml {
72
-			if err := im.unmount(); err != nil {
73
-				logrus.Error(err)
74
-				retErr = err
75
-			}
73
+func (s *buildStages) validateIndex(i int) error {
74
+	if i < 0 || i >= len(s.sequence)-1 {
75
+		if i == len(s.sequence)-1 {
76
+			return errors.New("refers to current build stage")
76 77
 		}
78
+		return errors.New("index out of bounds")
77 79
 	}
78
-	for _, im := range ic.byName {
80
+	return nil
81
+}
82
+
83
+func (s *buildStages) add(name string, image builder.Image) error {
84
+	stage := newBuildStageFromImage(image)
85
+	name = strings.ToLower(name)
86
+	if len(name) > 0 {
87
+		if _, ok := s.byName[name]; ok {
88
+			return errors.Errorf("duplicate name %s", name)
89
+		}
90
+		s.byName[name] = stage
91
+	}
92
+	s.sequence = append(s.sequence, stage)
93
+	return nil
94
+}
95
+
96
+func (s *buildStages) update(imageID string, runConfig *container.Config) {
97
+	s.sequence[len(s.sequence)-1].update(imageID, runConfig)
98
+}
99
+
100
+type getAndMountFunc func(string) (builder.Image, builder.ReleaseableLayer, error)
101
+
102
+// imageSources mounts images and provides a cache for mounted images. It tracks
103
+// all images so they can be unmounted at the end of the build.
104
+type imageSources struct {
105
+	byImageID map[string]*imageMount
106
+	getImage  getAndMountFunc
107
+	cache     pathCache // TODO: remove
108
+}
109
+
110
+func newImageSources(ctx context.Context, options builderOptions) *imageSources {
111
+	getAndMount := func(idOrRef string) (builder.Image, builder.ReleaseableLayer, error) {
112
+		return options.Backend.GetImageAndReleasableLayer(ctx, idOrRef, backend.GetImageAndLayerOptions{
113
+			ForcePull:  options.Options.PullParent,
114
+			AuthConfig: options.Options.AuthConfigs,
115
+			Output:     options.ProgressWriter.Output,
116
+		})
117
+	}
118
+
119
+	return &imageSources{
120
+		byImageID: make(map[string]*imageMount),
121
+		getImage:  getAndMount,
122
+	}
123
+}
124
+
125
+func (m *imageSources) Get(idOrRef string) (*imageMount, error) {
126
+	if im, ok := m.byImageID[idOrRef]; ok {
127
+		return im, nil
128
+	}
129
+
130
+	image, layer, err := m.getImage(idOrRef)
131
+	if err != nil {
132
+		return nil, err
133
+	}
134
+	im := newImageMount(image, layer)
135
+	m.byImageID[image.ImageID()] = im
136
+	return im, nil
137
+}
138
+
139
+func (m *imageSources) Unmount() (retErr error) {
140
+	for _, im := range m.byImageID {
79 141
 		if err := im.unmount(); err != nil {
80 142
 			logrus.Error(err)
81 143
 			retErr = err
... ...
@@ -84,47 +148,43 @@ func (ic *imageContexts) unmount() (retErr error) {
84 84
 	return
85 85
 }
86 86
 
87
-// TODO: remove getCache/setCache from imageContexts
88
-func (ic *imageContexts) getCache(id, path string) (interface{}, bool) {
89
-	if ic.cache != nil {
87
+// TODO: remove getCache/setCache from imageSources
88
+func (m *imageSources) getCache(id, path string) (interface{}, bool) {
89
+	if m.cache != nil {
90 90
 		if id == "" {
91 91
 			return nil, false
92 92
 		}
93
-		return ic.cache.Load(id + path)
93
+		return m.cache.Load(id + path)
94 94
 	}
95 95
 	return nil, false
96 96
 }
97 97
 
98
-func (ic *imageContexts) setCache(id, path string, v interface{}) {
99
-	if ic.cache != nil {
100
-		ic.cache.Store(id+path, v)
98
+func (m *imageSources) setCache(id, path string, v interface{}) {
99
+	if m.cache != nil {
100
+		m.cache.Store(id+path, v)
101 101
 	}
102 102
 }
103 103
 
104 104
 // imageMount is a reference to an image that can be used as a builder.Source
105 105
 type imageMount struct {
106
-	id        string
107
-	source    builder.Source
108
-	runConfig *container.Config
109
-	layer     builder.ReleaseableLayer
106
+	image  builder.Image
107
+	source builder.Source
108
+	layer  builder.ReleaseableLayer
110 109
 }
111 110
 
112 111
 func newImageMount(image builder.Image, layer builder.ReleaseableLayer) *imageMount {
113
-	im := &imageMount{layer: layer}
114
-	if image != nil {
115
-		im.update(image.ImageID(), image.RunConfig())
116
-	}
112
+	im := &imageMount{image: image, layer: layer}
117 113
 	return im
118 114
 }
119 115
 
120
-func (im *imageMount) context() (builder.Source, error) {
116
+func (im *imageMount) Source() (builder.Source, error) {
121 117
 	if im.source == nil {
122
-		if im.id == "" || im.layer == nil {
118
+		if im.layer == nil {
123 119
 			return nil, errors.Errorf("empty context")
124 120
 		}
125
-		mountPath, err := im.layer.Mount(im.id)
121
+		mountPath, err := im.layer.Mount()
126 122
 		if err != nil {
127
-			return nil, errors.Wrapf(err, "failed to mount %s", im.id)
123
+			return nil, errors.Wrapf(err, "failed to mount %s", im.image.ImageID())
128 124
 		}
129 125
 		source, err := remotecontext.NewLazyContext(mountPath)
130 126
 		if err != nil {
... ...
@@ -140,20 +200,11 @@ func (im *imageMount) unmount() error {
140 140
 		return nil
141 141
 	}
142 142
 	if err := im.layer.Release(); err != nil {
143
-		return errors.Wrapf(err, "failed to unmount previous build image %s", im.id)
143
+		return errors.Wrapf(err, "failed to unmount previous build image %s", im.image.ImageID())
144 144
 	}
145 145
 	return nil
146 146
 }
147 147
 
148
-func (im *imageMount) update(imageID string, runConfig *container.Config) {
149
-	im.id = imageID
150
-	im.runConfig = runConfig
151
-}
152
-
153
-func (im *imageMount) ImageID() string {
154
-	return im.id
155
-}
156
-
157
-func (im *imageMount) RunConfig() *container.Config {
158
-	return im.runConfig
148
+func (im *imageMount) Image() builder.Image {
149
+	return im.image
159 150
 }
... ...
@@ -78,7 +78,7 @@ func (b *Builder) commitContainer(dispatchState *dispatchState, id string, conta
78 78
 	}
79 79
 
80 80
 	dispatchState.imageID = imageID
81
-	b.imageContexts.update(imageID, dispatchState.runConfig)
81
+	b.buildStages.update(imageID, dispatchState.runConfig)
82 82
 	return nil
83 83
 }
84 84
 
... ...
@@ -369,7 +369,7 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
369 369
 	source := b.source
370 370
 	var err error
371 371
 	if imageSource != nil {
372
-		source, err = imageSource.context()
372
+		source, err = imageSource.Source()
373 373
 		if err != nil {
374 374
 			return nil, errors.Wrapf(err, "failed to copy")
375 375
 		}
... ...
@@ -427,7 +427,7 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
427 427
 
428 428
 	if imageSource != nil {
429 429
 		// fast-cache based on imageID
430
-		if h, ok := b.imageContexts.getCache(imageSource.id, origPath); ok {
430
+		if h, ok := b.imageSources.getCache(imageSource.Image().ImageID(), origPath); ok {
431 431
 			copyInfos[0].hash = h.(string)
432 432
 			return copyInfos, nil
433 433
 		}
... ...
@@ -473,7 +473,7 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
473 473
 	hasher.Write([]byte(strings.Join(subfiles, ",")))
474 474
 	copyInfos[0].hash = "dir:" + hex.EncodeToString(hasher.Sum(nil))
475 475
 	if imageSource != nil {
476
-		b.imageContexts.setCache(imageSource.id, origPath, copyInfos[0].hash)
476
+		b.imageSources.setCache(imageSource.Image().ImageID(), origPath, copyInfos[0].hash)
477 477
 	}
478 478
 
479 479
 	return copyInfos, nil
... ...
@@ -501,7 +501,7 @@ func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.
501 501
 	fmt.Fprint(b.Stdout, " ---> Using cache\n")
502 502
 	logrus.Debugf("[BUILDER] Use cached version: %s", runConfig.Cmd)
503 503
 	dispatchState.imageID = string(cache)
504
-	b.imageContexts.update(dispatchState.imageID, runConfig)
504
+	b.buildStages.update(dispatchState.imageID, runConfig)
505 505
 
506 506
 	return true, nil
507 507
 }
... ...
@@ -66,15 +66,7 @@ func (m *MockBackend) CopyOnBuild(containerID string, destPath string, srcRoot s
66 66
 	return nil
67 67
 }
68 68
 
69
-func (m *MockBackend) HasExperimental() bool {
70
-	return false
71
-}
72
-
73
-func (m *MockBackend) SquashImage(from string, to string) (string, error) {
74
-	return "", nil
75
-}
76
-
77
-func (m *MockBackend) GetImageAndLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
69
+func (m *MockBackend) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
78 70
 	if m.getImageFunc != nil {
79 71
 		return m.getImageFunc(refOrID)
80 72
 	}
... ...
@@ -112,6 +104,6 @@ func (l *mockLayer) Release() error {
112 112
 	return nil
113 113
 }
114 114
 
115
-func (l *mockLayer) Mount(_ string) (string, error) {
115
+func (l *mockLayer) Mount() (string, error) {
116 116
 	return "mountPath", nil
117 117
 }
... ...
@@ -1,6 +1,7 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"github.com/Sirupsen/logrus"
4 5
 	"github.com/docker/distribution/reference"
5 6
 	"github.com/docker/docker/api/types"
6 7
 	"github.com/docker/docker/api/types/backend"
... ...
@@ -15,54 +16,62 @@ import (
15 15
 )
16 16
 
17 17
 type releaseableLayer struct {
18
-	rwLayer layer.RWLayer
19
-	release func(layer.RWLayer) error
20
-	mount   func(string) (layer.RWLayer, error)
18
+	layerStore layer.Store
19
+	roLayer    layer.Layer
20
+	rwLayer    layer.RWLayer
21 21
 }
22 22
 
23
-func (rl *releaseableLayer) Release() error {
24
-	if rl.rwLayer == nil {
25
-		return nil
23
+func (rl *releaseableLayer) Mount() (string, error) {
24
+	if rl.roLayer == nil {
25
+		return "", errors.New("can not mount an image with no root FS")
26 26
 	}
27
-	rl.rwLayer.Unmount()
28
-	return rl.release(rl.rwLayer)
29
-}
30
-
31
-func (rl *releaseableLayer) Mount(imageID string) (string, error) {
32 27
 	var err error
33
-	rl.rwLayer, err = rl.mount(imageID)
28
+	mountID := stringid.GenerateRandomID()
29
+	rl.rwLayer, err = rl.layerStore.CreateRWLayer(mountID, rl.roLayer.ChainID(), nil)
34 30
 	if err != nil {
35 31
 		return "", errors.Wrap(err, "failed to create rwlayer")
36 32
 	}
37 33
 
38
-	mountPath, err := rl.rwLayer.Mount("")
34
+	return rl.rwLayer.Mount("")
35
+}
36
+
37
+func (rl *releaseableLayer) Release() error {
38
+	rl.releaseRWLayer()
39
+	return rl.releaseROLayer()
40
+}
41
+
42
+func (rl *releaseableLayer) releaseRWLayer() error {
43
+	if rl.rwLayer == nil {
44
+		return nil
45
+	}
46
+	metadata, err := rl.layerStore.ReleaseRWLayer(rl.rwLayer)
47
+	layer.LogReleaseMetadata(metadata)
39 48
 	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")
49
+		logrus.Errorf("Failed to release RWLayer: %s", err)
45 50
 	}
46
-	return mountPath, err
51
+	return err
47 52
 }
48 53
 
49
-func (daemon *Daemon) getReleasableLayerForImage() *releaseableLayer {
50
-	mountFunc := func(imageID string) (layer.RWLayer, error) {
51
-		img, err := daemon.GetImage(imageID)
52
-		if err != nil {
53
-			return nil, err
54
-		}
55
-		mountID := stringid.GenerateRandomID()
56
-		return daemon.layerStore.CreateRWLayer(mountID, img.RootFS.ChainID(), nil)
54
+func (rl *releaseableLayer) releaseROLayer() error {
55
+	if rl.roLayer == nil {
56
+		return nil
57 57
 	}
58
+	metadata, err := rl.layerStore.Release(rl.roLayer)
59
+	layer.LogReleaseMetadata(metadata)
60
+	return err
61
+}
58 62
 
59
-	releaseFunc := func(rwLayer layer.RWLayer) error {
60
-		metadata, err := daemon.layerStore.ReleaseRWLayer(rwLayer)
61
-		layer.LogReleaseMetadata(metadata)
62
-		return err
63
+func newReleasableLayerForImage(img *image.Image, layerStore layer.Store) (builder.ReleaseableLayer, error) {
64
+	if img.RootFS.ChainID() == "" {
65
+		return nil, nil
63 66
 	}
64
-
65
-	return &releaseableLayer{mount: mountFunc, release: releaseFunc}
67
+	// Hold a reference to the image layer so that it can't be removed before
68
+	// it is released
69
+	roLayer, err := layerStore.Get(img.RootFS.ChainID())
70
+	if err != nil {
71
+		return nil, errors.Wrapf(err, "failed to get layer for image %s", img.ImageID())
72
+	}
73
+	return &releaseableLayer{layerStore: layerStore, roLayer: roLayer}, nil
66 74
 }
67 75
 
68 76
 // TODO: could this use the regular daemon PullImage ?
... ...
@@ -75,7 +84,7 @@ func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfi
75 75
 
76 76
 	pullRegistryAuth := &types.AuthConfig{}
77 77
 	if len(authConfigs) > 0 {
78
-		// The request came with a full auth config file, we prefer to use that
78
+		// The request came with a full auth config, use it
79 79
 		repoInfo, err := daemon.RegistryService.ResolveRepository(ref)
80 80
 		if err != nil {
81 81
 			return nil, err
... ...
@@ -91,16 +100,23 @@ func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfi
91 91
 	return daemon.GetImage(name)
92 92
 }
93 93
 
94
-// GetImageAndLayer returns an image and releaseable layer for a reference or ID
95
-func (daemon *Daemon) GetImageAndLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
94
+// GetImageAndReleasableLayer returns an image and releaseable layer for a reference or ID.
95
+// Every call to GetImageAndReleasableLayer MUST call releasableLayer.Release() to prevent
96
+// leaking of layers.
97
+func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
96 98
 	if !opts.ForcePull {
97 99
 		image, _ := daemon.GetImage(refOrID)
98 100
 		// TODO: shouldn't we error out if error is different from "not found" ?
99 101
 		if image != nil {
100
-			return image, daemon.getReleasableLayerForImage(), nil
102
+			layer, err := newReleasableLayerForImage(image, daemon.layerStore)
103
+			return image, layer, err
101 104
 		}
102 105
 	}
103 106
 
104 107
 	image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output)
105
-	return image, daemon.getReleasableLayerForImage(), err
108
+	if err != nil {
109
+		return nil, nil, err
110
+	}
111
+	layer, err := newReleasableLayerForImage(image, daemon.layerStore)
112
+	return image, layer, err
106 113
 }