1
0
Fork 0

Segments Labels: Rancher & Marathon

This commit is contained in:
Ludovic Fernandez 2018-03-26 15:32:04 +02:00 committed by Traefiker Bot
parent 16bb9b6836
commit 0ea007b26f
31 changed files with 4288 additions and 3656 deletions

View file

@ -7,9 +7,7 @@ import (
"strconv"
"strings"
"github.com/containous/flaeg"
"github.com/containous/traefik/log"
"github.com/containous/traefik/types"
)
const (
@ -28,7 +26,6 @@ const (
DefaultFrontendPriority = "0" // TODO [breaking] int value
DefaultFrontendPriorityInt = 0 // TODO rename to DefaultFrontendPriority
DefaultCircuitBreakerExpression = "NetworkErrorRatio() > 1"
DefaultFrontendRedirectEntryPoint = ""
DefaultBackendLoadBalancerMethod = "wrr"
DefaultBackendMaxconnExtractorFunc = "request.host"
DefaultBackendLoadbalancerStickinessCookieName = ""
@ -57,14 +54,6 @@ func GetStringValue(labels map[string]string, labelName string, defaultValue str
return defaultValue
}
// GetStringValueP get string value associated to a label
func GetStringValueP(labels *map[string]string, labelName string, defaultValue string) string {
if labels == nil {
return defaultValue
}
return GetStringValue(*labels, labelName, defaultValue)
}
// GetBoolValue get bool value associated to a label
func GetBoolValue(labels map[string]string, labelName string, defaultValue bool) bool {
rawValue, ok := labels[labelName]
@ -77,14 +66,6 @@ func GetBoolValue(labels map[string]string, labelName string, defaultValue bool)
return defaultValue
}
// GetBoolValueP get bool value associated to a label
func GetBoolValueP(labels *map[string]string, labelName string, defaultValue bool) bool {
if labels == nil {
return defaultValue
}
return GetBoolValue(*labels, labelName, defaultValue)
}
// GetIntValue get int value associated to a label
func GetIntValue(labels map[string]string, labelName string, defaultValue int) int {
if rawValue, ok := labels[labelName]; ok {
@ -97,14 +78,6 @@ func GetIntValue(labels map[string]string, labelName string, defaultValue int) i
return defaultValue
}
// GetIntValueP get int value associated to a label
func GetIntValueP(labels *map[string]string, labelName string, defaultValue int) int {
if labels == nil {
return defaultValue
}
return GetIntValue(*labels, labelName, defaultValue)
}
// GetInt64Value get int64 value associated to a label
func GetInt64Value(labels map[string]string, labelName string, defaultValue int64) int64 {
if rawValue, ok := labels[labelName]; ok {
@ -117,14 +90,6 @@ func GetInt64Value(labels map[string]string, labelName string, defaultValue int6
return defaultValue
}
// GetInt64ValueP get int64 value associated to a label
func GetInt64ValueP(labels *map[string]string, labelName string, defaultValue int64) int64 {
if labels == nil {
return defaultValue
}
return GetInt64Value(*labels, labelName, defaultValue)
}
// GetSliceStringValue get a slice of string associated to a label
func GetSliceStringValue(labels map[string]string, labelName string) []string {
var value []string
@ -139,14 +104,6 @@ func GetSliceStringValue(labels map[string]string, labelName string) []string {
return value
}
// GetSliceStringValueP get a slice of string value associated to a label
func GetSliceStringValueP(labels *map[string]string, labelName string) []string {
if labels == nil {
return nil
}
return GetSliceStringValue(*labels, labelName)
}
// ParseMapValue get Map value for a label value
func ParseMapValue(labelName, values string) map[string]string {
mapValue := make(map[string]string)
@ -203,14 +160,6 @@ func Has(labels map[string]string, labelName string) bool {
return ok && len(value) > 0
}
// HasP Check if a value is associated to a label
func HasP(labels *map[string]string, labelName string) bool {
if labels == nil {
return false
}
return Has(*labels, labelName)
}
// HasPrefix Check if a value is associated to a less one label with a prefix
func HasPrefix(labels map[string]string, prefix string) bool {
for name, value := range labels {
@ -221,124 +170,11 @@ func HasPrefix(labels map[string]string, prefix string) bool {
return false
}
// HasPrefixP Check if a value is associated to a less one label with a prefix
func HasPrefixP(labels *map[string]string, prefix string) bool {
if labels == nil {
return false
}
return HasPrefix(*labels, prefix)
}
// ParseErrorPages parse error pages to create ErrorPage struct
func ParseErrorPages(labels map[string]string, labelPrefix string, labelRegex *regexp.Regexp) map[string]*types.ErrorPage {
var errorPages map[string]*types.ErrorPage
for lblName, value := range labels {
if strings.HasPrefix(lblName, labelPrefix) {
submatch := labelRegex.FindStringSubmatch(lblName)
if len(submatch) != 3 {
log.Errorf("Invalid page error label: %s, sub-match: %v", lblName, submatch)
continue
}
if errorPages == nil {
errorPages = make(map[string]*types.ErrorPage)
}
pageName := submatch[1]
ep, ok := errorPages[pageName]
if !ok {
ep = &types.ErrorPage{}
errorPages[pageName] = ep
}
switch submatch[2] {
case SuffixErrorPageStatus:
ep.Status = SplitAndTrimString(value, ",")
case SuffixErrorPageQuery:
ep.Query = value
case SuffixErrorPageBackend:
ep.Backend = value
default:
log.Errorf("Invalid page error label: %s", lblName)
continue
}
}
}
return errorPages
}
// ParseRateSets parse rate limits to create Rate struct
func ParseRateSets(labels map[string]string, labelPrefix string, labelRegex *regexp.Regexp) map[string]*types.Rate {
var rateSets map[string]*types.Rate
for lblName, rawValue := range labels {
if strings.HasPrefix(lblName, labelPrefix) && len(rawValue) > 0 {
submatch := labelRegex.FindStringSubmatch(lblName)
if len(submatch) != 3 {
log.Errorf("Invalid rate limit label: %s, sub-match: %v", lblName, submatch)
continue
}
if rateSets == nil {
rateSets = make(map[string]*types.Rate)
}
limitName := submatch[1]
ep, ok := rateSets[limitName]
if !ok {
ep = &types.Rate{}
rateSets[limitName] = ep
}
switch submatch[2] {
case "period":
var d flaeg.Duration
err := d.Set(rawValue)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Period = d
case "average":
value, err := strconv.ParseInt(rawValue, 10, 64)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Average = value
case "burst":
value, err := strconv.ParseInt(rawValue, 10, 64)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Burst = value
default:
log.Errorf("Invalid rate limit label: %s", lblName)
continue
}
}
}
return rateSets
}
// IsEnabled Check if a container is enabled in Træfik
func IsEnabled(labels map[string]string, exposedByDefault bool) bool {
return GetBoolValue(labels, TraefikEnable, exposedByDefault)
}
// IsEnabledP Check if a container is enabled in Træfik
func IsEnabledP(labels *map[string]string, exposedByDefault bool) bool {
if labels == nil {
return exposedByDefault
}
return IsEnabled(*labels, exposedByDefault)
}
// SplitAndTrimString splits separatedString at the separator character and trims each
// piece, filtering out empty pieces. Returns the list of pieces or nil if the input
// did not contain a non-empty piece.
@ -354,3 +190,31 @@ func SplitAndTrimString(base string, sep string) []string {
return trimmedStrings
}
// GetFuncString a func related to GetStringValue
func GetFuncString(labelName string, defaultValue string) func(map[string]string) string {
return func(labels map[string]string) string {
return GetStringValue(labels, labelName, defaultValue)
}
}
// GetFuncInt a func related to GetIntValue
func GetFuncInt(labelName string, defaultValue int) func(map[string]string) int {
return func(labels map[string]string) int {
return GetIntValue(labels, labelName, defaultValue)
}
}
// GetFuncBool a func related to GetBoolValue
func GetFuncBool(labelName string, defaultValue bool) func(map[string]string) bool {
return func(labels map[string]string) bool {
return GetBoolValue(labels, labelName, defaultValue)
}
}
// GetFuncSliceString a func related to GetSliceStringValue
func GetFuncSliceString(labelName string) func(map[string]string) []string {
return func(labels map[string]string) []string {
return GetSliceStringValue(labels, labelName)
}
}

View file

@ -1,11 +1,9 @@
package label
import (
"strconv"
"testing"
"time"
"github.com/containous/flaeg"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
)
@ -108,51 +106,6 @@ func TestGetStringValue(t *testing.T) {
}
}
func TestGetStringValueP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
labelName string
defaultValue string
expected string
}{
{
desc: "nil labels map",
labels: nil,
labelName: "foo",
defaultValue: "default",
expected: "default",
},
{
desc: "existing label",
labels: &map[string]string{
"foo": "bar",
},
labelName: "foo",
defaultValue: "default",
expected: "bar",
},
{
desc: "non existing label",
labels: &map[string]string{
"foo": "bar",
},
labelName: "fii",
defaultValue: "default",
expected: "default",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetStringValueP(test.labels, test.labelName, test.defaultValue)
assert.Equal(t, test.expected, got)
})
}
}
func TestGetBoolValue(t *testing.T) {
testCases := []struct {
desc string
@ -255,60 +208,6 @@ func TestGetIntValue(t *testing.T) {
}
}
func TestGetIntValueP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
labelName string
defaultValue int
expected int
}{
{
desc: "nil map",
labels: nil,
labelName: "foo",
defaultValue: 666,
expected: 666,
},
{
desc: "invalid int value",
labelName: "foo",
labels: &map[string]string{
"foo": "bar",
},
defaultValue: 666,
expected: 666,
},
{
desc: "negative int value",
labelName: "foo",
labels: &map[string]string{
"foo": "-1",
},
defaultValue: 666,
expected: -1,
},
{
desc: "positive int value",
labelName: "foo",
labels: &map[string]string{
"foo": "1",
},
defaultValue: 666,
expected: 1,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetIntValueP(test.labels, test.labelName, test.defaultValue)
assert.Equal(t, test.expected, got)
})
}
}
func TestGetInt64Value(t *testing.T) {
testCases := []struct {
desc string
@ -360,60 +259,6 @@ func TestGetInt64Value(t *testing.T) {
}
}
func TestGetInt64ValueP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
labelName string
defaultValue int64
expected int64
}{
{
desc: "nil map",
labels: nil,
labelName: "foo",
defaultValue: 666,
expected: 666,
},
{
desc: "invalid int value",
labelName: "foo",
labels: &map[string]string{
"foo": "bar",
},
defaultValue: 666,
expected: 666,
},
{
desc: "negative int value",
labelName: "foo",
labels: &map[string]string{
"foo": "-1",
},
defaultValue: 666,
expected: -1,
},
{
desc: "positive int value",
labelName: "foo",
labels: &map[string]string{
"foo": "1",
},
defaultValue: 666,
expected: 1,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetInt64ValueP(test.labels, test.labelName, test.defaultValue)
assert.Equal(t, test.expected, got)
})
}
}
func TestGetSliceStringValue(t *testing.T) {
testCases := []struct {
desc string
@ -461,47 +306,6 @@ func TestGetSliceStringValue(t *testing.T) {
}
}
func TestGetSliceStringValueP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
labelName string
expected []string
}{
{
desc: "nil map",
labels: nil,
labelName: "foo",
expected: nil,
},
{
desc: "one value, not split",
labels: &map[string]string{
"foo": "bar",
},
labelName: "foo",
expected: []string{"bar"},
},
{
desc: "several values",
labels: &map[string]string{
"foo": "bar,bir ,bur",
},
labelName: "foo",
expected: []string{"bar", "bir", "bur"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetSliceStringValueP(test.labels, test.labelName)
assert.EqualValues(t, test.expected, got)
})
}
}
func TestGetMapValue(t *testing.T) {
testCases := []struct {
desc string
@ -680,149 +484,6 @@ func TestHas(t *testing.T) {
}
}
func TestHasP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
labelName string
expected bool
}{
{
desc: "nil labels map",
labelName: "foo",
},
{
desc: "nonexistent label",
labels: &map[string]string{
"foo": "bar",
},
labelName: "fii",
expected: false,
},
{
desc: "existent label",
labels: &map[string]string{
"foo": "bar",
},
labelName: "foo",
expected: true,
},
{
desc: "existent label with empty value",
labels: &map[string]string{
"foo": "",
},
labelName: "foo",
expected: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := HasP(test.labels, test.labelName)
assert.Equal(t, test.expected, got)
})
}
}
func TestExtractServiceProperties(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected SegmentProperties
}{
{
desc: "empty labels map",
expected: SegmentProperties{},
},
{
desc: "valid label names",
labels: map[string]string{
"traefik.foo.port": "bar",
"traefik.foo.frontend.bar": "1bar",
"traefik.foo.backend": "3bar",
},
expected: SegmentProperties{
"foo": SegmentPropertyValues{
"port": "bar",
"frontend.bar": "1bar",
"backend": "3bar",
},
},
},
{
desc: "invalid label names",
labels: map[string]string{
"foo.frontend.bar": "1bar",
"traefik.foo.frontend.": "2bar",
"traefik.foo.port.bar": "barbar",
"traefik.foo.frontend": "0bar",
"traefik.frontend.foo.backend": "0bar",
},
expected: SegmentProperties{},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := ExtractServiceProperties(test.labels)
assert.EqualValues(t, test.expected, got)
})
}
}
func TestExtractServicePropertiesP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
expected SegmentProperties
}{
{
desc: "nil labels map",
expected: SegmentProperties{},
},
{
desc: "valid label names",
labels: &map[string]string{
"traefik.foo.port": "bar",
"traefik.foo.frontend.bar": "1bar",
"traefik.foo.backend": "3bar",
},
expected: SegmentProperties{
"foo": SegmentPropertyValues{
"port": "bar",
"frontend.bar": "1bar",
"backend": "3bar",
},
},
},
{
desc: "invalid label names",
labels: &map[string]string{
"foo.frontend.bar": "1bar",
"traefik.foo.frontend.": "2bar",
"traefik.foo.port.bar": "barbar",
"traefik.foo.frontend": "0bar",
"traefik.frontend.foo.backend": "0bar",
},
expected: SegmentProperties{},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := ExtractServicePropertiesP(test.labels)
assert.EqualValues(t, test.expected, got)
})
}
}
func TestIsEnabled(t *testing.T) {
testCases := []struct {
desc string
@ -884,97 +545,6 @@ func TestIsEnabled(t *testing.T) {
}
}
func TestIsEnabledP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
exposedByDefault bool
expected bool
}{
{
desc: "nil labels map & exposedByDefault true",
exposedByDefault: true,
expected: true,
},
{
desc: "nil labels map & exposedByDefault false",
exposedByDefault: false,
expected: false,
},
{
desc: "exposedByDefault false and label enable true",
labels: &map[string]string{
TraefikEnable: "true",
},
exposedByDefault: false,
expected: true,
},
{
desc: "exposedByDefault false and label enable false",
labels: &map[string]string{
TraefikEnable: "false",
},
exposedByDefault: false,
expected: false,
},
{
desc: "exposedByDefault true and label enable false",
labels: &map[string]string{
TraefikEnable: "false",
},
exposedByDefault: true,
expected: false,
},
{
desc: "exposedByDefault true and label enable true",
labels: &map[string]string{
TraefikEnable: "true",
},
exposedByDefault: true,
expected: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := IsEnabledP(test.labels, test.exposedByDefault)
assert.Equal(t, test.expected, got)
})
}
}
func TestGetServiceLabel(t *testing.T) {
testCases := []struct {
desc string
labelName string
serviceName string
expected string
}{
{
desc: "without service name",
labelName: TraefikPort,
expected: TraefikPort,
},
{
desc: "with service name",
labelName: TraefikPort,
serviceName: "bar",
expected: "traefik.bar.port",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetServiceLabel(test.labelName, test.serviceName)
assert.Equal(t, test.expected, got)
})
}
}
func TestHasPrefix(t *testing.T) {
testCases := []struct {
desc string
@ -1023,205 +593,100 @@ func TestHasPrefix(t *testing.T) {
}
}
func TestParseErrorPages(t *testing.T) {
func TestGetFuncString(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected map[string]*types.ErrorPage
labels map[string]string
labelName string
defaultValue string
expected string
}{
{
desc: "2 errors pages",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus: "404",
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageBackend: "foo_backend",
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageQuery: "foo_query",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageStatus: "500,600",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageBackend: "bar_backend",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageQuery: "bar_query",
},
expected: map[string]*types.ErrorPage{
"foo": {
Status: []string{"404"},
Query: "foo_query",
Backend: "foo_backend",
},
"bar": {
Status: []string{"500", "600"},
Query: "bar_query",
Backend: "bar_backend",
},
},
labels: nil,
labelName: TraefikWeight,
defaultValue: DefaultWeight,
expected: "0",
},
{
desc: "only status field",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus: "404",
TraefikWeight: "10",
},
expected: map[string]*types.ErrorPage{
"foo": {
Status: []string{"404"},
},
},
},
{
desc: "invalid field",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + "courgette": "404",
},
expected: map[string]*types.ErrorPage{"foo": {}},
},
{
desc: "no error pages labels",
labels: map[string]string{},
expected: nil,
labelName: TraefikWeight,
defaultValue: DefaultWeight,
expected: "10",
},
}
for _, test := range testCases {
for containerID, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Run(test.labelName+strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
pages := ParseErrorPages(test.labels, Prefix+BaseFrontendErrorPage, RegexpFrontendErrorPage)
assert.EqualValues(t, test.expected, pages)
})
}
}
func TestParseRateSets(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected map[string]*types.Rate
}{
{
desc: "2 rate limits",
labels: map[string]string{
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitPeriod: "6",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitAverage: "12",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitBurst: "18",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitPeriod: "3",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitAverage: "6",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitBurst: "9",
},
expected: map[string]*types.Rate{
"foo": {
Period: flaeg.Duration(6 * time.Second),
Average: 12,
Burst: 18,
},
"bar": {
Period: flaeg.Duration(3 * time.Second),
Average: 6,
Burst: 9,
},
},
},
{
desc: "no rate limits labels",
labels: map[string]string{},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rateSets := ParseRateSets(test.labels, Prefix+BaseFrontendRateLimit, RegexpFrontendRateLimit)
assert.EqualValues(t, test.expected, rateSets)
})
}
}
func TestExtractTraefikLabels(t *testing.T) {
testCases := []struct {
desc string
prefix string
originLabels map[string]string
expected SegmentProperties
}{
{
desc: "nil labels map",
prefix: "traefik",
originLabels: nil,
expected: SegmentProperties{"": {}},
},
{
desc: "container labels",
prefix: "traefik",
originLabels: map[string]string{
"frontend.priority": "foo", // missing prefix: skip
"traefik.port": "bar",
},
expected: SegmentProperties{
"": {
"traefik.port": "bar",
},
},
},
{
desc: "segment labels: only segment no default",
prefix: "traefik",
originLabels: map[string]string{
"traefik.goo.frontend.priority": "A",
"traefik.goo.port": "D",
"traefik.port": "C",
},
expected: SegmentProperties{
"goo": {
"traefik.frontend.priority": "A",
"traefik.port": "D",
},
},
},
{
desc: "segment labels: use default",
prefix: "traefik",
originLabels: map[string]string{
"traefik.guu.frontend.priority": "B",
"traefik.port": "C",
},
expected: SegmentProperties{
"guu": {
"traefik.frontend.priority": "B",
"traefik.port": "C",
},
},
},
{
desc: "segment labels: several segments",
prefix: "traefik",
originLabels: map[string]string{
"traefik.goo.frontend.priority": "A",
"traefik.goo.port": "D",
"traefik.guu.frontend.priority": "B",
"traefik.port": "C",
},
expected: SegmentProperties{
"goo": {
"traefik.frontend.priority": "A",
"traefik.port": "D",
},
"guu": {
"traefik.frontend.priority": "B",
"traefik.port": "C",
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := ExtractTraefikLabels(test.originLabels)
actual := GetFuncString(test.labelName, test.defaultValue)(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetSliceString(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
labelName string
expected []string
}{
{
desc: "no whitelist-label",
labels: nil,
expected: nil,
},
{
desc: "whitelist-label with empty string",
labels: map[string]string{
TraefikFrontendWhiteListSourceRange: "",
},
labelName: TraefikFrontendWhiteListSourceRange,
expected: nil,
},
{
desc: "whitelist-label with IPv4 mask",
labels: map[string]string{
TraefikFrontendWhiteListSourceRange: "1.2.3.4/16",
},
labelName: TraefikFrontendWhiteListSourceRange,
expected: []string{
"1.2.3.4/16",
},
},
{
desc: "whitelist-label with IPv6 mask",
labels: map[string]string{
TraefikFrontendWhiteListSourceRange: "fe80::/16",
},
labelName: TraefikFrontendWhiteListSourceRange,
expected: []string{
"fe80::/16",
},
},
{
desc: "whitelist-label with multiple masks",
labels: map[string]string{
TraefikFrontendWhiteListSourceRange: "1.1.1.1/24, 1234:abcd::42/32",
},
labelName: TraefikFrontendWhiteListSourceRange,
expected: []string{
"1.1.1.1/24",
"1234:abcd::42/32",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetFuncSliceString(test.labelName)(test.labels)
assert.EqualValues(t, test.expected, actual)
})
}
}

301
provider/label/partial.go Normal file
View file

@ -0,0 +1,301 @@
package label
import (
"math"
"regexp"
"strconv"
"strings"
"github.com/containous/flaeg"
"github.com/containous/traefik/log"
"github.com/containous/traefik/types"
)
// GetWhiteList Create white list from labels
func GetWhiteList(labels map[string]string) *types.WhiteList {
if Has(labels, TraefikFrontendWhitelistSourceRange) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", TraefikFrontendWhitelistSourceRange, TraefikFrontendWhiteListSourceRange)
}
ranges := GetSliceStringValue(labels, TraefikFrontendWhiteListSourceRange)
if len(ranges) > 0 {
return &types.WhiteList{
SourceRange: ranges,
UseXForwardedFor: GetBoolValue(labels, TraefikFrontendWhiteListUseXForwardedFor, false),
}
}
// TODO: Deprecated
values := GetSliceStringValue(labels, TraefikFrontendWhitelistSourceRange)
if len(values) > 0 {
return &types.WhiteList{
SourceRange: values,
UseXForwardedFor: false,
}
}
return nil
}
// GetRedirect Create redirect from labels
func GetRedirect(labels map[string]string) *types.Redirect {
permanent := GetBoolValue(labels, TraefikFrontendRedirectPermanent, false)
if Has(labels, TraefikFrontendRedirectEntryPoint) {
return &types.Redirect{
EntryPoint: GetStringValue(labels, TraefikFrontendRedirectEntryPoint, ""),
Permanent: permanent,
}
}
if Has(labels, TraefikFrontendRedirectRegex) &&
Has(labels, TraefikFrontendRedirectReplacement) {
return &types.Redirect{
Regex: GetStringValue(labels, TraefikFrontendRedirectRegex, ""),
Replacement: GetStringValue(labels, TraefikFrontendRedirectReplacement, ""),
Permanent: permanent,
}
}
return nil
}
// GetErrorPages Create error pages from labels
func GetErrorPages(labels map[string]string) map[string]*types.ErrorPage {
prefix := Prefix + BaseFrontendErrorPage
return ParseErrorPages(labels, prefix, RegexpFrontendErrorPage)
}
// ParseErrorPages parse error pages to create ErrorPage struct
func ParseErrorPages(labels map[string]string, labelPrefix string, labelRegex *regexp.Regexp) map[string]*types.ErrorPage {
var errorPages map[string]*types.ErrorPage
for lblName, value := range labels {
if strings.HasPrefix(lblName, labelPrefix) {
submatch := labelRegex.FindStringSubmatch(lblName)
if len(submatch) != 3 {
log.Errorf("Invalid page error label: %s, sub-match: %v", lblName, submatch)
continue
}
if errorPages == nil {
errorPages = make(map[string]*types.ErrorPage)
}
pageName := submatch[1]
ep, ok := errorPages[pageName]
if !ok {
ep = &types.ErrorPage{}
errorPages[pageName] = ep
}
switch submatch[2] {
case SuffixErrorPageStatus:
ep.Status = SplitAndTrimString(value, ",")
case SuffixErrorPageQuery:
ep.Query = value
case SuffixErrorPageBackend:
ep.Backend = value
default:
log.Errorf("Invalid page error label: %s", lblName)
continue
}
}
}
return errorPages
}
// GetRateLimit Create rate limits from labels
func GetRateLimit(labels map[string]string) *types.RateLimit {
extractorFunc := GetStringValue(labels, TraefikFrontendRateLimitExtractorFunc, "")
if len(extractorFunc) == 0 {
return nil
}
prefix := Prefix + BaseFrontendRateLimit
limits := ParseRateSets(labels, prefix, RegexpFrontendRateLimit)
return &types.RateLimit{
ExtractorFunc: extractorFunc,
RateSet: limits,
}
}
// ParseRateSets parse rate limits to create Rate struct
func ParseRateSets(labels map[string]string, labelPrefix string, labelRegex *regexp.Regexp) map[string]*types.Rate {
var rateSets map[string]*types.Rate
for lblName, rawValue := range labels {
if strings.HasPrefix(lblName, labelPrefix) && len(rawValue) > 0 {
submatch := labelRegex.FindStringSubmatch(lblName)
if len(submatch) != 3 {
log.Errorf("Invalid rate limit label: %s, sub-match: %v", lblName, submatch)
continue
}
if rateSets == nil {
rateSets = make(map[string]*types.Rate)
}
limitName := submatch[1]
ep, ok := rateSets[limitName]
if !ok {
ep = &types.Rate{}
rateSets[limitName] = ep
}
switch submatch[2] {
case "period":
var d flaeg.Duration
err := d.Set(rawValue)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Period = d
case "average":
value, err := strconv.ParseInt(rawValue, 10, 64)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Average = value
case "burst":
value, err := strconv.ParseInt(rawValue, 10, 64)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Burst = value
default:
log.Errorf("Invalid rate limit label: %s", lblName)
continue
}
}
}
return rateSets
}
// GetHeaders Create headers from labels
func GetHeaders(labels map[string]string) *types.Headers {
headers := &types.Headers{
CustomRequestHeaders: GetMapValue(labels, TraefikFrontendRequestHeaders),
CustomResponseHeaders: GetMapValue(labels, TraefikFrontendResponseHeaders),
SSLProxyHeaders: GetMapValue(labels, TraefikFrontendSSLProxyHeaders),
AllowedHosts: GetSliceStringValue(labels, TraefikFrontendAllowedHosts),
HostsProxyHeaders: GetSliceStringValue(labels, TraefikFrontendHostsProxyHeaders),
STSSeconds: GetInt64Value(labels, TraefikFrontendSTSSeconds, 0),
SSLRedirect: GetBoolValue(labels, TraefikFrontendSSLRedirect, false),
SSLTemporaryRedirect: GetBoolValue(labels, TraefikFrontendSSLTemporaryRedirect, false),
STSIncludeSubdomains: GetBoolValue(labels, TraefikFrontendSTSIncludeSubdomains, false),
STSPreload: GetBoolValue(labels, TraefikFrontendSTSPreload, false),
ForceSTSHeader: GetBoolValue(labels, TraefikFrontendForceSTSHeader, false),
FrameDeny: GetBoolValue(labels, TraefikFrontendFrameDeny, false),
ContentTypeNosniff: GetBoolValue(labels, TraefikFrontendContentTypeNosniff, false),
BrowserXSSFilter: GetBoolValue(labels, TraefikFrontendBrowserXSSFilter, false),
IsDevelopment: GetBoolValue(labels, TraefikFrontendIsDevelopment, false),
SSLHost: GetStringValue(labels, TraefikFrontendSSLHost, ""),
CustomFrameOptionsValue: GetStringValue(labels, TraefikFrontendCustomFrameOptionsValue, ""),
ContentSecurityPolicy: GetStringValue(labels, TraefikFrontendContentSecurityPolicy, ""),
PublicKey: GetStringValue(labels, TraefikFrontendPublicKey, ""),
ReferrerPolicy: GetStringValue(labels, TraefikFrontendReferrerPolicy, ""),
CustomBrowserXSSValue: GetStringValue(labels, TraefikFrontendCustomBrowserXSSValue, ""),
}
if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() {
return nil
}
return headers
}
// GetMaxConn Create max connection from labels
func GetMaxConn(labels map[string]string) *types.MaxConn {
amount := GetInt64Value(labels, TraefikBackendMaxConnAmount, math.MinInt64)
extractorFunc := GetStringValue(labels, TraefikBackendMaxConnExtractorFunc, DefaultBackendMaxconnExtractorFunc)
if amount == math.MinInt64 || len(extractorFunc) == 0 {
return nil
}
return &types.MaxConn{
Amount: amount,
ExtractorFunc: extractorFunc,
}
}
// GetHealthCheck Create health check from labels
func GetHealthCheck(labels map[string]string) *types.HealthCheck {
path := GetStringValue(labels, TraefikBackendHealthCheckPath, "")
if len(path) == 0 {
return nil
}
port := GetIntValue(labels, TraefikBackendHealthCheckPort, DefaultBackendHealthCheckPort)
interval := GetStringValue(labels, TraefikBackendHealthCheckInterval, "")
return &types.HealthCheck{
Path: path,
Port: port,
Interval: interval,
}
}
// GetBuffering Create buffering from labels
func GetBuffering(labels map[string]string) *types.Buffering {
if !HasPrefix(labels, TraefikBackendBuffering) {
return nil
}
return &types.Buffering{
MaxRequestBodyBytes: GetInt64Value(labels, TraefikBackendBufferingMaxRequestBodyBytes, 0),
MaxResponseBodyBytes: GetInt64Value(labels, TraefikBackendBufferingMaxResponseBodyBytes, 0),
MemRequestBodyBytes: GetInt64Value(labels, TraefikBackendBufferingMemRequestBodyBytes, 0),
MemResponseBodyBytes: GetInt64Value(labels, TraefikBackendBufferingMemResponseBodyBytes, 0),
RetryExpression: GetStringValue(labels, TraefikBackendBufferingRetryExpression, ""),
}
}
// GetCircuitBreaker Create circuit breaker from labels
func GetCircuitBreaker(labels map[string]string) *types.CircuitBreaker {
circuitBreaker := GetStringValue(labels, TraefikBackendCircuitBreakerExpression, "")
if len(circuitBreaker) == 0 {
return nil
}
return &types.CircuitBreaker{Expression: circuitBreaker}
}
// GetLoadBalancer Create load balancer from labels
func GetLoadBalancer(labels map[string]string) *types.LoadBalancer {
if !HasPrefix(labels, TraefikBackendLoadBalancer) {
return nil
}
method := GetStringValue(labels, TraefikBackendLoadBalancerMethod, DefaultBackendLoadBalancerMethod)
lb := &types.LoadBalancer{
Method: method,
Sticky: getSticky(labels),
}
if GetBoolValue(labels, TraefikBackendLoadBalancerStickiness, false) {
cookieName := GetStringValue(labels, TraefikBackendLoadBalancerStickinessCookieName, DefaultBackendLoadbalancerStickinessCookieName)
lb.Stickiness = &types.Stickiness{CookieName: cookieName}
}
return lb
}
// TODO: Deprecated
// replaced by Stickiness
// Deprecated
func getSticky(labels map[string]string) bool {
if Has(labels, TraefikBackendLoadBalancerSticky) {
log.Warnf("Deprecated configuration found: %s. Please use %s.", TraefikBackendLoadBalancerSticky, TraefikBackendLoadBalancerStickiness)
}
return GetBoolValue(labels, TraefikBackendLoadBalancerSticky, false)
}

View file

@ -0,0 +1,711 @@
package label
import (
"testing"
"time"
"github.com/containous/flaeg"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
)
func TestParseErrorPages(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected map[string]*types.ErrorPage
}{
{
desc: "2 errors pages",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus: "404",
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageBackend: "foo_backend",
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageQuery: "foo_query",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageStatus: "500,600",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageBackend: "bar_backend",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageQuery: "bar_query",
},
expected: map[string]*types.ErrorPage{
"foo": {
Status: []string{"404"},
Query: "foo_query",
Backend: "foo_backend",
},
"bar": {
Status: []string{"500", "600"},
Query: "bar_query",
Backend: "bar_backend",
},
},
},
{
desc: "only status field",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus: "404",
},
expected: map[string]*types.ErrorPage{
"foo": {
Status: []string{"404"},
},
},
},
{
desc: "invalid field",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + "courgette": "404",
},
expected: map[string]*types.ErrorPage{"foo": {}},
},
{
desc: "no error pages labels",
labels: map[string]string{},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
pages := ParseErrorPages(test.labels, Prefix+BaseFrontendErrorPage, RegexpFrontendErrorPage)
assert.EqualValues(t, test.expected, pages)
})
}
}
func TestParseRateSets(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected map[string]*types.Rate
}{
{
desc: "2 rate limits",
labels: map[string]string{
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitPeriod: "6",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitAverage: "12",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitBurst: "18",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitPeriod: "3",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitAverage: "6",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitBurst: "9",
},
expected: map[string]*types.Rate{
"foo": {
Period: flaeg.Duration(6 * time.Second),
Average: 12,
Burst: 18,
},
"bar": {
Period: flaeg.Duration(3 * time.Second),
Average: 6,
Burst: 9,
},
},
},
{
desc: "no rate limits labels",
labels: map[string]string{},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rateSets := ParseRateSets(test.labels, Prefix+BaseFrontendRateLimit, RegexpFrontendRateLimit)
assert.EqualValues(t, test.expected, rateSets)
})
}
}
func TestWhiteList(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.WhiteList
}{
{
desc: "should return nil when no white list labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when deprecated label",
labels: map[string]string{
TraefikFrontendWhitelistSourceRange: "10.10.10.10",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when only range",
labels: map[string]string{
TraefikFrontendWhiteListSourceRange: "10.10.10.10",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: false,
},
},
{
desc: "should return a struct when range and UseXForwardedFor",
labels: map[string]string{
TraefikFrontendWhiteListSourceRange: "10.10.10.10",
TraefikFrontendWhiteListUseXForwardedFor: "true",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return a struct when mix deprecated label and new labels",
labels: map[string]string{
TraefikFrontendWhitelistSourceRange: "20.20.20.20",
TraefikFrontendWhiteListSourceRange: "10.10.10.10",
TraefikFrontendWhiteListUseXForwardedFor: "true",
},
expected: &types.WhiteList{
SourceRange: []string{
"10.10.10.10",
},
UseXForwardedFor: true,
},
},
{
desc: "should return nil when only UseXForwardedFor",
labels: map[string]string{
TraefikFrontendWhiteListUseXForwardedFor: "true",
},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetWhiteList(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetCircuitBreaker(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.CircuitBreaker
}{
{
desc: "should return nil when no CB label",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when CB label is set",
labels: map[string]string{
TraefikBackendCircuitBreakerExpression: "NetworkErrorRatio() > 0.5",
},
expected: &types.CircuitBreaker{
Expression: "NetworkErrorRatio() > 0.5",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetCircuitBreaker(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetLoadBalancer(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.LoadBalancer
}{
{
desc: "should return nil when no LB labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when labels are set",
labels: map[string]string{
TraefikBackendLoadBalancerMethod: "drr",
TraefikBackendLoadBalancerSticky: "true",
TraefikBackendLoadBalancerStickiness: "true",
TraefikBackendLoadBalancerStickinessCookieName: "foo",
},
expected: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: &types.Stickiness{
CookieName: "foo",
},
},
},
{
desc: "should return a nil Stickiness when Stickiness is not set",
labels: map[string]string{
TraefikBackendLoadBalancerMethod: "drr",
TraefikBackendLoadBalancerSticky: "true",
TraefikBackendLoadBalancerStickinessCookieName: "foo",
},
expected: &types.LoadBalancer{
Method: "drr",
Sticky: true,
Stickiness: nil,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetLoadBalancer(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetMaxConn(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.MaxConn
}{
{
desc: "should return nil when no max conn labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return nil when no amount label",
labels: map[string]string{
TraefikBackendMaxConnExtractorFunc: "client.ip",
},
expected: nil,
},
{
desc: "should return default when no empty extractorFunc label",
labels: map[string]string{
TraefikBackendMaxConnExtractorFunc: "",
TraefikBackendMaxConnAmount: "666",
},
expected: &types.MaxConn{
ExtractorFunc: "request.host",
Amount: 666,
},
},
{
desc: "should return a struct when max conn labels are set",
labels: map[string]string{
TraefikBackendMaxConnExtractorFunc: "client.ip",
TraefikBackendMaxConnAmount: "666",
},
expected: &types.MaxConn{
ExtractorFunc: "client.ip",
Amount: 666,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetMaxConn(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetHealthCheck(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.HealthCheck
}{
{
desc: "should return nil when no health check labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return nil when no health check Path label",
labels: map[string]string{
TraefikBackendHealthCheckPort: "80",
TraefikBackendHealthCheckInterval: "6",
},
expected: nil,
},
{
desc: "should return a struct when health check labels are set",
labels: map[string]string{
TraefikBackendHealthCheckPath: "/health",
TraefikBackendHealthCheckPort: "80",
TraefikBackendHealthCheckInterval: "6",
},
expected: &types.HealthCheck{
Path: "/health",
Port: 80,
Interval: "6",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetHealthCheck(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetBuffering(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.Buffering
}{
{
desc: "should return nil when no buffering labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when buffering labels are set",
labels: map[string]string{
TraefikBackendBufferingMaxResponseBodyBytes: "10485760",
TraefikBackendBufferingMemResponseBodyBytes: "2097152",
TraefikBackendBufferingMaxRequestBodyBytes: "10485760",
TraefikBackendBufferingMemRequestBodyBytes: "2097152",
TraefikBackendBufferingRetryExpression: "IsNetworkError() && Attempts() <= 2",
},
expected: &types.Buffering{
MaxResponseBodyBytes: 10485760,
MemResponseBodyBytes: 2097152,
MaxRequestBodyBytes: 10485760,
MemRequestBodyBytes: 2097152,
RetryExpression: "IsNetworkError() && Attempts() <= 2",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetBuffering(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetRedirect(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.Redirect
}{
{
desc: "should return nil when no redirect labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should use only entry point tag when mix regex redirect and entry point redirect",
labels: map[string]string{
TraefikFrontendRedirectEntryPoint: "https",
TraefikFrontendRedirectRegex: "(.*)",
TraefikFrontendRedirectReplacement: "$1",
},
expected: &types.Redirect{
EntryPoint: "https",
},
},
{
desc: "should return a struct when entry point redirect label",
labels: map[string]string{
TraefikFrontendRedirectEntryPoint: "https",
},
expected: &types.Redirect{
EntryPoint: "https",
},
},
{
desc: "should return a struct when entry point redirect label (permanent)",
labels: map[string]string{
TraefikFrontendRedirectEntryPoint: "https",
TraefikFrontendRedirectPermanent: "true",
},
expected: &types.Redirect{
EntryPoint: "https",
Permanent: true,
},
},
{
desc: "should return a struct when regex redirect labels",
labels: map[string]string{
TraefikFrontendRedirectRegex: "(.*)",
TraefikFrontendRedirectReplacement: "$1",
},
expected: &types.Redirect{
Regex: "(.*)",
Replacement: "$1",
},
},
{
desc: "should return a struct when regex redirect labels (permanent)",
labels: map[string]string{
TraefikFrontendRedirectRegex: "(.*)",
TraefikFrontendRedirectReplacement: "$1",
TraefikFrontendRedirectPermanent: "true",
},
expected: &types.Redirect{
Regex: "(.*)",
Replacement: "$1",
Permanent: true,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetRedirect(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetRateLimit(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.RateLimit
}{
{
desc: "should return nil when no rate limit labels",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when rate limit labels are defined",
labels: map[string]string{
TraefikFrontendRateLimitExtractorFunc: "client.ip",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitPeriod: "6",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitAverage: "12",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitBurst: "18",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitPeriod: "3",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitAverage: "6",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitBurst: "9",
},
expected: &types.RateLimit{
ExtractorFunc: "client.ip",
RateSet: map[string]*types.Rate{
"foo": {
Period: flaeg.Duration(6 * time.Second),
Average: 12,
Burst: 18,
},
"bar": {
Period: flaeg.Duration(3 * time.Second),
Average: 6,
Burst: 9,
},
},
},
},
{
desc: "should return nil when ExtractorFunc is missing",
labels: map[string]string{
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitPeriod: "6",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitAverage: "12",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitBurst: "18",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitPeriod: "3",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitAverage: "6",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitBurst: "9",
},
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetRateLimit(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetHeaders(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected *types.Headers
}{
{
desc: "should return nil when no custom headers options are set",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a struct when all custom headers options are set",
labels: map[string]string{
TraefikFrontendRequestHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
TraefikFrontendResponseHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
TraefikFrontendSSLProxyHeaders: "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
TraefikFrontendAllowedHosts: "foo,bar,bor",
TraefikFrontendHostsProxyHeaders: "foo,bar,bor",
TraefikFrontendSSLHost: "foo",
TraefikFrontendCustomFrameOptionsValue: "foo",
TraefikFrontendContentSecurityPolicy: "foo",
TraefikFrontendPublicKey: "foo",
TraefikFrontendReferrerPolicy: "foo",
TraefikFrontendCustomBrowserXSSValue: "foo",
TraefikFrontendSTSSeconds: "666",
TraefikFrontendSSLRedirect: "true",
TraefikFrontendSSLTemporaryRedirect: "true",
TraefikFrontendSTSIncludeSubdomains: "true",
TraefikFrontendSTSPreload: "true",
TraefikFrontendForceSTSHeader: "true",
TraefikFrontendFrameDeny: "true",
TraefikFrontendContentTypeNosniff: "true",
TraefikFrontendBrowserXSSFilter: "true",
TraefikFrontendIsDevelopment: "true",
},
expected: &types.Headers{
CustomRequestHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
CustomResponseHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
SSLProxyHeaders: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
AllowedHosts: []string{"foo", "bar", "bor"},
HostsProxyHeaders: []string{"foo", "bar", "bor"},
SSLHost: "foo",
CustomFrameOptionsValue: "foo",
ContentSecurityPolicy: "foo",
PublicKey: "foo",
ReferrerPolicy: "foo",
CustomBrowserXSSValue: "foo",
STSSeconds: 666,
SSLRedirect: true,
SSLTemporaryRedirect: true,
STSIncludeSubdomains: true,
STSPreload: true,
ForceSTSHeader: true,
FrameDeny: true,
ContentTypeNosniff: true,
BrowserXSSFilter: true,
IsDevelopment: true,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := GetHeaders(test.labels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestProviderGetErrorPages(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected map[string]*types.ErrorPage
}{
{
desc: "should return nil when no tags",
labels: map[string]string{},
expected: nil,
},
{
desc: "should return a map when tags are present",
labels: map[string]string{
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus: "404",
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageBackend: "foo_backend",
Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageQuery: "foo_query",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageStatus: "500,600",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageBackend: "bar_backend",
Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageQuery: "bar_query",
},
expected: map[string]*types.ErrorPage{
"foo": {
Status: []string{"404"},
Query: "foo_query",
Backend: "foo_backend",
},
"bar": {
Status: []string{"500", "600"},
Query: "bar_query",
Backend: "bar_backend",
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
result := GetErrorPages(test.labels)
assert.Equal(t, test.expected, result)
})
}
}

View file

@ -0,0 +1,221 @@
package label
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestExtractTraefikLabels(t *testing.T) {
testCases := []struct {
desc string
prefix string
originLabels map[string]string
expected SegmentProperties
}{
{
desc: "nil labels map",
prefix: "traefik",
originLabels: nil,
expected: SegmentProperties{"": {}},
},
{
desc: "container labels",
prefix: "traefik",
originLabels: map[string]string{
"frontend.priority": "foo", // missing prefix: skip
"traefik.port": "bar",
},
expected: SegmentProperties{
"": {
"traefik.port": "bar",
},
},
},
{
desc: "segment labels: only segment no default",
prefix: "traefik",
originLabels: map[string]string{
"traefik.goo.frontend.priority": "A",
"traefik.goo.port": "D",
"traefik.port": "C",
},
expected: SegmentProperties{
"goo": {
"traefik.frontend.priority": "A",
"traefik.port": "D",
},
},
},
{
desc: "segment labels: use default",
prefix: "traefik",
originLabels: map[string]string{
"traefik.guu.frontend.priority": "B",
"traefik.port": "C",
},
expected: SegmentProperties{
"guu": {
"traefik.frontend.priority": "B",
"traefik.port": "C",
},
},
},
{
desc: "segment labels: several segments",
prefix: "traefik",
originLabels: map[string]string{
"traefik.goo.frontend.priority": "A",
"traefik.goo.port": "D",
"traefik.guu.frontend.priority": "B",
"traefik.port": "C",
},
expected: SegmentProperties{
"goo": {
"traefik.frontend.priority": "A",
"traefik.port": "D",
},
"guu": {
"traefik.frontend.priority": "B",
"traefik.port": "C",
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := ExtractTraefikLabels(test.originLabels)
assert.Equal(t, test.expected, actual)
})
}
}
func TestExtractServiceProperties(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected SegmentProperties
}{
{
desc: "empty labels map",
expected: SegmentProperties{},
},
{
desc: "valid label names",
labels: map[string]string{
"traefik.foo.port": "bar",
"traefik.foo.frontend.bar": "1bar",
"traefik.foo.backend": "3bar",
},
expected: SegmentProperties{
"foo": SegmentPropertyValues{
"port": "bar",
"frontend.bar": "1bar",
"backend": "3bar",
},
},
},
{
desc: "invalid label names",
labels: map[string]string{
"foo.frontend.bar": "1bar",
"traefik.foo.frontend.": "2bar",
"traefik.foo.port.bar": "barbar",
"traefik.foo.frontend": "0bar",
"traefik.frontend.foo.backend": "0bar",
},
expected: SegmentProperties{},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := ExtractServiceProperties(test.labels)
assert.EqualValues(t, test.expected, got)
})
}
}
func TestExtractServicePropertiesP(t *testing.T) {
testCases := []struct {
desc string
labels *map[string]string
expected SegmentProperties
}{
{
desc: "nil labels map",
expected: SegmentProperties{},
},
{
desc: "valid label names",
labels: &map[string]string{
"traefik.foo.port": "bar",
"traefik.foo.frontend.bar": "1bar",
"traefik.foo.backend": "3bar",
},
expected: SegmentProperties{
"foo": SegmentPropertyValues{
"port": "bar",
"frontend.bar": "1bar",
"backend": "3bar",
},
},
},
{
desc: "invalid label names",
labels: &map[string]string{
"foo.frontend.bar": "1bar",
"traefik.foo.frontend.": "2bar",
"traefik.foo.port.bar": "barbar",
"traefik.foo.frontend": "0bar",
"traefik.frontend.foo.backend": "0bar",
},
expected: SegmentProperties{},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := ExtractServicePropertiesP(test.labels)
assert.EqualValues(t, test.expected, got)
})
}
}
func TestGetServiceLabel(t *testing.T) {
testCases := []struct {
desc string
labelName string
serviceName string
expected string
}{
{
desc: "without service name",
labelName: TraefikPort,
expected: TraefikPort,
},
{
desc: "with service name",
labelName: TraefikPort,
serviceName: "bar",
expected: "traefik.bar.port",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetServiceLabel(test.labelName, test.serviceName)
assert.Equal(t, test.expected, got)
})
}
}