github.com/grafana/tanka@v0.26.1-0.20240506093700-c22cfc35c21a/pkg/process/filter.go (about)

     1  package process
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strings"
     7  
     8  	"github.com/grafana/tanka/pkg/kubernetes/manifest"
     9  	"golang.org/x/text/cases"
    10  	"golang.org/x/text/language"
    11  )
    12  
    13  // Filter returns all elements of the list that match at least one expression
    14  // and are not ignored
    15  func Filter(list manifest.List, exprs Matchers) manifest.List {
    16  	out := make(manifest.List, 0, len(list))
    17  	for _, m := range list {
    18  		if !exprs.MatchString(m.KindName()) {
    19  			continue
    20  		}
    21  		if exprs.IgnoreString(m.KindName()) {
    22  			continue
    23  		}
    24  		out = append(out, m)
    25  	}
    26  	return out
    27  }
    28  
    29  // Matcher is a single filter expression. The passed argument of Matcher is of the
    30  // form `kind/name` (manifest.KindName())
    31  type Matcher interface {
    32  	MatchString(string) bool
    33  }
    34  
    35  // Ignorer is like matcher, but for explicitly ignoring resources
    36  type Ignorer interface {
    37  	IgnoreString(string) bool
    38  }
    39  
    40  // Matchers is a collection of multiple expressions.
    41  // A matcher may also implement Ignorer to explicitly ignore fields
    42  type Matchers []Matcher
    43  
    44  // MatchString returns whether at least one expression (OR) matches the string
    45  func (e Matchers) MatchString(s string) bool {
    46  	b := false
    47  	for _, exp := range e {
    48  		b = b || exp.MatchString(s)
    49  	}
    50  	return b
    51  }
    52  
    53  func (e Matchers) IgnoreString(s string) bool {
    54  	b := false
    55  	for _, exp := range e {
    56  		i, ok := exp.(Ignorer)
    57  		if !ok {
    58  			continue
    59  		}
    60  		b = b || i.IgnoreString(s)
    61  	}
    62  	return b
    63  }
    64  
    65  // RegExps is a helper to construct Matchers from regular expressions
    66  func RegExps(rs []*regexp.Regexp) Matchers {
    67  	xprs := make(Matchers, 0, len(rs))
    68  	for _, r := range rs {
    69  		xprs = append(xprs, r)
    70  	}
    71  	return xprs
    72  }
    73  
    74  func StrExps(strs ...string) (Matchers, error) {
    75  	exps := make(Matchers, 0, len(strs))
    76  	for _, raw := range strs {
    77  		// trim exlamation mark, not supported by regex
    78  		s := fmt.Sprintf(`(?i)^%s$`, strings.TrimPrefix(raw, "!"))
    79  
    80  		// create regexp matcher
    81  		var exp Matcher
    82  		exp, err := regexp.Compile(s)
    83  		if err != nil {
    84  			return nil, ErrBadExpr{err}
    85  		}
    86  
    87  		// if negative (!), invert regex behaviour
    88  		if strings.HasPrefix(raw, "!") {
    89  			exp = NegMatcher{exp: exp}
    90  		}
    91  		exps = append(exps, exp)
    92  	}
    93  	return exps, nil
    94  }
    95  
    96  func MustStrExps(strs ...string) Matchers {
    97  	exps, err := StrExps(strs...)
    98  	if err != nil {
    99  		panic(err)
   100  	}
   101  	return exps
   102  }
   103  
   104  // ErrBadExpr occurs when the regexp compiling fails
   105  type ErrBadExpr struct {
   106  	inner error
   107  }
   108  
   109  func (e ErrBadExpr) Error() string {
   110  	caser := cases.Title(language.English)
   111  	return fmt.Sprintf("%s.\nSee https://tanka.dev/output-filtering/#regular-expressions for details on regular expressions.", caser.String(e.inner.Error()))
   112  }
   113  
   114  // NexMatcher is a matcher that inverts the original behaviour
   115  type NegMatcher struct {
   116  	exp Matcher
   117  }
   118  
   119  func (n NegMatcher) MatchString(_ string) bool {
   120  	return true
   121  }
   122  
   123  func (n NegMatcher) IgnoreString(s string) bool {
   124  	return n.exp.MatchString(s)
   125  }