Resolve and store manifest when creating container
This addresses the previous issue with the containerd store where, after a container is created, we can't deterministically resolve which image variant was used to run it (since we also don't store what platform the image was fetched for). This is required for things like `docker commit`, and computing the containers layer size later, since we need to resolve the specific image variant. Signed-off-by: Laura Brehm <laurabrehm@hey.com>
This commit is contained in:
parent
168ca2dcc8
commit
a34060cdb4
6 changed files with 71 additions and 16 deletions
|
@ -39,6 +39,7 @@ import (
|
|||
agentexec "github.com/moby/swarmkit/v2/agent/exec"
|
||||
"github.com/moby/sys/signal"
|
||||
"github.com/moby/sys/symlink"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -72,6 +73,7 @@ type Container struct {
|
|||
Args []string
|
||||
Config *containertypes.Config
|
||||
ImageID image.ID `json:"Image"`
|
||||
ImageManifest *ocispec.Descriptor
|
||||
NetworkSettings *network.Settings
|
||||
LogPath string
|
||||
Name string
|
||||
|
|
|
@ -122,6 +122,44 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options ima
|
|||
return img, nil
|
||||
}
|
||||
|
||||
func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*ocispec.Descriptor, error) {
|
||||
cs := i.client.ContentStore()
|
||||
|
||||
desc, err := i.resolveDescriptor(ctx, refOrID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if containerdimages.IsManifestType(desc.MediaType) {
|
||||
return &desc, nil
|
||||
}
|
||||
|
||||
if containerdimages.IsIndexType(desc.MediaType) {
|
||||
platform := platforms.AllPlatformsWithPreference(cplatforms.Default())
|
||||
if options.Platform != nil {
|
||||
platform = cplatforms.Only(*options.Platform)
|
||||
}
|
||||
|
||||
childManifests, err := containerdimages.LimitManifests(containerdimages.ChildrenHandler(cs), platform, 1)(ctx, desc)
|
||||
if err != nil {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
return nil, errdefs.NotFound(err)
|
||||
}
|
||||
return nil, errdefs.System(err)
|
||||
}
|
||||
|
||||
// len(childManifests) == 1 since we requested 1 and if none
|
||||
// were found LimitManifests would have thrown an error
|
||||
if !containerdimages.IsManifestType(childManifests[0].MediaType) {
|
||||
return nil, errdefs.NotFound(fmt.Errorf("manifest has incorrect mediatype: %s", childManifests[0].MediaType))
|
||||
}
|
||||
|
||||
return &childManifests[0], nil
|
||||
}
|
||||
|
||||
return nil, errdefs.NotFound(errors.New("failed to find manifest"))
|
||||
}
|
||||
|
||||
// size returns the total size of the image's packed resources.
|
||||
func (i *ImageService) size(ctx context.Context, desc ocispec.Descriptor, platform cplatforms.MatchComparer) (int64, error) {
|
||||
var size int64
|
||||
|
|
|
@ -16,7 +16,6 @@ import (
|
|||
cerrdefs "github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/leases"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/containerd/containerd/rootfs"
|
||||
"github.com/containerd/containerd/snapshots"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
|
@ -39,20 +38,19 @@ with adaptations to match the Moby data model and services.
|
|||
// CommitImage creates a new image from a commit config.
|
||||
func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig) (image.ID, error) {
|
||||
container := i.containers.Get(cc.ContainerID)
|
||||
|
||||
desc, err := i.resolveDescriptor(ctx, container.Config.Image)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cs := i.client.ContentStore()
|
||||
|
||||
ocimanifest, err := images.Manifest(ctx, cs, desc, platforms.DefaultStrict())
|
||||
imageManifestBytes, err := content.ReadBlob(ctx, cs, *container.ImageManifest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
imageConfigBytes, err := content.ReadBlob(ctx, cs, ocimanifest.Config)
|
||||
var manifest ocispec.Manifest
|
||||
if err := json.Unmarshal(imageManifestBytes, &manifest); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
imageConfigBytes, err := content.ReadBlob(ctx, cs, manifest.Config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -88,7 +86,7 @@ func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig)
|
|||
return "", fmt.Errorf("failed to apply diff: %w", err)
|
||||
}
|
||||
|
||||
layers := append(ocimanifest.Layers, diffLayerDesc)
|
||||
layers := append(manifest.Layers, diffLayerDesc)
|
||||
commitManifestDesc, configDigest, err := writeContentsForImage(ctx, i.snapshotter, cs, imageConfig, layers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -96,7 +94,7 @@ func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig)
|
|||
|
||||
// image create
|
||||
img := images.Image{
|
||||
Name: configDigest.String(),
|
||||
Name: danglingImageName(configDigest.Digest()),
|
||||
Target: commitManifestDesc,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
|
|
@ -115,11 +115,12 @@ func (daemon *Daemon) containerCreate(ctx context.Context, opts createOpts) (con
|
|||
// Create creates a new container from the given configuration with a given name.
|
||||
func (daemon *Daemon) create(ctx context.Context, opts createOpts) (retC *container.Container, retErr error) {
|
||||
var (
|
||||
ctr *container.Container
|
||||
img *image.Image
|
||||
imgID image.ID
|
||||
err error
|
||||
os = runtime.GOOS
|
||||
ctr *container.Container
|
||||
img *image.Image
|
||||
imgManifest *v1.Descriptor
|
||||
imgID image.ID
|
||||
err error
|
||||
os = runtime.GOOS
|
||||
)
|
||||
|
||||
if opts.params.Config.Image != "" {
|
||||
|
@ -127,6 +128,16 @@ func (daemon *Daemon) create(ctx context.Context, opts createOpts) (retC *contai
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// when using the containerd store, we need to get the actual
|
||||
// image manifest so we can store it and later deterministically
|
||||
// resolve the specific image the container is running
|
||||
if daemon.UsesSnapshotter() {
|
||||
imgManifest, err = daemon.imageService.GetImageManifest(ctx, opts.params.Config.Image, imagetypes.GetImageOpts{Platform: opts.params.Platform})
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("failed to find image manifest")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
os = img.OperatingSystem()
|
||||
imgID = img.ID()
|
||||
} else if isWindows {
|
||||
|
@ -169,6 +180,7 @@ func (daemon *Daemon) create(ctx context.Context, opts createOpts) (retC *contai
|
|||
}
|
||||
|
||||
ctr.HostConfig.StorageOpt = opts.params.HostConfig.StorageOpt
|
||||
ctr.ImageManifest = imgManifest
|
||||
|
||||
if daemon.UsesSnapshotter() {
|
||||
if err := daemon.imageService.PrepareSnapshot(ctx, ctr.ID, opts.params.Config.Image, opts.params.Platform); err != nil {
|
||||
|
|
|
@ -46,6 +46,7 @@ type ImageService interface {
|
|||
// Containerd related methods
|
||||
|
||||
PrepareSnapshot(ctx context.Context, id string, image string, platform *v1.Platform) error
|
||||
GetImageManifest(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*v1.Descriptor, error)
|
||||
|
||||
// Layers
|
||||
|
||||
|
|
|
@ -192,6 +192,10 @@ func (i *ImageService) GetImage(ctx context.Context, refOrID string, options ima
|
|||
return img, nil
|
||||
}
|
||||
|
||||
func (i *ImageService) GetImageManifest(ctx context.Context, refOrID string, options imagetypes.GetImageOpts) (*v1.Descriptor, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (i *ImageService) getImage(ctx context.Context, refOrID string, options imagetypes.GetImageOpts) (retImg *image.Image, retErr error) {
|
||||
defer func() {
|
||||
if retErr != nil || retImg == nil || options.Platform == nil {
|
||||
|
|
Loading…
Reference in a new issue