Skip to content

Commit

Permalink
Resolve and store manifest when creating container
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
laurazard committed Mar 6, 2023
1 parent 168ca2d commit a34060c
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 14 deletions.
2 changes: 2 additions & 0 deletions container/container.go
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
Expand Down
38 changes: 38 additions & 0 deletions daemon/containerd/image.go
Expand Up @@ -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
Expand Down
16 changes: 7 additions & 9 deletions daemon/containerd/image_commit.go
Expand Up @@ -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"
Expand All @@ -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)
cs := i.client.ContentStore()

desc, err := i.resolveDescriptor(ctx, container.Config.Image)
imageManifestBytes, err := content.ReadBlob(ctx, cs, *container.ImageManifest)
if err != nil {
return "", err
}

cs := i.client.ContentStore()

ocimanifest, err := images.Manifest(ctx, cs, desc, platforms.DefaultStrict())
if err != nil {
var manifest ocispec.Manifest
if err := json.Unmarshal(imageManifestBytes, &manifest); err != nil {
return "", err
}

imageConfigBytes, err := content.ReadBlob(ctx, cs, ocimanifest.Config)
imageConfigBytes, err := content.ReadBlob(ctx, cs, manifest.Config)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -88,15 +86,15 @@ 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
}

// image create
img := images.Image{
Name: configDigest.String(),
Name: danglingImageName(configDigest.Digest()),
Target: commitManifestDesc,
CreatedAt: time.Now(),
}
Expand Down
22 changes: 17 additions & 5 deletions daemon/create.go
Expand Up @@ -115,18 +115,29 @@ 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 != "" {
img, err = daemon.imageService.GetImage(ctx, opts.params.Config.Image, imagetypes.GetImageOpts{Platform: opts.params.Platform})
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 {
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions daemon/image_service.go
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions daemon/images/image.go
Expand Up @@ -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 {
Expand Down

0 comments on commit a34060c

Please sign in to comment.