github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/constraint/constraint.go (about)

     1  package constraint
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/docker/swarmkit/api"
    10  )
    11  
    12  const (
    13  	eq = iota
    14  	noteq
    15  
    16  	// NodeLabelPrefix is the constraint key prefix for node labels.
    17  	NodeLabelPrefix = "node.labels."
    18  	// EngineLabelPrefix is the constraint key prefix for engine labels.
    19  	EngineLabelPrefix = "engine.labels."
    20  )
    21  
    22  var (
    23  	alphaNumeric = regexp.MustCompile(`^(?i)[a-z_][a-z0-9\-_.]+$`)
    24  	// value can be alphanumeric and some special characters. it shouldn't container
    25  	// current or future operators like '>, <, ~', etc.
    26  	valuePattern = regexp.MustCompile(`^(?i)[a-z0-9:\-_\s\.\*\(\)\?\+\[\]\\\^\$\|\/]+$`)
    27  
    28  	// operators defines list of accepted operators
    29  	operators = []string{"==", "!="}
    30  )
    31  
    32  // Constraint defines a constraint.
    33  type Constraint struct {
    34  	key      string
    35  	operator int
    36  	exp      string
    37  }
    38  
    39  // Parse parses list of constraints.
    40  func Parse(env []string) ([]Constraint, error) {
    41  	exprs := []Constraint{}
    42  	for _, e := range env {
    43  		found := false
    44  		// each expr is in the form of "key op value"
    45  		for i, op := range operators {
    46  			if !strings.Contains(e, op) {
    47  				continue
    48  			}
    49  			// split with the op
    50  			parts := strings.SplitN(e, op, 2)
    51  
    52  			if len(parts) < 2 {
    53  				return nil, fmt.Errorf("invalid expr: %s", e)
    54  			}
    55  
    56  			part0 := strings.TrimSpace(parts[0])
    57  			// validate key
    58  			matched := alphaNumeric.MatchString(part0)
    59  			if !matched {
    60  				return nil, fmt.Errorf("key '%s' is invalid", part0)
    61  			}
    62  
    63  			part1 := strings.TrimSpace(parts[1])
    64  
    65  			// validate Value
    66  			matched = valuePattern.MatchString(part1)
    67  			if !matched {
    68  				return nil, fmt.Errorf("value '%s' is invalid", part1)
    69  			}
    70  			// TODO(dongluochen): revisit requirements to see if globing or regex are useful
    71  			exprs = append(exprs, Constraint{key: part0, operator: i, exp: part1})
    72  
    73  			found = true
    74  			break // found an op, move to next entry
    75  		}
    76  		if !found {
    77  			return nil, fmt.Errorf("constraint expected one operator from %s", strings.Join(operators, ", "))
    78  		}
    79  	}
    80  	return exprs, nil
    81  }
    82  
    83  // Match checks if the Constraint matches the target strings.
    84  func (c *Constraint) Match(whats ...string) bool {
    85  	var match bool
    86  
    87  	// full string match
    88  	for _, what := range whats {
    89  		// case insensitive compare
    90  		if strings.EqualFold(c.exp, what) {
    91  			match = true
    92  			break
    93  		}
    94  	}
    95  
    96  	switch c.operator {
    97  	case eq:
    98  		return match
    99  	case noteq:
   100  		return !match
   101  	}
   102  
   103  	return false
   104  }
   105  
   106  // NodeMatches returns true if the node satisfies the given constraints.
   107  func NodeMatches(constraints []Constraint, n *api.Node) bool {
   108  	for _, constraint := range constraints {
   109  		switch {
   110  		case strings.EqualFold(constraint.key, "node.id"):
   111  			if !constraint.Match(n.ID) {
   112  				return false
   113  			}
   114  		case strings.EqualFold(constraint.key, "node.hostname"):
   115  			// if this node doesn't have hostname
   116  			// it's equivalent to match an empty hostname
   117  			// where '==' would fail, '!=' matches
   118  			if n.Description == nil {
   119  				if !constraint.Match("") {
   120  					return false
   121  				}
   122  				continue
   123  			}
   124  			if !constraint.Match(n.Description.Hostname) {
   125  				return false
   126  			}
   127  		case strings.EqualFold(constraint.key, "node.ip"):
   128  			nodeIP := net.ParseIP(n.Status.Addr)
   129  			// single IP address, node.ip == 2001:db8::2
   130  			if ip := net.ParseIP(constraint.exp); ip != nil {
   131  				ipEq := ip.Equal(nodeIP)
   132  				if (ipEq && constraint.operator != eq) || (!ipEq && constraint.operator == eq) {
   133  					return false
   134  				}
   135  				continue
   136  			}
   137  			// CIDR subnet, node.ip != 210.8.4.0/24
   138  			if _, subnet, err := net.ParseCIDR(constraint.exp); err == nil {
   139  				within := subnet.Contains(nodeIP)
   140  				if (within && constraint.operator != eq) || (!within && constraint.operator == eq) {
   141  					return false
   142  				}
   143  				continue
   144  			}
   145  			// reject constraint with malformed address/network
   146  			return false
   147  		case strings.EqualFold(constraint.key, "node.role"):
   148  			if !constraint.Match(n.Role.String()) {
   149  				return false
   150  			}
   151  		case strings.EqualFold(constraint.key, "node.platform.os"):
   152  			if n.Description == nil || n.Description.Platform == nil {
   153  				if !constraint.Match("") {
   154  					return false
   155  				}
   156  				continue
   157  			}
   158  			if !constraint.Match(n.Description.Platform.OS) {
   159  				return false
   160  			}
   161  		case strings.EqualFold(constraint.key, "node.platform.arch"):
   162  			if n.Description == nil || n.Description.Platform == nil {
   163  				if !constraint.Match("") {
   164  					return false
   165  				}
   166  				continue
   167  			}
   168  			if !constraint.Match(n.Description.Platform.Architecture) {
   169  				return false
   170  			}
   171  
   172  		// node labels constraint in form like 'node.labels.key==value'
   173  		case len(constraint.key) > len(NodeLabelPrefix) && strings.EqualFold(constraint.key[:len(NodeLabelPrefix)], NodeLabelPrefix):
   174  			if n.Spec.Annotations.Labels == nil {
   175  				if !constraint.Match("") {
   176  					return false
   177  				}
   178  				continue
   179  			}
   180  			label := constraint.key[len(NodeLabelPrefix):]
   181  			// label itself is case sensitive
   182  			val := n.Spec.Annotations.Labels[label]
   183  			if !constraint.Match(val) {
   184  				return false
   185  			}
   186  
   187  		// engine labels constraint in form like 'engine.labels.key!=value'
   188  		case len(constraint.key) > len(EngineLabelPrefix) && strings.EqualFold(constraint.key[:len(EngineLabelPrefix)], EngineLabelPrefix):
   189  			if n.Description == nil || n.Description.Engine == nil || n.Description.Engine.Labels == nil {
   190  				if !constraint.Match("") {
   191  					return false
   192  				}
   193  				continue
   194  			}
   195  			label := constraint.key[len(EngineLabelPrefix):]
   196  			val := n.Description.Engine.Labels[label]
   197  			if !constraint.Match(val) {
   198  				return false
   199  			}
   200  		default:
   201  			// key doesn't match predefined syntax
   202  			return false
   203  		}
   204  	}
   205  
   206  	return true
   207  }