...

Source file src/sigs.k8s.io/kustomize/kyaml/fn/framework/selector.go

Documentation: sigs.k8s.io/kustomize/kyaml/fn/framework

     1  // Copyright 2021 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package framework
     5  
     6  import (
     7  	"sigs.k8s.io/kustomize/kyaml/errors"
     8  	"sigs.k8s.io/kustomize/kyaml/yaml"
     9  )
    10  
    11  // Selector matches resources.  A resource matches if and only if ALL of the Selector fields
    12  // match the resource.  An empty Selector matches all resources.
    13  type Selector struct {
    14  	// Names is a list of metadata.names to match.  If empty match all names.
    15  	// e.g. Names: ["foo", "bar"] matches if `metadata.name` is either "foo" or "bar".
    16  	Names []string `json:"names" yaml:"names"`
    17  
    18  	// Namespaces is a list of metadata.namespaces to match.  If empty match all namespaces.
    19  	// e.g. Namespaces: ["foo", "bar"] matches if `metadata.namespace` is either "foo" or "bar".
    20  	Namespaces []string `json:"namespaces" yaml:"namespaces"`
    21  
    22  	// Kinds is a list of kinds to match.  If empty match all kinds.
    23  	// e.g. Kinds: ["foo", "bar"] matches if `kind` is either "foo" or "bar".
    24  	Kinds []string `json:"kinds" yaml:"kinds"`
    25  
    26  	// APIVersions is a list of apiVersions to match.  If empty apply match all apiVersions.
    27  	// e.g. APIVersions: ["foo/v1", "bar/v1"] matches if `apiVersion` is either "foo/v1" or "bar/v1".
    28  	APIVersions []string `json:"apiVersions" yaml:"apiVersions"`
    29  
    30  	// Labels is a collection of labels to match.  All labels must match exactly.
    31  	// e.g. Labels: {"foo": "bar", "baz": "buz"] matches if BOTH "foo" and "baz" labels match.
    32  	Labels map[string]string `json:"labels" yaml:"labels"`
    33  
    34  	// Annotations is a collection of annotations to match.  All annotations must match exactly.
    35  	// e.g. Annotations: {"foo": "bar", "baz": "buz"] matches if BOTH "foo" and "baz" annotations match.
    36  	Annotations map[string]string `json:"annotations" yaml:"annotations"`
    37  
    38  	// ResourceMatcher is an arbitrary function used to match resources.
    39  	// Selector matches if the function returns true.
    40  	ResourceMatcher func(*yaml.RNode) bool
    41  
    42  	// TemplateData if present will cause the selector values to be parsed as templates
    43  	// and rendered using TemplateData before they are used.
    44  	TemplateData interface{}
    45  
    46  	// FailOnEmptyMatch makes the selector return an error when no items are selected.
    47  	FailOnEmptyMatch bool
    48  }
    49  
    50  // Filter implements kio.Filter, returning only those items from the list that the selector
    51  // matches.
    52  func (s *Selector) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
    53  	andSel := AndSelector{TemplateData: s.TemplateData, FailOnEmptyMatch: s.FailOnEmptyMatch}
    54  	if s.Names != nil {
    55  		andSel.Matchers = append(andSel.Matchers, NameMatcher(s.Names...))
    56  	}
    57  	if s.Namespaces != nil {
    58  		andSel.Matchers = append(andSel.Matchers, NamespaceMatcher(s.Namespaces...))
    59  	}
    60  	if s.Kinds != nil {
    61  		andSel.Matchers = append(andSel.Matchers, KindMatcher(s.Kinds...))
    62  	}
    63  	if s.APIVersions != nil {
    64  		andSel.Matchers = append(andSel.Matchers, APIVersionMatcher(s.APIVersions...))
    65  	}
    66  	if s.Labels != nil {
    67  		andSel.Matchers = append(andSel.Matchers, LabelMatcher(s.Labels))
    68  	}
    69  	if s.Annotations != nil {
    70  		andSel.Matchers = append(andSel.Matchers, AnnotationMatcher(s.Annotations))
    71  	}
    72  	if s.ResourceMatcher != nil {
    73  		andSel.Matchers = append(andSel.Matchers, ResourceMatcherFunc(s.ResourceMatcher))
    74  	}
    75  	return andSel.Filter(items)
    76  }
    77  
    78  // MatchAll is a shorthand for building an AndSelector from a list of ResourceMatchers.
    79  func MatchAll(matchers ...ResourceMatcher) *AndSelector {
    80  	return &AndSelector{Matchers: matchers}
    81  }
    82  
    83  // MatchAny is a shorthand for building an OrSelector from a list of ResourceMatchers.
    84  func MatchAny(matchers ...ResourceMatcher) *OrSelector {
    85  	return &OrSelector{Matchers: matchers}
    86  }
    87  
    88  // OrSelector is a kio.Filter that selects resources when that match at least one of its embedded
    89  // matchers.
    90  type OrSelector struct {
    91  	// Matchers is the list of ResourceMatchers to try on the input resources.
    92  	Matchers []ResourceMatcher
    93  	// TemplateData, if present, is used to initialize any matchers that implement
    94  	// ResourceTemplateMatcher.
    95  	TemplateData interface{}
    96  	// FailOnEmptyMatch makes the selector return an error when no items are selected.
    97  	FailOnEmptyMatch bool
    98  }
    99  
   100  // Match implements ResourceMatcher so that OrSelectors can be composed
   101  func (s *OrSelector) Match(item *yaml.RNode) bool {
   102  	for _, matcher := range s.Matchers {
   103  		if matcher.Match(item) {
   104  			return true
   105  		}
   106  	}
   107  	return false
   108  }
   109  
   110  // Filter implements kio.Filter, returning only those items from the list that the selector
   111  // matches.
   112  func (s *OrSelector) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
   113  	if err := initMatcherTemplates(s.Matchers, s.TemplateData); err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	var selectedItems []*yaml.RNode
   118  	for i := range items {
   119  		for _, matcher := range s.Matchers {
   120  			if matcher.Match(items[i]) {
   121  				selectedItems = append(selectedItems, items[i])
   122  				break
   123  			}
   124  		}
   125  	}
   126  	if s.FailOnEmptyMatch && len(selectedItems) == 0 {
   127  		return nil, errors.Errorf("selector did not select any items")
   128  	}
   129  	return selectedItems, nil
   130  }
   131  
   132  // DefaultTemplateData makes OrSelector a ResourceTemplateMatcher.
   133  // Although it does not contain templates itself, this allows it to support ResourceTemplateMatchers
   134  // when being used as a matcher itself.
   135  func (s *OrSelector) DefaultTemplateData(data interface{}) {
   136  	if s.TemplateData == nil {
   137  		s.TemplateData = data
   138  	}
   139  }
   140  
   141  func (s *OrSelector) InitTemplates() error {
   142  	return initMatcherTemplates(s.Matchers, s.TemplateData)
   143  }
   144  
   145  func initMatcherTemplates(matchers []ResourceMatcher, data interface{}) error {
   146  	for _, matcher := range matchers {
   147  		if tm, ok := matcher.(ResourceTemplateMatcher); ok {
   148  			tm.DefaultTemplateData(data)
   149  			if err := tm.InitTemplates(); err != nil {
   150  				return err
   151  			}
   152  		}
   153  	}
   154  	return nil
   155  }
   156  
   157  var _ ResourceTemplateMatcher = &OrSelector{}
   158  
   159  // OrSelector is a kio.Filter that selects resources when that match all of its embedded
   160  // matchers.
   161  type AndSelector struct {
   162  	// Matchers is the list of ResourceMatchers to try on the input resources.
   163  	Matchers []ResourceMatcher
   164  	// TemplateData, if present, is used to initialize any matchers that implement
   165  	// ResourceTemplateMatcher.
   166  	TemplateData interface{}
   167  	// FailOnEmptyMatch makes the selector return an error when no items are selected.
   168  	FailOnEmptyMatch bool
   169  }
   170  
   171  // Match implements ResourceMatcher so that AndSelectors can be composed
   172  func (s *AndSelector) Match(item *yaml.RNode) bool {
   173  	for _, matcher := range s.Matchers {
   174  		if !matcher.Match(item) {
   175  			return false
   176  		}
   177  	}
   178  	return true
   179  }
   180  
   181  // Filter implements kio.Filter, returning only those items from the list that the selector
   182  // matches.
   183  func (s *AndSelector) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
   184  	if err := initMatcherTemplates(s.Matchers, s.TemplateData); err != nil {
   185  		return nil, err
   186  	}
   187  	var selectedItems []*yaml.RNode
   188  	for i := range items {
   189  		isSelected := true
   190  		for _, matcher := range s.Matchers {
   191  			if !matcher.Match(items[i]) {
   192  				isSelected = false
   193  				break
   194  			}
   195  		}
   196  		if isSelected {
   197  			selectedItems = append(selectedItems, items[i])
   198  		}
   199  	}
   200  	if s.FailOnEmptyMatch && len(selectedItems) == 0 {
   201  		return nil, errors.Errorf("selector did not select any items")
   202  	}
   203  	return selectedItems, nil
   204  }
   205  
   206  // DefaultTemplateData makes AndSelector a ResourceTemplateMatcher.
   207  // Although it does not contain templates itself, this allows it to support ResourceTemplateMatchers
   208  // when being used as a matcher itself.
   209  func (s *AndSelector) DefaultTemplateData(data interface{}) {
   210  	if s.TemplateData == nil {
   211  		s.TemplateData = data
   212  	}
   213  }
   214  
   215  func (s *AndSelector) InitTemplates() error {
   216  	return initMatcherTemplates(s.Matchers, s.TemplateData)
   217  }
   218  
   219  var _ ResourceTemplateMatcher = &AndSelector{}
   220  

View as plain text