github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/label/selector.go (about)

     1  // Copyright 2022 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package label
    15  
    16  import (
    17  	"regexp"
    18  
    19  	"github.com/pingcap/errors"
    20  	"github.com/pingcap/log"
    21  	"go.uber.org/atomic"
    22  	"go.uber.org/zap"
    23  )
    24  
    25  // Op represents the type of operator associated with a selector.
    26  type Op string
    27  
    28  const (
    29  	// OpEq represents the Equals operator.
    30  	OpEq = Op("eq")
    31  
    32  	// OpNeq represents the Not Equal operator.
    33  	OpNeq = Op("neq")
    34  
    35  	// OpRegex represents the Regular Expression match operator.
    36  	OpRegex = Op("regex")
    37  )
    38  
    39  // Selector is one selector on a label Set.
    40  type Selector struct {
    41  	// Key is the key of the label that could potentially match the selector.
    42  	Key Key `json:"label"`
    43  	// Target is the argument to the operator. In the case of the
    44  	// Eq operator, it is the exact string that needs to match the value.
    45  	Target string `json:"target"`
    46  	// Op is the operator.
    47  	Op Op `json:"op"`
    48  
    49  	// regex stores a compiled Regular Expression.
    50  	// It is not nil only if Op == OpRegex.
    51  	regex atomic.Value // *regexp.Regexp
    52  }
    53  
    54  // Validate returns whether the selector is valid.
    55  // Calling Matches on invalid selectors results in undefined behavior.
    56  func (s *Selector) Validate() error {
    57  	if err := checkLabelStrValid(string(s.Key)); err != nil {
    58  		return errors.Annotate(err, "validate selector key")
    59  	}
    60  
    61  	if s.Op != OpEq && s.Op != OpNeq && s.Op != OpRegex {
    62  		return errors.Errorf("invalid selector op: key: %s, op: %s", string(s.Key), string(s.Op))
    63  	}
    64  
    65  	if s.Op == OpRegex {
    66  		_, err := s.getRegex()
    67  		if err != nil {
    68  			return errors.Annotatef(err, "validating selector key: %s", string(s.Key))
    69  		}
    70  	}
    71  	return nil
    72  }
    73  
    74  // Matches returns whether the given selector matches
    75  // the labelSet.
    76  // Calling Matches on invalid selectors results in undefined behavior.
    77  // Refer to Validate().
    78  func (s *Selector) Matches(labelSet Set) bool {
    79  	value, exists := labelSet.Get(s.Key)
    80  	switch s.Op {
    81  	case OpEq:
    82  		if !exists {
    83  			// OpEq should fail if the specified key
    84  			// does not exist.
    85  			return false
    86  		}
    87  		return s.Target == string(value)
    88  	case OpNeq:
    89  		if !exists {
    90  			// OpNeq succeeds when the specified key
    91  			// does not exist.
    92  			return true
    93  		}
    94  		return s.Target != string(value)
    95  	case OpRegex:
    96  		if !exists {
    97  			// OpRegex should fail if the specified key
    98  			// does not exist.
    99  			return false
   100  		}
   101  		regex, err := s.getRegex()
   102  		if err != nil {
   103  			log.Warn("illegal regular expression",
   104  				zap.Any("selector", s))
   105  
   106  			// We only make the selector not match,
   107  			// because it only complicates things to force the caller to
   108  			// handle such errors that are preventable with a pre-check.
   109  			return false
   110  		}
   111  
   112  		return regex.MatchString(string(value))
   113  	default:
   114  	}
   115  
   116  	panic("unreachable")
   117  }
   118  
   119  func (s *Selector) getRegex() (*regexp.Regexp, error) {
   120  	if s.Op != OpRegex {
   121  		panic("unreachable")
   122  	}
   123  
   124  	if s.regex.Load() == nil {
   125  		regex, err := regexp.Compile(s.Target)
   126  		if err != nil {
   127  			// Handles error in case that the regular expression
   128  			// is not legal.
   129  			return nil, errors.Trace(err)
   130  		}
   131  		s.regex.CompareAndSwap(nil, regex)
   132  	}
   133  
   134  	return s.regex.Load().(*regexp.Regexp), nil
   135  }