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>
... | ... |
@@ -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 |
} |