1
0
mirror of https://github.com/containous/traefik.git synced 2024-12-22 13:34:03 +03:00

feat: labels/annotations parser.

This commit is contained in:
Fernandez Ludovic 2017-12-02 19:25:29 +01:00 committed by Traefiker
parent dc74f76a03
commit ce6bbbaa33
8 changed files with 1361 additions and 14 deletions

View File

@ -56,7 +56,7 @@ func runStoreConfig(kv *staert.KvSource, traefikConfiguration *TraefikConfigurat
} }
stdlog.Printf("Storing file configuration: %s\n", jsonConf) stdlog.Printf("Storing file configuration: %s\n", jsonConf)
config, err := fileConfig.LoadConfig() config, err := fileConfig.BuildConfiguration()
if err != nil { if err != nil {
return err return err
} }

View File

@ -95,8 +95,8 @@ func (p *Provider) scanTable(client *dynamoClient) ([]map[string]*dynamodb.Attri
return items, nil return items, nil
} }
// loadDynamoConfig retrieves items from dynamodb and converts them into Backends and Frontends in a Configuration // buildConfiguration retrieves items from dynamodb and converts them into Backends and Frontends in a Configuration
func (p *Provider) loadDynamoConfig(client *dynamoClient) (*types.Configuration, error) { func (p *Provider) buildConfiguration(client *dynamoClient) (*types.Configuration, error) {
items, err := p.scanTable(client) items, err := p.scanTable(client)
if err != nil { if err != nil {
return nil, err return nil, err
@ -167,7 +167,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
return handleCanceled(ctx, err) return handleCanceled(ctx, err)
} }
configuration, err := p.loadDynamoConfig(awsClient) configuration, err := p.buildConfiguration(awsClient)
if err != nil { if err != nil {
return handleCanceled(ctx, err) return handleCanceled(ctx, err)
} }
@ -184,7 +184,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
log.Debug("Watching Provider...") log.Debug("Watching Provider...")
select { select {
case <-reload.C: case <-reload.C:
configuration, err := p.loadDynamoConfig(awsClient) configuration, err := p.buildConfiguration(awsClient)
if err != nil { if err != nil {
return handleCanceled(ctx, err) return handleCanceled(ctx, err)
} }

View File

@ -84,14 +84,14 @@ func (m *mockDynamoDBClient) ScanPages(input *dynamodb.ScanInput, fn func(*dynam
return nil return nil
} }
func TestLoadDynamoConfigSuccessful(t *testing.T) { func TestBuildConfigurationSuccessful(t *testing.T) {
dbiface := &dynamoClient{ dbiface := &dynamoClient{
db: &mockDynamoDBClient{ db: &mockDynamoDBClient{
testWithError: false, testWithError: false,
}, },
} }
provider := Provider{} provider := Provider{}
loadedConfig, err := provider.loadDynamoConfig(dbiface) loadedConfig, err := provider.buildConfiguration(dbiface)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -108,14 +108,14 @@ func TestLoadDynamoConfigSuccessful(t *testing.T) {
} }
} }
func TestLoadDynamoConfigFailure(t *testing.T) { func TestBuildConfigurationFailure(t *testing.T) {
dbiface := &dynamoClient{ dbiface := &dynamoClient{
db: &mockDynamoDBClient{ db: &mockDynamoDBClient{
testWithError: true, testWithError: true,
}, },
} }
provider := Provider{} provider := Provider{}
_, err := provider.loadDynamoConfig(dbiface) _, err := provider.buildConfiguration(dbiface)
if err == nil { if err == nil {
t.Fatal("Expected error") t.Fatal("Expected error")
} }

View File

@ -28,7 +28,7 @@ type Provider struct {
// Provide allows the file provider to provide configurations to traefik // Provide allows the file provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error { func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
configuration, err := p.LoadConfig() configuration, err := p.BuildConfiguration()
if err != nil { if err != nil {
return err return err
@ -52,9 +52,9 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
return nil return nil
} }
// LoadConfig loads configuration either from file or a directory specified by 'Filename'/'Directory' // BuildConfiguration loads configuration either from file or a directory specified by 'Filename'/'Directory'
// and returns a 'Configuration' object // and returns a 'Configuration' object
func (p *Provider) LoadConfig() (*types.Configuration, error) { func (p *Provider) BuildConfiguration() (*types.Configuration, error) {
if p.Directory != "" { if p.Directory != "" {
return loadFileConfigFromDirectory(p.Directory, nil) return loadFileConfigFromDirectory(p.Directory, nil)
} }
@ -108,7 +108,7 @@ func (p *Provider) watcherCallback(configurationChan chan<- types.ConfigMessage,
return return
} }
configuration, err := p.LoadConfig() configuration, err := p.BuildConfiguration()
if err != nil { if err != nil {
log.Errorf("Error occurred during watcher callback: %s", err) log.Errorf("Error occurred during watcher callback: %s", err)

273
provider/label/label.go Normal file
View File

@ -0,0 +1,273 @@
package label
import (
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
"github.com/containous/traefik/log"
)
const (
mapEntrySeparator = "||"
mapValueSeparator = ":"
)
// Default values
const (
DefaultWeight = "0"
DefaultProtocol = "http"
DefaultPassHostHeader = "true"
DefaultFrontendPriority = "0"
DefaultCircuitBreakerExpression = "NetworkErrorRatio() > 1"
DefaultFrontendRedirect = ""
DefaultBackendLoadBalancerMethod = "wrr"
DefaultBackendMaxconnExtractorFunc = "request.host"
DefaultBackendLoadbalancerStickinessCookieName = ""
)
// ServicesPropertiesRegexp used to extract the name of the service and the name of the property for this service
// All properties are under the format traefik.<servicename>.frontend.*= except the port/portIndex/weight/protocol/backend directly after traefik.<servicename>.
var ServicesPropertiesRegexp = regexp.MustCompile(`^traefik\.(?P<service_name>.+?)\.(?P<property_name>port|portIndex|weight|protocol|backend|frontend\.(.+))$`)
// PortRegexp used to extract the port label of the service
var PortRegexp = regexp.MustCompile(`^traefik\.(?P<service_name>.+?)\.port$`)
// ServicePropertyValues is a map of services properties
// an example value is: weight=42
type ServicePropertyValues map[string]string
// ServiceProperties is a map of service properties per service,
// which we can get with label[serviceName][propertyName].
// It yields a property value.
type ServiceProperties map[string]ServicePropertyValues
// GetStringValue get string value associated to a label
func GetStringValue(labels map[string]string, labelName string, defaultValue string) string {
if value, ok := labels[labelName]; ok && len(value) > 0 {
return value
}
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]
if ok {
v, err := strconv.ParseBool(rawValue)
if err == nil {
return v
}
}
return 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 {
value, err := strconv.Atoi(rawValue)
if err == nil {
return value
}
log.Errorf("Unable to parse %q: %q, falling back to %v. %v", labelName, rawValue, defaultValue, err)
}
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 {
value, err := strconv.ParseInt(rawValue, 10, 64)
if err == nil {
return value
}
log.Errorf("Unable to parse %q: %q, falling back to %v. %v", labelName, rawValue, defaultValue, err)
}
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
if values, ok := labels[labelName]; ok {
value = SplitAndTrimString(values, ",")
if len(value) == 0 {
log.Debugf("Could not load %q.", labelName)
}
}
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)
}
// GetMapValue get Map value associated to a label
func GetMapValue(labels map[string]string, labelName string) map[string]string {
if values, ok := labels[labelName]; ok {
if len(values) == 0 {
log.Errorf("Missing value for %q, skipping...", labelName)
return nil
}
mapValue := make(map[string]string)
for _, parts := range strings.Split(values, mapEntrySeparator) {
pair := strings.SplitN(parts, mapValueSeparator, 2)
if len(pair) != 2 {
log.Warnf("Could not load %q: %q, skipping...", labelName, parts)
} else {
mapValue[http.CanonicalHeaderKey(strings.TrimSpace(pair[0]))] = strings.TrimSpace(pair[1])
}
}
if len(mapValue) == 0 {
log.Errorf("Could not load %q, skipping...", labelName)
return nil
}
return mapValue
}
return nil
}
// GetStringMultipleStrict get multiple string values associated to several labels
// Fail if one label is missing
func GetStringMultipleStrict(labels map[string]string, labelNames ...string) (map[string]string, error) {
foundLabels := map[string]string{}
for _, name := range labelNames {
value := GetStringValue(labels, name, "")
// Error out only if one of them is not defined.
if len(value) == 0 {
return nil, fmt.Errorf("label not found: %s", name)
}
foundLabels[name] = value
}
return foundLabels, nil
}
// Has Check if a value is associated to a label
func Has(labels map[string]string, labelName string) bool {
value, ok := labels[labelName]
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)
}
// ExtractServiceProperties Extract services labels
func ExtractServiceProperties(labels map[string]string) ServiceProperties {
v := make(ServiceProperties)
for name, value := range labels {
matches := ServicesPropertiesRegexp.FindStringSubmatch(name)
if matches == nil {
continue
}
var serviceName string
var propertyName string
for i, name := range ServicesPropertiesRegexp.SubexpNames() {
if i != 0 {
if name == "service_name" {
serviceName = matches[i]
} else if name == "property_name" {
propertyName = matches[i]
}
}
}
if _, ok := v[serviceName]; !ok {
v[serviceName] = make(ServicePropertyValues)
}
v[serviceName][propertyName] = value
}
return v
}
// ExtractServicePropertiesP Extract services labels
func ExtractServicePropertiesP(labels *map[string]string) ServiceProperties {
if labels == nil {
return make(ServiceProperties)
}
return ExtractServiceProperties(*labels)
}
// 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.
func SplitAndTrimString(base string, sep string) []string {
var trimmedStrings []string
for _, s := range strings.Split(base, sep) {
s = strings.TrimSpace(s)
if len(s) > 0 {
trimmedStrings = append(trimmedStrings, s)
}
}
return trimmedStrings
}
// GetServiceLabel converts a key value of Label*, given a serviceName,
// into a pattern <LabelPrefix>.<serviceName>.<property>
// i.e. For LabelFrontendRule and serviceName=app it will return "traefik.app.frontend.rule"
func GetServiceLabel(labelName, serviceName string) string {
if len(serviceName) > 0 {
property := strings.TrimPrefix(labelName, Prefix)
return Prefix + serviceName + "." + property
}
return labelName
}

View File

@ -0,0 +1,969 @@
package label
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSplitAndTrimString(t *testing.T) {
testCases := []struct {
desc string
input string
expected []string
}{
{
desc: "empty string",
input: "",
expected: nil,
}, {
desc: "one piece",
input: "foo",
expected: []string{"foo"},
}, {
desc: "two pieces",
input: "foo,bar",
expected: []string{"foo", "bar"},
}, {
desc: "three pieces",
input: "foo,bar,zoo",
expected: []string{"foo", "bar", "zoo"},
}, {
desc: "two pieces with whitespace",
input: " foo , bar ",
expected: []string{"foo", "bar"},
}, {
desc: "consecutive commas",
input: " foo ,, bar ",
expected: []string{"foo", "bar"},
}, {
desc: "consecutive commas with whitespace",
input: " foo , , bar ",
expected: []string{"foo", "bar"},
}, {
desc: "leading and trailing commas",
input: ",, foo , , bar,, , ",
expected: []string{"foo", "bar"},
}, {
desc: "no valid pieces",
input: ", , , ,, ,",
expected: nil,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := SplitAndTrimString(test.input, ",")
assert.Equal(t, test.expected, actual)
})
}
}
func TestGetStringValue(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
labelName string
defaultValue string
expected string
}{
{
desc: "empty labels map",
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 := GetStringValue(test.labels, test.labelName, test.defaultValue)
assert.Equal(t, test.expected, got)
})
}
}
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
labels map[string]string
labelName string
defaultValue bool
expected bool
}{
{
desc: "empty map",
labelName: "foo",
},
{
desc: "invalid boolean value",
labels: map[string]string{
"foo": "bar",
},
labelName: "foo",
defaultValue: true,
expected: true,
},
{
desc: "valid boolean value: true",
labels: map[string]string{
"foo": "true",
},
labelName: "foo",
defaultValue: false,
expected: true,
},
{
desc: "valid boolean value: false",
labels: map[string]string{
"foo": "false",
},
labelName: "foo",
defaultValue: true,
expected: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetBoolValue(test.labels, test.labelName, test.defaultValue)
assert.Equal(t, test.expected, got)
})
}
}
func TestGetIntValue(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
labelName string
defaultValue int
expected int
}{
{
desc: "empty map",
labelName: "foo",
},
{
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 := GetIntValue(test.labels, test.labelName, test.defaultValue)
assert.Equal(t, test.expected, got)
})
}
}
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
labels map[string]string
labelName string
defaultValue int64
expected int64
}{
{
desc: "empty map",
labelName: "foo",
},
{
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 := GetInt64Value(test.labels, test.labelName, test.defaultValue)
assert.Equal(t, test.expected, got)
})
}
}
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
labels map[string]string
labelName string
expected []string
}{
{
desc: "empty map",
labelName: "foo",
},
{
desc: "empty value",
labels: map[string]string{
"foo": "",
},
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 := GetSliceStringValue(test.labels, test.labelName)
assert.EqualValues(t, test.expected, got)
})
}
}
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
labels map[string]string
labelName string
expected map[string]string
}{
{
desc: "empty map",
labelName: "foo",
},
{
desc: "existent label with empty entry",
labelName: "foo",
labels: map[string]string{
"foo": "",
},
expected: nil,
},
{
desc: "existent label with invalid entry",
labelName: "foo",
labels: map[string]string{
"foo": "bar",
},
expected: nil,
},
{
desc: "existent label with empty value",
labelName: "foo",
labels: map[string]string{
"foo": "bar:",
},
expected: map[string]string{
"Bar": "",
},
},
{
desc: "one entry",
labelName: "foo",
labels: map[string]string{
"foo": " Access-Control-Allow-Methods:POST,GET,OPTIONS ",
},
expected: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
},
},
{
desc: "several entry",
labelName: "foo",
labels: map[string]string{
"foo": "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
},
expected: map[string]string{
"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
"Content-Type": "application/json; charset=utf-8",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := GetMapValue(test.labels, test.labelName)
assert.EqualValues(t, test.expected, got)
})
}
}
func TestGetStringMultipleStrict(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
labelNames []string
expected map[string]string
expectedErr bool
}{
{
desc: "empty labels names and empty labels map",
labels: map[string]string{},
expected: map[string]string{},
},
{
desc: "empty labels names",
labels: map[string]string{
"foo": "bar",
"fii": "bir",
},
expected: map[string]string{},
},
{
desc: "one label missing",
labels: map[string]string{
"foo": "bar",
"fii": "bir",
"fyy": "byr",
},
labelNames: []string{"foo", "fii", "fuu"},
expected: nil,
expectedErr: true,
},
{
desc: "all labels are present",
labels: map[string]string{
"foo": "bar",
"fii": "bir",
"fyy": "byr",
},
labelNames: []string{"foo", "fii"},
expected: map[string]string{
"foo": "bar",
"fii": "bir",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got, err := GetStringMultipleStrict(test.labels, test.labelNames...)
if (err != nil) != test.expectedErr {
t.Errorf("error = %v, wantErr %v", err, test.expectedErr)
return
}
assert.EqualValues(t, test.expected, got)
})
}
}
func TestHas(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 := Has(test.labels, test.labelName)
assert.Equal(t, test.expected, got)
})
}
}
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 ServiceProperties
}{
{
desc: "empty labels map",
expected: ServiceProperties{},
},
{
desc: "valid label names",
labels: map[string]string{
"traefik.foo.port": "bar",
"traefik.foo.frontend.bar": "1bar",
"traefik.foo.frontend.": "2bar",
},
expected: ServiceProperties{
"foo": ServicePropertyValues{
"port": "bar",
"frontend.bar": "1bar",
},
},
},
{
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",
},
expected: ServiceProperties{},
},
}
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 ServiceProperties
}{
{
desc: "nil labels map",
expected: ServiceProperties{},
},
{
desc: "valid label names",
labels: &map[string]string{
"traefik.foo.port": "bar",
"traefik.foo.frontend.bar": "1bar",
"traefik.foo.frontend.": "2bar",
},
expected: ServiceProperties{
"foo": ServicePropertyValues{
"port": "bar",
"frontend.bar": "1bar",
},
},
},
{
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",
},
expected: ServiceProperties{},
},
}
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
labels map[string]string
exposedByDefault bool
expected bool
}{
{
desc: "empty labels map & exposedByDefault true",
exposedByDefault: true,
expected: true,
},
{
desc: "empty 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 := IsEnabled(test.labels, test.exposedByDefault)
assert.Equal(t, test.expected, got)
})
}
}
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)
})
}
}

