9a849cc83a
Currently only provides the existing "platform" option, but more options will be added in follow-ups. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
256 lines
7.4 KiB
Go
256 lines
7.4 KiB
Go
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/backend"
|
|
imagetypes "github.com/docker/docker/api/types/image"
|
|
"github.com/docker/docker/api/types/registry"
|
|
"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"
|
|
registrypkg "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
|
|
lyr, 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: lyr}, nil
|
|
}
|
|
|
|
// TODO: could this use the regular daemon PullImage ?
|
|
func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConfigs map[string]registry.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 := ®istry.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 := registrypkg.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, imagetypes.GetImageOpts{Platform: 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
|
|
}
|
|
lyr, err := newROLayerForImage(nil, i.layerStore)
|
|
return nil, lyr, err
|
|
}
|
|
|
|
if opts.PullOption != backend.PullOptionForcePull {
|
|
img, err := i.GetImage(refOrID, imagetypes.GetImageOpts{Platform: 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 img != nil {
|
|
if !system.IsOSSupported(img.OperatingSystem()) {
|
|
return nil, nil, system.ErrNotSupportedOperatingSystem
|
|
}
|
|
lyr, err := newROLayerForImage(img, i.layerStore)
|
|
return img, lyr, err
|
|
}
|
|
}
|
|
|
|
img, err := i.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.Platform)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if !system.IsOSSupported(img.OperatingSystem()) {
|
|
return nil, nil, system.ErrNotSupportedOperatingSystem
|
|
}
|
|
lyr, err := newROLayerForImage(img, i.layerStore)
|
|
return img, lyr, 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)
|
|
}
|