Skip to content

feat(image): add --tree flag to image list - #5092

Open
ekalinin wants to merge 1 commit into
containerd:mainfrom
ekalinin:feat/images-tree
Open

feat(image): add --tree flag to image list#5092
ekalinin wants to merge 1 commit into
containerd:mainfrom
ekalinin:feat/images-tree

Conversation

@ekalinin

@ekalinin ekalinin commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Adds --tree to nerdctl images, expanding the Docker v29 view from #5093 with a row per
platform, like docker image ls --tree: same columns (IMAGE, ID, DISK USAGE, CONTENT SIZE,
EXTRA) and the same branch glyphs docker/cli uses.

  • Every platform the index declares gets a row; the ones that were never pulled show zero sizes, as
    docker does. This is confined to the tree - the collapsed and legacy views, and
    EnsureAllContent, keep describing only what is in the store.
  • A platform row carries its own manifest digest as the ID, its own sizes, and U when a container
    runs that platform. Matching is on the full platform form rather than on the displayed name, so
    several windows/amd64 manifests differing only by OSVersion stay distinct; the container label
    is normalized first, since platforms.DefaultString does not (on arm64 it carries a variant).
  • Conflicting flags are rejected the way docker's shouldUseTree does, extended to the
    nerdctl-only --names. Validated in pkg/cmd/image so library callers are covered too, and in
    the CLI so the error precedes the containerd connection.
  • Image groups are not separated by a blank line: the rows share a tabwriter with the header, where
    a blank line would end the column block and misalign every following group. docker/cli sizes its
    columns itself and has no such constraint.

Also fixes the platform of a multi-platform image being read from the image config instead of the
index descriptor: alpine's linux/arm/v6 and linux/arm/v7 configs both declare a bare
linux/arm, which collapsed them onto one key and dropped a platform. This affects the non-tree
view too
, whose aggregated sizes were missing it - happy to split it into its own PR if you
prefer.

Review feedback: the expected per-platform content sizes now live in testutil (contentsize in
images.yaml plus accessors) and the test asserts them for every pulled platform, while
DISK USAGE is only asserted to cover CONTENT SIZE, since what is unpacked depends on what the
rest of the suite did with the shared image store. The test is no longer skipped for Docker and
only diverges where docker does: docker pull has no --all-platforms, docker's --digests
conflict message names its internal flag, and --names does not exist there. It lives in a
_linux_test.go because the testutil image registry is Linux-only.

Known difference: with several tags on one target, nerdctl prints a tree per image record while
docker groups the names into a single node - the collapsed view from #5093 behaves the same way.

Closes #5005

Comment thread pkg/cmd/image/list.go Outdated
Comment thread cmd/nerdctl/image/image_list_test.go Outdated
Comment thread cmd/nerdctl/image/image_list_test.go Outdated
@AkihiroSuda AkihiroSuda added this to the v2.4.0 milestone Aug 4, 2026
Expand the Docker v29 view added in containerd#5093 with `--tree`, matching
`docker image ls --tree`: the same IMAGE, ID, DISK USAGE, CONTENT SIZE and
EXTRA columns, plus one row per platform the image declares, prefixed with the
branch glyphs docker/cli uses. Like docker, a platform that an index lists but
that was never pulled is shown with zero sizes. The collapsed and the legacy
views keep describing only what is in the store, and so does EnsureAllContent,
which would otherwise reach out for platforms the user never asked for.

A platform row carries its own manifest digest as the ID, its own sizes, and
the "U" flag only when a container actually runs that platform. The image row
keeps aggregating its platforms and reuses the existing target-digest lookup,
so the default view is unchanged.

Matching a container to a platform is done on the full platform form, not on
the name the row displays: an index can carry several windows/amd64 manifests
that differ only by OSVersion, and keying on the displayed name would let a
container on one build flag all of them. The container label is normalized
first, since platforms.DefaultString does not normalize: on arm64 it carries a
variant while the manifest platform normalizes to a bare linux/arm64, and a raw
comparison would never match. The rows are sorted on that same full form, so
the ones that render identically keep a stable order.

Unlike docker/cli, which computes its column widths itself and separates the
images with a blank line, the rows go through a tabwriter shared with the
header, where a blank line would terminate the column block and misalign
every following group. The groups are therefore separated by the glyphs alone.

The flag combinations docker rejects in shouldUseTree are rejected here too,
with the same messages. They are validated in pkg/cmd/image, where the options
are consumed, so that library callers are covered as well: the tree branch
takes precedence over the formatter, so Tree together with Format used to
print unaligned rows with no header. The CLI validates too, so that the error
still surfaces before a containerd connection is attempted.

Also read the platform of a multi-platform image from its index descriptor
rather than from the image config. The config may be less specific: alpine
ships linux/arm/v6 and linux/arm/v7 manifests whose configs both declare a
bare "linux/arm", which normalizes to linux/arm/v7 and collapsed the two onto
a single key, dropping one platform. That also left it out of the aggregated
sizes of the default view.

The expected per-platform content sizes are declared in testutil, and the
integration test runs against docker as well, only diverging where docker
does: `docker pull` has no --all-platforms, and its message for the digests
conflict names its internal flag. DISK USAGE is only asserted to cover CONTENT
SIZE, because which platforms are unpacked depends on what the rest of the
suite did with the shared image store.

Closes containerd#5005

Signed-off-by: Eugene Kalinin <e.v.kalinin@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

nerdctl images lacks --tree

2 participants