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