Skip to content

Commit

Permalink
Merge pull request moby#48861 from vvoland/c8d-list-index
Browse files Browse the repository at this point in the history
c8d/list: Return `Descriptor`
  • Loading branch information
thaJeztah authored Nov 14, 2024
2 parents da3cc1c + b15cd28 commit cd8d2c5
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 25 deletions.
4 changes: 4 additions & 0 deletions api/server/router/image/image_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ func (ir *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter,

useNone := versions.LessThan(version, "1.43")
withVirtualSize := versions.LessThan(version, "1.44")
noDescriptor := versions.LessThan(version, "1.48")
for _, img := range images {
if useNone {
if len(img.RepoTags) == 0 && len(img.RepoDigests) == 0 {
Expand All @@ -414,6 +415,9 @@ func (ir *imageRouter) getImagesJSON(ctx context.Context, w http.ResponseWriter,
if withVirtualSize {
img.VirtualSize = img.Size //nolint:staticcheck // ignore SA1019: field is deprecated, but still set on API < v1.44.
}
if noDescriptor {
img.Descriptor = nil
}
}

return httputils.WriteJSON(w, http.StatusOK, images)
Expand Down
12 changes: 12 additions & 0 deletions api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2278,6 +2278,18 @@ definitions:
x-omitempty: true
items:
$ref: "#/definitions/ImageManifestSummary"
Descriptor:
description: |
Descriptor is an OCI descriptor of the image target.
In case of a multi-platform image, this descriptor points to the OCI index
or a manifest list.
This field is only present if the daemon provides a multi-platform image store.
WARNING: This is experimental and may change at any time without any backward
compatibility.
x-nullable: true
$ref: "#/definitions/OCIDescriptor"

AuthConfig:
type: "object"
Expand Down
9 changes: 9 additions & 0 deletions api/types/image/summary.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package image

import ocispec "github.com/opencontainers/image-spec/specs-go/v1"

type Summary struct {

// Number of containers using this image. Includes both stopped and running
Expand Down Expand Up @@ -42,6 +44,13 @@ type Summary struct {
// Required: true
ParentID string `json:"ParentId"`

// Descriptor is the OCI descriptor of the image target.
// It's only set if the daemon provides a multi-platform image store.
//
// 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.
Expand Down
3 changes: 3 additions & 0 deletions daemon/containerd/image_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
// consider both "0" and "nil" to be "empty".
SharedSize: -1,
Containers: -1,
Descriptor: &target,
}, nil, nil
}

Expand All @@ -401,6 +402,8 @@ func (i *ImageService) imageSummary(ctx context.Context, img images.Image, platf
}
image.Size = totalSize
image.Manifests = manifestSummaries
target := img.Target
image.Descriptor = &target

if opts.ContainerCount {
image.Containers = containersCount
Expand Down
73 changes: 48 additions & 25 deletions daemon/containerd/image_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,20 +186,18 @@ func TestImageList(t *testing.T) {

blobsDir := t.TempDir()

multilayer, err := specialimage.MultiLayer(blobsDir)
assert.NilError(t, err)

twoplatform, err := specialimage.TwoPlatform(blobsDir)
assert.NilError(t, err)

emptyIndex, err := specialimage.EmptyIndex(blobsDir)
assert.NilError(t, err)
toContainerdImage := func(t *testing.T, imageFunc specialimage.SpecialImageFunc) images.Image {
idx, err := imageFunc(blobsDir)
assert.NilError(t, err)

configTarget, err := specialimage.ConfigTarget(blobsDir)
assert.NilError(t, err)
return imagesFromIndex(idx)[0]
}

textplain, err := specialimage.TextPlain(blobsDir)
assert.NilError(t, err)
multilayer := toContainerdImage(t, specialimage.MultiLayer)
twoplatform := toContainerdImage(t, specialimage.TwoPlatform)
emptyIndex := toContainerdImage(t, specialimage.EmptyIndex)
configTarget := toContainerdImage(t, specialimage.ConfigTarget)
textplain := toContainerdImage(t, specialimage.TextPlain)

cs := &blobsDirContentStore{blobs: filepath.Join(blobsDir, "blobs/sha256")}

Expand All @@ -212,11 +210,14 @@ func TestImageList(t *testing.T) {
}{
{
name: "one multi-layer image",
images: imagesFromIndex(multilayer),
images: []images.Image{multilayer},
check: func(t *testing.T, all []*imagetypes.Summary) {
assert.Check(t, is.Len(all, 1))

assert.Check(t, is.Equal(all[0].ID, multilayer.Manifests[0].Digest.String()))
if assert.Check(t, all[0].Descriptor != nil) {
assert.Check(t, is.DeepEqual(*all[0].Descriptor, multilayer.Target))
}
assert.Check(t, is.Equal(all[0].ID, multilayer.Target.Digest.String()))
assert.Check(t, is.DeepEqual(all[0].RepoTags, []string{"multilayer:latest"}))

assert.Check(t, is.Len(all[0].Manifests, 1))
Expand All @@ -226,11 +227,15 @@ func TestImageList(t *testing.T) {
},
{
name: "one image with two platforms is still one entry",
images: imagesFromIndex(twoplatform),
images: []images.Image{twoplatform},
check: func(t *testing.T, all []*imagetypes.Summary) {
assert.Check(t, is.Len(all, 1))

assert.Check(t, is.Equal(all[0].ID, twoplatform.Manifests[0].Digest.String()))
if assert.Check(t, all[0].Descriptor != nil) {
assert.Check(t, is.DeepEqual(*all[0].Descriptor, twoplatform.Target))
}
assert.Check(t, is.Equal(all[0].ID, twoplatform.Target.Digest.String()))

assert.Check(t, is.DeepEqual(all[0].RepoTags, []string{"twoplatform:latest"}))

i := all[0]
Expand All @@ -248,14 +253,22 @@ func TestImageList(t *testing.T) {
},
{
name: "two images are two entries",
images: imagesFromIndex(multilayer, twoplatform),
images: []images.Image{multilayer, twoplatform},
check: func(t *testing.T, all []*imagetypes.Summary) {
assert.Check(t, is.Len(all, 2))

assert.Check(t, is.Equal(all[0].ID, multilayer.Manifests[0].Digest.String()))
if assert.Check(t, all[0].Descriptor != nil) {
assert.Check(t, is.DeepEqual(*all[0].Descriptor, multilayer.Target))
}
assert.Check(t, is.Equal(all[0].ID, multilayer.Target.Digest.String()))

assert.Check(t, is.DeepEqual(all[0].RepoTags, []string{"multilayer:latest"}))

assert.Check(t, is.Equal(all[1].ID, twoplatform.Manifests[0].Digest.String()))
if assert.Check(t, all[1].Descriptor != nil) {
assert.Check(t, is.DeepEqual(*all[1].Descriptor, twoplatform.Target))
}
assert.Check(t, is.Equal(all[1].ID, twoplatform.Target.Digest.String()))

assert.Check(t, is.DeepEqual(all[1].RepoTags, []string{"twoplatform:latest"}))

assert.Check(t, is.Len(all[0].Manifests, 1))
Expand All @@ -269,34 +282,44 @@ func TestImageList(t *testing.T) {
},
{
name: "three images, one is an empty index",
images: imagesFromIndex(multilayer, emptyIndex, twoplatform),
images: []images.Image{multilayer, emptyIndex, twoplatform},
check: func(t *testing.T, all []*imagetypes.Summary) {
assert.Check(t, is.Len(all, 3))
},
},
{
name: "one good image, second has config as a target",
images: imagesFromIndex(multilayer, configTarget),
images: []images.Image{multilayer, configTarget},
check: func(t *testing.T, all []*imagetypes.Summary) {
assert.Check(t, is.Len(all, 2))

sort.Slice(all, func(i, j int) bool {
return slices.Contains(all[i].RepoTags, "multilayer:latest")
})

assert.Check(t, is.Equal(all[0].ID, multilayer.Manifests[0].Digest.String()))
if assert.Check(t, all[0].Descriptor != nil) {
assert.Check(t, is.DeepEqual(*all[0].Descriptor, multilayer.Target))
}
assert.Check(t, is.Equal(all[0].ID, multilayer.Target.Digest.String()))

assert.Check(t, is.Len(all[0].Manifests, 1))

assert.Check(t, is.Equal(all[1].ID, configTarget.Manifests[0].Digest.String()))
if assert.Check(t, all[1].Descriptor != nil) {
assert.Check(t, is.DeepEqual(*all[1].Descriptor, configTarget.Target))
}
assert.Check(t, is.Len(all[1].Manifests, 0))
},
},
{
name: "a non-container image manifest",
images: imagesFromIndex(textplain),
images: []images.Image{textplain},
check: func(t *testing.T, all []*imagetypes.Summary) {
assert.Check(t, is.Len(all, 1))
assert.Check(t, is.Equal(all[0].ID, textplain.Manifests[0].Digest.String()))

if assert.Check(t, all[0].Descriptor != nil) {
assert.Check(t, is.DeepEqual(*all[0].Descriptor, textplain.Target))
}
assert.Check(t, is.Equal(all[0].ID, textplain.Target.Digest.String()))

assert.Assert(t, is.Len(all[0].Manifests, 0))
},
Expand Down
6 changes: 6 additions & 0 deletions docs/api/version-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ keywords: "API, Docker, rcli, REST, documentation"
and will be omitted in API v1.49.
* `Sysctls` in `HostConfig` (top level `--sysctl` settings) for `eth0` are
no longer migrated to `DriverOpts`, as described in the changes for v1.46.
* `GET /images/json` response now includes `Descriptor` field, which contains
an OCI descriptor of the image target.
The new field will only be populated if the daemon provides a multi-platform
image store.
WARNING: This is experimental and may change at any time without any backward
compatibility.

## v1.47 API changes

Expand Down

0 comments on commit cd8d2c5

Please sign in to comment.