c8d/list: Embed platform specific information

Add the PlatformImages field to `ImageSummary` which describes each
platform-specific manifest in that image.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski 2024-03-07 20:56:00 +01:00
parent eecb36e822
commit ea31ce0fce
No known key found for this signature in database
GPG key ID: B85EFCFE26DEF92A
6 changed files with 176 additions and 24 deletions

View file

@ -407,9 +407,10 @@ func (ir *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter,
}
images, err := ir.backend.Images(ctx, imagetypes.ListOptions{
All: httputils.BoolValue(r, "all"),
Filters: imageFilters,
SharedSize: sharedSize,
All: httputils.BoolValue(r, "all"),
Filters: imageFilters,
SharedSize: sharedSize,
ContainerCount: true,
})
if err != nil {
return err

View file

@ -1988,6 +1988,19 @@ definitions:
x-nullable: false
type: "integer"
example: 2
PlatformImages:
description: |
Platform-specific images available for this image.
Only present with the containerd integration enabled.
WARNING: This is experimental and may change at any time without any backward
compatibility.
type: "array"
x-nullable: false
x-omitempty: true
items:
$ref: "#/definitions/PlatformImage"
AuthConfig:
type: "object"
@ -6200,6 +6213,50 @@ definitions:
additionalProperties:
type: "string"
PlatformImage:
x-nullable: false
required: [Id, Descriptor, Available, Platform, ContentSize, UnpackedSize, Containers]
description: |
PlatformImage represents a platform-specific image that is part of a
multi-platform image.
type: "object"
properties:
Id:
description: |
Content-addressable ID of an image derived from the platform-specific
image manifest.
type: "string"
example: "sha256:95869fbcf224d947ace8d61d0e931d49e31bb7fc67fffbbe9c3198c33aa8e93f"
Descriptor:
$ref: "#/definitions/OCIDescriptor"
Available:
description: Indicates whether the image is locally available.
type: "boolean"
example: true
Platform:
$ref: "#/definitions/OCIPlatform"
ContentSize:
description: |
The size of the available distributable (possibly compressed) image content
in bytes.
type: "integer"
format: "int64"
example: 3987495
UnpackedSize:
description: |
The size of the unpacked and uncompressed image content (needed for
the image to be useable by containers) in bytes.
type: "integer"
format: "int64"
example: 3987495
Containers:
description: |
The number of containers that are using this specific platform image.
type: "integer"
format: "int64"
example: 2
paths:
/containers/json:
get:

View file

@ -0,0 +1,43 @@
package image
import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
type PlatformImage struct {
// ID is the content-addressable ID of an image and is the same as the
// digest of the platform-specific image manifest.
//
// Required: true
ID string `json:"Id"`
// Descriptor is the OCI descriptor of the image.
//
// Required: true
Descriptor ocispec.Descriptor `json:"Descriptor"`
// Available indicates whether the image is locally available.
//
// Required: true
Available bool `json:"Available"`
// Platform is the platform of the image
//
// Required: true
Platform ocispec.Platform `json:"Platform"`
// ContentSize is the size of all the locally available distributable content size.
//
// Required: true
ContentSize int64 `json:"ContentSize"`
// UnpackedSize is the size of the image when unpacked.
//
// Required: true
UnpackedSize int64 `json:"UnpackedSize"`
// Containers is the number of containers created from this image.
//
// Required: true
Containers int64 `json:"Containers"`
}

View file

@ -47,6 +47,15 @@ type Summary struct {
// Required: true
ParentID string `json:"ParentId"`
// Platform-specific images available for this image.
//
// Only present with the containerd integration enabled.
//
// WARNING: This is experimental and may change at any time without any backward
// compatibility.
//
PlatformImages []PlatformImage `json:"PlatformImages,omitempty"`
// List of content-addressable digests of locally available image manifests
// that the image is referenced from. Multiple manifests can refer to the
// same image.

View file

@ -210,6 +210,7 @@ func (i *ImageService) Images(ctx context.Context, opts imagetypes.ListOptions)
func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platformMatcher platforms.MatchComparer,
opts imagetypes.ListOptions, tagsByDigest map[digest.Digest][]string,
) (_ *imagetypes.Summary, allChainIDs []digest.Digest, _ error) {
var platformImages []imagetypes.PlatformImage
// Total size of the image including all its platform
var totalSize int64
@ -224,9 +225,20 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
var best *ImageManifest
var bestPlatform ocispec.Platform
err := i.walkImageManifests(ctx, img, func(img *ImageManifest) error {
err := i.walkReachableImageManifests(ctx, img, func(img *ImageManifest) error {
target := img.Target()
if isPseudo, err := img.IsPseudoImage(ctx); isPseudo || err != nil {
return nil
log.G(ctx).WithFields(log.Fields{
"error": err,
"image": img.Name(),
"digest": target.Digest,
"isPseudo": isPseudo,
}).Debug("skipping pseudo image")
if !errdefs.IsNotFound(err) {
return nil
}
}
available, err := img.CheckContentAvailable(ctx)
@ -239,7 +251,19 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
return nil
}
platformSummary := imagetypes.PlatformImage{
ID: target.Digest.String(),
Available: available,
Descriptor: target,
Containers: -1,
}
if target.Platform != nil {
platformSummary.Platform = *target.Platform
}
if !available {
platformImages = append(platformImages, platformSummary)
return nil
}
@ -253,7 +277,9 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
return err
}
target := img.Target()
if target.Platform == nil {
platformSummary.Platform = dockerImage.Platform
}
diffIDs, err := img.RootFS(ctx)
if err != nil {
@ -262,40 +288,45 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
chainIDs := identity.ChainIDs(diffIDs)
ts, _, err := i.singlePlatformSize(ctx, img)
unpackedSize, contentSize, err := i.singlePlatformSize(ctx, img)
if err != nil {
return err
}
totalSize += ts
totalSize += unpackedSize + contentSize
allChainsIDs = append(allChainsIDs, chainIDs...)
platformSummary.ContentSize = contentSize
platformSummary.UnpackedSize = unpackedSize
if opts.ContainerCount {
i.containers.ApplyAll(func(c *container.Container) {
if c.ImageManifest != nil && c.ImageManifest.Digest == target.Digest {
containersCount++
}
})
}
var platform ocispec.Platform
if target.Platform != nil {
platform = *target.Platform
} else {
platform = dockerImage.Platform
platformSummary.Containers = containersCount
}
// Filter out platforms that don't match the requested platform. Do it
// after the size, container count and chainIDs are summed up to have
// the single combined entry still represent the whole multi-platform
// image.
if !platformMatcher.Match(platform) {
if !platformMatcher.Match(platformSummary.Platform) {
return nil
}
if best == nil || platformMatcher.Less(platform, bestPlatform) {
// If the platform is available, prepend it to the list of platforms
// otherwise append it at the end.
if platformSummary.Available {
platformImages = append([]imagetypes.PlatformImage{platformSummary}, platformImages...)
} else {
platformImages = append(platformImages, platformSummary)
}
if best == nil || platformMatcher.Less(platformSummary.Platform, bestPlatform) {
best = img
bestPlatform = platform
bestPlatform = platformSummary.Platform
}
return nil
@ -317,6 +348,7 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
return nil, nil, err
}
image.Size = totalSize
image.PlatformImages = platformImages
if opts.ContainerCount {
image.Containers = containersCount
@ -324,7 +356,7 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
return image, allChainsIDs, nil
}
func (i *ImageService) singlePlatformSize(ctx context.Context, imgMfst *ImageManifest) (totalSize int64, contentSize int64, _ error) {
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)
@ -350,10 +382,7 @@ func (i *ImageService) singlePlatformSize(ctx context.Context, imgMfst *ImageMan
return -1, -1, err
}
// totalSize is the size of the image's packed layers and snapshots
// (unpacked layers) combined.
totalSize = contentSize + unpackedUsage.Size
return totalSize, contentSize, nil
return unpackedUsage.Size, contentSize, nil
}
func (i *ImageService) singlePlatformImage(ctx context.Context, contentStore content.Store, repoTags []string, imageManifest *ImageManifest) (*imagetypes.Summary, error) {
@ -395,11 +424,15 @@ func (i *ImageService) singlePlatformImage(ctx context.Context, contentStore con
return nil, err
}
totalSize, _, err := i.singlePlatformSize(ctx, imageManifest)
unpackedSize, contentSize, err := i.singlePlatformSize(ctx, imageManifest)
if err != nil {
return nil, errors.Wrapf(err, "failed to calculate size of image %s", imageManifest.Name())
}
// totalSize is the size of the image's packed layers and snapshots
// (unpacked layers) combined.
totalSize := contentSize + unpackedSize
summary := &imagetypes.Summary{
ParentID: rawImg.Labels[imageLabelClassicBuilderParent],
ID: target.String(),

View file

@ -13,6 +13,15 @@ keywords: "API, Docker, rcli, REST, documentation"
will be rejected.
-->
## v1.46 API changes
[Docker Engine API v1.46](https://docs.docker.com/engine/api/v1.46/) documentation
* `GET /images/json` response now includes `PlatformImages` field, which contains
information about the platform-specific manifests available for the image.
WARNING: This is experimental and may change at any time without any backward
compatibility.
## v1.45 API changes
[Docker Engine API v1.45](https://docs.docker.com/engine/api/v1.45/) documentation