1
0
Fork 0

fix: consul catalog constraints.

This commit is contained in:
Ludovic Fernandez 2019-11-29 17:16:05 +01:00 committed by Traefiker Bot
parent a99673122e
commit cf1ace3a73
13 changed files with 332 additions and 126 deletions

View file

@ -12,23 +12,23 @@ import (
// It is used in order to create a specific and unique pattern for these labels.
const MarathonConstraintPrefix = "Traefik-Marathon-505F9E15-BDC7-45E7-828D-C06C7BAB8091"
type constraintFunc func(map[string]string) bool
type constraintLabelFunc func(map[string]string) bool
// Match reports whether the expression matches with the given labels.
// MatchLabels reports whether the expression matches with the given labels.
// The expression must match any logical boolean combination of:
// - `Label(labelName, labelValue)`
// - `LabelRegex(labelName, regexValue)`
// - `MarathonConstraint(field:operator:value)`
func Match(labels map[string]string, expr string) (bool, error) {
func MatchLabels(labels map[string]string, expr string) (bool, error) {
if expr == "" {
return true, nil
}
p, err := predicate.NewParser(predicate.Def{
Operators: predicate.Operators{
AND: andFunc,
NOT: notFunc,
OR: orFunc,
AND: andLabelFunc,
NOT: notLabelFunc,
OR: orLabelFunc,
},
Functions: map[string]interface{}{
"Label": labelFn,
@ -45,20 +45,20 @@ func Match(labels map[string]string, expr string) (bool, error) {
return false, err
}
fn, ok := parse.(constraintFunc)
fn, ok := parse.(constraintLabelFunc)
if !ok {
return false, errors.New("not a constraintFunc")
return false, errors.New("not a constraintLabelFunc")
}
return fn(labels), nil
}
func labelFn(name, value string) constraintFunc {
func labelFn(name, value string) constraintLabelFunc {
return func(labels map[string]string) bool {
return labels[name] == value
}
}
func labelRegexFn(name, expr string) constraintFunc {
func labelRegexFn(name, expr string) constraintLabelFunc {
return func(labels map[string]string) bool {
matched, err := regexp.MatchString(expr, labels[name])
if err != nil {
@ -68,7 +68,7 @@ func labelRegexFn(name, expr string) constraintFunc {
}
}
func marathonFn(value string) constraintFunc {
func marathonFn(value string) constraintLabelFunc {
return func(labels map[string]string) bool {
for k, v := range labels {
if strings.HasPrefix(k, MarathonConstraintPrefix) {
@ -81,19 +81,19 @@ func marathonFn(value string) constraintFunc {
}
}
func andFunc(a, b constraintFunc) constraintFunc {
func andLabelFunc(a, b constraintLabelFunc) constraintLabelFunc {
return func(labels map[string]string) bool {
return a(labels) && b(labels)
}
}
func orFunc(a, b constraintFunc) constraintFunc {
func orLabelFunc(a, b constraintLabelFunc) constraintLabelFunc {
return func(labels map[string]string) bool {
return a(labels) || b(labels)
}
}
func notFunc(a constraintFunc) constraintFunc {
func notLabelFunc(a constraintLabelFunc) constraintLabelFunc {
return func(labels map[string]string) bool {
return !a(labels)
}

View file

@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestMatch(t *testing.T) {
func TestMatchLabels(t *testing.T) {
testCases := []struct {
expr string
labels map[string]string
@ -192,7 +192,7 @@ func TestMatch(t *testing.T) {
t.Run(test.expr, func(t *testing.T) {
t.Parallel()
matches, err := Match(test.labels, test.expr)
matches, err := MatchLabels(test.labels, test.expr)
if test.expectedErr {
require.Error(t, err)
} else {

View file

@ -0,0 +1,92 @@
package constraints
import (
"errors"
"regexp"
"github.com/vulcand/predicate"
)
type constraintTagFunc func([]string) bool
// MatchTags reports whether the expression matches with the given tags.
// The expression must match any logical boolean combination of:
// - `Tag(tagValue)`
// - `TagRegex(regexValue)`
func MatchTags(tags []string, expr string) (bool, error) {
if expr == "" {
return true, nil
}
p, err := predicate.NewParser(predicate.Def{
Operators: predicate.Operators{
AND: andTagFunc,
NOT: notTagFunc,
OR: orTagFunc,
},
Functions: map[string]interface{}{
"Tag": tagFn,
"TagRegex": tagRegexFn,
},
})
if err != nil {
return false, err
}
parse, err := p.Parse(expr)
if err != nil {
return false, err
}
fn, ok := parse.(constraintTagFunc)
if !ok {
return false, errors.New("not a constraintTagFunc")
}
return fn(tags), nil
}
func tagFn(name string) constraintTagFunc {
return func(tags []string) bool {
for _, tag := range tags {
if tag == name {
return true
}
}
return false
}
}
func tagRegexFn(expr string) constraintTagFunc {
return func(tags []string) bool {
exp, err := regexp.Compile(expr)
if err != nil {
return false
}
for _, tag := range tags {
if exp.MatchString(tag) {
return true
}
}
return false
}
}
func andTagFunc(a, b constraintTagFunc) constraintTagFunc {
return func(tags []string) bool {
return a(tags) && b(tags)
}
}
func orTagFunc(a, b constraintTagFunc) constraintTagFunc {
return func(tags []string) bool {
return a(tags) || b(tags)
}
}
func notTagFunc(a constraintTagFunc) constraintTagFunc {
return func(tags []string) bool {
return !a(tags)
}
}

View file

@ -0,0 +1,111 @@
package constraints
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMatchTags(t *testing.T) {
testCases := []struct {
expr string
tags []string
expected bool
expectedErr bool
}{
{
expr: `Tag("world")`,
tags: []string{"hello", "world"},
expected: true,
},
{
expr: `Tag("worlds")`,
tags: []string{"hello", "world"},
expected: false,
},
{
expr: `!Tag("world")`,
tags: []string{"hello", "world"},
expected: false,
},
{
expr: `Tag("hello") && Tag("world")`,
tags: []string{"hello", "world"},
expected: true,
},
{
expr: `Tag("hello") && Tag("worlds")`,
tags: []string{"hello", "world"},
expected: false,
},
{
expr: `Tag("hello") && !Tag("world")`,
tags: []string{"hello", "world"},
expected: false,
},
{
expr: `Tag("hello") || Tag( "world")`,
tags: []string{"hello", "world"},
expected: true,
},
{
expr: `Tag( "worlds") || Tag("hello")`,
tags: []string{"hello", "world"},
expected: true,
},
{
expr: `Tag("hello") || !Tag("world")`,
tags: []string{"hello", "world"},
expected: true,
},
{
expr: `Tag()`,
tags: []string{"hello", "world"},
expectedErr: true,
},
{
expr: `Foo("hello")`,
tags: []string{"hello", "world"},
expectedErr: true,
},
{
expr: `Tag("hello")`,
expected: false,
},
{
expr: ``,
expected: true,
},
{
expr: `TagRegex("hel\\w+")`,
tags: []string{"hello", "world"},
expected: true,
},
{
expr: `TagRegex("hell\\w+s")`,
tags: []string{"hello", "world"},
expected: false,
},
{
expr: `!TagRegex("hel\\w+")`,
tags: []string{"hello", "world"},
expected: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.expr, func(t *testing.T) {
t.Parallel()
matches, err := MatchTags(test.tags, test.expr)
if test.expectedErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Equal(t, test.expected, matches)
})
}
}