Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
| ... | ... |
@@ -53,7 +53,7 @@ type Backend interface {
|
| 53 | 53 |
|
| 54 | 54 |
// ImageBackend are the interface methods required from an image component |
| 55 | 55 |
type ImageBackend interface {
|
| 56 |
- GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (Image, ReleaseableLayer, error) |
|
| 56 |
+ GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (Image, ROLayer, error) |
|
| 57 | 57 |
} |
| 58 | 58 |
|
| 59 | 59 |
// ExecBackend contains the interface methods required for executing containers |
| ... | ... |
@@ -100,10 +100,16 @@ type Image interface {
|
| 100 | 100 |
OperatingSystem() string |
| 101 | 101 |
} |
| 102 | 102 |
|
| 103 |
-// ReleaseableLayer is an image layer that can be mounted and released |
|
| 104 |
-type ReleaseableLayer interface {
|
|
| 103 |
+// ROLayer is a reference to image rootfs layer |
|
| 104 |
+type ROLayer interface {
|
|
| 105 | 105 |
Release() error |
| 106 |
- Mount() (containerfs.ContainerFS, error) |
|
| 107 |
- Commit() (ReleaseableLayer, error) |
|
| 106 |
+ NewRWLayer() (RWLayer, error) |
|
| 108 | 107 |
DiffID() layer.DiffID |
| 109 | 108 |
} |
| 109 |
+ |
|
| 110 |
+// RWLayer is active layer that can be read/modified |
|
| 111 |
+type RWLayer interface {
|
|
| 112 |
+ Release() error |
|
| 113 |
+ Root() containerfs.ContainerFS |
|
| 114 |
+ Commit() (ROLayer, error) |
|
| 115 |
+} |
| ... | ... |
@@ -72,8 +72,12 @@ type copier struct {
|
| 72 | 72 |
source builder.Source |
| 73 | 73 |
pathCache pathCache |
| 74 | 74 |
download sourceDownloader |
| 75 |
- tmpPaths []string |
|
| 76 | 75 |
platform string |
| 76 |
+ // for cleanup. TODO: having copier.cleanup() is error prone and hard to |
|
| 77 |
+ // follow. Code calling performCopy should manage the lifecycle of its params. |
|
| 78 |
+ // Copier should take override source as input, not imageMount. |
|
| 79 |
+ activeLayer builder.RWLayer |
|
| 80 |
+ tmpPaths []string |
|
| 77 | 81 |
} |
| 78 | 82 |
|
| 79 | 83 |
func copierFromDispatchRequest(req dispatchRequest, download sourceDownloader, imageSource *imageMount) copier {
|
| ... | ... |
@@ -155,6 +159,10 @@ func (o *copier) Cleanup() {
|
| 155 | 155 |
os.RemoveAll(path) |
| 156 | 156 |
} |
| 157 | 157 |
o.tmpPaths = []string{}
|
| 158 |
+ if o.activeLayer != nil {
|
|
| 159 |
+ o.activeLayer.Release() |
|
| 160 |
+ o.activeLayer = nil |
|
| 161 |
+ } |
|
| 158 | 162 |
} |
| 159 | 163 |
|
| 160 | 164 |
// TODO: allowWildcards can probably be removed by refactoring this function further. |
| ... | ... |
@@ -166,9 +174,15 @@ func (o *copier) calcCopyInfo(origPath string, allowWildcards bool) ([]copyInfo, |
| 166 | 166 |
// done on image Source? |
| 167 | 167 |
if imageSource != nil {
|
| 168 | 168 |
var err error |
| 169 |
- o.source, err = imageSource.Source() |
|
| 169 |
+ rwLayer, err := imageSource.NewRWLayer() |
|
| 170 |
+ if err != nil {
|
|
| 171 |
+ return nil, err |
|
| 172 |
+ } |
|
| 173 |
+ o.activeLayer = rwLayer |
|
| 174 |
+ |
|
| 175 |
+ o.source, err = remotecontext.NewLazySource(rwLayer.Root()) |
|
| 170 | 176 |
if err != nil {
|
| 171 |
- return nil, errors.Wrapf(err, "failed to copy from %s", imageSource.ImageID()) |
|
| 177 |
+ return nil, errors.Wrapf(err, "failed to create context for copy from %s", rwLayer.Root().Path()) |
|
| 172 | 178 |
} |
| 173 | 179 |
} |
| 174 | 180 |
|
| ... | ... |
@@ -127,7 +127,7 @@ func TestFromScratch(t *testing.T) {
|
| 127 | 127 |
func TestFromWithArg(t *testing.T) {
|
| 128 | 128 |
tag, expected := ":sometag", "expectedthisid" |
| 129 | 129 |
|
| 130 |
- getImage := func(name string) (builder.Image, builder.ReleaseableLayer, error) {
|
|
| 130 |
+ getImage := func(name string) (builder.Image, builder.ROLayer, error) {
|
|
| 131 | 131 |
assert.Equal(t, "alpine"+tag, name) |
| 132 | 132 |
return &mockImage{id: "expectedthisid"}, nil, nil
|
| 133 | 133 |
} |
| ... | ... |
@@ -159,7 +159,7 @@ func TestFromWithArg(t *testing.T) {
|
| 159 | 159 |
func TestFromWithUndefinedArg(t *testing.T) {
|
| 160 | 160 |
tag, expected := "sometag", "expectedthisid" |
| 161 | 161 |
|
| 162 |
- getImage := func(name string) (builder.Image, builder.ReleaseableLayer, error) {
|
|
| 162 |
+ getImage := func(name string) (builder.Image, builder.ROLayer, error) {
|
|
| 163 | 163 |
assert.Equal(t, "alpine", name) |
| 164 | 164 |
return &mockImage{id: "expectedthisid"}, nil, nil
|
| 165 | 165 |
} |
| ... | ... |
@@ -433,7 +433,7 @@ func TestRunWithBuildArgs(t *testing.T) {
|
| 433 | 433 |
return imageCache |
| 434 | 434 |
} |
| 435 | 435 |
b.imageProber = newImageProber(mockBackend, nil, false) |
| 436 |
- mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ReleaseableLayer, error) {
|
|
| 436 |
+ mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) {
|
|
| 437 | 437 |
return &mockImage{
|
| 438 | 438 |
id: "abcdef", |
| 439 | 439 |
config: &container.Config{Cmd: origCmd},
|
| ... | ... |
@@ -5,7 +5,6 @@ import ( |
| 5 | 5 |
|
| 6 | 6 |
"github.com/docker/docker/api/types/backend" |
| 7 | 7 |
"github.com/docker/docker/builder" |
| 8 |
- "github.com/docker/docker/builder/remotecontext" |
|
| 9 | 8 |
dockerimage "github.com/docker/docker/image" |
| 10 | 9 |
"github.com/docker/docker/pkg/system" |
| 11 | 10 |
"github.com/pkg/errors" |
| ... | ... |
@@ -13,7 +12,7 @@ import ( |
| 13 | 13 |
"golang.org/x/net/context" |
| 14 | 14 |
) |
| 15 | 15 |
|
| 16 |
-type getAndMountFunc func(string, bool) (builder.Image, builder.ReleaseableLayer, error) |
|
| 16 |
+type getAndMountFunc func(string, bool) (builder.Image, builder.ROLayer, error) |
|
| 17 | 17 |
|
| 18 | 18 |
// imageSources mounts images and provides a cache for mounted images. It tracks |
| 19 | 19 |
// all images so they can be unmounted at the end of the build. |
| ... | ... |
@@ -24,7 +23,7 @@ type imageSources struct {
|
| 24 | 24 |
} |
| 25 | 25 |
|
| 26 | 26 |
func newImageSources(ctx context.Context, options builderOptions) *imageSources {
|
| 27 |
- getAndMount := func(idOrRef string, localOnly bool) (builder.Image, builder.ReleaseableLayer, error) {
|
|
| 27 |
+ getAndMount := func(idOrRef string, localOnly bool) (builder.Image, builder.ROLayer, error) {
|
|
| 28 | 28 |
pullOption := backend.PullOptionNoPull |
| 29 | 29 |
if !localOnly {
|
| 30 | 30 |
if options.Options.PullParent {
|
| ... | ... |
@@ -92,32 +91,14 @@ func (m *imageSources) Add(im *imageMount) {
|
| 92 | 92 |
type imageMount struct {
|
| 93 | 93 |
image builder.Image |
| 94 | 94 |
source builder.Source |
| 95 |
- layer builder.ReleaseableLayer |
|
| 95 |
+ layer builder.ROLayer |
|
| 96 | 96 |
} |
| 97 | 97 |
|
| 98 |
-func newImageMount(image builder.Image, layer builder.ReleaseableLayer) *imageMount {
|
|
| 98 |
+func newImageMount(image builder.Image, layer builder.ROLayer) *imageMount {
|
|
| 99 | 99 |
im := &imageMount{image: image, layer: layer}
|
| 100 | 100 |
return im |
| 101 | 101 |
} |
| 102 | 102 |
|
| 103 |
-func (im *imageMount) Source() (builder.Source, error) {
|
|
| 104 |
- if im.source == nil {
|
|
| 105 |
- if im.layer == nil {
|
|
| 106 |
- return nil, errors.Errorf("empty context")
|
|
| 107 |
- } |
|
| 108 |
- mountPath, err := im.layer.Mount() |
|
| 109 |
- if err != nil {
|
|
| 110 |
- return nil, errors.Wrapf(err, "failed to mount %s", im.image.ImageID()) |
|
| 111 |
- } |
|
| 112 |
- source, err := remotecontext.NewLazySource(mountPath) |
|
| 113 |
- if err != nil {
|
|
| 114 |
- return nil, errors.Wrapf(err, "failed to create lazycontext for %s", mountPath) |
|
| 115 |
- } |
|
| 116 |
- im.source = source |
|
| 117 |
- } |
|
| 118 |
- return im.source, nil |
|
| 119 |
-} |
|
| 120 |
- |
|
| 121 | 103 |
func (im *imageMount) unmount() error {
|
| 122 | 104 |
if im.layer == nil {
|
| 123 | 105 |
return nil |
| ... | ... |
@@ -133,8 +114,8 @@ func (im *imageMount) Image() builder.Image {
|
| 133 | 133 |
return im.image |
| 134 | 134 |
} |
| 135 | 135 |
|
| 136 |
-func (im *imageMount) Layer() builder.ReleaseableLayer {
|
|
| 137 |
- return im.layer |
|
| 136 |
+func (im *imageMount) NewRWLayer() (builder.RWLayer, error) {
|
|
| 137 |
+ return im.layer.NewRWLayer() |
|
| 138 | 138 |
} |
| 139 | 139 |
|
| 140 | 140 |
func (im *imageMount) ImageID() string {
|
| ... | ... |
@@ -17,6 +17,7 @@ import ( |
| 17 | 17 |
"github.com/docker/docker/api/types" |
| 18 | 18 |
"github.com/docker/docker/api/types/backend" |
| 19 | 19 |
"github.com/docker/docker/api/types/container" |
| 20 |
+ "github.com/docker/docker/builder" |
|
| 20 | 21 |
"github.com/docker/docker/image" |
| 21 | 22 |
"github.com/docker/docker/pkg/archive" |
| 22 | 23 |
"github.com/docker/docker/pkg/chrootarchive" |
| ... | ... |
@@ -114,8 +115,8 @@ func (b *Builder) commitContainer(dispatchState *dispatchState, id string, conta |
| 114 | 114 |
return err |
| 115 | 115 |
} |
| 116 | 116 |
|
| 117 |
-func (b *Builder) exportImage(state *dispatchState, imageMount *imageMount, runConfig *container.Config) error {
|
|
| 118 |
- newLayer, err := imageMount.Layer().Commit() |
|
| 117 |
+func (b *Builder) exportImage(state *dispatchState, layer builder.RWLayer, parent builder.Image, runConfig *container.Config) error {
|
|
| 118 |
+ newLayer, err := layer.Commit() |
|
| 119 | 119 |
if err != nil {
|
| 120 | 120 |
return err |
| 121 | 121 |
} |
| ... | ... |
@@ -124,7 +125,7 @@ func (b *Builder) exportImage(state *dispatchState, imageMount *imageMount, runC |
| 124 | 124 |
// if there is an error before we can add the full mount with image |
| 125 | 125 |
b.imageSources.Add(newImageMount(nil, newLayer)) |
| 126 | 126 |
|
| 127 |
- parentImage, ok := imageMount.Image().(*image.Image) |
|
| 127 |
+ parentImage, ok := parent.(*image.Image) |
|
| 128 | 128 |
if !ok {
|
| 129 | 129 |
return errors.Errorf("unexpected image type")
|
| 130 | 130 |
} |
| ... | ... |
@@ -177,7 +178,13 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error |
| 177 | 177 |
return errors.Wrapf(err, "failed to get destination image %q", state.imageID) |
| 178 | 178 |
} |
| 179 | 179 |
|
| 180 |
- destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, imageMount, b.options.Platform) |
|
| 180 |
+ rwLayer, err := imageMount.NewRWLayer() |
|
| 181 |
+ if err != nil {
|
|
| 182 |
+ return err |
|
| 183 |
+ } |
|
| 184 |
+ defer rwLayer.Release() |
|
| 185 |
+ |
|
| 186 |
+ destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, rwLayer, b.options.Platform) |
|
| 181 | 187 |
if err != nil {
|
| 182 | 188 |
return err |
| 183 | 189 |
} |
| ... | ... |
@@ -203,10 +210,10 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error |
| 203 | 203 |
return errors.Wrapf(err, "failed to copy files") |
| 204 | 204 |
} |
| 205 | 205 |
} |
| 206 |
- return b.exportImage(state, imageMount, runConfigWithCommentCmd) |
|
| 206 |
+ return b.exportImage(state, rwLayer, imageMount.Image(), runConfigWithCommentCmd) |
|
| 207 | 207 |
} |
| 208 | 208 |
|
| 209 |
-func createDestInfo(workingDir string, inst copyInstruction, imageMount *imageMount, platform string) (copyInfo, error) {
|
|
| 209 |
+func createDestInfo(workingDir string, inst copyInstruction, rwLayer builder.RWLayer, platform string) (copyInfo, error) {
|
|
| 210 | 210 |
// Twiddle the destination when it's a relative path - meaning, make it |
| 211 | 211 |
// relative to the WORKINGDIR |
| 212 | 212 |
dest, err := normalizeDest(workingDir, inst.dest, platform) |
| ... | ... |
@@ -214,12 +221,7 @@ func createDestInfo(workingDir string, inst copyInstruction, imageMount *imageMo |
| 214 | 214 |
return copyInfo{}, errors.Wrapf(err, "invalid %s", inst.cmdName)
|
| 215 | 215 |
} |
| 216 | 216 |
|
| 217 |
- destMount, err := imageMount.Source() |
|
| 218 |
- if err != nil {
|
|
| 219 |
- return copyInfo{}, errors.Wrapf(err, "failed to mount copy source")
|
|
| 220 |
- } |
|
| 221 |
- |
|
| 222 |
- return newCopyInfoFromSource(destMount, dest, ""), nil |
|
| 217 |
+ return copyInfo{root: rwLayer.Root(), path: dest}, nil
|
|
| 223 | 218 |
} |
| 224 | 219 |
|
| 225 | 220 |
// normalizeDest normalises the destination of a COPY/ADD command in a |
| ... | ... |
@@ -20,7 +20,7 @@ import ( |
| 20 | 20 |
type MockBackend struct {
|
| 21 | 21 |
containerCreateFunc func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) |
| 22 | 22 |
commitFunc func(backend.CommitConfig) (image.ID, error) |
| 23 |
- getImageFunc func(string) (builder.Image, builder.ReleaseableLayer, error) |
|
| 23 |
+ getImageFunc func(string) (builder.Image, builder.ROLayer, error) |
|
| 24 | 24 |
makeImageCacheFunc func(cacheFrom []string) builder.ImageCache |
| 25 | 25 |
} |
| 26 | 26 |
|
| ... | ... |
@@ -66,7 +66,7 @@ func (m *MockBackend) CopyOnBuild(containerID string, destPath string, srcRoot s |
| 66 | 66 |
return nil |
| 67 | 67 |
} |
| 68 | 68 |
|
| 69 |
-func (m *MockBackend) GetImageAndReleasableLayer(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.ROLayer, error) {
|
|
| 70 | 70 |
if m.getImageFunc != nil {
|
| 71 | 71 |
return m.getImageFunc(refOrID) |
| 72 | 72 |
} |
| ... | ... |
@@ -124,14 +124,25 @@ func (l *mockLayer) Release() error {
|
| 124 | 124 |
return nil |
| 125 | 125 |
} |
| 126 | 126 |
|
| 127 |
-func (l *mockLayer) Mount() (containerfs.ContainerFS, error) {
|
|
| 128 |
- return containerfs.NewLocalContainerFS("mountPath"), nil
|
|
| 127 |
+func (l *mockLayer) NewRWLayer() (builder.RWLayer, error) {
|
|
| 128 |
+ return &mockRWLayer{}, nil
|
|
| 129 | 129 |
} |
| 130 | 130 |
|
| 131 |
-func (l *mockLayer) Commit() (builder.ReleaseableLayer, error) {
|
|
| 131 |
+func (l *mockLayer) DiffID() layer.DiffID {
|
|
| 132 |
+ return layer.DiffID("abcdef")
|
|
| 133 |
+} |
|
| 134 |
+ |
|
| 135 |
+type mockRWLayer struct {
|
|
| 136 |
+} |
|
| 137 |
+ |
|
| 138 |
+func (l *mockRWLayer) Release() error {
|
|
| 139 |
+ return nil |
|
| 140 |
+} |
|
| 141 |
+ |
|
| 142 |
+func (l *mockRWLayer) Commit() (builder.ROLayer, error) {
|
|
| 132 | 143 |
return nil, nil |
| 133 | 144 |
} |
| 134 | 145 |
|
| 135 |
-func (l *mockLayer) DiffID() layer.DiffID {
|
|
| 136 |
- return layer.DiffID("abcdef")
|
|
| 146 |
+func (l *mockRWLayer) Root() containerfs.ContainerFS {
|
|
| 147 |
+ return nil |
|
| 137 | 148 |
} |
| ... | ... |
@@ -15,130 +15,126 @@ import ( |
| 15 | 15 |
"github.com/docker/docker/pkg/system" |
| 16 | 16 |
"github.com/docker/docker/registry" |
| 17 | 17 |
"github.com/pkg/errors" |
| 18 |
- "github.com/sirupsen/logrus" |
|
| 19 | 18 |
"golang.org/x/net/context" |
| 20 | 19 |
) |
| 21 | 20 |
|
| 22 |
-type releaseableLayer struct {
|
|
| 21 |
+type roLayer struct {
|
|
| 23 | 22 |
released bool |
| 24 | 23 |
layerStore layer.Store |
| 25 | 24 |
roLayer layer.Layer |
| 26 |
- rwLayer layer.RWLayer |
|
| 27 | 25 |
} |
| 28 | 26 |
|
| 29 |
-func (rl *releaseableLayer) Mount() (containerfs.ContainerFS, error) {
|
|
| 30 |
- var err error |
|
| 31 |
- var mountPath containerfs.ContainerFS |
|
| 32 |
- var chainID layer.ChainID |
|
| 33 |
- if rl.roLayer != nil {
|
|
| 34 |
- chainID = rl.roLayer.ChainID() |
|
| 27 |
+func (l *roLayer) DiffID() layer.DiffID {
|
|
| 28 |
+ if l.roLayer == nil {
|
|
| 29 |
+ return layer.DigestSHA256EmptyTar |
|
| 35 | 30 |
} |
| 31 |
+ return l.roLayer.DiffID() |
|
| 32 |
+} |
|
| 36 | 33 |
|
| 37 |
- mountID := stringid.GenerateRandomID() |
|
| 38 |
- rl.rwLayer, err = rl.layerStore.CreateRWLayer(mountID, chainID, nil) |
|
| 39 |
- if err != nil {
|
|
| 40 |
- return nil, errors.Wrap(err, "failed to create rwlayer") |
|
| 34 |
+func (l *roLayer) Release() error {
|
|
| 35 |
+ if l.released {
|
|
| 36 |
+ return nil |
|
| 41 | 37 |
} |
| 42 |
- |
|
| 43 |
- mountPath, err = rl.rwLayer.Mount("")
|
|
| 44 |
- if err != nil {
|
|
| 45 |
- // Clean up the layer if we fail to mount it here. |
|
| 46 |
- metadata, err := rl.layerStore.ReleaseRWLayer(rl.rwLayer) |
|
| 38 |
+ if l.roLayer != nil {
|
|
| 39 |
+ metadata, err := l.layerStore.Release(l.roLayer) |
|
| 47 | 40 |
layer.LogReleaseMetadata(metadata) |
| 48 | 41 |
if err != nil {
|
| 49 |
- logrus.Errorf("Failed to release RWLayer: %s", err)
|
|
| 42 |
+ return errors.Wrap(err, "failed to release ROLayer") |
|
| 50 | 43 |
} |
| 51 |
- rl.rwLayer = nil |
|
| 52 |
- return nil, err |
|
| 53 | 44 |
} |
| 54 |
- |
|
| 55 |
- return mountPath, nil |
|
| 45 |
+ l.roLayer = nil |
|
| 46 |
+ l.released = true |
|
| 47 |
+ return nil |
|
| 56 | 48 |
} |
| 57 | 49 |
|
| 58 |
-func (rl *releaseableLayer) Commit() (builder.ReleaseableLayer, error) {
|
|
| 50 |
+func (l *roLayer) NewRWLayer() (builder.RWLayer, error) {
|
|
| 59 | 51 |
var chainID layer.ChainID |
| 60 |
- if rl.roLayer != nil {
|
|
| 61 |
- chainID = rl.roLayer.ChainID() |
|
| 52 |
+ if l.roLayer != nil {
|
|
| 53 |
+ chainID = l.roLayer.ChainID() |
|
| 62 | 54 |
} |
| 63 | 55 |
|
| 64 |
- stream, err := rl.rwLayer.TarStream() |
|
| 56 |
+ mountID := stringid.GenerateRandomID() |
|
| 57 |
+ newLayer, err := l.layerStore.CreateRWLayer(mountID, chainID, nil) |
|
| 65 | 58 |
if err != nil {
|
| 66 |
- return nil, err |
|
| 59 |
+ return nil, errors.Wrap(err, "failed to create rwlayer") |
|
| 67 | 60 |
} |
| 68 |
- defer stream.Close() |
|
| 69 | 61 |
|
| 70 |
- newLayer, err := rl.layerStore.Register(stream, chainID) |
|
| 62 |
+ rwLayer := &rwLayer{layerStore: l.layerStore, rwLayer: newLayer}
|
|
| 63 |
+ |
|
| 64 |
+ fs, err := newLayer.Mount("")
|
|
| 71 | 65 |
if err != nil {
|
| 66 |
+ rwLayer.Release() |
|
| 72 | 67 |
return nil, err |
| 73 | 68 |
} |
| 74 |
- // TODO: An optimization would be to handle empty layers before returning |
|
| 75 |
- return &releaseableLayer{layerStore: rl.layerStore, roLayer: newLayer}, nil
|
|
| 69 |
+ |
|
| 70 |
+ rwLayer.fs = fs |
|
| 71 |
+ |
|
| 72 |
+ return rwLayer, nil |
|
| 76 | 73 |
} |
| 77 | 74 |
|
| 78 |
-func (rl *releaseableLayer) DiffID() layer.DiffID {
|
|
| 79 |
- if rl.roLayer == nil {
|
|
| 80 |
- return layer.DigestSHA256EmptyTar |
|
| 81 |
- } |
|
| 82 |
- return rl.roLayer.DiffID() |
|
| 75 |
+type rwLayer struct {
|
|
| 76 |
+ released bool |
|
| 77 |
+ layerStore layer.Store |
|
| 78 |
+ rwLayer layer.RWLayer |
|
| 79 |
+ fs containerfs.ContainerFS |
|
| 83 | 80 |
} |
| 84 | 81 |
|
| 85 |
-func (rl *releaseableLayer) Release() error {
|
|
| 86 |
- if rl.released {
|
|
| 87 |
- return nil |
|
| 88 |
- } |
|
| 89 |
- if err := rl.releaseRWLayer(); err != nil {
|
|
| 90 |
- // Best effort attempt at releasing read-only layer before returning original error. |
|
| 91 |
- rl.releaseROLayer() |
|
| 92 |
- return err |
|
| 93 |
- } |
|
| 94 |
- if err := rl.releaseROLayer(); err != nil {
|
|
| 95 |
- return err |
|
| 96 |
- } |
|
| 97 |
- rl.released = true |
|
| 98 |
- return nil |
|
| 82 |
+func (l *rwLayer) Root() containerfs.ContainerFS {
|
|
| 83 |
+ return l.fs |
|
| 99 | 84 |
} |
| 100 | 85 |
|
| 101 |
-func (rl *releaseableLayer) releaseRWLayer() error {
|
|
| 102 |
- if rl.rwLayer == nil {
|
|
| 103 |
- return nil |
|
| 86 |
+func (l *rwLayer) Commit() (builder.ROLayer, error) {
|
|
| 87 |
+ stream, err := l.rwLayer.TarStream() |
|
| 88 |
+ if err != nil {
|
|
| 89 |
+ return nil, err |
|
| 104 | 90 |
} |
| 105 |
- if err := rl.rwLayer.Unmount(); err != nil {
|
|
| 106 |
- logrus.Errorf("Failed to unmount RWLayer: %s", err)
|
|
| 107 |
- return err |
|
| 91 |
+ defer stream.Close() |
|
| 92 |
+ |
|
| 93 |
+ var chainID layer.ChainID |
|
| 94 |
+ if parent := l.rwLayer.Parent(); parent != nil {
|
|
| 95 |
+ chainID = parent.ChainID() |
|
| 108 | 96 |
} |
| 109 |
- metadata, err := rl.layerStore.ReleaseRWLayer(rl.rwLayer) |
|
| 110 |
- layer.LogReleaseMetadata(metadata) |
|
| 97 |
+ |
|
| 98 |
+ newLayer, err := l.layerStore.Register(stream, chainID) |
|
| 111 | 99 |
if err != nil {
|
| 112 |
- logrus.Errorf("Failed to release RWLayer: %s", err)
|
|
| 100 |
+ return nil, err |
|
| 113 | 101 |
} |
| 114 |
- rl.rwLayer = nil |
|
| 115 |
- return err |
|
| 102 |
+ // TODO: An optimization would be to handle empty layers before returning |
|
| 103 |
+ return &roLayer{layerStore: l.layerStore, roLayer: newLayer}, nil
|
|
| 116 | 104 |
} |
| 117 | 105 |
|
| 118 |
-func (rl *releaseableLayer) releaseROLayer() error {
|
|
| 119 |
- if rl.roLayer == nil {
|
|
| 106 |
+func (l *rwLayer) Release() error {
|
|
| 107 |
+ if l.released {
|
|
| 120 | 108 |
return nil |
| 121 | 109 |
} |
| 122 |
- metadata, err := rl.layerStore.Release(rl.roLayer) |
|
| 110 |
+ |
|
| 111 |
+ if l.fs != nil {
|
|
| 112 |
+ if err := l.rwLayer.Unmount(); err != nil {
|
|
| 113 |
+ return errors.Wrap(err, "failed to unmount RWLayer") |
|
| 114 |
+ } |
|
| 115 |
+ l.fs = nil |
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ metadata, err := l.layerStore.ReleaseRWLayer(l.rwLayer) |
|
| 123 | 119 |
layer.LogReleaseMetadata(metadata) |
| 124 | 120 |
if err != nil {
|
| 125 |
- logrus.Errorf("Failed to release ROLayer: %s", err)
|
|
| 121 |
+ return errors.Wrap(err, "failed to release RWLayer") |
|
| 126 | 122 |
} |
| 127 |
- rl.roLayer = nil |
|
| 128 |
- return err |
|
| 123 |
+ l.released = true |
|
| 124 |
+ return nil |
|
| 129 | 125 |
} |
| 130 | 126 |
|
| 131 |
-func newReleasableLayerForImage(img *image.Image, layerStore layer.Store) (builder.ReleaseableLayer, error) {
|
|
| 127 |
+func newROLayerForImage(img *image.Image, layerStore layer.Store) (builder.ROLayer, error) {
|
|
| 132 | 128 |
if img == nil || img.RootFS.ChainID() == "" {
|
| 133 |
- return &releaseableLayer{layerStore: layerStore}, nil
|
|
| 129 |
+ return &roLayer{layerStore: layerStore}, nil
|
|
| 134 | 130 |
} |
| 135 | 131 |
// Hold a reference to the image layer so that it can't be removed before |
| 136 | 132 |
// it is released |
| 137 |
- roLayer, err := layerStore.Get(img.RootFS.ChainID()) |
|
| 133 |
+ layer, err := layerStore.Get(img.RootFS.ChainID()) |
|
| 138 | 134 |
if err != nil {
|
| 139 | 135 |
return nil, errors.Wrapf(err, "failed to get layer for image %s", img.ImageID()) |
| 140 | 136 |
} |
| 141 |
- return &releaseableLayer{layerStore: layerStore, roLayer: roLayer}, nil
|
|
| 137 |
+ return &roLayer{layerStore: layerStore, roLayer: layer}, nil
|
|
| 142 | 138 |
} |
| 143 | 139 |
|
| 144 | 140 |
// TODO: could this use the regular daemon PullImage ? |
| ... | ... |
@@ -170,12 +166,12 @@ func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfi |
| 170 | 170 |
// GetImageAndReleasableLayer returns an image and releaseable layer for a reference or ID. |
| 171 | 171 |
// Every call to GetImageAndReleasableLayer MUST call releasableLayer.Release() to prevent |
| 172 | 172 |
// leaking of layers. |
| 173 |
-func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
|
|
| 173 |
+func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ROLayer, error) {
|
|
| 174 | 174 |
if refOrID == "" {
|
| 175 | 175 |
if !system.IsOSSupported(opts.OS) {
|
| 176 | 176 |
return nil, nil, system.ErrNotSupportedOperatingSystem |
| 177 | 177 |
} |
| 178 |
- layer, err := newReleasableLayerForImage(nil, daemon.layerStores[opts.OS]) |
|
| 178 |
+ layer, err := newROLayerForImage(nil, daemon.layerStores[opts.OS]) |
|
| 179 | 179 |
return nil, layer, err |
| 180 | 180 |
} |
| 181 | 181 |
|
| ... | ... |
@@ -189,7 +185,7 @@ func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID st |
| 189 | 189 |
if !system.IsOSSupported(image.OperatingSystem()) {
|
| 190 | 190 |
return nil, nil, system.ErrNotSupportedOperatingSystem |
| 191 | 191 |
} |
| 192 |
- layer, err := newReleasableLayerForImage(image, daemon.layerStores[image.OperatingSystem()]) |
|
| 192 |
+ layer, err := newROLayerForImage(image, daemon.layerStores[image.OperatingSystem()]) |
|
| 193 | 193 |
return image, layer, err |
| 194 | 194 |
} |
| 195 | 195 |
} |
| ... | ... |
@@ -201,7 +197,7 @@ func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID st |
| 201 | 201 |
if !system.IsOSSupported(image.OperatingSystem()) {
|
| 202 | 202 |
return nil, nil, system.ErrNotSupportedOperatingSystem |
| 203 | 203 |
} |
| 204 |
- layer, err := newReleasableLayerForImage(image, daemon.layerStores[image.OperatingSystem()]) |
|
| 204 |
+ layer, err := newROLayerForImage(image, daemon.layerStores[image.OperatingSystem()]) |
|
| 205 | 205 |
return image, layer, err |
| 206 | 206 |
} |
| 207 | 207 |
|
| ... | ... |
@@ -301,6 +301,46 @@ COPY bar /` |
| 301 | 301 |
require.NotContains(t, out.String(), "Using cache") |
| 302 | 302 |
} |
| 303 | 303 |
|
| 304 |
+// docker/for-linux#135 |
|
| 305 |
+// #35641 |
|
| 306 |
+func TestBuildMultiStageLayerLeak(t *testing.T) {
|
|
| 307 |
+ ctx := context.TODO() |
|
| 308 |
+ defer setupTest(t)() |
|
| 309 |
+ |
|
| 310 |
+ // all commands need to match until COPY |
|
| 311 |
+ dockerfile := `FROM busybox |
|
| 312 |
+WORKDIR /foo |
|
| 313 |
+COPY foo . |
|
| 314 |
+FROM busybox |
|
| 315 |
+WORKDIR /foo |
|
| 316 |
+COPY bar . |
|
| 317 |
+RUN [ -f bar ] |
|
| 318 |
+RUN [ ! -f foo ] |
|
| 319 |
+` |
|
| 320 |
+ |
|
| 321 |
+ source := fakecontext.New(t, "", |
|
| 322 |
+ fakecontext.WithFile("foo", "0"),
|
|
| 323 |
+ fakecontext.WithFile("bar", "1"),
|
|
| 324 |
+ fakecontext.WithDockerfile(dockerfile)) |
|
| 325 |
+ defer source.Close() |
|
| 326 |
+ |
|
| 327 |
+ apiclient := testEnv.APIClient() |
|
| 328 |
+ resp, err := apiclient.ImageBuild(ctx, |
|
| 329 |
+ source.AsTarReader(t), |
|
| 330 |
+ types.ImageBuildOptions{
|
|
| 331 |
+ Remove: true, |
|
| 332 |
+ ForceRemove: true, |
|
| 333 |
+ }) |
|
| 334 |
+ |
|
| 335 |
+ out := bytes.NewBuffer(nil) |
|
| 336 |
+ require.NoError(t, err) |
|
| 337 |
+ _, err = io.Copy(out, resp.Body) |
|
| 338 |
+ resp.Body.Close() |
|
| 339 |
+ require.NoError(t, err) |
|
| 340 |
+ |
|
| 341 |
+ assert.Contains(t, out.String(), "Successfully built") |
|
| 342 |
+} |
|
| 343 |
+ |
|
| 304 | 344 |
func writeTarRecord(t *testing.T, w *tar.Writer, fn, contents string) {
|
| 305 | 345 |
err := w.WriteHeader(&tar.Header{
|
| 306 | 346 |
Name: fn, |