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

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