Bläddra i källkod

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>
Paweł Gronowski 1 år sedan
förälder
incheckning
ea31ce0fce

+ 4 - 3
api/server/router/image/image_routes.go

@@ -407,9 +407,10 @@ func (ir *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter,
 	}
 	}
 
 
 	images, err := ir.backend.Images(ctx, imagetypes.ListOptions{
 	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 {
 	if err != nil {
 		return err
 		return err

+ 57 - 0
api/swagger.yaml

@@ -1988,6 +1988,19 @@ definitions:
         x-nullable: false
         x-nullable: false
         type: "integer"
         type: "integer"
         example: 2
         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:
   AuthConfig:
     type: "object"
     type: "object"
@@ -6200,6 +6213,50 @@ definitions:
     additionalProperties:
     additionalProperties:
       type: "string"
       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:
 paths:
   /containers/json:
   /containers/json:
     get:
     get:

+ 43 - 0
api/types/image/platform_image.go

@@ -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"`
+}

+ 9 - 0
api/types/image/summary.go

@@ -47,6 +47,15 @@ type Summary struct {
 	// Required: true
 	// Required: true
 	ParentID string `json:"ParentId"`
 	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
 	// List of content-addressable digests of locally available image manifests
 	// that the image is referenced from. Multiple manifests can refer to the
 	// that the image is referenced from. Multiple manifests can refer to the
 	// same image.
 	// same image.

+ 54 - 21
daemon/containerd/image_list.go

@@ -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,
 func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platformMatcher platforms.MatchComparer,
 	opts imagetypes.ListOptions, tagsByDigest map[digest.Digest][]string,
 	opts imagetypes.ListOptions, tagsByDigest map[digest.Digest][]string,
 ) (_ *imagetypes.Summary, allChainIDs []digest.Digest, _ error) {
 ) (_ *imagetypes.Summary, allChainIDs []digest.Digest, _ error) {
+	var platformImages []imagetypes.PlatformImage
 
 
 	// Total size of the image including all its platform
 	// Total size of the image including all its platform
 	var totalSize int64
 	var totalSize int64
@@ -224,9 +225,20 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
 	var best *ImageManifest
 	var best *ImageManifest
 	var bestPlatform ocispec.Platform
 	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 {
 		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)
 		available, err := img.CheckContentAvailable(ctx)
@@ -239,7 +251,19 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
 			return nil
 			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 {
 		if !available {
+			platformImages = append(platformImages, platformSummary)
 			return nil
 			return nil
 		}
 		}
 
 
@@ -253,7 +277,9 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
 			return err
 			return err
 		}
 		}
 
 
-		target := img.Target()
+		if target.Platform == nil {
+			platformSummary.Platform = dockerImage.Platform
+		}
 
 
 		diffIDs, err := img.RootFS(ctx)
 		diffIDs, err := img.RootFS(ctx)
 		if err != nil {
 		if err != nil {
@@ -262,40 +288,45 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
 
 
 		chainIDs := identity.ChainIDs(diffIDs)
 		chainIDs := identity.ChainIDs(diffIDs)
 
 
-		ts, _, err := i.singlePlatformSize(ctx, img)
+		unpackedSize, contentSize, err := i.singlePlatformSize(ctx, img)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
 
 
-		totalSize += ts
+		totalSize += unpackedSize + contentSize
 		allChainsIDs = append(allChainsIDs, chainIDs...)
 		allChainsIDs = append(allChainsIDs, chainIDs...)
 
 
+		platformSummary.ContentSize = contentSize
+		platformSummary.UnpackedSize = unpackedSize
+
 		if opts.ContainerCount {
 		if opts.ContainerCount {
 			i.containers.ApplyAll(func(c *container.Container) {
 			i.containers.ApplyAll(func(c *container.Container) {
 				if c.ImageManifest != nil && c.ImageManifest.Digest == target.Digest {
 				if c.ImageManifest != nil && c.ImageManifest.Digest == target.Digest {
 					containersCount++
 					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
 		// Filter out platforms that don't match the requested platform.  Do it
 		// after the size, container count and chainIDs are summed up to have
 		// after the size, container count and chainIDs are summed up to have
 		// the single combined entry still represent the whole multi-platform
 		// the single combined entry still represent the whole multi-platform
 		// image.
 		// image.
-		if !platformMatcher.Match(platform) {
+		if !platformMatcher.Match(platformSummary.Platform) {
 			return nil
 			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
 			best = img
-			bestPlatform = platform
+			bestPlatform = platformSummary.Platform
 		}
 		}
 
 
 		return nil
 		return nil
@@ -317,6 +348,7 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
 	image.Size = totalSize
 	image.Size = totalSize
+	image.PlatformImages = platformImages
 
 
 	if opts.ContainerCount {
 	if opts.ContainerCount {
 		image.Containers = containersCount
 		image.Containers = containersCount
@@ -324,7 +356,7 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
 	return image, allChainsIDs, nil
 	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
 	// TODO(thaJeztah): do we need to take multiple snapshotters into account? See https://github.com/moby/moby/issues/45273
 	snapshotter := i.snapshotterService(i.snapshotter)
 	snapshotter := i.snapshotterService(i.snapshotter)
 
 
@@ -350,10 +382,7 @@ func (i *ImageService) singlePlatformSize(ctx context.Context, imgMfst *ImageMan
 		return -1, -1, err
 		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) {
 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
 		return nil, err
 	}
 	}
 
 
-	totalSize, _, err := i.singlePlatformSize(ctx, imageManifest)
+	unpackedSize, contentSize, err := i.singlePlatformSize(ctx, imageManifest)
 	if err != nil {
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to calculate size of image %s", imageManifest.Name())
 		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{
 	summary := &imagetypes.Summary{
 		ParentID:    rawImg.Labels[imageLabelClassicBuilderParent],
 		ParentID:    rawImg.Labels[imageLabelClassicBuilderParent],
 		ID:          target.String(),
 		ID:          target.String(),

+ 9 - 0
docs/api/version-history.md

@@ -13,6 +13,15 @@ keywords: "API, Docker, rcli, REST, documentation"
      will be rejected.
      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
 ## v1.45 API changes
 
 
 [Docker Engine API v1.45](https://docs.docker.com/engine/api/v1.45/) documentation
 [Docker Engine API v1.45](https://docs.docker.com/engine/api/v1.45/) documentation