image/list: Show collapsed tree by default
Use the new tree view by default and only fallback if format or old view-related options are used. The expanded view is shown when `--tree` is passed. Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
+38
-19
@@ -82,38 +82,28 @@ func newListCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
return &cmd
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func runImages(ctx context.Context, dockerCLI command.Cli, options imagesOptions) error {
|
||||
filters := options.filter.Value()
|
||||
if options.matchName != "" {
|
||||
filters.Add("reference", options.matchName)
|
||||
}
|
||||
|
||||
if options.tree {
|
||||
if options.quiet {
|
||||
return errors.New("--quiet is not yet supported with --tree")
|
||||
}
|
||||
if options.noTrunc {
|
||||
return errors.New("--no-trunc is not yet supported with --tree")
|
||||
}
|
||||
if options.showDigests {
|
||||
return errors.New("--show-digest is not yet supported with --tree")
|
||||
}
|
||||
if options.format != "" {
|
||||
return errors.New("--format is not yet supported with --tree")
|
||||
}
|
||||
useTree, err := shouldUseTree(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
listOpts := client.ImageListOptions{
|
||||
All: options.all,
|
||||
Filters: filters,
|
||||
Manifests: options.tree,
|
||||
Manifests: useTree,
|
||||
}
|
||||
|
||||
res, err := dockerCLI.Client().ImageList(ctx, listOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
images := res.Items
|
||||
if !options.all {
|
||||
if _, ok := filters["dangling"]; !ok {
|
||||
@@ -121,11 +111,12 @@ func runImages(ctx context.Context, dockerCLI command.Cli, options imagesOptions
|
||||
}
|
||||
}
|
||||
|
||||
if options.tree {
|
||||
if useTree {
|
||||
return runTree(ctx, dockerCLI, treeOptions{
|
||||
images: images,
|
||||
all: options.all,
|
||||
filters: filters,
|
||||
images: images,
|
||||
all: options.all,
|
||||
filters: filters,
|
||||
expanded: options.tree,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -155,6 +146,34 @@ func runImages(ctx context.Context, dockerCLI command.Cli, options imagesOptions
|
||||
return 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 && len(img.RepoDigests) == 0 {
|
||||
|
||||
+35
-17
@@ -24,9 +24,10 @@ import (
|
||||
)
|
||||
|
||||
type treeOptions struct {
|
||||
images []imagetypes.Summary
|
||||
all bool
|
||||
filters client.Filters
|
||||
images []imagetypes.Summary
|
||||
all bool
|
||||
filters client.Filters
|
||||
expanded bool
|
||||
}
|
||||
|
||||
type treeView struct {
|
||||
@@ -48,7 +49,7 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
details := imageDetails{
|
||||
topDetails := imageDetails{
|
||||
ID: img.ID,
|
||||
DiskUsage: units.HumanSizeWithPrecision(float64(img.Size), 3),
|
||||
InUse: img.Containers > 0,
|
||||
@@ -67,41 +68,58 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
|
||||
continue
|
||||
}
|
||||
|
||||
inUse := len(im.ImageData.Containers) > 0
|
||||
if inUse {
|
||||
// Mark top-level parent image as used if any of its subimages are used.
|
||||
topDetails.InUse = true
|
||||
}
|
||||
|
||||
if !opts.expanded {
|
||||
continue
|
||||
}
|
||||
|
||||
sub := subImage{
|
||||
Platform: platforms.Format(im.ImageData.Platform),
|
||||
Available: im.Available,
|
||||
Details: imageDetails{
|
||||
ID: im.ID,
|
||||
DiskUsage: units.HumanSizeWithPrecision(float64(im.Size.Total), 3),
|
||||
InUse: len(im.ImageData.Containers) > 0,
|
||||
InUse: inUse,
|
||||
ContentSize: units.HumanSizeWithPrecision(float64(im.Size.Content), 3),
|
||||
},
|
||||
}
|
||||
|
||||
if sub.Details.InUse {
|
||||
// Mark top-level parent image as used if any of its subimages are used.
|
||||
details.InUse = true
|
||||
}
|
||||
|
||||
children = append(children, sub)
|
||||
|
||||
// Add extra spacing between images if there's at least one entry with children.
|
||||
view.imageSpacing = true
|
||||
}
|
||||
|
||||
details.ContentSize = units.HumanSizeWithPrecision(float64(totalContent), 3)
|
||||
topDetails.ContentSize = units.HumanSizeWithPrecision(float64(totalContent), 3)
|
||||
|
||||
// Sort tags for this image
|
||||
sortedTags := make([]string, len(img.RepoTags))
|
||||
copy(sortedTags, img.RepoTags)
|
||||
slices.Sort(sortedTags)
|
||||
|
||||
view.images = append(view.images, topImage{
|
||||
Names: sortedTags,
|
||||
Details: details,
|
||||
Children: children,
|
||||
created: img.Created,
|
||||
})
|
||||
if opts.expanded {
|
||||
view.images = append(view.images, topImage{
|
||||
Names: sortedTags,
|
||||
Details: topDetails,
|
||||
Children: children,
|
||||
created: img.Created,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
for _, tag := range sortedTags {
|
||||
view.images = append(view.images, topImage{
|
||||
Names: []string{tag},
|
||||
Details: topDetails,
|
||||
Children: children,
|
||||
created: img.Created,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(view.images, func(a, b topImage) int {
|
||||
|
||||
Reference in New Issue
Block a user