API: add Platform (OS and Architecture) to /containers/json

Adds platform information to containers (for `docker ps`).

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
This commit is contained in:
Jonathan A. Sternberg
2025-02-13 14:52:21 -06:00
parent b570831cc3
commit 927e07e46e
8 changed files with 86 additions and 22 deletions

View File

@@ -113,6 +113,13 @@ func (c *containerRouter) getContainersJSON(ctx context.Context, w http.Response
}
}
if versions.LessThan(version, "1.48") {
// ImageManifestDescriptor information was added in API 1.48
for _, c := range containers {
c.ImageManifestDescriptor = nil
}
}
return httputils.WriteJSON(w, http.StatusOK, containers)
}

View File

@@ -5109,6 +5109,17 @@ definitions:
ImageID:
description: "The ID of the image that this container was created from"
type: "string"
ImageManifestDescriptor:
$ref: "#/definitions/OCIDescriptor"
x-nullable: true
description: |
OCI descriptor of the platform-specific manifest of the image
the container was created from.
Note: Only available if the daemon provides a multi-platform
image store.
This field is not populated in the `GET /system/df` endpoint.
Command:
description: "Command to run when starting the container"
type: "string"

View File

@@ -121,19 +121,20 @@ type State struct {
// Summary contains response of Engine API:
// GET "/containers/json"
type Summary struct {
ID string `json:"Id"`
Names []string
Image string
ImageID string
Command string
Created int64
Ports []Port
SizeRw int64 `json:",omitempty"`
SizeRootFs int64 `json:",omitempty"`
Labels map[string]string
State string
Status string
HostConfig struct {
ID string `json:"Id"`
Names []string
Image string
ImageID string
ImageManifestDescriptor *ocispec.Descriptor `json:"ImageManifestDescriptor,omitempty"`
Command string
Created int64
Ports []Port
SizeRw int64 `json:",omitempty"`
SizeRootFs int64 `json:",omitempty"`
Labels map[string]string
State string
Status string
HostConfig struct {
NetworkMode string `json:",omitempty"`
Annotations map[string]string `json:",omitempty"`
}
@@ -183,5 +184,5 @@ type InspectResponse struct {
Config *Config
NetworkSettings *NetworkSettings
// ImageManifestDescriptor is the descriptor of a platform-specific manifest of the image used to create the container.
ImageManifestDescriptor *ocispec.Descriptor `json:",omitempty"`
ImageManifestDescriptor *ocispec.Descriptor `json:"ImageManifestDescriptor,omitempty"`
}

View File

@@ -301,14 +301,15 @@ func (v *View) transform(ctr *Container) *Snapshot {
}
snapshot := &Snapshot{
Summary: container.Summary{
ID: ctr.ID,
Names: v.getNames(ctr.ID),
ImageID: ctr.ImageID.String(),
Ports: []container.Port{},
Mounts: ctr.GetMountPoints(),
State: ctr.State.StateString(),
Status: ctr.State.String(),
Created: ctr.Created.Unix(),
ID: ctr.ID,
Names: v.getNames(ctr.ID),
ImageID: ctr.ImageID.String(),
ImageManifestDescriptor: ctr.ImageManifest,
Ports: []container.Port{},
Mounts: ctr.GetMountPoints(),
State: ctr.State.StateString(),
Status: ctr.State.String(),
Created: ctr.Created.Unix(),
},
CreatedAt: ctr.Created,
StartedAt: ctr.StartedAt,
@@ -416,6 +417,10 @@ func (v *View) transform(ctr *Container) *Snapshot {
}
snapshot.NetworkSettings = &container.NetworkSettingsSummary{Networks: networks}
if ctr.ImageManifest != nil && ctr.ImageManifest.Platform == nil {
ctr.ImageManifest.Platform = &ctr.ImagePlatform
}
return snapshot
}

View File

@@ -26,6 +26,12 @@ func (daemon *Daemon) containerDiskUsage(ctx context.Context) ([]*container.Summ
if err != nil {
return nil, fmt.Errorf("failed to retrieve container list: %v", err)
}
// Remove image manifest descriptor from the result as it should not be included.
// https://github.com/moby/moby/pull/49407#discussion_r1954396666
for _, c := range containers {
c.ImageManifestDescriptor = nil
}
return containers, nil
})
return res, err

View File

@@ -89,6 +89,10 @@ keywords: "API, Docker, rcli, REST, documentation"
paths (`/v<API-version>/<endpoint>`).
* `POST /build/prune` renames `keep-bytes` to `reserved-space` and now supports
additional prune parameters `max-used-space` and `min-free-space`.
* `GET /containers/json` now returns an `ImageManifestDescriptor` field
matching the same field in `/containers/{name}/json`.
This field is only populated if the daemon provides a multi-platform image
store.
## v1.47 API changes

View File

@@ -9,6 +9,7 @@ import (
"github.com/docker/docker/testutil"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/skip"
)
func TestPsFilter(t *testing.T) {
@@ -47,3 +48,29 @@ func TestPsFilter(t *testing.T) {
assert.Check(t, is.Contains(containerIDs(results), prev))
})
}
// TestPsPlatform verifies that containers have a platform set
func TestPsImageManifestPlatform(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon)
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, !testEnv.UsingSnapshotter())
ctx := setupTest(t)
apiClient := testEnv.APIClient()
container.Create(ctx, t, apiClient)
containers, err := apiClient.ContainerList(ctx, containertypes.ListOptions{
All: true,
})
assert.NilError(t, err)
assert.Check(t, len(containers) > 0)
ctr := containers[0]
if assert.Check(t, ctr.ImageManifestDescriptor != nil && ctr.ImageManifestDescriptor.Platform != nil) {
// Check that at least OS and Architecture have a value. Other values
// depend on the platform on which we're running the test.
assert.Equal(t, ctr.ImageManifestDescriptor.Platform.OS, testEnv.DaemonInfo.OSType)
assert.Check(t, ctr.ImageManifestDescriptor.Platform.Architecture != "")
}
}

View File

@@ -107,6 +107,9 @@ func TestDiskUsage(t *testing.T) {
// previously used prev.Images[0].Size, which may differ from content data
assert.Check(t, is.Equal(du.Containers[0].SizeRootFs, du.LayersSize))
// ImageManifestDescriptor should NOT be populated.
assert.Check(t, is.Nil(du.Containers[0].ImageManifestDescriptor))
return du
},
},