github.com/argoproj/argo-cd/v2@v2.10.9/applicationset/utils/selector.go (about)

     1  package utils
     2  
     3  import (
     4  	"fmt"
     5  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     6  	"k8s.io/apimachinery/pkg/labels"
     7  	"k8s.io/apimachinery/pkg/selection"
     8  	"k8s.io/apimachinery/pkg/util/validation"
     9  	"k8s.io/apimachinery/pkg/util/validation/field"
    10  	"k8s.io/klog/v2"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  )
    15  
    16  var (
    17  	unaryOperators = []string{
    18  		string(selection.Exists), string(selection.DoesNotExist),
    19  	}
    20  	binaryOperators = []string{
    21  		string(selection.In), string(selection.NotIn),
    22  		string(selection.Equals), string(selection.DoubleEquals), string(selection.NotEquals),
    23  		string(selection.GreaterThan), string(selection.LessThan),
    24  	}
    25  	validRequirementOperators = append(binaryOperators, unaryOperators...)
    26  )
    27  
    28  // Selector represents a label selector.
    29  type Selector interface {
    30  	// Matches returns true if this selector matches the given set of labels.
    31  	Matches(labels.Labels) bool
    32  
    33  	// Add adds requirements to the Selector
    34  	Add(r ...Requirement) Selector
    35  }
    36  
    37  type internalSelector []Requirement
    38  
    39  // ByKey sorts requirements by key to obtain deterministic parser
    40  type ByKey []Requirement
    41  
    42  func (a ByKey) Len() int { return len(a) }
    43  
    44  func (a ByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    45  
    46  func (a ByKey) Less(i, j int) bool { return a[i].key < a[j].key }
    47  
    48  // Matches for a internalSelector returns true if all
    49  // its Requirements match the input Labels. If any
    50  // Requirement does not match, false is returned.
    51  func (s internalSelector) Matches(l labels.Labels) bool {
    52  	for ix := range s {
    53  		if matches := s[ix].Matches(l); !matches {
    54  			return false
    55  		}
    56  	}
    57  	return true
    58  }
    59  
    60  // Add adds requirements to the selector. It copies the current selector returning a new one
    61  func (s internalSelector) Add(reqs ...Requirement) Selector {
    62  	ret := make(internalSelector, 0, len(s)+len(reqs))
    63  	ret = append(ret, s...)
    64  	ret = append(ret, reqs...)
    65  	sort.Sort(ByKey(ret))
    66  	return ret
    67  }
    68  
    69  type nothingSelector struct{}
    70  
    71  func (n nothingSelector) Matches(l labels.Labels) bool {
    72  	return false
    73  }
    74  
    75  func (n nothingSelector) Add(r ...Requirement) Selector {
    76  	return n
    77  }
    78  
    79  // Nothing returns a selector that matches no labels
    80  func nothing() Selector {
    81  	return nothingSelector{}
    82  }
    83  
    84  // Everything returns a selector that matches all labels.
    85  func everything() Selector {
    86  	return internalSelector{}
    87  }
    88  
    89  // LabelSelectorAsSelector converts the LabelSelector api type into a struct that implements
    90  // labels.Selector
    91  // Note: This function should be kept in sync with the selector methods in pkg/labels/selector.go
    92  func LabelSelectorAsSelector(ps *v1.LabelSelector) (Selector, error) {
    93  	if ps == nil {
    94  		return nothing(), nil
    95  	}
    96  	if len(ps.MatchLabels)+len(ps.MatchExpressions) == 0 {
    97  		return everything(), nil
    98  	}
    99  	requirements := make([]Requirement, 0, len(ps.MatchLabels)+len(ps.MatchExpressions))
   100  	for k, v := range ps.MatchLabels {
   101  		r, err := newRequirement(k, selection.Equals, []string{v})
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  		requirements = append(requirements, *r)
   106  	}
   107  	for _, expr := range ps.MatchExpressions {
   108  		var op selection.Operator
   109  		switch expr.Operator {
   110  		case v1.LabelSelectorOpIn:
   111  			op = selection.In
   112  		case v1.LabelSelectorOpNotIn:
   113  			op = selection.NotIn
   114  		case v1.LabelSelectorOpExists:
   115  			op = selection.Exists
   116  		case v1.LabelSelectorOpDoesNotExist:
   117  			op = selection.DoesNotExist
   118  		default:
   119  			return nil, fmt.Errorf("%q is not a valid pod selector operator", expr.Operator)
   120  		}
   121  		r, err := newRequirement(expr.Key, op, append([]string(nil), expr.Values...))
   122  		if err != nil {
   123  			return nil, err
   124  		}
   125  		requirements = append(requirements, *r)
   126  	}
   127  	selector := newSelector()
   128  	selector = selector.Add(requirements...)
   129  	return selector, nil
   130  }
   131  
   132  // NewSelector returns a nil selector
   133  func newSelector() Selector {
   134  	return internalSelector(nil)
   135  }
   136  
   137  func validateLabelKey(k string, path *field.Path) *field.Error {
   138  	if errs := validation.IsQualifiedName(k); len(errs) != 0 {
   139  		return field.Invalid(path, k, strings.Join(errs, "; "))
   140  	}
   141  	return nil
   142  }
   143  
   144  // NewRequirement is the constructor for a Requirement.
   145  // If any of these rules is violated, an error is returned:
   146  // (1) The operator can only be In, NotIn, Equals, DoubleEquals, Gt, Lt, NotEquals, Exists, or DoesNotExist.
   147  // (2) If the operator is In or NotIn, the values set must be non-empty.
   148  // (3) If the operator is Equals, DoubleEquals, or NotEquals, the values set must contain one value.
   149  // (4) If the operator is Exists or DoesNotExist, the value set must be empty.
   150  // (5) If the operator is Gt or Lt, the values set must contain only one value, which will be interpreted as an integer.
   151  // (6) The key is invalid due to its length, or sequence
   152  //
   153  //	of characters. See validateLabelKey for more details.
   154  //
   155  // The empty string is a valid value in the input values set.
   156  // Returned error, if not nil, is guaranteed to be an aggregated field.ErrorList
   157  func newRequirement(key string, op selection.Operator, vals []string, opts ...field.PathOption) (*Requirement, error) {
   158  	var allErrs field.ErrorList
   159  	path := field.ToPath(opts...)
   160  	if err := validateLabelKey(key, path.Child("key")); err != nil {
   161  		allErrs = append(allErrs, err)
   162  	}
   163  
   164  	valuePath := path.Child("values")
   165  	switch op {
   166  	case selection.In, selection.NotIn:
   167  		if len(vals) == 0 {
   168  			allErrs = append(allErrs, field.Invalid(valuePath, vals, "for 'in', 'notin' operators, values set can't be empty"))
   169  		}
   170  	case selection.Equals, selection.DoubleEquals, selection.NotEquals:
   171  		if len(vals) != 1 {
   172  			allErrs = append(allErrs, field.Invalid(valuePath, vals, "exact-match compatibility requires one single value"))
   173  		}
   174  	case selection.Exists, selection.DoesNotExist:
   175  		if len(vals) != 0 {
   176  			allErrs = append(allErrs, field.Invalid(valuePath, vals, "values set must be empty for exists and does not exist"))
   177  		}
   178  	case selection.GreaterThan, selection.LessThan:
   179  		if len(vals) != 1 {
   180  			allErrs = append(allErrs, field.Invalid(valuePath, vals, "for 'Gt', 'Lt' operators, exactly one value is required"))
   181  		}
   182  		for i := range vals {
   183  			if _, err := strconv.ParseInt(vals[i], 10, 64); err != nil {
   184  				allErrs = append(allErrs, field.Invalid(valuePath.Index(i), vals[i], "for 'Gt', 'Lt' operators, the value must be an integer"))
   185  			}
   186  		}
   187  	default:
   188  		allErrs = append(allErrs, field.NotSupported(path.Child("operator"), op, validRequirementOperators))
   189  	}
   190  
   191  	return &Requirement{key: key, operator: op, strValues: vals}, allErrs.ToAggregate()
   192  }
   193  
   194  // Requirement contains values, a key, and an operator that relates the key and values.
   195  // The zero value of Requirement is invalid.
   196  // Requirement implements both set based match and exact match
   197  // Requirement should be initialized via NewRequirement constructor for creating a valid Requirement.
   198  // +k8s:deepcopy-gen=true
   199  type Requirement struct {
   200  	key      string
   201  	operator selection.Operator
   202  	// In the majority of cases we have at most one value here.
   203  	// It is generally faster to operate on a single-element slice
   204  	// than on a single-element map, so we have a slice here.
   205  	strValues []string
   206  }
   207  
   208  func (r *Requirement) hasValue(value string) bool {
   209  	for i := range r.strValues {
   210  		if r.strValues[i] == value {
   211  			return true
   212  		}
   213  	}
   214  	return false
   215  }
   216  
   217  func (r *Requirement) Matches(ls labels.Labels) bool {
   218  	switch r.operator {
   219  	case selection.In, selection.Equals, selection.DoubleEquals:
   220  		if !ls.Has(r.key) {
   221  			return false
   222  		}
   223  		return r.hasValue(ls.Get(r.key))
   224  	case selection.NotIn, selection.NotEquals:
   225  		if !ls.Has(r.key) {
   226  			return true
   227  		}
   228  		return !r.hasValue(ls.Get(r.key))
   229  	case selection.Exists:
   230  		return ls.Has(r.key)
   231  	case selection.DoesNotExist:
   232  		return !ls.Has(r.key)
   233  	case selection.GreaterThan, selection.LessThan:
   234  		if !ls.Has(r.key) {
   235  			return false
   236  		}
   237  		lsValue, err := strconv.ParseInt(ls.Get(r.key), 10, 64)
   238  		if err != nil {
   239  			klog.V(10).Infof("ParseInt failed for value %+v in label %+v, %+v", ls.Get(r.key), ls, err)
   240  			return false
   241  		}
   242  
   243  		// There should be only one strValue in r.strValues, and can be converted to an integer.
   244  		if len(r.strValues) != 1 {
   245  			klog.V(10).Infof("Invalid values count %+v of requirement %#v, for 'Gt', 'Lt' operators, exactly one value is required", len(r.strValues), r)
   246  			return false
   247  		}
   248  
   249  		var rValue int64
   250  		for i := range r.strValues {
   251  			rValue, err = strconv.ParseInt(r.strValues[i], 10, 64)
   252  			if err != nil {
   253  				klog.V(10).Infof("ParseInt failed for value %+v in requirement %#v, for 'Gt', 'Lt' operators, the value must be an integer", r.strValues[i], r)
   254  				return false
   255  			}
   256  		}
   257  		return (r.operator == selection.GreaterThan && lsValue > rValue) || (r.operator == selection.LessThan && lsValue < rValue)
   258  	default:
   259  		return false
   260  	}
   261  }