105
provider/label/names.go Normal file
View File

@ -0,0 +1,105 @@
package label
// Traefik labels
const (
Prefix = "traefik."
SuffixBackend = "backend"
SuffixDomain = "domain"
SuffixEnable = "enable"
SuffixPort = "port"
SuffixPortIndex = "portIndex"
SuffixProtocol = "protocol"
SuffixTags = "tags"
SuffixWeight = "weight"
SuffixBackendID = "backend.id"
SuffixBackendCircuitBreaker = "backend.circuitbreaker"
SuffixBackendCircuitBreakerExpression = "backend.circuitbreaker.expression"
SuffixBackendHealthCheckPath = "backend.healthcheck.path"
SuffixBackendHealthCheckInterval = "backend.healthcheck.interval"
SuffixBackendLoadBalancerMethod = "backend.loadbalancer.method"
SuffixBackendLoadBalancerSticky = "backend.loadbalancer.sticky"
SuffixBackendLoadBalancerStickiness = "backend.loadbalancer.stickiness"
SuffixBackendLoadBalancerStickinessCookieName = "backend.loadbalancer.stickiness.cookieName"
SuffixBackendMaxConnAmount = "backend.maxconn.amount"
SuffixBackendMaxConnExtractorFunc = "backend.maxconn.extractorfunc"
SuffixFrontendAuthBasic = "frontend.auth.basic"
SuffixFrontendBackend = "frontend.backend"
SuffixFrontendEntryPoints = "frontend.entryPoints"
SuffixFrontendRequestHeaders = "frontend.headers.customRequestHeaders"
SuffixFrontendResponseHeaders = "frontend.headers.customResponseHeaders"
SuffixFrontendHeadersAllowedHosts = "frontend.headers.allowedHosts"
SuffixFrontendHeadersHostsProxyHeaders = "frontend.headers.hostsProxyHeaders"
SuffixFrontendHeadersSSLRedirect = "frontend.headers.SSLRedirect"
SuffixFrontendHeadersSSLTemporaryRedirect = "frontend.headers.SSLTemporaryRedirect"
SuffixFrontendHeadersSSLHost = "frontend.headers.SSLHost"
SuffixFrontendHeadersSSLProxyHeaders = "frontend.headers.SSLProxyHeaders"
SuffixFrontendHeadersSTSSeconds = "frontend.headers.STSSeconds"
SuffixFrontendHeadersSTSIncludeSubdomains = "frontend.headers.STSIncludeSubdomains"
SuffixFrontendHeadersSTSPreload = "frontend.headers.STSPreload"
SuffixFrontendHeadersForceSTSHeader = "frontend.headers.forceSTSHeader"
SuffixFrontendHeadersFrameDeny = "frontend.headers.frameDeny"
SuffixFrontendHeadersCustomFrameOptionsValue = "frontend.headers.customFrameOptionsValue"
SuffixFrontendHeadersContentTypeNosniff = "frontend.headers.contentTypeNosniff"
SuffixFrontendHeadersBrowserXSSFilter = "frontend.headers.browserXSSFilter"
SuffixFrontendHeadersContentSecurityPolicy = "frontend.headers.contentSecurityPolicy"
SuffixFrontendHeadersPublicKey = "frontend.headers.publicKey"
SuffixFrontendHeadersReferrerPolicy = "frontend.headers.referrerPolicy"
SuffixFrontendHeadersIsDevelopment = "frontend.headers.isDevelopment"
SuffixFrontendPassHostHeader = "frontend.passHostHeader"
SuffixFrontendPassTLSCert = "frontend.passTLSCert"
SuffixFrontendPriority = "frontend.priority"
SuffixFrontendRedirect = "frontend.redirect"
SuffixFrontendRule = "frontend.rule"
SuffixFrontendRuleType = "frontend.rule.type"
SuffixFrontendWhitelistSourceRange = "frontend.whitelistSourceRange"
SuffixFrontendValue = "frontend.value"
TraefikDomain = Prefix + SuffixDomain
TraefikEnable = Prefix + SuffixEnable
TraefikPort = Prefix + SuffixPort
TraefikPortIndex = Prefix + SuffixPortIndex
TraefikProtocol = Prefix + SuffixProtocol
TraefikTags = Prefix + SuffixTags
TraefikWeight = Prefix + SuffixWeight
TraefikBackend = Prefix + SuffixBackend
TraefikBackendID = Prefix + SuffixBackendID
TraefikBackendCircuitBreaker = Prefix + SuffixBackendCircuitBreaker
TraefikBackendCircuitBreakerExpression = Prefix + SuffixBackendCircuitBreakerExpression
TraefikBackendHealthCheckPath = Prefix + SuffixBackendHealthCheckPath
TraefikBackendHealthCheckInterval = Prefix + SuffixBackendHealthCheckInterval
TraefikBackendLoadBalancerMethod = Prefix + SuffixBackendLoadBalancerMethod
TraefikBackendLoadBalancerSticky = Prefix + SuffixBackendLoadBalancerSticky
TraefikBackendLoadBalancerStickiness = Prefix + SuffixBackendLoadBalancerStickiness
TraefikBackendLoadBalancerStickinessCookieName = Prefix + SuffixBackendLoadBalancerStickinessCookieName
TraefikBackendMaxConnAmount = Prefix + SuffixBackendMaxConnAmount
TraefikBackendMaxConnExtractorFunc = Prefix + SuffixBackendMaxConnExtractorFunc
TraefikFrontendAuthBasic = Prefix + SuffixFrontendAuthBasic
TraefikFrontendEntryPoints = Prefix + SuffixFrontendEntryPoints
TraefikFrontendPassHostHeader = Prefix + SuffixFrontendPassHostHeader
TraefikFrontendPassTLSCert = Prefix + SuffixFrontendPassTLSCert
TraefikFrontendPriority = Prefix + SuffixFrontendPriority
TraefikFrontendRule = Prefix + SuffixFrontendRule
TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType
TraefikFrontendRedirect = Prefix + SuffixFrontendRedirect
TraefikFrontendValue = Prefix + SuffixFrontendValue
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange
TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders
TraefikFrontendResponseHeaders = Prefix + SuffixFrontendResponseHeaders
TraefikFrontendAllowedHosts = Prefix + SuffixFrontendHeadersAllowedHosts
TraefikFrontendHostsProxyHeaders = Prefix + SuffixFrontendHeadersHostsProxyHeaders
TraefikFrontendSSLRedirect = Prefix + SuffixFrontendHeadersSSLRedirect
TraefikFrontendSSLTemporaryRedirect = Prefix + SuffixFrontendHeadersSSLTemporaryRedirect
TraefikFrontendSSLHost = Prefix + SuffixFrontendHeadersSSLHost
TraefikFrontendSSLProxyHeaders = Prefix + SuffixFrontendHeadersSSLProxyHeaders
TraefikFrontendSTSSeconds = Prefix + SuffixFrontendHeadersSTSSeconds
TraefikFrontendSTSIncludeSubdomains = Prefix + SuffixFrontendHeadersSTSIncludeSubdomains
TraefikFrontendSTSPreload = Prefix + SuffixFrontendHeadersSTSPreload
TraefikFrontendForceSTSHeader = Prefix + SuffixFrontendHeadersForceSTSHeader
TraefikFrontendFrameDeny = Prefix + SuffixFrontendHeadersFrameDeny
TraefikFrontendCustomFrameOptionsValue = Prefix + SuffixFrontendHeadersCustomFrameOptionsValue
TraefikFrontendContentTypeNosniff = Prefix + SuffixFrontendHeadersContentTypeNosniff
TraefikFrontendBrowserXSSFilter = Prefix + SuffixFrontendHeadersBrowserXSSFilter
TraefikFrontendContentSecurityPolicy = Prefix + SuffixFrontendHeadersContentSecurityPolicy
TraefikFrontendPublicKey = Prefix + SuffixFrontendHeadersPublicKey
TraefikFrontendReferrerPolicy = Prefix + SuffixFrontendHeadersReferrerPolicy
TraefikFrontendIsDevelopment = Prefix + SuffixFrontendHeadersIsDevelopment
)

View File

@ -888,7 +888,7 @@ func (s *Server) getRoundTripper(entryPointName string, globalConfiguration conf
return s.defaultForwardingRoundTripper, nil return s.defaultForwardingRoundTripper, nil
} }
// LoadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic // loadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic
// provider configurations. // provider configurations.
func (s *Server) loadConfig(configurations types.Configurations, globalConfiguration configuration.GlobalConfiguration) (map[string]*serverEntryPoint, error) { func (s *Server) loadConfig(configurations types.Configurations, globalConfiguration configuration.GlobalConfiguration) (map[string]*serverEntryPoint, error) {
serverEntryPoints := s.buildEntryPoints(globalConfiguration) serverEntryPoints := s.buildEntryPoints(globalConfiguration)