package images // import "github.com/docker/docker/daemon/images" import ( "context" "io" "runtime" "github.com/containerd/containerd/platforms" "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/errdefs" "github.com/docker/docker/image" "github.com/docker/docker/layer" "github.com/docker/docker/pkg/containerfs" "github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/system" "github.com/docker/docker/registry" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) type roLayer struct { released bool layerStore layer.Store roLayer layer.Layer } func (l *roLayer) DiffID() layer.DiffID { if l.roLayer == nil { return layer.DigestSHA256EmptyTar } return l.roLayer.DiffID() } func (l *roLayer) Release() error { if l.released { return nil } if l.roLayer != nil { metadata, err := l.layerStore.Release(l.roLayer) layer.LogReleaseMetadata(metadata) if err != nil { return errors.Wrap(err, "failed to release ROLayer") } } l.roLayer = nil l.released = true return nil } func (l *roLayer) NewRWLayer() (builder.RWLayer, error) { var chainID layer.ChainID if l.roLayer != nil { chainID = l.roLayer.ChainID() } mountID := stringid.GenerateRandomID() newLayer, err := l.layerStore.CreateRWLayer(mountID, chainID, nil) if err != nil { return nil, errors.Wrap(err, "failed to create rwlayer") } rwLayer := &rwLayer{layerStore: l.layerStore, rwLayer: newLayer} fs, err := newLayer.Mount("") if err != nil { rwLayer.Release() return nil, err } rwLayer.fs = fs return rwLayer, nil } type rwLayer struct { released bool layerStore layer.Store rwLayer layer.RWLayer fs containerfs.ContainerFS } func (l *rwLayer) Root() containerfs.ContainerFS { return l.fs } func (l *rwLayer) Commit() (builder.ROLayer, error) { stream, err := l.rwLayer.TarStream() if err != nil { return nil, err } defer stream.Close() var chainID layer.ChainID if parent := l.rwLayer.Parent(); parent != nil { chainID = parent.ChainID() } newLayer, err := l.layerStore.Register(stream, chainID) if err != nil { return nil, err } // TODO: An optimization would be to handle empty layers before returning return &roLayer{layerStore: l.layerStore, roLayer: newLayer}, nil } func (l *rwLayer) Release() error { if l.released { return nil } if l.fs != nil { if err := l.rwLayer.Unmount(); err != nil { return errors.Wrap(err, "failed to unmount RWLayer") } l.fs = nil } metadata, err := l.layerStore.ReleaseRWLayer(l.rwLayer) layer.LogReleaseMetadata(metadata) if err != nil { return errors.Wrap(err, "failed to release RWLayer") } l.released = true return nil } func newROLayerForImage(img *image.Image, layerStore layer.Store) (builder.ROLayer, error) { if img == nil || img.RootFS.ChainID() == "" { return &roLayer{layerStore: layerStore}, nil } // Hold a reference to the image layer so that it can't be removed before // it is released layer, err := layerStore.Get(img.RootFS.ChainID()) if err != nil { return nil, errors.Wrapf(err, "failed to get layer for image %s", img.ImageID()) } return &roLayer{layerStore: layerStore, roLayer: layer}, nil } // TODO: could this use the regular daemon PullImage ? func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer, platform *specs.Platform) (*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 := i.registryService.ResolveRepository(ref) if err != nil { return nil, err } resolvedConfig := registry.ResolveAuthConfig(authConfigs, repoInfo.Index) pullRegistryAuth = &resolvedConfig } if err := i.pullImageWithReference(ctx, ref, platform, nil, pullRegistryAuth, output); err != nil { return nil, err } img, err := i.GetImage(name, platform) if errdefs.IsNotFound(err) && img != nil && platform != nil { imgPlat := specs.Platform{ OS: img.OS, Architecture: img.BaseImgArch(), Variant: img.BaseImgVariant(), } p := *platform if !platforms.Only(p).Match(imgPlat) { po := streamformatter.NewJSONProgressOutput(output, false) progress.Messagef(po, "", ` WARNING: Pulled image with specified platform (%s), but the resulting image's configured platform (%s) does not match. This is most likely caused by a bug in the build system that created the fetched image (%s). Please notify the image author to correct the configuration.`, platforms.Format(p), platforms.Format(imgPlat), name, ) logrus.WithError(err).WithField("image", name).Warn("Ignoring error about platform mismatch where the manifest list points to an image whose configuration does not match the platform in the manifest.") err = nil } } return img, err } // 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 (i *ImageService) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ROLayer, error) { if refOrID == "" { // ie FROM scratch os := runtime.GOOS if runtime.GOOS == "windows" { os = "linux" } if opts.Platform != nil { os = opts.Platform.OS } if !system.IsOSSupported(os) { return nil, nil, system.ErrNotSupportedOperatingSystem } layer, err := newROLayerForImage(nil, i.layerStore) return nil, layer, err } if opts.PullOption != backend.PullOptionForcePull { image, err := i.GetImage(refOrID, opts.Platform) 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 { if !system.IsOSSupported(image.OperatingSystem()) { return nil, nil, system.ErrNotSupportedOperatingSystem } layer, err := newROLayerForImage(image, i.layerStore) return image, layer, err } } image, err := i.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.Platform) if err != nil { return nil, nil, err } if !system.IsOSSupported(image.OperatingSystem()) { return nil, nil, system.ErrNotSupportedOperatingSystem } layer, err := newROLayerForImage(image, i.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 (i *ImageService) CreateImage(config []byte, parent string) (builder.Image, error) { id, err := i.imageStore.Create(config) if err != nil { return nil, errors.Wrapf(err, "failed to create image") } if parent != "" { if err := i.imageStore.SetParent(id, image.ID(parent)); err != nil { return nil, errors.Wrapf(err, "failed to set parent %s", parent) } } return i.imageStore.Get(id) }