fix: consul catalog constraints.
This commit is contained in:
parent
a99673122e
commit
cf1ace3a73
13 changed files with 332 additions and 126 deletions
|
@ -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)
|
||||
}
|
|
@ -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 {
|
92
pkg/provider/constraints/constraints_tags.go
Normal file
92
pkg/provider/constraints/constraints_tags.go
Normal 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)
|
||||
}
|
||||
}
|
111
pkg/provider/constraints/constraints_tags_test.go
Normal file
111
pkg/provider/constraints/constraints_tags_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue