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 }