| package daemon |
| |
| import ( |
| "io" |
| "runtime" |
| |
| "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/containerfs" |
| "github.com/docker/docker/pkg/idtools" |
| "github.com/docker/docker/pkg/stringid" |
| "github.com/docker/docker/registry" |
| "github.com/pkg/errors" |
| "github.com/sirupsen/logrus" |
| "golang.org/x/net/context" |
| ) |
| |
| type releaseableLayer struct { |
| released bool |
| layerStore layer.Store |
| roLayer layer.Layer |
| rwLayer layer.RWLayer |
| } |
| |
| func (rl *releaseableLayer) Mount() (containerfs.ContainerFS, error) { |
| var err error |
| var mountPath containerfs.ContainerFS |
| 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 nil, 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 nil, err |
| } |
| |
| return mountPath, nil |
| } |
| |
| func (rl *releaseableLayer) Commit(os 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.OS(os)) |
| if err != nil { |
| return nil, err |
| } |
| // TODO: An optimization woudld be to handle empty layers before returning |
| 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.OS].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.OS].layerStore) |
| return image, layer, err |
| } |
| } |
| |
| image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.OS) |
| if err != nil { |
| return nil, nil, err |
| } |
| layer, err := newReleasableLayerForImage(image, daemon.stores[opts.OS].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 |
| } |