chore(images): filter images by name with wildcard pattern using Docker filter

This commit is contained in:
Pasha Sviderski
2025-10-08 12:33:13 +10:00
parent 1f9c2c873d
commit a2c6cbe633
3 changed files with 17 additions and 37 deletions
+7 -37
View File
@@ -29,7 +29,7 @@ func NewListCommand() *cobra.Command {
opts := listOptions{}
cmd := &cobra.Command{
Use: "ls [IMAGE]",
Use: "ls [REPO:[TAG]]",
Aliases: []string{"list"},
Short: "List images on machines in the cluster.",
Long: "List images on machines in the cluster. By default, on all machines. Optionally filter by image name.",
@@ -45,8 +45,8 @@ func NewListCommand() *cobra.Command {
# List images filtered by name (with any tag) on all machines.
uc image ls myapp
# List images filtered by name and tag on specific machine.
uc image ls myapp:1.2.3 -m machine1`,
# List images filtered by name pattern on specific machine.
uc image ls "myapp:1.*" -m machine1`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
@@ -102,7 +102,10 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
machines := cli.ExpandCommaSeparatedValues(opts.machines)
clusterImages, err := clusterClient.ListImages(ctx, api.ImageFilter{Machines: machines})
clusterImages, err := clusterClient.ListImages(ctx, api.ImageFilter{
Machines: machines,
Name: opts.nameFilter,
})
if err != nil {
return fmt.Errorf("list images: %w", err)
}
@@ -156,8 +159,6 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
}
}
rows = filterImagesByName(rows, opts.nameFilter)
if len(rows) == 0 {
if opts.nameFilter != "" {
fmt.Printf("No images matching '%s' found.\n", opts.nameFilter)
@@ -181,37 +182,6 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
return nil
}
// filterImagesByName filters the image rows by image name. It matches any tag of the image if only the name
// is provided. For example, if filter is "nginx", it matches "nginx", "nginx:latest", "nginx:1.25", etc.
// If filter is "nginx:latest", it matches only "nginx:latest".
func filterImagesByName(rows []imageRow, nameFilter string) []imageRow {
if nameFilter == "" {
return rows
}
var filteredRows []imageRow
for _, row := range rows {
if row.name == "<none>" {
continue
}
if strings.Contains(nameFilter, ":") {
// Exact match if filter contains a tag.
if row.name == nameFilter {
filteredRows = append(filteredRows, row)
}
continue
}
// Match by name prefix if filter does not contain a tag.
if strings.HasPrefix(row.name, nameFilter) &&
len(row.name) > len(nameFilter) && row.name[len(nameFilter)] == ':' {
filteredRows = append(filteredRows, row)
}
}
return filteredRows
}
// imagePlatforms returns a list of platforms supported by the image and a boolean indicating if it's multi-platform.
func imagePlatforms(img image.Summary) ([]string, bool) {
var formattedPlatforms []string
+3
View File
@@ -28,6 +28,9 @@ type ImageFilter struct {
// Machines filters images to those present on the specified machines (names or IDs).
// If empty, it matches images on all machines.
Machines []string
// Name filters images by name (with or without tag). Accepts a wildcard pattern.
// If empty, it matches all image names.
Name string
}
// MachineRemoteImage represents an image in a remote registry fetched by a particular machine.
+7
View File
@@ -14,6 +14,7 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/docker/compose/v2/pkg/progress"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
dockerclient "github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
@@ -52,6 +53,12 @@ func (cli *Client) ListImages(ctx context.Context, filter api.ImageFilter) ([]ap
}
opts := image.ListOptions{Manifests: true}
if filter.Name != "" {
opts.Filters = filters.NewArgs(
filters.Arg("reference", filter.Name),
)
}
optsBytes, err := json.Marshal(opts)
if err != nil {
return nil, fmt.Errorf("marshal options: %w", err)