@@ -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,11 @@ 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+ }
144164
145165 if quiet && format != "table" {
146166 return errors .New ("option --quiet can only be used with '--format table'" )
@@ -220,15 +240,35 @@ func listAction(cmd *cobra.Command, args []string) error {
220240 options .TerminalWidth = w
221241 }
222242 }
223- // --yq implies --format json unless --format yaml has been explicitly specified
243+
244+ // --yq implies --format json unless --format has been explicitly specified
224245 if len (yq ) != 0 && ! cmd .Flags ().Changed ("format" ) {
225246 format = "json"
226247 }
248+
227249 // Always pipe JSON and YAML through yq to colorize it if isTTY
228250 if len (yq ) == 0 && (format == "json" || format == "yaml" ) {
229251 yq = append (yq , "." )
230252 }
231253
254+ for _ , f := range filter {
255+ // only allow fields, ==, !=, and literals.
256+ valid := regexp .MustCompile (`^[a-zA-Z0-9_.\s"'-=><!]+$` )
257+ if ! valid .MatchString (f ) {
258+ return fmt .Errorf ("unsafe characters in filter expression: %q" , f )
259+ }
260+
261+ yq = append (yq , "select(" + f + ")" )
262+ }
263+
264+ if len (filter ) != 0 && (format != "json" && format != "yaml" ) {
265+ instances , err = filterInstances (instances , yq )
266+ if err != nil {
267+ return err
268+ }
269+ yq = nil
270+ }
271+
232272 if len (yq ) == 0 {
233273 err = store .PrintInstances (cmd .OutOrStdout (), instances , format , & options )
234274 if err == nil && unmatchedInstances {
@@ -320,3 +360,31 @@ func listAction(cmd *cobra.Command, args []string) error {
320360func listBashComplete (cmd * cobra.Command , _ []string , _ string ) ([]string , cobra.ShellCompDirective ) {
321361 return bashCompleteInstanceNames (cmd )
322362}
363+
364+ // filterInstances applies yq expressions to instances and returns the filtered results.
365+ func filterInstances (instances []* limatype.Instance , yqExprs []string ) ([]* limatype.Instance , error ) {
366+ if len (yqExprs ) == 0 {
367+ return instances , nil
368+ }
369+
370+ yqExpr := strings .Join (yqExprs , " | " )
371+
372+ var filteredInstances []* limatype.Instance
373+ for _ , instance := range instances {
374+ jsonBytes , err := json .Marshal (instance )
375+ if err != nil {
376+ return nil , fmt .Errorf ("failed to marshal instance %q: %w" , instance .Name , err )
377+ }
378+
379+ result , err := yqutil .EvaluateExpression (yqExpr , jsonBytes )
380+ if err != nil {
381+ return nil , fmt .Errorf ("failed to apply filter %q: %w" , yqExpr , err )
382+ }
383+
384+ if len (result ) > 0 {
385+ filteredInstances = append (filteredInstances , instance )
386+ }
387+ }
388+
389+ return filteredInstances , nil
390+ }
0 commit comments