1
0
mirror of https://github.com/docker/cli.git synced 2026-01-26 15:41:42 +03:00
Files
cli/cli/command/image/list.go
Paweł Gronowski 67f5e3413b image: Fix dangling image detection with graphdrivers
The isDangling function was incorrectly identifying images as dangling
when they had no RepoTags but had valid RepoDigests.

This can occur when the graphdrivers are used instead of the containerd
image store.

An image should only be considered dangling if it has no RepoTags,
regardless of whether it has RepoDigests.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-12-12 11:16:01 +01:00

218 lines
5.9 KiB
Go

// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package image
import (
"context"
"errors"
"fmt"
"io"
"slices"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
type imagesOptions struct {
matchName string
quiet bool
all bool
noTrunc bool
showDigests bool
format string
filter opts.FilterOpt
tree bool
}
// newImagesCommand creates a new `docker images` command
func newImagesCommand(dockerCLI command.Cli) *cobra.Command {
options := imagesOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "images [OPTIONS] [REPOSITORY[:TAG]]",
Short: "List images",
Args: cli.RequiresMaxArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
options.matchName = args[0]
}
numImages, err := runImages(cmd.Context(), dockerCLI, options)
if err != nil {
return err
}
if numImages == 0 && options.matchName != "" && cmd.CalledAs() == "images" {
printAmbiguousHint(dockerCLI.Err(), options.matchName)
}
return nil
},
Annotations: map[string]string{
"category-top": "7",
"aliases": "docker image ls, docker image list, docker images",
},
DisableFlagsInUseLine: true,
ValidArgsFunction: completion.ImageNamesWithBase(dockerCLI, 1),
}
flags := cmd.Flags()
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only show image IDs")
flags.BoolVarP(&options.all, "all", "a", false, "Show all images (default hides intermediate images)")
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
flags.BoolVar(&options.showDigests, "digests", false, "Show digests")
flags.StringVar(&options.format, "format", "", flagsHelper.FormatHelp)
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
flags.BoolVar(&options.tree, "tree", false, "List multi-platform images as a tree (EXPERIMENTAL)")
flags.SetAnnotation("tree", "version", []string{"1.47"})
flags.SetAnnotation("tree", "experimentalCLI", nil)
return cmd
}
func newListCommand(dockerCLI command.Cli) *cobra.Command {
cmd := *newImagesCommand(dockerCLI)
cmd.Aliases = []string{"list"}
cmd.Use = "ls [OPTIONS] [REPOSITORY[:TAG]]"
return &cmd
}
func runImages(ctx context.Context, dockerCLI command.Cli, options imagesOptions) (int, error) {
filters := options.filter.Value()
if options.matchName != "" {
filters.Add("reference", options.matchName)
}
useTree, err := shouldUseTree(options)
if err != nil {
return 0, err
}
listOpts := client.ImageListOptions{
All: options.all,
Filters: filters,
Manifests: useTree,
}
res, err := dockerCLI.Client().ImageList(ctx, listOpts)
if err != nil {
return 0, err
}
images := res.Items
if !options.all {
if _, ok := filters["dangling"]; !ok {
images = slices.DeleteFunc(images, isDangling)
}
}
format := options.format
if len(format) == 0 {
if len(dockerCLI.ConfigFile().ImagesFormat) > 0 && !options.quiet && !options.tree {
format = dockerCLI.ConfigFile().ImagesFormat
useTree = false
} else {
format = formatter.TableFormatKey
}
}
if useTree {
return runTree(ctx, dockerCLI, treeOptions{
images: images,
all: options.all,
filters: filters,
expanded: options.tree,
})
}
imageCtx := formatter.ImageContext{
Context: formatter.Context{
Output: dockerCLI.Out(),
Format: formatter.NewImageFormat(format, options.quiet, options.showDigests),
Trunc: !options.noTrunc,
},
Digest: options.showDigests,
}
if err := formatter.ImageWrite(imageCtx, images); err != nil {
return 0, err
}
return len(images), nil
}
func shouldUseTree(options imagesOptions) (bool, error) {
if options.quiet {
if options.tree {
return false, errors.New("--quiet is not yet supported with --tree")
}
return false, nil
}
if options.noTrunc {
if options.tree {
return false, errors.New("--no-trunc is not yet supported with --tree")
}
return false, nil
}
if options.showDigests {
if options.tree {
return false, errors.New("--show-digest is not yet supported with --tree")
}
return false, nil
}
if options.format != "" {
if options.tree {
return false, errors.New("--format is not yet supported with --tree")
}
return false, nil
}
return true, nil
}
// isDangling is a copy of [formatter.isDangling].
func isDangling(img image.Summary) bool {
if len(img.RepoTags) == 0 {
return true
}
return len(img.RepoTags) == 1 && img.RepoTags[0] == "<none>:<none>" && len(img.RepoDigests) == 1 && img.RepoDigests[0] == "<none>@<none>"
}
// printAmbiguousHint prints an informational warning if the provided filter
// argument is ambiguous.
//
// The "docker images" top-level subcommand predates the "docker <object> <verb>"
// convention (e.g. "docker image ls"), but accepts a positional argument to
// search/filter images by name (globbing). It's common for users to accidentally
// mistake these commands, and to use (e.g.) "docker images ls", expecting
// to see all images, but ending up with an empty list because no image named
// "ls" was found.
//
// Disallowing these search-terms would be a breaking change, but we can print
// and informational message to help the users correct their mistake.
func printAmbiguousHint(stdErr io.Writer, matchName string) {
switch matchName {
// List of subcommands for "docker image" and their aliases (see "docker image --help"):
case "build",
"history",
"import",
"inspect",
"list",
"load",
"ls",
"prune",
"pull",
"push",
"rm",
"save",
"tag":
_, _ = fmt.Fprintf(stdErr, "\nNo images found matching %q: did you mean \"docker image %[1]s\"?\n", matchName)
}
}