1
0
Fork 0

New constraints management.

Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
This commit is contained in:
Ludovic Fernandez 2019-06-21 09:24:04 +02:00 committed by Traefiker Bot
parent e9792b446f
commit fe68e9e243
40 changed files with 658 additions and 630 deletions

View file

@ -0,0 +1,100 @@
package constraints
import (
"errors"
"regexp"
"strings"
"github.com/vulcand/predicate"
)
// MarathonConstraintPrefix is the prefix for each label's key created from a Marathon application constraint.
// 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
// Match 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) {
if expr == "" {
return true, nil
}
p, err := predicate.NewParser(predicate.Def{
Operators: predicate.Operators{
AND: andFunc,
NOT: notFunc,
OR: orFunc,
},
Functions: map[string]interface{}{
"Label": labelFn,
"LabelRegex": labelRegexFn,
"MarathonConstraint": marathonFn,
},
})
if err != nil {
return false, err
}
parse, err := p.Parse(expr)
if err != nil {
return false, err
}
fn, ok := parse.(constraintFunc)
if !ok {
return false, errors.New("not a constraintFunc")
}
return fn(labels), nil
}
func labelFn(name, value string) constraintFunc {
return func(labels map[string]string) bool {
return labels[name] == value
}
}
func labelRegexFn(name, expr string) constraintFunc {
return func(labels map[string]string) bool {
matched, err := regexp.MatchString(expr, labels[name])
if err != nil {
return false
}
return matched
}
}
func marathonFn(value string) constraintFunc {
return func(labels map[string]string) bool {
for k, v := range labels {
if strings.HasPrefix(k, MarathonConstraintPrefix) {
if v == value {
return true
}
}
}
return false
}
}
func andFunc(a, b constraintFunc) constraintFunc {
return func(labels map[string]string) bool {
return a(labels) && b(labels)
}
}
func orFunc(a, b constraintFunc) constraintFunc {
return func(labels map[string]string) bool {
return a(labels) || b(labels)
}
}
func notFunc(a constraintFunc) constraintFunc {
return func(labels map[string]string) bool {
return !a(labels)
}
}

View file

@ -0,0 +1,204 @@
package constraints
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMatch(t *testing.T) {
testCases := []struct {
expr string
labels map[string]string
expected bool
expectedErr bool
}{
{
expr: `Label("hello", "world")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: true,
},
{
expr: `Label("hello", "worlds")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `Label("hi", "world")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `!Label("hello", "world")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `Label("hello", "world") && Label("foo", "bar")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: true,
},
{
expr: `Label("hello", "worlds") && Label("foo", "bar")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `Label("hello", "world") && !Label("foo", "bar")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `Label("hello", "world") || Label("foo", "bar")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: true,
},
{
expr: `Label("hello", "worlds") || Label("foo", "bar")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: true,
},
{
expr: `Label("hello", "world") || !Label("foo", "bar")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: true,
},
{
expr: `Label("hello")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expectedErr: true,
},
{
expr: `Foo("hello")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expectedErr: true,
},
{
expr: `Label("hello", "bar")`,
expected: false,
},
{
expr: ``,
expected: true,
},
{
expr: `MarathonConstraint("bar")`,
labels: map[string]string{
"hello": "world",
MarathonConstraintPrefix + "-1": "bar",
MarathonConstraintPrefix + "-2": "foo",
},
expected: true,
},
{
expr: `MarathonConstraint("bur")`,
labels: map[string]string{
"hello": "world",
MarathonConstraintPrefix + "-1": "bar",
MarathonConstraintPrefix + "-2": "foo",
},
expected: false,
},
{
expr: `Label("hello", "world") && MarathonConstraint("bar")`,
labels: map[string]string{
"hello": "world",
MarathonConstraintPrefix + "-1": "bar",
MarathonConstraintPrefix + "-2": "foo",
},
expected: true,
},
{
expr: `LabelRegex("hello", "w\\w+")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: true,
},
{
expr: `LabelRegex("hello", "w\\w+s")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `LabelRegex("hi", "w\\w+")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `!LabelRegex("hello", "w\\w+")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
{
expr: `LabelRegex("hello", "w(\\w+")`,
labels: map[string]string{
"hello": "world",
"foo": "bar",
},
expected: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.expr, func(t *testing.T) {
t.Parallel()
matches, err := Match(test.labels, test.expr)
if test.expectedErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Equal(t, test.expected, matches)
})
}
}