@@ -6,9 +6,11 @@ package main
66import (
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 + `
6171The 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 {
320363func 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+ }
0 commit comments