diff --git a/api/server/router/image/image_routes.go b/api/server/router/image/image_routes.go index ada7cad77e..231527836c 100644 --- a/api/server/router/image/image_routes.go +++ b/api/server/router/image/image_routes.go @@ -332,7 +332,18 @@ func (ir *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, } func (ir *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - imageInspect, err := ir.backend.ImageInspect(ctx, vars["name"], backend.ImageInspectOpts{}) + if err := httputils.ParseForm(r); err != nil { + return err + } + + var manifests bool + if r.Form.Get("manifests") != "" && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.48") { + manifests = httputils.BoolValue(r, "manifests") + } + + imageInspect, err := ir.backend.ImageInspect(ctx, vars["name"], backend.ImageInspectOpts{ + Manifests: manifests, + }) if err != nil { return err } diff --git a/api/swagger.yaml b/api/swagger.yaml index de308a8c2a..21cdf288cb 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -2016,6 +2016,21 @@ definitions: compatibility. x-nullable: true $ref: "#/definitions/OCIDescriptor" + Manifests: + description: | + Manifests is a list of image manifests available in this image. It + provides a more detailed view of the platform-specific image manifests or + other image-attached data like build attestations. + + Only available if the daemon provides a multi-platform image store + and the `manifests` option is set in the inspect request. + + WARNING: This is experimental and may change at any time without any backward + compatibility. + type: "array" + x-nullable: true + items: + $ref: "#/definitions/ImageManifestSummary" RepoTags: description: | List of image names/tags in the local image cache that reference this @@ -9648,6 +9663,12 @@ paths: description: "Image name or id" type: "string" required: true + - name: "manifests" + in: "query" + description: "Include Manifests in the image summary." + type: "boolean" + default: false + required: false tags: ["Image"] /images/{name}/history: get: diff --git a/api/types/backend/backend.go b/api/types/backend/backend.go index c5514d06d1..63b272773c 100644 --- a/api/types/backend/backend.go +++ b/api/types/backend/backend.go @@ -151,7 +151,9 @@ type GetImageOpts struct { } // ImageInspectOpts holds parameters to inspect an image. -type ImageInspectOpts struct{} +type ImageInspectOpts struct { + Manifests bool +} // CommitConfig is the configuration for creating an image as part of a build. type CommitConfig struct { diff --git a/api/types/image/image_inspect.go b/api/types/image/image_inspect.go index 5d24dd62a2..78e81f052c 100644 --- a/api/types/image/image_inspect.go +++ b/api/types/image/image_inspect.go @@ -127,4 +127,14 @@ type InspectResponse struct { // WARNING: This is experimental and may change at any time without any backward // compatibility. Descriptor *ocispec.Descriptor `json:"Descriptor,omitempty"` + + // Manifests is a list of image manifests available in this image. It + // provides a more detailed view of the platform-specific image manifests or + // other image-attached data like build attestations. + // + // Only available if the daemon provides a multi-platform image store. + // + // WARNING: This is experimental and may change at any time without any backward + // compatibility. + Manifests []ManifestSummary `json:"Manifests,omitempty"` } diff --git a/api/types/image/opts.go b/api/types/image/opts.go index 0636583021..919510fe37 100644 --- a/api/types/image/opts.go +++ b/api/types/image/opts.go @@ -103,6 +103,11 @@ type LoadOptions struct { Platforms []ocispec.Platform } +type InspectOptions struct { + // Manifests returns the image manifests. + Manifests bool +} + // SaveOptions holds parameters to save images. type SaveOptions struct { // Platforms selects the platforms to save if the image is a diff --git a/client/image_inspect.go b/client/image_inspect.go index ecafee278f..fb23c310eb 100644 --- a/client/image_inspect.go +++ b/client/image_inspect.go @@ -30,6 +30,17 @@ func ImageInspectWithRawResponse(raw *bytes.Buffer) ImageInspectOption { }) } +// ImageInspectWithManifests sets manifests API option for the image inspect operation. +// This option is only available for API version 1.48 and up. +// With this option set, the image inspect operation response will have the +// [image.InspectResponse.Manifests] field populated if the server is multi-platform capable. +func ImageInspectWithManifests(manifests bool) ImageInspectOption { + return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error { + clientOpts.apiOptions.Manifests = manifests + return nil + }) +} + // ImageInspectWithAPIOpts sets the API options for the image inspect operation. func ImageInspectWithAPIOpts(opts image.InspectOptions) ImageInspectOption { return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error { @@ -57,6 +68,13 @@ func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts } query := url.Values{} + if opts.apiOptions.Manifests { + if err := cli.NewVersionError(ctx, "1.48", "manifests"); err != nil { + return image.InspectResponse{}, err + } + query.Set("manifests", "1") + } + serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", query, nil) defer ensureReaderClosed(serverResp) if err != nil { diff --git a/daemon/containerd/image_inspect.go b/daemon/containerd/image_inspect.go index c36afa3e2f..5356dd44c0 100644 --- a/daemon/containerd/image_inspect.go +++ b/daemon/containerd/image_inspect.go @@ -22,7 +22,7 @@ import ( "golang.org/x/sync/semaphore" ) -func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, _ backend.ImageInspectOpts) (*imagetypes.InspectResponse, error) { +func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, opts backend.ImageInspectOpts) (*imagetypes.InspectResponse, error) { c8dImg, err := i.resolveImage(ctx, refOrID) if err != nil { return nil, err @@ -90,13 +90,18 @@ func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, _ backe log.G(ctx).WithError(err).Warn("failed to determine Parent property") } - repoTags, repoDigests := i.collectRepoTagsAndDigests(ctx, tagged) + var manifests []imagetypes.ManifestSummary + if opts.Manifests { + manifests = multi.Manifests + } + + repoTags, repoDigests := collectRepoTagsAndDigests(ctx, tagged) return &imagetypes.InspectResponse{ ID: target.Digest.String(), RepoTags: repoTags, Descriptor: &target, - RepoDigests: sliceutil.Dedup(repoDigests), + RepoDigests: repoDigests, Parent: parent, Comment: comment, Created: created, @@ -108,6 +113,7 @@ func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, _ backe Os: img.OS, OsVersion: img.OSVersion, Size: size, + Manifests: manifests, GraphDriver: storage.DriverData{ Name: i.snapshotter, Data: nil, @@ -122,10 +128,7 @@ func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, _ backe }, nil } -// collectRepoTagsAndDigests returns repoTags and repoDigests for images with the same target. -func (i *ImageService) collectRepoTagsAndDigests(ctx context.Context, tagged []c8dimages.Image) ( - repoTags []string, repoDigests []string, -) { +func collectRepoTagsAndDigests(ctx context.Context, tagged []c8dimages.Image) (repoTags []string, repoDigests []string) { repoTags = make([]string, 0, len(tagged)) repoDigests = make([]string, 0, len(tagged)) for _, img := range tagged { @@ -164,7 +167,7 @@ func (i *ImageService) collectRepoTagsAndDigests(ctx context.Context, tagged []c } repoDigests = append(repoDigests, reference.FamiliarString(digested)) } - return repoTags, repoDigests + return sliceutil.Dedup(repoTags), sliceutil.Dedup(repoDigests) } // size returns the total size of the image's packed resources. diff --git a/daemon/containerd/image_list.go b/daemon/containerd/image_list.go index 8d909fd8fb..91b52d0b70 100644 --- a/daemon/containerd/image_list.go +++ b/daemon/containerd/image_list.go @@ -169,6 +169,9 @@ func (i *ImageService) Images(ctx context.Context, opts imagetypes.ListOptions) return nil } + if !opts.Manifests { + image.Manifests = nil + } resultsMut.Lock() summaries = append(summaries, image) diff --git a/docs/api/version-history.md b/docs/api/version-history.md index 018b87b4af..eb737bac97 100644 --- a/docs/api/version-history.md +++ b/docs/api/version-history.md @@ -51,6 +51,13 @@ keywords: "API, Docker, rcli, REST, documentation" image store. WARNING: This is experimental and may change at any time without any backward compatibility. +* `GET /images/{name}/json` response now will return the `Manifests` field + containing information about the sub-manifests contained in the image index. + This includes things like platform-specific manifests and build attestations. + The new field will only be populated if the request also sets the `manifests` + query parameter to `true`. + This acts the same as in the `GET /images/json` endpoint. + WARNING: This is experimental and may change at any time without any backward compatibility. * `GET /containers/{name}/json` now returns an `ImageManifestDescriptor` field containing the OCI descriptor of the platform-specific image manifest of the image that was used to create the container.