mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
1d457999c4
releaseableLayer includes automatic handling for creating a read/write layer and mounting it on a call to Mount(), but then does not correspondingly unmount the layer before trying to delete it, which will fail for some graphdrivers. Commit on a releaseable layer also leaks the tarstream for the layer. To fix this, the stream close is deferred in Commit and releaseRWLayer now correctly handles unmounting the layer before trying to delete it. In addition, the changes include better error handling in Release() to make sure that errors are returned to the caller for failures on read/write layers instead of being ignored.# Please enter the commit message for your changes. Lines starting Signed-off-by: Stefan Wernli <swernli@ntdev.microsoft.com>
226 lines
6.4 KiB
Go
226 lines
6.4 KiB
Go
package daemon
|
|
|
|
import (
|
|
"io"
|
|
"runtime"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/distribution/reference"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/backend"
|
|
"github.com/docker/docker/builder"
|
|
"github.com/docker/docker/image"
|
|
"github.com/docker/docker/layer"
|
|
"github.com/docker/docker/pkg/idtools"
|
|
"github.com/docker/docker/pkg/stringid"
|
|
"github.com/docker/docker/registry"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
type releaseableLayer struct {
|
|
released bool
|
|
layerStore layer.Store
|
|
roLayer layer.Layer
|
|
rwLayer layer.RWLayer
|
|
}
|
|
|
|
func (rl *releaseableLayer) Mount() (string, error) {
|
|
var err error
|
|
var mountPath string
|
|
var chainID layer.ChainID
|
|
if rl.roLayer != nil {
|
|
chainID = rl.roLayer.ChainID()
|
|
}
|
|
|
|
mountID := stringid.GenerateRandomID()
|
|
rl.rwLayer, err = rl.layerStore.CreateRWLayer(mountID, chainID, nil)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to create rwlayer")
|
|
}
|
|
|
|
mountPath, err = rl.rwLayer.Mount("")
|
|
if err != nil {
|
|
// Clean up the layer if we fail to mount it here.
|
|
metadata, err := rl.layerStore.ReleaseRWLayer(rl.rwLayer)
|
|
layer.LogReleaseMetadata(metadata)
|
|
if err != nil {
|
|
logrus.Errorf("Failed to release RWLayer: %s", err)
|
|
}
|
|
rl.rwLayer = nil
|
|
return "", err
|
|
}
|
|
|
|
return mountPath, nil
|
|
}
|
|
|
|
func (rl *releaseableLayer) Commit(platform string) (builder.ReleaseableLayer, error) {
|
|
var chainID layer.ChainID
|
|
if rl.roLayer != nil {
|
|
chainID = rl.roLayer.ChainID()
|
|
}
|
|
|
|
stream, err := rl.rwLayer.TarStream()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer stream.Close()
|
|
|
|
newLayer, err := rl.layerStore.Register(stream, chainID, layer.Platform(platform))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if layer.IsEmpty(newLayer.DiffID()) {
|
|
_, err := rl.layerStore.Release(newLayer)
|
|
return &releaseableLayer{layerStore: rl.layerStore}, err
|
|
}
|
|
return &releaseableLayer{layerStore: rl.layerStore, roLayer: newLayer}, nil
|
|
}
|
|
|
|
func (rl *releaseableLayer) DiffID() layer.DiffID {
|
|
if rl.roLayer == nil {
|
|
return layer.DigestSHA256EmptyTar
|
|
}
|
|
return rl.roLayer.DiffID()
|
|
}
|
|
|
|
func (rl *releaseableLayer) Release() error {
|
|
if rl.released {
|
|
return nil
|
|
}
|
|
if err := rl.releaseRWLayer(); err != nil {
|
|
// Best effort attempt at releasing read-only layer before returning original error.
|
|
rl.releaseROLayer()
|
|
return err
|
|
}
|
|
if err := rl.releaseROLayer(); err != nil {
|
|
return err
|
|
}
|
|
rl.released = true
|
|
return nil
|
|
}
|
|
|
|
func (rl *releaseableLayer) releaseRWLayer() error {
|
|
if rl.rwLayer == nil {
|
|
return nil
|
|
}
|
|
if err := rl.rwLayer.Unmount(); err != nil {
|
|
logrus.Errorf("Failed to unmount RWLayer: %s", err)
|
|
return err
|
|
}
|
|
metadata, err := rl.layerStore.ReleaseRWLayer(rl.rwLayer)
|
|
layer.LogReleaseMetadata(metadata)
|
|
if err != nil {
|
|
logrus.Errorf("Failed to release RWLayer: %s", err)
|
|
}
|
|
rl.rwLayer = nil
|
|
return err
|
|
}
|
|
|
|
func (rl *releaseableLayer) releaseROLayer() error {
|
|
if rl.roLayer == nil {
|
|
return nil
|
|
}
|
|
metadata, err := rl.layerStore.Release(rl.roLayer)
|
|
layer.LogReleaseMetadata(metadata)
|
|
if err != nil {
|
|
logrus.Errorf("Failed to release ROLayer: %s", err)
|
|
}
|
|
rl.roLayer = nil
|
|
return err
|
|
}
|
|
|
|
func newReleasableLayerForImage(img *image.Image, layerStore layer.Store) (builder.ReleaseableLayer, error) {
|
|
if img == nil || img.RootFS.ChainID() == "" {
|
|
return &releaseableLayer{layerStore: layerStore}, nil
|
|
}
|
|
// Hold a reference to the image layer so that it can't be removed before
|
|
// it is released
|
|
roLayer, err := layerStore.Get(img.RootFS.ChainID())
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get layer for image %s", img.ImageID())
|
|
}
|
|
return &releaseableLayer{layerStore: layerStore, roLayer: roLayer}, nil
|
|
}
|
|
|
|
// TODO: could this use the regular daemon PullImage ?
|
|
func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer, platform string) (*image.Image, error) {
|
|
ref, err := reference.ParseNormalizedNamed(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ref = reference.TagNameOnly(ref)
|
|
|
|
pullRegistryAuth := &types.AuthConfig{}
|
|
if len(authConfigs) > 0 {
|
|
// The request came with a full auth config, use it
|
|
repoInfo, err := daemon.RegistryService.ResolveRepository(ref)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resolvedConfig := registry.ResolveAuthConfig(authConfigs, repoInfo.Index)
|
|
pullRegistryAuth = &resolvedConfig
|
|
}
|
|
|
|
if err := daemon.pullImageWithReference(ctx, ref, platform, nil, pullRegistryAuth, output); err != nil {
|
|
return nil, err
|
|
}
|
|
return daemon.GetImage(name)
|
|
}
|
|
|
|
// GetImageAndReleasableLayer returns an image and releaseable layer for a reference or ID.
|
|
// Every call to GetImageAndReleasableLayer MUST call releasableLayer.Release() to prevent
|
|
// leaking of layers.
|
|
func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
|
|
if refOrID == "" {
|
|
layer, err := newReleasableLayerForImage(nil, daemon.stores[opts.Platform].layerStore)
|
|
return nil, layer, err
|
|
}
|
|
|
|
if opts.PullOption != backend.PullOptionForcePull {
|
|
image, err := daemon.GetImage(refOrID)
|
|
if err != nil && opts.PullOption == backend.PullOptionNoPull {
|
|
return nil, nil, err
|
|
}
|
|
// TODO: shouldn't we error out if error is different from "not found" ?
|
|
if image != nil {
|
|
layer, err := newReleasableLayerForImage(image, daemon.stores[opts.Platform].layerStore)
|
|
return image, layer, err
|
|
}
|
|
}
|
|
|
|
image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.Platform)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
layer, err := newReleasableLayerForImage(image, daemon.stores[opts.Platform].layerStore)
|
|
return image, layer, err
|
|
}
|
|
|
|
// CreateImage creates a new image by adding a config and ID to the image store.
|
|
// This is similar to LoadImage() except that it receives JSON encoded bytes of
|
|
// an image instead of a tar archive.
|
|
func (daemon *Daemon) CreateImage(config []byte, parent string, platform string) (builder.Image, error) {
|
|
if platform == "" {
|
|
platform = runtime.GOOS
|
|
}
|
|
id, err := daemon.stores[platform].imageStore.Create(config)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to create image")
|
|
}
|
|
|
|
if parent != "" {
|
|
if err := daemon.stores[platform].imageStore.SetParent(id, image.ID(parent)); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to set parent %s", parent)
|
|
}
|
|
}
|
|
|
|
return daemon.stores[platform].imageStore.Get(id)
|
|
}
|
|
|
|
// IDMappings returns uid/gid mappings for the builder
|
|
func (daemon *Daemon) IDMappings() *idtools.IDMappings {
|
|
return daemon.idMappings
|
|
}
|