Skip to content

Commit

Permalink
c8d/inspect: Add Manifests field
Browse files Browse the repository at this point in the history
Add `Manifests` field to image inspect (`/images/{name}/json`) response.
This is the same as in `/images/json`.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
  • Loading branch information
vvoland committed Jan 21, 2025
1 parent 820f73f commit 3397225
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 14 deletions.
12 changes: 11 additions & 1 deletion api/server/router/image/image_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,17 @@ 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
}

if r.Form.Get("manifests") != "" && versions.LessThan(httputils.VersionFromContext(ctx), "1.48") {
return errdefs.InvalidParameter(errors.New("manifests parameter is not supported for API version < 1.48"))
}

imageInspect, err := ir.backend.ImageInspect(ctx, vars["name"], backend.ImageInspectOpts{
Manifests: httputils.BoolValue(r, "manifests"),
})
if err != nil {
return err
}
Expand Down
20 changes: 20 additions & 0 deletions api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2004,6 +2004,20 @@ 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.
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
Expand Down Expand Up @@ -9611,6 +9625,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:
Expand Down
4 changes: 3 additions & 1 deletion api/types/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 10 additions & 0 deletions api/types/image/image_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
5 changes: 5 additions & 0 deletions api/types/image/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 63 additions & 11 deletions client/image_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,80 @@ import (
"context"
"encoding/json"
"io"
"net/url"

"github.com/docker/docker/api/types/image"
)

// ImageInspectWithRaw returns the image information and its raw representation.
func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (image.InspectResponse, []byte, error) {
type imageInspectClientOpt struct {
raw *bytes.Buffer
apiOptions image.InspectOptions
}

type ImageInspectOpt func(*imageInspectClientOpt)

// ImageInspectWithRawResponse instructs the client to additionally store the
// raw inspect response in the provided buffer.
func ImageInspectWithRawResponse(raw *bytes.Buffer) ImageInspectOpt {
return func(opts *imageInspectClientOpt) {
opts.raw = raw
}
}

// ImageInspectWithOpts sets the API options for the image inspect operation.
func ImageInspectWithOpts(opts image.InspectOptions) ImageInspectOpt {
return func(clientOpts *imageInspectClientOpt) {
clientOpts.apiOptions = opts
}
}

// ImageInspect returns the image information.
func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts ...ImageInspectOpt) (image.InspectResponse, error) {
if imageID == "" {
return image.InspectResponse{}, nil, objectNotFoundError{object: "image", id: imageID}
return image.InspectResponse{}, objectNotFoundError{object: "image", id: imageID}
}

var opts imageInspectClientOpt
for _, opt := range inspectOpts {
opt(&opts)
}
serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil)

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 {
return image.InspectResponse{}, nil, err
return image.InspectResponse{}, err
}

body, err := io.ReadAll(serverResp.body)
if err != nil {
return image.InspectResponse{}, nil, err
buf := opts.raw
if buf == nil {
buf = &bytes.Buffer{}
}

if _, err := io.Copy(buf, serverResp.body); err != nil {
return image.InspectResponse{}, err
}

var response image.InspectResponse
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&response)
return response, body, err
err = json.Unmarshal(buf.Bytes(), &response)
return response, err
}

// ImageInspectWithRaw returns the image information and its raw representation.
//
// Deprecated: Use [ImageInspect] instead.
func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (image.InspectResponse, []byte, error) {
var buf bytes.Buffer
resp, err := cli.ImageInspect(ctx, imageID, ImageInspectWithRawResponse(&buf))
if err != nil {
return resp, nil, err
}
return resp, buf.Bytes(), err
}
1 change: 1 addition & 0 deletions client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type ImageAPIClient interface {
ImageHistory(ctx context.Context, image string, opts image.HistoryOptions) ([]image.HistoryResponseItem, error)
ImageImport(ctx context.Context, source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error)
ImageInspectWithRaw(ctx context.Context, image string) (image.InspectResponse, []byte, error)
ImageInspect(ctx context.Context, image string, _ ...ImageInspectOpt) (image.InspectResponse, error)
ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error)
ImageLoad(ctx context.Context, input io.Reader, opts image.LoadOptions) (image.LoadResponse, error)
ImagePull(ctx context.Context, ref string, options image.PullOptions) (io.ReadCloser, error)
Expand Down
8 changes: 7 additions & 1 deletion daemon/containerd/image_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -90,6 +90,11 @@ func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, _ backe
log.G(ctx).WithError(err).Warn("failed to determine Parent property")
}

var manifests []imagetypes.ManifestSummary
if opts.Manifests {
manifests = multi.Manifests
}

repoTags, repoDigests := i.collectRepoTagsAndDigests(ctx, tagged)

return &imagetypes.InspectResponse{
Expand All @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions docs/api/version-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,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.
Expand Down

0 comments on commit 3397225

Please sign in to comment.