Skip to content

Commit 404b08c

Browse files
committed
add filter option to list command
Signed-off-by: olalekan odukoya <odukoyaonline@gmail.com>
1 parent 67edfc2 commit 404b08c

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed

cmd/limactl/list.go

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package main
66
import (
77
"bufio"
88
"bytes"
9+
"encoding/json"
910
"errors"
1011
"fmt"
1112
"reflect"
@@ -57,6 +58,14 @@ The output can be presented in one of several formats, using the --format <forma
5758
--format yaml - Output in YAML format
5859
--format table - Output in table format
5960
--format '{{ <go template> }}' - If the format begins and ends with '{{ }}', then it is used as a go template.
61+
62+
Filtering instances:
63+
--filter EXPR - Filter instances using yq expression (this is equivalent to --yq 'select(EXPR)')
64+
Can be specified multiple times and it works with all output formats.
65+
Examples:
66+
--filter '.status == "Running"'
67+
--filter '.vmType == "vz"'
68+
--filter '.status == "Running"' --filter '.vmType == "vz"'
6069
` + store.FormatHelp + `
6170
The following legacy flags continue to function:
6271
--json - equal to '--format json'`,
@@ -72,6 +81,7 @@ The following legacy flags continue to function:
7281
listCommand.Flags().BoolP("quiet", "q", false, "Only show names")
7382
listCommand.Flags().Bool("all-fields", false, "Show all fields")
7483
listCommand.Flags().StringArray("yq", nil, "Apply yq expression to each instance")
84+
listCommand.Flags().StringArrayP("filter", "l", nil, "Filter instances using yq expression (equivalent to --yq 'select(EXPR)')")
7585

7686
return listCommand
7787
}
@@ -121,6 +131,10 @@ func listAction(cmd *cobra.Command, args []string) error {
121131
if err != nil {
122132
return err
123133
}
134+
filter, err := cmd.Flags().GetStringArray("filter")
135+
if err != nil {
136+
return err
137+
}
124138

125139
if jsonFormat {
126140
format = "json"
@@ -141,6 +155,14 @@ func listAction(cmd *cobra.Command, args []string) error {
141155
return errors.New("option --list-fields conflicts with option --yq")
142156
}
143157
}
158+
if len(filter) != 0 {
159+
if listFields {
160+
return errors.New("option --list-fields conflicts with option --filter")
161+
}
162+
if len(yq) != 0 {
163+
return errors.New("option --filter conflicts with option --yq")
164+
}
165+
}
144166

145167
if quiet && format != "table" {
146168
return errors.New("option --quiet can only be used with '--format table'")
@@ -220,15 +242,31 @@ func listAction(cmd *cobra.Command, args []string) error {
220242
options.TerminalWidth = w
221243
}
222244
}
223-
// --yq implies --format json unless --format yaml has been explicitly specified
245+
246+
// --yq implies --format json unless --format has been explicitly specified
224247
if len(yq) != 0 && !cmd.Flags().Changed("format") {
225248
format = "json"
226249
}
250+
227251
// Always pipe JSON and YAML through yq to colorize it if isTTY
228252
if len(yq) == 0 && (format == "json" || format == "yaml") {
229253
yq = append(yq, ".")
230254
}
231255

256+
for _, f := range filter {
257+
yq = append(yq, "select("+f+")")
258+
}
259+
260+
// handle --filter with table and go-template formats
261+
isGoTemplate := strings.HasPrefix(format, "{{") && strings.HasSuffix(format, "}}")
262+
if len(filter) != 0 && (format == "table" || isGoTemplate) {
263+
instances, err = filterInstances(instances, yq)
264+
if err != nil {
265+
return err
266+
}
267+
yq = nil
268+
}
269+
232270
if len(yq) == 0 {
233271
err = store.PrintInstances(cmd.OutOrStdout(), instances, format, &options)
234272
if err == nil && unmatchedInstances {
@@ -320,3 +358,31 @@ func listAction(cmd *cobra.Command, args []string) error {
320358
func listBashComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
321359
return bashCompleteInstanceNames(cmd)
322360
}
361+
362+
// filterInstances applies yq expressions to instances and returns the filtered results.
363+
func filterInstances(instances []*limatype.Instance, yqExprs []string) ([]*limatype.Instance, error) {
364+
if len(yqExprs) == 0 {
365+
return instances, nil
366+
}
367+
368+
yqExpr := strings.Join(yqExprs, " | ")
369+
370+
var filteredInstances []*limatype.Instance
371+
for _, instance := range instances {
372+
jsonBytes, err := json.Marshal(instance)
373+
if err != nil {
374+
return nil, fmt.Errorf("failed to marshal instance %s: %v", instance.Name, err)
375+
}
376+
377+
result, err := yqutil.EvaluateExpression(yqExpr, jsonBytes)
378+
if err != nil {
379+
return nil, fmt.Errorf("failed to apply filter %s: %v", yqExpr, err)
380+
}
381+
382+
if len(result) > 0 {
383+
filteredInstances = append(filteredInstances, instance)
384+
}
385+
}
386+
387+
return filteredInstances, nil
388+
}

hack/bats/tests/list.bats

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,26 @@ local_setup() {
264264
run -0 limactl ls --quiet --yq 'select(.name == "foo")'
265265
assert_output "foo"
266266
}
267+
268+
@test '--filter option filters instances' {
269+
run -0 limactl ls --filter '.name == "foo"'
270+
assert_line --index 0 --regexp '^NAME'
271+
assert_line --index 1 --regexp '^foo'
272+
assert_output_lines_count 2
273+
}
274+
275+
@test '--filter option works with all output formats' {
276+
run -0 limactl ls --filter '.name == "foo"'
277+
assert_line --index 1 --regexp '^foo'
278+
279+
run -0 limactl ls --filter '.name == "foo"' --format json
280+
assert_line --index 0 --regexp '^\{"name":"foo",'
281+
282+
run -0 limactl ls --filter '.name == "foo"' --format '{{.Name}}'
283+
assert_output "foo"
284+
}
285+
286+
@test '--filter option is incompatible with --yq' {
287+
run_e -1 limactl ls --filter '.name == "foo"' --yq '.name'
288+
assert_fatal "option --filter conflicts with option --yq"
289+
}

0 commit comments

Comments
 (0)