diff --git a/daemon/containerd/image_list.go b/daemon/containerd/image_list.go index 61c7c45e55..2a520cbd96 100644 --- a/daemon/containerd/image_list.go +++ b/daemon/containerd/image_list.go @@ -11,7 +11,6 @@ import ( "github.com/containerd/containerd/v2/core/content" c8dimages "github.com/containerd/containerd/v2/core/images" - "github.com/containerd/containerd/v2/core/snapshots" "github.com/containerd/containerd/v2/pkg/labels" cerrdefs "github.com/containerd/errdefs" "github.com/containerd/log" @@ -317,33 +316,15 @@ func (i *ImageService) multiPlatformSummary(ctx context.Context, img c8dimages.I if target.Platform == nil { mfstSummary.ImageData.Platform = dockerImage.Platform } + logger = logger.WithField("platform", mfstSummary.ImageData.Platform) chainIDs := identity.ChainIDs(dockerImage.RootFS.DiffIDs) - unpackedSize, imgContentSize, err := i.singlePlatformSize(ctx, img) + snapshotUsage, err := img.SnapshotUsage(ctx, i.snapshotterService(i.snapshotter)) if err != nil { - logger.WithError(err).Warn("failed to determine platform specific size") - return nil - } - - // If the image-specific content size calculation produces different result - // than the "generic" one, adjust the total size with the difference. - // Note: This shouldn't happen unless the implementation changes or the - // content is added/removed during the list operation. - if contentSize != imgContentSize { - logger.WithFields(log.Fields{ - "contentSize": contentSize, - "imgContentSize": imgContentSize, - }).Warn("content size calculation mismatch") - - mfstSummary.Size.Content = contentSize - - // contentSize was already added to total, adjust it by the difference - // between the newly calculated size and the old size. - d := imgContentSize - contentSize - summary.TotalSize += d - mfstSummary.Size.Total += d + logger.WithFields(log.Fields{"error": err}).Warn("failed to determine platform specific unpacked size") } + unpackedSize := snapshotUsage.Size mfstSummary.ImageData.Size.Unpacked = unpackedSize mfstSummary.Size.Total += unpackedSize @@ -433,35 +414,6 @@ func (i *ImageService) imageSummary(ctx context.Context, img c8dimages.Image, pl return image, summary, nil } -func (i *ImageService) singlePlatformSize(ctx context.Context, imgMfst *ImageManifest) (unpackedSize int64, contentSize int64, _ error) { - // TODO(thaJeztah): do we need to take multiple snapshotters into account? See https://github.com/moby/moby/issues/45273 - snapshotter := i.snapshotterService(i.snapshotter) - - diffIDs, err := imgMfst.RootFS(ctx) - if err != nil { - return -1, -1, errors.Wrapf(err, "failed to get rootfs of image %s", imgMfst.Name()) - } - - imageSnapshotID := identity.ChainID(diffIDs).String() - unpackedUsage, err := calculateSnapshotTotalUsage(ctx, snapshotter, imageSnapshotID) - if err != nil { - if !cerrdefs.IsNotFound(err) { - log.G(ctx).WithError(err).WithFields(log.Fields{ - "image": imgMfst.Name(), - "snapshotID": imageSnapshotID, - }).Warn("failed to calculate unpacked size of image") - } - unpackedUsage = snapshots.Usage{Size: 0} - } - - contentSize, err = imgMfst.Size(ctx) - if err != nil { - return -1, -1, err - } - - return unpackedUsage.Size, contentSize, nil -} - func (i *ImageService) singlePlatformImage(ctx context.Context, contentStore content.Store, repoTags []string, imageManifest *ImageManifest) (*imagetypes.Summary, error) { var repoDigests []string rawImg := imageManifest.Metadata() @@ -501,9 +453,15 @@ func (i *ImageService) singlePlatformImage(ctx context.Context, contentStore con return nil, err } - unpackedSize, contentSize, err := i.singlePlatformSize(ctx, imageManifest) + snapshotUsage, err := imageManifest.SnapshotUsage(ctx, i.snapshotterService(i.snapshotter)) if err != nil { - return nil, errors.Wrapf(err, "failed to calculate size of image %s", imageManifest.Name()) + log.G(ctx).WithFields(log.Fields{"image": imageManifest.Name(), "error": err}).Warn("failed to calculate unpacked size of image") + } + unpackedSize := snapshotUsage.Size + + contentSize, err := imageManifest.PresentContentSize(ctx) + if err != nil { + log.G(ctx).WithFields(log.Fields{"image": imageManifest.Name(), "error": err}).Warn("failed to calculate content size of image") } // totalSize is the size of the image's packed layers and snapshots diff --git a/daemon/containerd/image_manifest.go b/daemon/containerd/image_manifest.go index f6f95e3601..6107a0a904 100644 --- a/daemon/containerd/image_manifest.go +++ b/daemon/containerd/image_manifest.go @@ -7,10 +7,13 @@ import ( containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd/v2/core/content" c8dimages "github.com/containerd/containerd/v2/core/images" + "github.com/containerd/containerd/v2/core/snapshots" cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/log" "github.com/containerd/platforms" "github.com/docker/docker/errdefs" "github.com/moby/buildkit/util/attestation" + "github.com/opencontainers/image-spec/identity" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) @@ -237,3 +240,38 @@ func (m *ImageManifest) ReadConfig(ctx context.Context, outConfig interface{}) e return readJSON(ctx, m.ContentStore(), configDesc, outConfig) } + +// PresentContentSize returns the size of the image's content that is present in the content store. +func (m *ImageManifest) PresentContentSize(ctx context.Context) (int64, error) { + cs := m.ContentStore() + var size int64 + err := c8dimages.Walk(ctx, presentChildrenHandler(cs, func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { + size += desc.Size + return nil, nil + }), m.Target()) + return size, err +} + +// SnapshotUsage returns the disk usage of the image's snapshots. +func (m *ImageManifest) SnapshotUsage(ctx context.Context, snapshotter snapshots.Snapshotter) (snapshots.Usage, error) { + diffIDs, err := m.RootFS(ctx) + if err != nil { + return snapshots.Usage{}, errors.Wrapf(err, "failed to get rootfs of image %s", m.Name()) + } + + imageSnapshotID := identity.ChainID(diffIDs).String() + unpackedUsage, err := calculateSnapshotTotalUsage(ctx, snapshotter, imageSnapshotID) + if err != nil { + if cerrdefs.IsNotFound(err) { + return snapshots.Usage{Size: 0}, nil + } + log.G(ctx).WithError(err).WithFields(log.Fields{ + "image": m.Name(), + "target": m.Target(), + "snapshotID": imageSnapshotID, + }).Warn("failed to calculate snapshot usage of image") + + return snapshots.Usage{}, errors.Wrapf(err, "failed to calculate snapshot usage of image %s", m.Name()) + } + return unpackedUsage, nil +}