Skip to content

Commit 224e6e4

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

File tree

3 files changed

+96
-2
lines changed

3 files changed

+96
-2
lines changed

cmd/limactl/list.go

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ package main
66
import (
77
"bufio"
88
"bytes"
9+
"encoding/json"
910
"errors"
1011
"fmt"
1112
"reflect"
13+
"regexp"
1214
"sort"
1315
"strings"
1416

@@ -57,6 +59,14 @@ The output can be presented in one of several formats, using the --format <forma
5759
--format yaml - Output in YAML format
5860
--format table - Output in table format
5961
--format '{{ <go template> }}' - If the format begins and ends with '{{ }}', then it is used as a go template.
62+
63+
Filtering instances:
64+
--filter EXPR - Filter instances using yq expression (this is equivalent to --yq 'select(EXPR)')
65+
Can be specified multiple times and it works with all output formats.
66+
Examples:
67+
--filter '.status == "Running"'
68+
--filter '.vmType == "vz"'
69+
--filter '.status == "Running"' --filter '.vmType == "vz"'
6070
` + store.FormatHelp + `
6171
The following legacy flags continue to function:
6272
--json - equal to '--format json'`,
@@ -72,6 +82,7 @@ The following legacy flags continue to function:
7282
listCommand.Flags().BoolP("quiet", "q", false, "Only show names")
7383
listCommand.Flags().Bool("all-fields", false, "Show all fields")
7484
listCommand.Flags().StringArray("yq", nil, "Apply yq expression to each instance")
85+
listCommand.Flags().StringArrayP("filter", "l", nil, "Filter instances using yq expression (equivalent to --yq 'select(EXPR)')")
7586

7687
return listCommand
7788
}
@@ -121,6 +132,10 @@ func listAction(cmd *cobra.Command, args []string) error {
121132
if err != nil {
122133
return err
123134
}
135+
filter, err := cmd.Flags().GetStringArray("filter")
136+
if err != nil {
137+
return err
138+
}
124139

125140
if jsonFormat {
126141
format = "json"
@@ -141,6 +156,14 @@ func listAction(cmd *cobra.Command, args []string) error {
141156
return errors.New("option --list-fields conflicts with option --yq")
142157
}
143158
}
159+
if len(filter) != 0 {
160+
if listFields {
161+
return errors.New("option --list-fields conflicts with option --filter")
162+
}
163+
if len(yq) != 0 {
164+
return errors.New("option --filter conflicts with option --yq")
165+
}
166+
}
144167

145168
if quiet && format != "table" {
146169
return errors.New("option --quiet can only be used with '--format table'")
@@ -220,15 +243,35 @@ func listAction(cmd *cobra.Command, args []string) error {
220243
options.TerminalWidth = w
221244
}
222245
}
223-
// --yq implies --format json unless --format yaml has been explicitly specified
246+
247+
// --yq implies --format json unless --format has been explicitly specified
224248
if len(yq) != 0 && !cmd.Flags().Changed("format") {
225249
format = "json"
226250
}
251+
227252
// Always pipe JSON and YAML through yq to colorize it if isTTY
228253
if len(yq) == 0 && (format == "json" || format == "yaml") {
229254
yq = append(yq, ".")
230255
}
231256

257+
for _, f := range filter {
258+
// only allow fields, ==, !=, and literals.
259+
valid := regexp.MustCompile(`^[a-zA-Z0-9_.\s"'-=><!]+$`)
260+
if !valid.MatchString(f) {
261+
return fmt.Errorf("unsafe characters in filter expression: %q", f)
262+
}
263+
264+
yq = append(yq, "select("+f+")")
265+
}
266+
267+
if len(filter) != 0 && (format != "json" && format != "yaml") {
268+
instances, err = filterInstances(instances, yq)
269+
if err != nil {
270+
return err
271+
}
272+
yq = nil
273+
}
274+
232275
if len(yq) == 0 {
233276
err = store.PrintInstances(cmd.OutOrStdout(), instances, format, &options)
234277
if err == nil && unmatchedInstances {
@@ -320,3 +363,31 @@ func listAction(cmd *cobra.Command, args []string) error {
320363
func listBashComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
321364
return bashCompleteInstanceNames(cmd)
322365
}
366+
367+
// filterInstances applies yq expressions to instances and returns the filtered results.
368+
func filterInstances(instances []*limatype.Instance, yqExprs []string) ([]*limatype.Instance, error) {
369+
if len(yqExprs) == 0 {
370+
return instances, nil
371+
}
372+
373+
yqExpr := strings.Join(yqExprs, " | ")
374+
375+
var filteredInstances []*limatype.Instance
376+
for _, instance := range instances {
377+
jsonBytes, err := json.Marshal(instance)
378+
if err != nil {
379+
return nil, fmt.Errorf("failed to marshal instance %q: %w", instance.Name, err)
380+
}
381+
382+
result, err := yqutil.EvaluateExpression(yqExpr, jsonBytes)
383+
if err != nil {
384+
return nil, fmt.Errorf("failed to apply filter %q: %w", yqExpr, err)
385+
}
386+
387+
if len(result) > 0 {
388+
filteredInstances = append(filteredInstances, instance)
389+
}
390+
}
391+
392+
return filteredInstances, nil
393+
}

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+
}

pkg/version/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
package version
55

66
// Version is filled on compilation time.
7-
var Version = "<unknown>"
7+
var Version = "1.2.1"

0 commit comments

Comments
 (0)