1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/vendor/github.com/docker/swarmkit/manager/constraint/constraint.go
Alexander Morozov f2614f2107 project: use vndr for vendoring
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2016-11-03 15:31:46 -07:00

184 lines
4.6 KiB
Go

package constraint
import (
"fmt"
"regexp"
"strings"
"github.com/docker/swarmkit/api"
)
const (
eq = iota
noteq
nodeLabelPrefix = "node.labels."
engineLabelPrefix = "engine.labels."
)
var (
alphaNumeric = regexp.MustCompile(`^(?i)[a-z_][a-z0-9\-_.]+$`)
// value can be alphanumeric and some special characters. it shouldn't container
// current or future operators like '>, <, ~', etc.
valuePattern = regexp.MustCompile(`^(?i)[a-z0-9:\-_\s\.\*\(\)\?\+\[\]\\\^\$\|\/]+$`)
// operators defines list of accepted operators
operators = []string{"==", "!="}
)
// Constraint defines a constraint.
type Constraint struct {
key string
operator int
exp string
}
// Parse parses list of constraints.
func Parse(env []string) ([]Constraint, error) {
exprs := []Constraint{}
for _, e := range env {
found := false
// each expr is in the form of "key op value"
for i, op := range operators {
if !strings.Contains(e, op) {
continue
}
// split with the op
parts := strings.SplitN(e, op, 2)
if len(parts) < 2 {
return nil, fmt.Errorf("invalid expr: %s", e)
}
part0 := strings.TrimSpace(parts[0])
// validate key
matched := alphaNumeric.MatchString(part0)
if matched == false {
return nil, fmt.Errorf("key '%s' is invalid", part0)
}
part1 := strings.TrimSpace(parts[1])
// validate Value
matched = valuePattern.MatchString(part1)
if matched == false {
return nil, fmt.Errorf("value '%s' is invalid", part1)
}
// TODO(dongluochen): revisit requirements to see if globing or regex are useful
exprs = append(exprs, Constraint{key: part0, operator: i, exp: part1})
found = true
break // found an op, move to next entry
}
if !found {
return nil, fmt.Errorf("constraint expected one operator from %s", strings.Join(operators, ", "))
}
}
return exprs, nil
}
// Match checks if the Constraint matches the target strings.
func (c *Constraint) Match(whats ...string) bool {
var match bool
// full string match
for _, what := range whats {
// case insensitive compare
if strings.EqualFold(c.exp, what) {
match = true
break
}
}
switch c.operator {
case eq:
return match
case noteq:
return !match
}
return false
}
// NodeMatches returns true if the node satisfies the given constraints.
func NodeMatches(constraints []Constraint, n *api.Node) bool {
for _, constraint := range constraints {
switch {
case strings.EqualFold(constraint.key, "node.id"):
if !constraint.Match(n.ID) {
return false
}
case strings.EqualFold(constraint.key, "node.hostname"):
// if this node doesn't have hostname
// it's equivalent to match an empty hostname
// where '==' would fail, '!=' matches
if n.Description == nil {
if !constraint.Match("") {
return false
}
continue
}
if !constraint.Match(n.Description.Hostname) {
return false
}
case strings.EqualFold(constraint.key, "node.role"):
if !constraint.Match(n.Spec.Role.String()) {
return false
}
case strings.EqualFold(constraint.key, "node.platform.os"):
if n.Description == nil || n.Description.Platform == nil {
if !constraint.Match("") {
return false
}
continue
}
if !constraint.Match(n.Description.Platform.OS) {
return false
}
case strings.EqualFold(constraint.key, "node.platform.arch"):
if n.Description == nil || n.Description.Platform == nil {
if !constraint.Match("") {
return false
}
continue
}
if !constraint.Match(n.Description.Platform.Architecture) {
return false
}
// node labels constraint in form like 'node.labels.key==value'
case len(constraint.key) > len(nodeLabelPrefix) && strings.EqualFold(constraint.key[:len(nodeLabelPrefix)], nodeLabelPrefix):
if n.Spec.Annotations.Labels == nil {
if !constraint.Match("") {
return false
}
continue
}
label := constraint.key[len(nodeLabelPrefix):]
// label itself is case sensitive
val := n.Spec.Annotations.Labels[label]
if !constraint.Match(val) {
return false
}
// engine labels constraint in form like 'engine.labels.key!=value'
case len(constraint.key) > len(engineLabelPrefix) && strings.EqualFold(constraint.key[:len(engineLabelPrefix)], engineLabelPrefix):
if n.Description == nil || n.Description.Engine == nil || n.Description.Engine.Labels == nil {
if !constraint.Match("") {
return false
}
continue
}
label := constraint.key[len(engineLabelPrefix):]
val := n.Description.Engine.Labels[label]
if !constraint.Match(val) {
return false
}
default:
// key doesn't match predefined syntax
return false
}
}
return true
}