constraint.go 4.6 KB

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