diff --git a/daemon/containerd/image_changes.go b/daemon/containerd/image_changes.go index 6cc486ec81..94c56cf616 100644 --- a/daemon/containerd/image_changes.go +++ b/daemon/containerd/image_changes.go @@ -17,7 +17,12 @@ import ( func (i *ImageService) Changes(ctx context.Context, container *container.Container) ([]archive.Change, error) { cs := i.client.ContentStore() - imageManifestBytes, err := content.ReadBlob(ctx, cs, *container.ImageManifest) + imageManifest, err := getContainerImageManifest(container) + if err != nil { + return nil, err + } + + imageManifestBytes, err := content.ReadBlob(ctx, cs, imageManifest) if err != nil { return nil, err } diff --git a/daemon/containerd/image_commit.go b/daemon/containerd/image_commit.go index 9c1ceabd09..f163aa1fb2 100644 --- a/daemon/containerd/image_commit.go +++ b/daemon/containerd/image_commit.go @@ -40,7 +40,12 @@ func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig) container := i.containers.Get(cc.ContainerID) cs := i.client.ContentStore() - imageManifestBytes, err := content.ReadBlob(ctx, cs, *container.ImageManifest) + imageManifest, err := getContainerImageManifest(container) + if err != nil { + return "", err + } + + imageManifestBytes, err := content.ReadBlob(ctx, cs, imageManifest) if err != nil { return "", err } diff --git a/daemon/containerd/service.go b/daemon/containerd/service.go index 91bb139470..d46bbd8c53 100644 --- a/daemon/containerd/service.go +++ b/daemon/containerd/service.go @@ -10,6 +10,7 @@ import ( "github.com/containerd/containerd/plugin" "github.com/containerd/containerd/remotes/docker" "github.com/containerd/containerd/snapshots" + imagetypes "github.com/docker/docker/api/types/image" "github.com/docker/docker/container" daemonevents "github.com/docker/docker/daemon/events" "github.com/docker/docker/daemon/images" @@ -20,6 +21,7 @@ import ( "github.com/opencontainers/image-spec/identity" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // ImageService implements daemon.ImageService @@ -154,9 +156,35 @@ func (i *ImageService) GetContainerLayerSize(ctx context.Context, containerID st if ctr == nil { return 0, 0, nil } + + snapshotter := i.client.SnapshotService(ctr.Driver) + + usage, err := snapshotter.Usage(ctx, containerID) + if err != nil { + return 0, 0, err + } + + imageManifest, err := getContainerImageManifest(ctr) + if err != nil { + // Best efforts attempt to pick an image. + // We don't have platform information at this point, so we can only + // assume that the platform matches host. + // Otherwise this will give a wrong base image size (different + // platform), but should be close enough. + mfst, err := i.GetImageManifest(ctx, ctr.Config.Image, imagetypes.GetImageOpts{}) + if err != nil { + // Log error, don't error out whole operation. + logrus.WithFields(logrus.Fields{ + logrus.ErrorKey: err, + "container": containerID, + }).Warn("empty ImageManifest, can't calculate base image size") + return usage.Size, 0, nil + } + imageManifest = *mfst + } cs := i.client.ContentStore() - imageManifestBytes, err := content.ReadBlob(ctx, cs, *ctr.ImageManifest) + imageManifestBytes, err := content.ReadBlob(ctx, cs, imageManifest) if err != nil { return 0, 0, err } @@ -175,12 +203,6 @@ func (i *ImageService) GetContainerLayerSize(ctx context.Context, containerID st return 0, 0, err } - snapshotter := i.client.SnapshotService(ctr.Driver) - usage, err := snapshotter.Usage(ctx, containerID) - if err != nil { - return 0, 0, err - } - sizeCache := make(map[digest.Digest]int64) snapshotSizeFn := func(d digest.Digest) (int64, error) { if s, ok := sizeCache[d]; ok { @@ -203,3 +225,14 @@ func (i *ImageService) GetContainerLayerSize(ctx context.Context, containerID st // TODO(thaJeztah): include content-store size for the image (similar to "GET /images/json") return usage.Size, usage.Size + snapShotSize, nil } + +// getContainerImageManifest safely dereferences ImageManifest. +// ImageManifest can be nil for containers created with Docker Desktop with old +// containerd image store integration enabled which didn't set this field. +func getContainerImageManifest(ctr *container.Container) (ocispec.Descriptor, error) { + if ctr.ImageManifest == nil { + return ocispec.Descriptor{}, errdefs.InvalidParameter(errors.New("container is missing ImageManifest (probably created on old version), please recreate it")) + } + + return *ctr.ImageManifest, nil +}