Labels parser.
This commit is contained in:
parent
92f8e5cd3f
commit
11a0078966
16 changed files with 4462 additions and 4 deletions
|
@ -21,8 +21,8 @@ type Router struct {
|
|||
|
||||
// LoadBalancerService holds the LoadBalancerService configuration.
|
||||
type LoadBalancerService struct {
|
||||
Stickiness *Stickiness `json:"stickiness,omitempty" toml:",omitempty"`
|
||||
Servers []Server `json:"servers,omitempty" toml:",omitempty"`
|
||||
Stickiness *Stickiness `json:"stickiness,omitempty" toml:",omitempty" label:"allowEmpty"`
|
||||
Servers []Server `json:"servers,omitempty" toml:",omitempty" label-slice-as-struct:"server"`
|
||||
Method string `json:"method,omitempty" toml:",omitempty"`
|
||||
HealthCheck *HealthCheck `json:"healthCheck,omitempty" toml:",omitempty"`
|
||||
PassHostHeader bool `json:"passHostHeader" toml:",omitempty"`
|
||||
|
@ -151,7 +151,7 @@ type Configuration struct {
|
|||
Routers map[string]*Router `json:"routers,omitempty" toml:",omitempty"`
|
||||
Middlewares map[string]*Middleware `json:"middlewares,omitempty" toml:",omitempty"`
|
||||
Services map[string]*Service `json:"services,omitempty" toml:",omitempty"`
|
||||
TLS []*traefiktls.Configuration `json:"-"`
|
||||
TLS []*traefiktls.Configuration `json:"-" label:"-"`
|
||||
}
|
||||
|
||||
// Service holds a service configuration (can only be of one type at the same time).
|
||||
|
|
|
@ -24,7 +24,7 @@ type Middleware struct {
|
|||
MaxConn *MaxConn `json:"maxConn,omitempty"`
|
||||
Buffering *Buffering `json:"buffering,omitempty"`
|
||||
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"`
|
||||
Compress *Compress `json:"compress,omitempty"`
|
||||
Compress *Compress `json:"compress,omitempty" label:"allowEmpty"`
|
||||
PassTLSClientCert *PassTLSClientCert `json:"passTLSClientCert,omitempty"`
|
||||
Retry *Retry `json:"retry,omitempty"`
|
||||
}
|
||||
|
|
266
provider/label/internal/element_fill.go
Normal file
266
provider/label/internal/element_fill.go
Normal file
|
@ -0,0 +1,266 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Fill the fields of the element.
|
||||
// nodes -> element
|
||||
func Fill(element interface{}, node *Node) error {
|
||||
if element == nil || node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.Kind == 0 {
|
||||
return fmt.Errorf("missing node type: %s", node.Name)
|
||||
}
|
||||
|
||||
elem := reflect.ValueOf(element)
|
||||
if elem.Kind() == reflect.Struct {
|
||||
return fmt.Errorf("struct are not supported, use pointer instead")
|
||||
}
|
||||
|
||||
return fill(elem.Elem(), node)
|
||||
}
|
||||
|
||||
func fill(field reflect.Value, node *Node) error {
|
||||
// related to allow-empty tag
|
||||
if node.Disabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch field.Kind() {
|
||||
case reflect.String:
|
||||
field.SetString(node.Value)
|
||||
return nil
|
||||
case reflect.Bool:
|
||||
val, err := strconv.ParseBool(node.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetBool(val)
|
||||
return nil
|
||||
case reflect.Int8:
|
||||
return setInt(field, node.Value, 8)
|
||||
case reflect.Int16:
|
||||
return setInt(field, node.Value, 16)
|
||||
case reflect.Int32:
|
||||
return setInt(field, node.Value, 32)
|
||||
case reflect.Int64, reflect.Int:
|
||||
return setInt(field, node.Value, 64)
|
||||
case reflect.Uint8:
|
||||
return setUint(field, node.Value, 8)
|
||||
case reflect.Uint16:
|
||||
return setUint(field, node.Value, 16)
|
||||
case reflect.Uint32:
|
||||
return setUint(field, node.Value, 32)
|
||||
case reflect.Uint64, reflect.Uint:
|
||||
return setUint(field, node.Value, 64)
|
||||
case reflect.Float32:
|
||||
return setFloat(field, node.Value, 32)
|
||||
case reflect.Float64:
|
||||
return setFloat(field, node.Value, 64)
|
||||
case reflect.Struct:
|
||||
return setStruct(field, node)
|
||||
case reflect.Ptr:
|
||||
return setPtr(field, node)
|
||||
case reflect.Map:
|
||||
return setMap(field, node)
|
||||
case reflect.Slice:
|
||||
return setSlice(field, node)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func setPtr(field reflect.Value, node *Node) error {
|
||||
if field.IsNil() {
|
||||
field.Set(reflect.New(field.Type().Elem()))
|
||||
}
|
||||
|
||||
return fill(field.Elem(), node)
|
||||
}
|
||||
|
||||
func setStruct(field reflect.Value, node *Node) error {
|
||||
for _, child := range node.Children {
|
||||
fd := field.FieldByName(child.FieldName)
|
||||
|
||||
zeroValue := reflect.Value{}
|
||||
if fd == zeroValue {
|
||||
return fmt.Errorf("field not found, node: %s (%s)", child.Name, child.FieldName)
|
||||
}
|
||||
|
||||
err := fill(fd, child)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSlice(field reflect.Value, node *Node) error {
|
||||
if field.Type().Elem().Kind() == reflect.Struct {
|
||||
return setSliceAsStruct(field, node)
|
||||
}
|
||||
|
||||
if len(node.Value) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
values := strings.Split(node.Value, ",")
|
||||
|
||||
slice := reflect.MakeSlice(field.Type(), len(values), len(values))
|
||||
field.Set(slice)
|
||||
|
||||
for i := 0; i < len(values); i++ {
|
||||
value := strings.TrimSpace(values[i])
|
||||
|
||||
switch field.Type().Elem().Kind() {
|
||||
case reflect.String:
|
||||
field.Index(i).Set(reflect.ValueOf(value))
|
||||
case reflect.Int:
|
||||
val, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Index(i).SetInt(val)
|
||||
case reflect.Int8:
|
||||
err := setInt(field.Index(i), value, 8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Int16:
|
||||
err := setInt(field.Index(i), value, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Int32:
|
||||
err := setInt(field.Index(i), value, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Int64:
|
||||
err := setInt(field.Index(i), value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Uint:
|
||||
val, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Index(i).SetUint(val)
|
||||
case reflect.Uint8:
|
||||
err := setUint(field.Index(i), value, 8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Uint16:
|
||||
err := setUint(field.Index(i), value, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Uint32:
|
||||
err := setUint(field.Index(i), value, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Uint64:
|
||||
err := setUint(field.Index(i), value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Float32:
|
||||
err := setFloat(field.Index(i), value, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Float64:
|
||||
err := setFloat(field.Index(i), value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Bool:
|
||||
val, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Index(i).SetBool(val)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type: %s", field.Type().Elem())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSliceAsStruct(field reflect.Value, node *Node) error {
|
||||
if len(node.Children) == 0 {
|
||||
return fmt.Errorf("invalid slice: node %s", node.Name)
|
||||
}
|
||||
|
||||
elem := reflect.New(field.Type().Elem()).Elem()
|
||||
err := setStruct(elem, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field.Set(reflect.MakeSlice(field.Type(), 1, 1))
|
||||
field.Index(0).Set(elem)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setMap(field reflect.Value, node *Node) error {
|
||||
if field.IsNil() {
|
||||
field.Set(reflect.MakeMap(field.Type()))
|
||||
}
|
||||
|
||||
for _, child := range node.Children {
|
||||
ptrValue := reflect.New(reflect.PtrTo(field.Type().Elem()))
|
||||
|
||||
err := fill(ptrValue, child)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value := ptrValue.Elem().Elem()
|
||||
|
||||
key := reflect.ValueOf(child.Name)
|
||||
field.SetMapIndex(key, value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setInt(field reflect.Value, value string, bitSize int) error {
|
||||
val, err := strconv.ParseInt(value, 10, bitSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field.Set(reflect.ValueOf(val).Convert(field.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func setUint(field reflect.Value, value string, bitSize int) error {
|
||||
val, err := strconv.ParseUint(value, 10, bitSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field.Set(reflect.ValueOf(val).Convert(field.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func setFloat(field reflect.Value, value string, bitSize int) error {
|
||||
val, err := strconv.ParseFloat(value, bitSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field.Set(reflect.ValueOf(val).Convert(field.Type()))
|
||||
return nil
|
||||
}
|
1088
provider/label/internal/element_fill_test.go
Normal file
1088
provider/label/internal/element_fill_test.go
Normal file
File diff suppressed because it is too large
Load diff
163
provider/label/internal/element_nodes.go
Normal file
163
provider/label/internal/element_nodes.go
Normal file
|
@ -0,0 +1,163 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EncodeToNode Converts an element to a node.
|
||||
// element -> nodes
|
||||
func EncodeToNode(element interface{}) (*Node, error) {
|
||||
rValue := reflect.ValueOf(element)
|
||||
node := &Node{Name: "traefik"}
|
||||
|
||||
err := setNodeValue(node, rValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func setNodeValue(node *Node, rValue reflect.Value) error {
|
||||
switch rValue.Kind() {
|
||||
case reflect.String:
|
||||
node.Value = rValue.String()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
node.Value = strconv.FormatInt(rValue.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
node.Value = strconv.FormatUint(rValue.Uint(), 10)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
node.Value = strconv.FormatFloat(rValue.Float(), 'f', 6, 64)
|
||||
case reflect.Bool:
|
||||
node.Value = strconv.FormatBool(rValue.Bool())
|
||||
case reflect.Struct:
|
||||
return setStructValue(node, rValue)
|
||||
case reflect.Ptr:
|
||||
return setNodeValue(node, rValue.Elem())
|
||||
case reflect.Map:
|
||||
return setMapValue(node, rValue)
|
||||
case reflect.Slice:
|
||||
return setSliceValue(node, rValue)
|
||||
default:
|
||||
// noop
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setStructValue(node *Node, rValue reflect.Value) error {
|
||||
rType := rValue.Type()
|
||||
|
||||
for i := 0; i < rValue.NumField(); i++ {
|
||||
field := rType.Field(i)
|
||||
fieldValue := rValue.Field(i)
|
||||
|
||||
if !isExported(field) {
|
||||
continue
|
||||
}
|
||||
|
||||
if field.Tag.Get(TagLabel) == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := isSupportedType(field); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isSkippedField(field, fieldValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
nodeName := field.Name
|
||||
if field.Type.Kind() == reflect.Slice && len(field.Tag.Get(TagLabelSliceAsStruct)) != 0 {
|
||||
nodeName = field.Tag.Get(TagLabelSliceAsStruct)
|
||||
}
|
||||
|
||||
child := &Node{Name: nodeName, FieldName: field.Name}
|
||||
|
||||
if err := setNodeValue(child, fieldValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if field.Type.Kind() == reflect.Ptr && len(child.Children) == 0 {
|
||||
if field.Tag.Get(TagLabel) != "allowEmpty" {
|
||||
continue
|
||||
}
|
||||
|
||||
child.Value = "true"
|
||||
}
|
||||
|
||||
node.Children = append(node.Children, child)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setMapValue(node *Node, rValue reflect.Value) error {
|
||||
for _, key := range rValue.MapKeys() {
|
||||
child := &Node{Name: key.String(), FieldName: key.String()}
|
||||
node.Children = append(node.Children, child)
|
||||
|
||||
if err := setNodeValue(child, rValue.MapIndex(key)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSliceValue(node *Node, rValue reflect.Value) error {
|
||||
// label-slice-as-struct
|
||||
if rValue.Type().Elem().Kind() == reflect.Struct && !strings.EqualFold(node.Name, node.FieldName) {
|
||||
if rValue.Len() > 1 {
|
||||
return fmt.Errorf("node %s has too many slice entries: %d", node.Name, rValue.Len())
|
||||
}
|
||||
|
||||
if err := setNodeValue(node, rValue.Index(0)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var values []string
|
||||
|
||||
for i := 0; i < rValue.Len(); i++ {
|
||||
eValue := rValue.Index(i)
|
||||
|
||||
switch eValue.Kind() {
|
||||
case reflect.String:
|
||||
values = append(values, eValue.String())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
values = append(values, strconv.FormatInt(eValue.Int(), 10))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
values = append(values, strconv.FormatUint(eValue.Uint(), 10))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
values = append(values, strconv.FormatFloat(eValue.Float(), 'f', 6, 64))
|
||||
case reflect.Bool:
|
||||
values = append(values, strconv.FormatBool(eValue.Bool()))
|
||||
default:
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
node.Value = strings.Join(values, ", ")
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSkippedField(field reflect.StructField, fieldValue reflect.Value) bool {
|
||||
if field.Type.Kind() == reflect.String && fieldValue.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct && fieldValue.IsNil() {
|
||||
return true
|
||||
}
|
||||
|
||||
if (field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Map) &&
|
||||
(fieldValue.IsNil() || fieldValue.Len() == 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
593
provider/label/internal/element_nodes_test.go
Normal file
593
provider/label/internal/element_nodes_test.go
Normal file
|
@ -0,0 +1,593 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEncodeToNode(t *testing.T) {
|
||||
type expected struct {
|
||||
node *Node
|
||||
error bool
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
element interface{}
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "string",
|
||||
element: struct {
|
||||
Foo string
|
||||
}{Foo: "bar"},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "bar"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 string fields",
|
||||
element: struct {
|
||||
Foo string
|
||||
Fii string
|
||||
}{Foo: "bar", Fii: "hii"},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "bar"},
|
||||
{Name: "Fii", FieldName: "Fii", Value: "hii"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "int",
|
||||
element: struct {
|
||||
Foo int
|
||||
}{Foo: 1},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "1"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "int8",
|
||||
element: struct {
|
||||
Foo int8
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "int16",
|
||||
element: struct {
|
||||
Foo int16
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "int32",
|
||||
element: struct {
|
||||
Foo int32
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "int64",
|
||||
element: struct {
|
||||
Foo int64
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "uint",
|
||||
element: struct {
|
||||
Foo uint
|
||||
}{Foo: 1},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "1"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "uint8",
|
||||
element: struct {
|
||||
Foo uint8
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "uint16",
|
||||
element: struct {
|
||||
Foo uint16
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "uint32",
|
||||
element: struct {
|
||||
Foo uint32
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "uint64",
|
||||
element: struct {
|
||||
Foo uint64
|
||||
}{Foo: 2},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "2"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "float32",
|
||||
element: struct {
|
||||
Foo float32
|
||||
}{Foo: 1.12},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "1.120000"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "float64",
|
||||
element: struct {
|
||||
Foo float64
|
||||
}{Foo: 1.12},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "1.120000"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "bool",
|
||||
element: struct {
|
||||
Foo bool
|
||||
}{Foo: true},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "true"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct",
|
||||
element: struct {
|
||||
Foo struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}{
|
||||
Fii: "hii",
|
||||
Fuu: "huu",
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Fii", FieldName: "Fii", Value: "hii"},
|
||||
{Name: "Fuu", FieldName: "Fuu", Value: "huu"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct unexported field",
|
||||
element: struct {
|
||||
Foo struct {
|
||||
Fii string
|
||||
fuu string
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
Fii string
|
||||
fuu string
|
||||
}{
|
||||
Fii: "hii",
|
||||
fuu: "huu",
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Fii", FieldName: "Fii", Value: "hii"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}{
|
||||
Fii: "hii",
|
||||
Fuu: "huu",
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Fii", FieldName: "Fii", Value: "hii"},
|
||||
{Name: "Fuu", FieldName: "Fuu", Value: "huu"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "string pointer",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii *string
|
||||
Fuu string
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii *string
|
||||
Fuu string
|
||||
}{
|
||||
Fii: func(v string) *string { return &v }("hii"),
|
||||
Fuu: "huu",
|
||||
},
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "struct nil pointer",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii *string
|
||||
Fuu string
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii *string
|
||||
Fuu string
|
||||
}{
|
||||
Fuu: "huu",
|
||||
},
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "struct nil struct pointer",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii *string
|
||||
Fuu string
|
||||
}
|
||||
}{
|
||||
Foo: nil,
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer, not allowEmpty",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}{},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer, allowEmpty",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
} `label:"allowEmpty"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}{},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "true"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map",
|
||||
element: struct {
|
||||
Foo struct {
|
||||
Bar map[string]string
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
Bar map[string]string
|
||||
}{
|
||||
Bar: map[string]string{
|
||||
"name1": "huu",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Children: []*Node{
|
||||
{Name: "name1", FieldName: "name1", Value: "huu"},
|
||||
}},
|
||||
}},
|
||||
}}},
|
||||
},
|
||||
{
|
||||
desc: "empty map",
|
||||
element: struct {
|
||||
Bar map[string]string
|
||||
}{
|
||||
Bar: map[string]string{},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
{
|
||||
desc: "map nil",
|
||||
element: struct {
|
||||
Bar map[string]string
|
||||
}{
|
||||
Bar: nil,
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
{
|
||||
desc: "map with non string key",
|
||||
element: struct {
|
||||
Foo struct {
|
||||
Bar map[int]string
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
Bar map[int]string
|
||||
}{
|
||||
Bar: map[int]string{
|
||||
1: "huu",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "slice of string",
|
||||
element: struct{ Bar []string }{Bar: []string{"huu", "hii"}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "huu, hii"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of int",
|
||||
element: struct{ Bar []int }{Bar: []int{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of int8",
|
||||
element: struct{ Bar []int8 }{Bar: []int8{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of int16",
|
||||
element: struct{ Bar []int16 }{Bar: []int16{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of int32",
|
||||
element: struct{ Bar []int32 }{Bar: []int32{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of int64",
|
||||
element: struct{ Bar []int64 }{Bar: []int64{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of uint",
|
||||
element: struct{ Bar []uint }{Bar: []uint{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of uint8",
|
||||
element: struct{ Bar []uint8 }{Bar: []uint8{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of uint16",
|
||||
element: struct{ Bar []uint16 }{Bar: []uint16{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of uint32",
|
||||
element: struct{ Bar []uint32 }{Bar: []uint32{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of uint64",
|
||||
element: struct{ Bar []uint64 }{Bar: []uint64{4, 2, 3}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4, 2, 3"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of float32",
|
||||
element: struct{ Bar []float32 }{Bar: []float32{4.1, 2, 3.2}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4.100000, 2.000000, 3.200000"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of float64",
|
||||
element: struct{ Bar []float64 }{Bar: []float64{4.1, 2, 3.2}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "4.100000, 2.000000, 3.200000"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of bool",
|
||||
element: struct{ Bar []bool }{Bar: []bool{true, false, true}},
|
||||
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "true, false, true"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice label-slice-as-struct",
|
||||
element: &struct {
|
||||
Foo []struct {
|
||||
Bar string
|
||||
Bir string
|
||||
} `label-slice-as-struct:"Fii"`
|
||||
}{
|
||||
Foo: []struct {
|
||||
Bar string
|
||||
Bir string
|
||||
}{
|
||||
{
|
||||
Bar: "haa",
|
||||
Bir: "hii",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{{
|
||||
Name: "Fii",
|
||||
FieldName: "Foo",
|
||||
Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "haa"},
|
||||
{Name: "Bir", FieldName: "Bir", Value: "hii"},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice label-slice-as-struct several slice entries",
|
||||
element: &struct {
|
||||
Foo []struct {
|
||||
Bar string
|
||||
Bir string
|
||||
} `label-slice-as-struct:"Fii"`
|
||||
}{
|
||||
Foo: []struct {
|
||||
Bar string
|
||||
Bir string
|
||||
}{
|
||||
{
|
||||
Bar: "haa",
|
||||
Bir: "hii",
|
||||
},
|
||||
{
|
||||
Bar: "haa",
|
||||
Bir: "hii",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "empty slice",
|
||||
element: struct {
|
||||
Bar []string
|
||||
}{
|
||||
Bar: []string{},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
{
|
||||
desc: "nil slice",
|
||||
element: struct {
|
||||
Bar []string
|
||||
}{
|
||||
Bar: nil,
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
{
|
||||
desc: "ignore slice",
|
||||
element: struct {
|
||||
Bar []string `label:"-"`
|
||||
}{
|
||||
Bar: []string{"huu", "hii"},
|
||||
},
|
||||
expected: expected{node: &Node{Name: "traefik"}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, err := EncodeToNode(test.element)
|
||||
|
||||
if test.expected.error {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected.node, node)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
68
provider/label/internal/labels_decode.go
Normal file
68
provider/label/internal/labels_decode.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DecodeToNode Converts the labels to a node.
|
||||
// labels -> nodes
|
||||
func DecodeToNode(labels map[string]string) (*Node, error) {
|
||||
var sortedKeys []string
|
||||
for key := range labels {
|
||||
sortedKeys = append(sortedKeys, key)
|
||||
}
|
||||
sort.Strings(sortedKeys)
|
||||
|
||||
labelRoot := "traefik"
|
||||
|
||||
var node *Node
|
||||
for i, key := range sortedKeys {
|
||||
split := strings.Split(key, ".")
|
||||
|
||||
if split[0] != labelRoot {
|
||||
// TODO (@ldez): error or continue
|
||||
return nil, fmt.Errorf("invalid label root %s", split[0])
|
||||
}
|
||||
|
||||
labelRoot = split[0]
|
||||
|
||||
if i == 0 {
|
||||
node = &Node{}
|
||||
}
|
||||
decodeToNode(node, split, labels[key])
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func decodeToNode(root *Node, path []string, value string) {
|
||||
if len(root.Name) == 0 {
|
||||
root.Name = path[0]
|
||||
}
|
||||
|
||||
// it's a leaf or not -> children
|
||||
if len(path) > 1 {
|
||||
if n := containsNode(root.Children, path[1]); n != nil {
|
||||
// the child already exists
|
||||
decodeToNode(n, path[1:], value)
|
||||
} else {
|
||||
// new child
|
||||
child := &Node{Name: path[1]}
|
||||
decodeToNode(child, path[1:], value)
|
||||
root.Children = append(root.Children, child)
|
||||
}
|
||||
} else {
|
||||
root.Value = value
|
||||
}
|
||||
}
|
||||
|
||||
func containsNode(nodes []*Node, name string) *Node {
|
||||
for _, n := range nodes {
|
||||
if name == n.Name {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
190
provider/label/internal/labels_decode_test.go
Normal file
190
provider/label/internal/labels_decode_test.go
Normal file
|
@ -0,0 +1,190 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecodeToNode(t *testing.T) {
|
||||
type expected struct {
|
||||
error bool
|
||||
node *Node
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
in map[string]string
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "level 0",
|
||||
in: map[string]string{"traefik": "bar"},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Value: "bar",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "level 1",
|
||||
in: map[string]string{
|
||||
"traefik.foo": "bar",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "level 1 empty value",
|
||||
in: map[string]string{
|
||||
"traefik.foo": "",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Value: ""},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "level 2",
|
||||
in: map[string]string{
|
||||
"traefik.foo.bar": "bar",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{{
|
||||
Name: "foo",
|
||||
Children: []*Node{
|
||||
{Name: "bar", Value: "bar"},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, level 0",
|
||||
in: map[string]string{
|
||||
"traefik": "bar",
|
||||
"traefic": "bur",
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "several entries, level 1",
|
||||
in: map[string]string{
|
||||
"traefik.foo": "bar",
|
||||
"traefik.fii": "bur",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "fii", Value: "bur"},
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, level 2",
|
||||
in: map[string]string{
|
||||
"traefik.foo.aaa": "bar",
|
||||
"traefik.foo.bbb": "bur",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, level 2, 3 children",
|
||||
in: map[string]string{
|
||||
"traefik.foo.aaa": "bar",
|
||||
"traefik.foo.bbb": "bur",
|
||||
"traefik.foo.ccc": "bir",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
{Name: "ccc", Value: "bir"},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, level 3",
|
||||
in: map[string]string{
|
||||
"traefik.foo.bar.aaa": "bar",
|
||||
"traefik.foo.bar.bbb": "bur",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, level 3, 2 children level 1",
|
||||
in: map[string]string{
|
||||
"traefik.foo.bar.aaa": "bar",
|
||||
"traefik.foo.bar.bbb": "bur",
|
||||
"traefik.bar.foo.bbb": "bir",
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bbb", Value: "bir"},
|
||||
}},
|
||||
}},
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
out, err := DecodeToNode(test.in)
|
||||
|
||||
if test.expected.error {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
|
||||
if !assert.Equal(t, test.expected.node, out) {
|
||||
bytes, err := json.MarshalIndent(out, "", " ")
|
||||
require.NoError(t, err)
|
||||
fmt.Println(string(bytes))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
24
provider/label/internal/labels_encode.go
Normal file
24
provider/label/internal/labels_encode.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package internal
|
||||
|
||||
// EncodeNode Converts a node to labels.
|
||||
// nodes -> labels
|
||||
func EncodeNode(node *Node) map[string]string {
|
||||
labels := make(map[string]string)
|
||||
encodeNode(labels, node.Name, node)
|
||||
return labels
|
||||
}
|
||||
|
||||
func encodeNode(labels map[string]string, root string, node *Node) {
|
||||
for _, child := range node.Children {
|
||||
if child.Disabled {
|
||||
continue
|
||||
}
|
||||
|
||||
childName := root + "." + child.Name
|
||||
if len(child.Children) > 0 {
|
||||
encodeNode(labels, childName, child)
|
||||
} else if len(child.Name) > 0 {
|
||||
labels[childName] = child.Value
|
||||
}
|
||||
}
|
||||
}
|
156
provider/label/internal/labels_encode_test.go
Normal file
156
provider/label/internal/labels_encode_test.go
Normal file
|
@ -0,0 +1,156 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEncodeNode(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
node *Node
|
||||
expected map[string]string
|
||||
}{
|
||||
{
|
||||
desc: "1 label",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.aaa": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 labels",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.aaa": "bar",
|
||||
"traefik.bbb": "bur",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 labels, 1 disabled",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur", Disabled: true},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.aaa": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 levels",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.aaa": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "3 levels",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.bar.aaa": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 levels, same root",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.bar.aaa": "bar",
|
||||
"traefik.foo.bar.bbb": "bur",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "several levels, different root",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "ccc", Value: "bir"},
|
||||
}},
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.bar.aaa": "bar",
|
||||
"traefik.bar.ccc": "bir",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple labels, multiple levels",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "ccc", Value: "bir"},
|
||||
}},
|
||||
{Name: "foo", Children: []*Node{
|
||||
{Name: "bar", Children: []*Node{
|
||||
{Name: "aaa", Value: "bar"},
|
||||
{Name: "bbb", Value: "bur"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.bar.aaa": "bar",
|
||||
"traefik.foo.bar.bbb": "bur",
|
||||
"traefik.bar.ccc": "bir",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
labels := EncodeNode(test.node)
|
||||
|
||||
assert.Equal(t, test.expected, labels)
|
||||
})
|
||||
}
|
||||
}
|
13
provider/label/internal/node.go
Normal file
13
provider/label/internal/node.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package internal
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Node a label node.
|
||||
type Node struct {
|
||||
Name string `json:"name"`
|
||||
FieldName string `json:"fieldName"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
Kind reflect.Kind `json:"kind,omitempty"`
|
||||
Children []*Node `json:"children,omitempty"`
|
||||
}
|
164
provider/label/internal/nodes_metadata.go
Normal file
164
provider/label/internal/nodes_metadata.go
Normal file
|
@ -0,0 +1,164 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AddMetadata Adds metadata to a node.
|
||||
// nodes + element -> nodes
|
||||
func AddMetadata(structure interface{}, node *Node) error {
|
||||
if len(node.Children) == 0 {
|
||||
return fmt.Errorf("invalid node %s: no child", node.Name)
|
||||
}
|
||||
|
||||
if structure == nil {
|
||||
return errors.New("nil structure")
|
||||
}
|
||||
|
||||
rootType := reflect.TypeOf(structure)
|
||||
node.Kind = rootType.Kind()
|
||||
|
||||
return browseChildren(rootType, node)
|
||||
}
|
||||
|
||||
func addMetadata(rootType reflect.Type, node *Node) error {
|
||||
rType := rootType
|
||||
if rootType.Kind() == reflect.Ptr {
|
||||
rType = rootType.Elem()
|
||||
}
|
||||
|
||||
field, err := findTypedField(rType, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = isSupportedType(field); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fType := field.Type
|
||||
node.Kind = fType.Kind()
|
||||
|
||||
if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Ptr && fType.Elem().Kind() == reflect.Struct ||
|
||||
fType.Kind() == reflect.Map {
|
||||
if len(node.Children) == 0 && field.Tag.Get(TagLabel) != "allowEmpty" {
|
||||
return fmt.Errorf("node %s (type %s) must have children", node.Name, fType)
|
||||
}
|
||||
|
||||
node.Disabled = len(node.Value) > 0 && node.Value != "true" && field.Tag.Get(TagLabel) == "allowEmpty"
|
||||
}
|
||||
|
||||
if len(node.Children) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Ptr && fType.Elem().Kind() == reflect.Struct {
|
||||
return browseChildren(fType, node)
|
||||
}
|
||||
|
||||
if fType.Kind() == reflect.Map {
|
||||
for _, child := range node.Children {
|
||||
// elem is a map entry value type
|
||||
elem := fType.Elem()
|
||||
child.Kind = elem.Kind()
|
||||
|
||||
if elem.Kind() == reflect.Map || elem.Kind() == reflect.Struct ||
|
||||
(elem.Kind() == reflect.Ptr && elem.Elem().Kind() == reflect.Struct) {
|
||||
if err = browseChildren(elem, child); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// only for struct/Ptr with label-slice-as-struct tag
|
||||
if fType.Kind() == reflect.Slice {
|
||||
return browseChildren(fType.Elem(), node)
|
||||
}
|
||||
|
||||
return fmt.Errorf("invalid node %s: %v", node.Name, fType.Kind())
|
||||
}
|
||||
|
||||
func findTypedField(rType reflect.Type, node *Node) (reflect.StructField, error) {
|
||||
for i := 0; i < rType.NumField(); i++ {
|
||||
cField := rType.Field(i)
|
||||
|
||||
fieldName := cField.Tag.Get(TagLabelSliceAsStruct)
|
||||
if len(fieldName) == 0 {
|
||||
fieldName = cField.Name
|
||||
}
|
||||
|
||||
if isExported(cField) && strings.EqualFold(fieldName, node.Name) {
|
||||
node.FieldName = cField.Name
|
||||
return cField, nil
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.StructField{}, fmt.Errorf("field not found, node: %s", node.Name)
|
||||
}
|
||||
|
||||
func browseChildren(fType reflect.Type, node *Node) error {
|
||||
for _, child := range node.Children {
|
||||
if err := addMetadata(fType, child); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isExported return true is a struct field is exported, else false
|
||||
// https://golang.org/pkg/reflect/#StructField
|
||||
func isExported(f reflect.StructField) bool {
|
||||
if f.PkgPath != "" && !f.Anonymous {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isSupportedType(field reflect.StructField) error {
|
||||
fType := field.Type
|
||||
|
||||
if fType.Kind() == reflect.Slice {
|
||||
switch fType.Elem().Kind() {
|
||||
case reflect.String,
|
||||
reflect.Bool,
|
||||
reflect.Int,
|
||||
reflect.Int8,
|
||||
reflect.Int16,
|
||||
reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint,
|
||||
reflect.Uint8,
|
||||
reflect.Uint16,
|
||||
reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Uintptr,
|
||||
reflect.Float32,
|
||||
reflect.Float64:
|
||||
return nil
|
||||
default:
|
||||
if len(field.Tag.Get(TagLabelSliceAsStruct)) > 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unsupported slice type: %v", fType)
|
||||
}
|
||||
}
|
||||
|
||||
if fType.Kind() == reflect.Ptr && fType.Elem().Kind() != reflect.Struct {
|
||||
return fmt.Errorf("unsupported pointer type: %v", fType.Elem())
|
||||
}
|
||||
|
||||
if fType.Kind() == reflect.Map && fType.Key().Kind() != reflect.String {
|
||||
return fmt.Errorf("unsupported map key type: %v", fType.Key())
|
||||
}
|
||||
|
||||
if fType.Kind() == reflect.Func {
|
||||
return fmt.Errorf("unsupported type: %v", fType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
769
provider/label/internal/nodes_metadata_test.go
Normal file
769
provider/label/internal/nodes_metadata_test.go
Normal file
|
@ -0,0 +1,769 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAddMetadata(t *testing.T) {
|
||||
type expected struct {
|
||||
node *Node
|
||||
error bool
|
||||
}
|
||||
|
||||
type interf interface{}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
tree *Node
|
||||
structure interface{}
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "Empty Node",
|
||||
tree: &Node{},
|
||||
structure: nil,
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "Nil structure",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
structure: nil,
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "level 0",
|
||||
tree: &Node{Name: "traefik", Value: "bar"},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "level 1",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
structure: struct{ Foo string }{},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "bar", Kind: reflect.String},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 1, pointer",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
structure: &struct{ Foo string }{},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Ptr,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "bar", Kind: reflect.String},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 1, slice",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "bar,bur"},
|
||||
},
|
||||
},
|
||||
structure: struct{ Foo []string }{},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "bar,bur", Kind: reflect.Slice},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 1, interface",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "", Children: []*Node{
|
||||
{Name: "Fii", Value: "hii"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct{ Foo interf }{},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "level 1, slice struct",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "1,2"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo []struct{ Foo string }
|
||||
}{},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "level 1, map string",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Children: []*Node{
|
||||
{Name: "name1", Value: "bar"},
|
||||
{Name: "name2", Value: "bur"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct{ Foo map[string]string }{},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Kind: reflect.Map, Children: []*Node{
|
||||
{Name: "name1", Value: "bar", Kind: reflect.String},
|
||||
{Name: "name2", Value: "bur", Kind: reflect.String},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 1, map struct",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Children: []*Node{
|
||||
{Name: "name1", Children: []*Node{
|
||||
{Name: "Fii", Value: "bar"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo map[string]struct{ Fii string }
|
||||
}{},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Kind: reflect.Map, Children: []*Node{
|
||||
{Name: "name1", Kind: reflect.Struct, Children: []*Node{
|
||||
{Name: "Fii", FieldName: "Fii", Value: "bar", Kind: reflect.String},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 1, map int as key",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Children: []*Node{
|
||||
{Name: "name1", Children: []*Node{
|
||||
{Name: "Fii", Value: "bar"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo map[int]struct{ Fii string }
|
||||
}{},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "level 1, int pointer",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "0"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo *int
|
||||
}{},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "level 1, 2 children with different types",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "bar"},
|
||||
{Name: "Fii", Value: "1"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo string
|
||||
Fii int
|
||||
}{},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "bar", Kind: reflect.String},
|
||||
{Name: "Fii", FieldName: "Fii", Value: "1", Kind: reflect.Int},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 1, use exported instead of unexported",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
foo int
|
||||
Foo string
|
||||
}{},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "foo", Value: "bar", FieldName: "Foo", Kind: reflect.String},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 1, unexported",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "foo", Value: "bar"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
foo string
|
||||
}{},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "level 1, 3 children with different types",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "bar"},
|
||||
{Name: "Fii", Value: "1"},
|
||||
{Name: "Fuu", Value: "true"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo string
|
||||
Fii int
|
||||
Fuu bool
|
||||
}{},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "bar", Kind: reflect.String},
|
||||
{Name: "Fii", FieldName: "Fii", Value: "1", Kind: reflect.Int},
|
||||
{Name: "Fuu", FieldName: "Fuu", Value: "true", Kind: reflect.Bool},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 2",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Children: []*Node{
|
||||
{Name: "Bar", Value: "bir"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo struct {
|
||||
Bar string
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
Bar string
|
||||
}{},
|
||||
},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Kind: reflect.Struct, Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "bir", Kind: reflect.String},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 2, struct without children",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo struct {
|
||||
Bar string
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
Bar string
|
||||
}{},
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "level 2, slice-as-struct",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Fii", Children: []*Node{
|
||||
{Name: "bar", Value: "haa"},
|
||||
{Name: "bir", Value: "hii"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo []struct {
|
||||
Bar string
|
||||
Bir string
|
||||
} `label-slice-as-struct:"Fii"`
|
||||
}{
|
||||
Foo: []struct {
|
||||
Bar string
|
||||
Bir string
|
||||
}{},
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{
|
||||
Name: "Fii",
|
||||
FieldName: "Foo",
|
||||
Kind: reflect.Slice,
|
||||
Children: []*Node{
|
||||
{Name: "bar", FieldName: "Bar", Kind: reflect.String, Value: "haa"},
|
||||
{Name: "bir", FieldName: "Bir", Kind: reflect.String, Value: "hii"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "level 2, slice-as-struct without children",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Fii"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo []struct {
|
||||
Bar string
|
||||
Bir string
|
||||
} `label-slice-as-struct:"Fii"`
|
||||
}{
|
||||
Foo: []struct {
|
||||
Bar string
|
||||
Bir string
|
||||
}{},
|
||||
},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{
|
||||
Name: "Fii",
|
||||
FieldName: "Foo",
|
||||
Kind: reflect.Slice,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "level 2, struct with allowEmpty, value true",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "true"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo struct {
|
||||
Bar string
|
||||
} `label:"allowEmpty"`
|
||||
}{
|
||||
Foo: struct {
|
||||
Bar string
|
||||
}{},
|
||||
},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "true", Kind: reflect.Struct},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 2, struct with allowEmpty, value false",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "false"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo struct {
|
||||
Bar string
|
||||
} `label:"allowEmpty"`
|
||||
}{
|
||||
Foo: struct {
|
||||
Bar string
|
||||
}{},
|
||||
},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "false", Disabled: true, Kind: reflect.Struct},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 2, struct with allowEmpty with children, value false",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "false", Children: []*Node{
|
||||
{Name: "Bar", Value: "hii"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo struct {
|
||||
Bar string
|
||||
} `label:"allowEmpty"`
|
||||
}{
|
||||
Foo: struct {
|
||||
Bar string
|
||||
}{},
|
||||
},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{
|
||||
Name: "Foo",
|
||||
FieldName: "Foo",
|
||||
Value: "false",
|
||||
Disabled: true,
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "hii", Kind: reflect.String},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 2, struct pointer without children",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo *struct {
|
||||
Bar string
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Bar string
|
||||
}{},
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "level 2, map without children",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo map[string]string
|
||||
}{
|
||||
Foo: map[string]string{},
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "level 2, pointer",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Children: []*Node{
|
||||
{Name: "Bar", Value: "bir"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo *struct {
|
||||
Bar string
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Bar string
|
||||
}{},
|
||||
},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Kind: reflect.Ptr, Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "bir", Kind: reflect.String},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 2, 2 children",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Children: []*Node{
|
||||
{Name: "Bar", Value: "bir"},
|
||||
{Name: "Bur", Value: "fuu"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo struct {
|
||||
Bar string
|
||||
Bur string
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
Bar string
|
||||
Bur string
|
||||
}{},
|
||||
},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{
|
||||
Name: "Foo",
|
||||
FieldName: "Foo",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "bir", Kind: reflect.String},
|
||||
{Name: "Bur", FieldName: "Bur", Value: "fuu", Kind: reflect.String},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 3",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Children: []*Node{
|
||||
{Name: "Bar", Children: []*Node{
|
||||
{Name: "Bur", Value: "fuu"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo struct {
|
||||
Bar struct {
|
||||
Bur string
|
||||
}
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
Bar struct {
|
||||
Bur string
|
||||
}
|
||||
}{},
|
||||
},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{
|
||||
Name: "Foo",
|
||||
FieldName: "Foo",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{
|
||||
Name: "Bar",
|
||||
FieldName: "Bar",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Bur", FieldName: "Bur", Value: "fuu", Kind: reflect.String},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 3, 2 children level 1, 2 children level 2, 2 children level 3",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Children: []*Node{
|
||||
{Name: "Bar", Children: []*Node{
|
||||
{Name: "Fii", Value: "fii"},
|
||||
{Name: "Fee", Value: "1"},
|
||||
}},
|
||||
{Name: "Bur", Children: []*Node{
|
||||
{Name: "Faa", Value: "faa"},
|
||||
}},
|
||||
}},
|
||||
{Name: "Fii", Children: []*Node{
|
||||
{Name: "FiiBar", Value: "fiiBar"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo struct {
|
||||
Bar struct {
|
||||
Fii string
|
||||
Fee int
|
||||
}
|
||||
Bur struct {
|
||||
Faa string
|
||||
}
|
||||
}
|
||||
Fii struct {
|
||||
FiiBar string
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
Bar struct {
|
||||
Fii string
|
||||
Fee int
|
||||
}
|
||||
Bur struct {
|
||||
Faa string
|
||||
}
|
||||
}{},
|
||||
Fii: struct {
|
||||
FiiBar string
|
||||
}{},
|
||||
},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{
|
||||
Name: "Foo",
|
||||
FieldName: "Foo",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{
|
||||
Name: "Bar",
|
||||
FieldName: "Bar",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Fii", FieldName: "Fii", Kind: reflect.String, Value: "fii"},
|
||||
{Name: "Fee", FieldName: "Fee", Kind: reflect.Int, Value: "1"},
|
||||
}},
|
||||
{
|
||||
Name: "Bur",
|
||||
FieldName: "Bur",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Faa", FieldName: "Faa", Kind: reflect.String, Value: "faa"},
|
||||
}},
|
||||
}},
|
||||
{
|
||||
Name: "Fii",
|
||||
FieldName: "Fii",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "FiiBar", FieldName: "FiiBar", Kind: reflect.String, Value: "fiiBar"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := AddMetadata(test.structure, test.tree)
|
||||
|
||||
if test.expected.error {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
|
||||
if !assert.Equal(t, test.expected.node, test.tree) {
|
||||
bytes, errM := json.MarshalIndent(test.tree, "", " ")
|
||||
require.NoError(t, errM)
|
||||
fmt.Println(string(bytes))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
12
provider/label/internal/tags.go
Normal file
12
provider/label/internal/tags.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package internal
|
||||
|
||||
const (
|
||||
// TagLabel allow to apply a custom behavior.
|
||||
// - "allowEmpty": allow to create an empty struct.
|
||||
// - "-": ignore the field.
|
||||
TagLabel = "label"
|
||||
|
||||
// TagLabelSliceAsStruct allow to use a slice of struct by creating one entry into the slice.
|
||||
// The value is the substitution name use in the label to access the slice.
|
||||
TagLabelSliceAsStruct = "label-slice-as-struct"
|
||||
)
|
39
provider/label/parser.go
Normal file
39
provider/label/parser.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package label
|
||||
|
||||
import (
|
||||
"github.com/containous/traefik/config"
|
||||
"github.com/containous/traefik/provider/label/internal"
|
||||
)
|
||||
|
||||
// Decode Converts the labels to a configuration.
|
||||
// labels -> [ node -> node + metadata (type) ] -> element (node)
|
||||
func Decode(labels map[string]string) (*config.Configuration, error) {
|
||||
node, err := internal.DecodeToNode(labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf := &config.Configuration{}
|
||||
err = internal.AddMetadata(conf, node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = internal.Fill(conf, node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// Encode Converts a configuration to labels.
|
||||
// element -> node (value) -> label (node)
|
||||
func Encode(conf *config.Configuration) (map[string]string, error) {
|
||||
node, err := internal.EncodeToNode(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return internal.EncodeNode(node), nil
|
||||
}
|
913
provider/label/parser_test.go
Normal file
913
provider/label/parser_test.go
Normal file
|
@ -0,0 +1,913 @@
|
|||
package label
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
labels := map[string]string{
|
||||
"traefik.middlewares.Middleware0.addprefix.prefix": "foobar",
|
||||
"traefik.middlewares.Middleware1.basicauth.headerfield": "foobar",
|
||||
"traefik.middlewares.Middleware1.basicauth.realm": "foobar",
|
||||
"traefik.middlewares.Middleware1.basicauth.removeheader": "true",
|
||||
"traefik.middlewares.Middleware1.basicauth.users": "foobar, fiibar",
|
||||
"traefik.middlewares.Middleware1.basicauth.usersfile": "foobar",
|
||||
"traefik.middlewares.Middleware2.buffering.maxrequestbodybytes": "42",
|
||||
"traefik.middlewares.Middleware2.buffering.maxresponsebodybytes": "42",
|
||||
"traefik.middlewares.Middleware2.buffering.memrequestbodybytes": "42",
|
||||
"traefik.middlewares.Middleware2.buffering.memresponsebodybytes": "42",
|
||||
"traefik.middlewares.Middleware2.buffering.retryexpression": "foobar",
|
||||
"traefik.middlewares.Middleware3.chain.middlewares": "foobar, fiibar",
|
||||
"traefik.middlewares.Middleware4.circuitbreaker.expression": "foobar",
|
||||
"traefik.middlewares.Middleware5.digestauth.headerfield": "foobar",
|
||||
"traefik.middlewares.Middleware5.digestauth.realm": "foobar",
|
||||
"traefik.middlewares.Middleware5.digestauth.removeheader": "true",
|
||||
"traefik.middlewares.Middleware5.digestauth.users": "foobar, fiibar",
|
||||
"traefik.middlewares.Middleware5.digestauth.usersfile": "foobar",
|
||||
"traefik.middlewares.Middleware6.errors.query": "foobar",
|
||||
"traefik.middlewares.Middleware6.errors.service": "foobar",
|
||||
"traefik.middlewares.Middleware6.errors.status": "foobar, fiibar",
|
||||
"traefik.middlewares.Middleware7.forwardauth.address": "foobar",
|
||||
"traefik.middlewares.Middleware7.forwardauth.authresponseheaders": "foobar, fiibar",
|
||||
"traefik.middlewares.Middleware7.forwardauth.tls.ca": "foobar",
|
||||
"traefik.middlewares.Middleware7.forwardauth.tls.caoptional": "true",
|
||||
"traefik.middlewares.Middleware7.forwardauth.tls.cert": "foobar",
|
||||
"traefik.middlewares.Middleware7.forwardauth.tls.insecureskipverify": "true",
|
||||
"traefik.middlewares.Middleware7.forwardauth.tls.key": "foobar",
|
||||
"traefik.middlewares.Middleware7.forwardauth.trustforwardheader": "true",
|
||||
"traefik.middlewares.Middleware8.headers.allowedhosts": "foobar, fiibar",
|
||||
"traefik.middlewares.Middleware8.headers.browserxssfilter": "true",
|
||||
"traefik.middlewares.Middleware8.headers.contentsecuritypolicy": "foobar",
|
||||
"traefik.middlewares.Middleware8.headers.contenttypenosniff": "true",
|
||||
"traefik.middlewares.Middleware8.headers.custombrowserxssvalue": "foobar",
|
||||
"traefik.middlewares.Middleware8.headers.customframeoptionsvalue": "foobar",
|
||||
"traefik.middlewares.Middleware8.headers.customrequestheaders.name0": "foobar",
|
||||
"traefik.middlewares.Middleware8.headers.customrequestheaders.name1": "foobar",
|
||||
"traefik.middlewares.Middleware8.headers.customresponseheaders.name0": "foobar",
|
||||
"traefik.middlewares.Middleware8.headers.customresponseheaders.name1": "foobar",
|
||||
"traefik.middlewares.Middleware8.headers.forcestsheader": "true",
|
||||
"traefik.middlewares.Middleware8.headers.framedeny": "true",
|
||||
"traefik.middlewares.Middleware8.headers.hostsproxyheaders": "foobar, fiibar",
|
||||
"traefik.middlewares.Middleware8.headers.isdevelopment": "true",
|
||||
"traefik.middlewares.Middleware8.headers.publickey": "foobar",
|
||||
"traefik.middlewares.Middleware8.headers.referrerpolicy": "foobar",
|
||||
"traefik.middlewares.Middleware8.headers.sslforcehost": "true",
|
||||
"traefik.middlewares.Middleware8.headers.sslhost": "foobar",
|
||||
"traefik.middlewares.Middleware8.headers.sslproxyheaders.name0": "foobar",
|
||||
"traefik.middlewares.Middleware8.headers.sslproxyheaders.name1": "foobar",
|
||||
"traefik.middlewares.Middleware8.headers.sslredirect": "true",
|
||||
"traefik.middlewares.Middleware8.headers.ssltemporaryredirect": "true",
|
||||
"traefik.middlewares.Middleware8.headers.stsincludesubdomains": "true",
|
||||
"traefik.middlewares.Middleware8.headers.stspreload": "true",
|
||||
"traefik.middlewares.Middleware8.headers.stsseconds": "42",
|
||||
"traefik.middlewares.Middleware9.ipwhitelist.ipstrategy.depth": "42",
|
||||
"traefik.middlewares.Middleware9.ipwhitelist.ipstrategy.excludedips": "foobar, fiibar",
|
||||
"traefik.middlewares.Middleware9.ipwhitelist.sourcerange": "foobar, fiibar",
|
||||
"traefik.middlewares.Middleware10.maxconn.amount": "42",
|
||||
"traefik.middlewares.Middleware10.maxconn.extractorfunc": "foobar",
|
||||
"traefik.middlewares.Middleware11.passtlsclientcert.infos.notafter": "true",
|
||||
"traefik.middlewares.Middleware11.passtlsclientcert.infos.notbefore": "true",
|
||||
"traefik.middlewares.Middleware11.passtlsclientcert.infos.sans": "true",
|
||||
"traefik.middlewares.Middleware11.passtlsclientcert.infos.subject.commonname": "true",
|
||||
"traefik.middlewares.Middleware11.passtlsclientcert.infos.subject.country": "true",
|
||||
"traefik.middlewares.Middleware11.passtlsclientcert.infos.subject.locality": "true",
|
||||
"traefik.middlewares.Middleware11.passtlsclientcert.infos.subject.organization": "true",
|
||||
"traefik.middlewares.Middleware11.passtlsclientcert.infos.subject.province": "true",
|
||||
"traefik.middlewares.Middleware11.passtlsclientcert.infos.subject.serialnumber": "true",
|
||||
"traefik.middlewares.Middleware11.passtlsclientcert.pem": "true",
|
||||
"traefik.middlewares.Middleware12.ratelimit.extractorfunc": "foobar",
|
||||
"traefik.middlewares.Middleware12.ratelimit.rateset.Rate0.average": "42",
|
||||
"traefik.middlewares.Middleware12.ratelimit.rateset.Rate0.burst": "42",
|
||||
"traefik.middlewares.Middleware12.ratelimit.rateset.Rate0.period": "42",
|
||||
"traefik.middlewares.Middleware12.ratelimit.rateset.Rate1.average": "42",
|
||||
"traefik.middlewares.Middleware12.ratelimit.rateset.Rate1.burst": "42",
|
||||
"traefik.middlewares.Middleware12.ratelimit.rateset.Rate1.period": "42",
|
||||
"traefik.middlewares.Middleware13.redirect.permanent": "true",
|
||||
"traefik.middlewares.Middleware13.redirect.regex": "foobar",
|
||||
"traefik.middlewares.Middleware13.redirect.replacement": "foobar",
|
||||
"traefik.middlewares.Middleware14.replacepath.path": "foobar",
|
||||
"traefik.middlewares.Middleware15.replacepathregex.regex": "foobar",
|
||||
"traefik.middlewares.Middleware15.replacepathregex.replacement": "foobar",
|
||||
"traefik.middlewares.Middleware16.retry.attempts": "42",
|
||||
"traefik.middlewares.Middleware17.stripprefix.prefixes": "foobar, fiibar",
|
||||
"traefik.middlewares.Middleware18.stripprefixregex.regex": "foobar, fiibar",
|
||||
"traefik.middlewares.Middleware19.compress": "true",
|
||||
|
||||
"traefik.routers.Router0.entrypoints": "foobar, fiibar",
|
||||
"traefik.routers.Router0.middlewares": "foobar, fiibar",
|
||||
"traefik.routers.Router0.priority": "42",
|
||||
"traefik.routers.Router0.rule": "foobar",
|
||||
"traefik.routers.Router0.service": "foobar",
|
||||
"traefik.routers.Router1.entrypoints": "foobar, fiibar",
|
||||
"traefik.routers.Router1.middlewares": "foobar, fiibar",
|
||||
"traefik.routers.Router1.priority": "42",
|
||||
"traefik.routers.Router1.rule": "foobar",
|
||||
"traefik.routers.Router1.service": "foobar",
|
||||
|
||||
"traefik.services.Service0.loadbalancer.healthcheck.headers.name0": "foobar",
|
||||
"traefik.services.Service0.loadbalancer.healthcheck.headers.name1": "foobar",
|
||||
"traefik.services.Service0.loadbalancer.healthcheck.hostname": "foobar",
|
||||
"traefik.services.Service0.loadbalancer.healthcheck.interval": "foobar",
|
||||
"traefik.services.Service0.loadbalancer.healthcheck.path": "foobar",
|
||||
"traefik.services.Service0.loadbalancer.healthcheck.port": "42",
|
||||
"traefik.services.Service0.loadbalancer.healthcheck.scheme": "foobar",
|
||||
"traefik.services.Service0.loadbalancer.healthcheck.timeout": "foobar",
|
||||
"traefik.services.Service0.loadbalancer.method": "foobar",
|
||||
"traefik.services.Service0.loadbalancer.passhostheader": "true",
|
||||
"traefik.services.Service0.loadbalancer.responseforwarding.flushinterval": "foobar",
|
||||
"traefik.services.Service0.loadbalancer.server.url": "foobar",
|
||||
"traefik.services.Service0.loadbalancer.server.weight": "42",
|
||||
"traefik.services.Service0.loadbalancer.stickiness.cookiename": "foobar",
|
||||
"traefik.services.Service1.loadbalancer.healthcheck.headers.name0": "foobar",
|
||||
"traefik.services.Service1.loadbalancer.healthcheck.headers.name1": "foobar",
|
||||
"traefik.services.Service1.loadbalancer.healthcheck.hostname": "foobar",
|
||||
"traefik.services.Service1.loadbalancer.healthcheck.interval": "foobar",
|
||||
"traefik.services.Service1.loadbalancer.healthcheck.path": "foobar",
|
||||
"traefik.services.Service1.loadbalancer.healthcheck.port": "42",
|
||||
"traefik.services.Service1.loadbalancer.healthcheck.scheme": "foobar",
|
||||
"traefik.services.Service1.loadbalancer.healthcheck.timeout": "foobar",
|
||||
"traefik.services.Service1.loadbalancer.method": "foobar",
|
||||
"traefik.services.Service1.loadbalancer.passhostheader": "true",
|
||||
"traefik.services.Service1.loadbalancer.responseforwarding.flushinterval": "foobar",
|
||||
"traefik.services.Service1.loadbalancer.server.url": "foobar",
|
||||
"traefik.services.Service1.loadbalancer.server.weight": "42",
|
||||
"traefik.services.Service1.loadbalancer.stickiness": "false",
|
||||
"traefik.services.Service1.loadbalancer.stickiness.cookiename": "fui",
|
||||
}
|
||||
|
||||
configuration, err := Decode(labels)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := &config.Configuration{
|
||||
Routers: map[string]*config.Router{
|
||||
"Router0": {
|
||||
EntryPoints: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Middlewares: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Priority: 42,
|
||||
},
|
||||
"Router1": {
|
||||
EntryPoints: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Middlewares: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Priority: 42,
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*config.Middleware{
|
||||
"Middleware0": {
|
||||
AddPrefix: &config.AddPrefix{
|
||||
Prefix: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware1": {
|
||||
BasicAuth: &config.BasicAuth{
|
||||
Users: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
UsersFile: "foobar",
|
||||
Realm: "foobar",
|
||||
RemoveHeader: true,
|
||||
HeaderField: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware10": {
|
||||
MaxConn: &config.MaxConn{
|
||||
Amount: 42,
|
||||
ExtractorFunc: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware11": {
|
||||
PassTLSClientCert: &config.PassTLSClientCert{
|
||||
PEM: true,
|
||||
Infos: &config.TLSClientCertificateInfos{
|
||||
NotAfter: true,
|
||||
NotBefore: true,
|
||||
Subject: &config.TLSCLientCertificateSubjectInfos{
|
||||
Country: true,
|
||||
Province: true,
|
||||
Locality: true,
|
||||
Organization: true,
|
||||
CommonName: true,
|
||||
SerialNumber: true,
|
||||
},
|
||||
Sans: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"Middleware12": {
|
||||
RateLimit: &config.RateLimit{
|
||||
RateSet: map[string]*config.Rate{
|
||||
"Rate0": {
|
||||
Period: parse.Duration(42 * time.Nanosecond),
|
||||
Average: 42,
|
||||
Burst: 42,
|
||||
},
|
||||
"Rate1": {
|
||||
Period: parse.Duration(42 * time.Nanosecond),
|
||||
Average: 42,
|
||||
Burst: 42,
|
||||
},
|
||||
},
|
||||
ExtractorFunc: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware13": {
|
||||
Redirect: &config.Redirect{
|
||||
Regex: "foobar",
|
||||
Replacement: "foobar",
|
||||
Permanent: true,
|
||||
},
|
||||
},
|
||||
"Middleware14": {
|
||||
ReplacePath: &config.ReplacePath{
|
||||
Path: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware15": {
|
||||
ReplacePathRegex: &config.ReplacePathRegex{
|
||||
Regex: "foobar",
|
||||
Replacement: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware16": {
|
||||
Retry: &config.Retry{
|
||||
Attempts: 42,
|
||||
},
|
||||
},
|
||||
"Middleware17": {
|
||||
StripPrefix: &config.StripPrefix{
|
||||
Prefixes: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Middleware18": {
|
||||
StripPrefixRegex: &config.StripPrefixRegex{
|
||||
Regex: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Middleware19": {
|
||||
Compress: &config.Compress{},
|
||||
},
|
||||
"Middleware2": {
|
||||
Buffering: &config.Buffering{
|
||||
MaxRequestBodyBytes: 42,
|
||||
MemRequestBodyBytes: 42,
|
||||
MaxResponseBodyBytes: 42,
|
||||
MemResponseBodyBytes: 42,
|
||||
RetryExpression: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware3": {
|
||||
Chain: &config.Chain{
|
||||
Middlewares: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Middleware4": {
|
||||
CircuitBreaker: &config.CircuitBreaker{
|
||||
Expression: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware5": {
|
||||
DigestAuth: &config.DigestAuth{
|
||||
Users: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
UsersFile: "foobar",
|
||||
RemoveHeader: true,
|
||||
Realm: "foobar",
|
||||
HeaderField: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware6": {
|
||||
Errors: &config.ErrorPage{
|
||||
Status: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Service: "foobar",
|
||||
Query: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware7": {
|
||||
ForwardAuth: &config.ForwardAuth{
|
||||
Address: "foobar",
|
||||
TLS: &config.ClientTLS{
|
||||
CA: "foobar",
|
||||
CAOptional: true,
|
||||
Cert: "foobar",
|
||||
Key: "foobar",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
AuthResponseHeaders: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Middleware8": {
|
||||
Headers: &config.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
CustomResponseHeaders: map[string]string{
|
||||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
AllowedHosts: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
HostsProxyHeaders: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
SSLRedirect: true,
|
||||
SSLTemporaryRedirect: true,
|
||||
SSLHost: "foobar",
|
||||
SSLProxyHeaders: map[string]string{
|
||||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
SSLForceHost: true,
|
||||
STSSeconds: 42,
|
||||
STSIncludeSubdomains: true,
|
||||
STSPreload: true,
|
||||
ForceSTSHeader: true,
|
||||
FrameDeny: true,
|
||||
CustomFrameOptionsValue: "foobar",
|
||||
ContentTypeNosniff: true,
|
||||
BrowserXSSFilter: true,
|
||||
CustomBrowserXSSValue: "foobar",
|
||||
ContentSecurityPolicy: "foobar",
|
||||
PublicKey: "foobar",
|
||||
ReferrerPolicy: "foobar",
|
||||
IsDevelopment: true,
|
||||
},
|
||||
},
|
||||
"Middleware9": {
|
||||
IPWhiteList: &config.IPWhiteList{
|
||||
SourceRange: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
IPStrategy: &config.IPStrategy{
|
||||
Depth: 42,
|
||||
ExcludedIPs: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*config.Service{
|
||||
"Service0": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Stickiness: &config.Stickiness{
|
||||
CookieName: "foobar",
|
||||
},
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: "foobar",
|
||||
Weight: 42,
|
||||
},
|
||||
},
|
||||
Method: "foobar",
|
||||
HealthCheck: &config.HealthCheck{
|
||||
Scheme: "foobar",
|
||||
Path: "foobar",
|
||||
Port: 42,
|
||||
Interval: "foobar",
|
||||
Timeout: "foobar",
|
||||
Hostname: "foobar",
|
||||
Headers: map[string]string{
|
||||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
},
|
||||
PassHostHeader: true,
|
||||
ResponseForwarding: &config.ResponseForwarding{
|
||||
FlushInterval: "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Service1": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: "foobar",
|
||||
Weight: 42,
|
||||
},
|
||||
},
|
||||
Method: "foobar",
|
||||
HealthCheck: &config.HealthCheck{
|
||||
Scheme: "foobar",
|
||||
Path: "foobar",
|
||||
Port: 42,
|
||||
Interval: "foobar",
|
||||
Timeout: "foobar",
|
||||
Hostname: "foobar",
|
||||
Headers: map[string]string{
|
||||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
},
|
||||
PassHostHeader: true,
|
||||
ResponseForwarding: &config.ResponseForwarding{
|
||||
FlushInterval: "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, configuration)
|
||||
}
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
configuration := &config.Configuration{
|
||||
Routers: map[string]*config.Router{
|
||||
"Router0": {
|
||||
EntryPoints: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Middlewares: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Priority: 42,
|
||||
},
|
||||
"Router1": {
|
||||
EntryPoints: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Middlewares: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Priority: 42,
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*config.Middleware{
|
||||
"Middleware0": {
|
||||
AddPrefix: &config.AddPrefix{
|
||||
Prefix: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware1": {
|
||||
BasicAuth: &config.BasicAuth{
|
||||
Users: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
UsersFile: "foobar",
|
||||
Realm: "foobar",
|
||||
RemoveHeader: true,
|
||||
HeaderField: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware10": {
|
||||
MaxConn: &config.MaxConn{
|
||||
Amount: 42,
|
||||
ExtractorFunc: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware11": {
|
||||
PassTLSClientCert: &config.PassTLSClientCert{
|
||||
PEM: true,
|
||||
Infos: &config.TLSClientCertificateInfos{
|
||||
NotAfter: true,
|
||||
NotBefore: true,
|
||||
Subject: &config.TLSCLientCertificateSubjectInfos{
|
||||
Country: true,
|
||||
Province: true,
|
||||
Locality: true,
|
||||
Organization: true,
|
||||
CommonName: true,
|
||||
SerialNumber: true,
|
||||
},
|
||||
Sans: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"Middleware12": {
|
||||
RateLimit: &config.RateLimit{
|
||||
RateSet: map[string]*config.Rate{
|
||||
"Rate0": {
|
||||
Period: parse.Duration(42 * time.Nanosecond),
|
||||
Average: 42,
|
||||
Burst: 42,
|
||||
},
|
||||
"Rate1": {
|
||||
Period: parse.Duration(42 * time.Nanosecond),
|
||||
Average: 42,
|
||||
Burst: 42,
|
||||
},
|
||||
},
|
||||
ExtractorFunc: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware13": {
|
||||
Redirect: &config.Redirect{
|
||||
Regex: "foobar",
|
||||
Replacement: "foobar",
|
||||
Permanent: true,
|
||||
},
|
||||
},
|
||||
"Middleware14": {
|
||||
ReplacePath: &config.ReplacePath{
|
||||
Path: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware15": {
|
||||
ReplacePathRegex: &config.ReplacePathRegex{
|
||||
Regex: "foobar",
|
||||
Replacement: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware16": {
|
||||
Retry: &config.Retry{
|
||||
Attempts: 42,
|
||||
},
|
||||
},
|
||||
"Middleware17": {
|
||||
StripPrefix: &config.StripPrefix{
|
||||
Prefixes: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Middleware18": {
|
||||
StripPrefixRegex: &config.StripPrefixRegex{
|
||||
Regex: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Middleware19": {
|
||||
Compress: &config.Compress{},
|
||||
},
|
||||
"Middleware2": {
|
||||
Buffering: &config.Buffering{
|
||||
MaxRequestBodyBytes: 42,
|
||||
MemRequestBodyBytes: 42,
|
||||
MaxResponseBodyBytes: 42,
|
||||
MemResponseBodyBytes: 42,
|
||||
RetryExpression: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware3": {
|
||||
Chain: &config.Chain{
|
||||
Middlewares: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Middleware4": {
|
||||
CircuitBreaker: &config.CircuitBreaker{
|
||||
Expression: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware5": {
|
||||
DigestAuth: &config.DigestAuth{
|
||||
Users: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
UsersFile: "foobar",
|
||||
RemoveHeader: true,
|
||||
Realm: "foobar",
|
||||
HeaderField: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware6": {
|
||||
Errors: &config.ErrorPage{
|
||||
Status: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
Service: "foobar",
|
||||
Query: "foobar",
|
||||
},
|
||||
},
|
||||
"Middleware7": {
|
||||
ForwardAuth: &config.ForwardAuth{
|
||||
Address: "foobar",
|
||||
TLS: &config.ClientTLS{
|
||||
CA: "foobar",
|
||||
CAOptional: true,
|
||||
Cert: "foobar",
|
||||
Key: "foobar",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
AuthResponseHeaders: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Middleware8": {
|
||||
Headers: &config.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
CustomResponseHeaders: map[string]string{
|
||||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
AllowedHosts: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
HostsProxyHeaders: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
SSLRedirect: true,
|
||||
SSLTemporaryRedirect: true,
|
||||
SSLHost: "foobar",
|
||||
SSLProxyHeaders: map[string]string{
|
||||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
SSLForceHost: true,
|
||||
STSSeconds: 42,
|
||||
STSIncludeSubdomains: true,
|
||||
STSPreload: true,
|
||||
ForceSTSHeader: true,
|
||||
FrameDeny: true,
|
||||
CustomFrameOptionsValue: "foobar",
|
||||
ContentTypeNosniff: true,
|
||||
BrowserXSSFilter: true,
|
||||
CustomBrowserXSSValue: "foobar",
|
||||
ContentSecurityPolicy: "foobar",
|
||||
PublicKey: "foobar",
|
||||
ReferrerPolicy: "foobar",
|
||||
IsDevelopment: true,
|
||||
},
|
||||
},
|
||||
"Middleware9": {
|
||||
IPWhiteList: &config.IPWhiteList{
|
||||
SourceRange: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
IPStrategy: &config.IPStrategy{
|
||||
Depth: 42,
|
||||
ExcludedIPs: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*config.Service{
|
||||
"Service0": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Stickiness: &config.Stickiness{
|
||||
CookieName: "foobar",
|
||||
},
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: "foobar",
|
||||
Weight: 42,
|
||||
},
|
||||
},
|
||||
Method: "foobar",
|
||||
HealthCheck: &config.HealthCheck{
|
||||
Scheme: "foobar",
|
||||
Path: "foobar",
|
||||
Port: 42,
|
||||
Interval: "foobar",
|
||||
Timeout: "foobar",
|
||||
Hostname: "foobar",
|
||||
Headers: map[string]string{
|
||||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
},
|
||||
PassHostHeader: true,
|
||||
ResponseForwarding: &config.ResponseForwarding{
|
||||
FlushInterval: "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Service1": {
|
||||
LoadBalancer: &config.LoadBalancerService{
|
||||
Servers: []config.Server{
|
||||
{
|
||||
URL: "foobar",
|
||||
Weight: 42,
|
||||
},
|
||||
},
|
||||
Method: "foobar",
|
||||
HealthCheck: &config.HealthCheck{
|
||||
Scheme: "foobar",
|
||||
Path: "foobar",
|
||||
Port: 42,
|
||||
Interval: "foobar",
|
||||
Timeout: "foobar",
|
||||
Hostname: "foobar",
|
||||
Headers: map[string]string{
|
||||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
},
|
||||
PassHostHeader: true,
|
||||
ResponseForwarding: &config.ResponseForwarding{
|
||||
FlushInterval: "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
labels, err := Encode(configuration)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := map[string]string{
|
||||
"traefik.Middlewares.Middleware0.AddPrefix.Prefix": "foobar",
|
||||
"traefik.Middlewares.Middleware1.BasicAuth.HeaderField": "foobar",
|
||||
"traefik.Middlewares.Middleware1.BasicAuth.Realm": "foobar",
|
||||
"traefik.Middlewares.Middleware1.BasicAuth.RemoveHeader": "true",
|
||||
"traefik.Middlewares.Middleware1.BasicAuth.Users": "foobar, fiibar",
|
||||
"traefik.Middlewares.Middleware1.BasicAuth.UsersFile": "foobar",
|
||||
"traefik.Middlewares.Middleware2.Buffering.MaxRequestBodyBytes": "42",
|
||||
"traefik.Middlewares.Middleware2.Buffering.MaxResponseBodyBytes": "42",
|
||||
"traefik.Middlewares.Middleware2.Buffering.MemRequestBodyBytes": "42",
|
||||
"traefik.Middlewares.Middleware2.Buffering.MemResponseBodyBytes": "42",
|
||||
"traefik.Middlewares.Middleware2.Buffering.RetryExpression": "foobar",
|
||||
"traefik.Middlewares.Middleware3.Chain.Middlewares": "foobar, fiibar",
|
||||
"traefik.Middlewares.Middleware4.CircuitBreaker.Expression": "foobar",
|
||||
"traefik.Middlewares.Middleware5.DigestAuth.HeaderField": "foobar",
|
||||
"traefik.Middlewares.Middleware5.DigestAuth.Realm": "foobar",
|
||||
"traefik.Middlewares.Middleware5.DigestAuth.RemoveHeader": "true",
|
||||
"traefik.Middlewares.Middleware5.DigestAuth.Users": "foobar, fiibar",
|
||||
"traefik.Middlewares.Middleware5.DigestAuth.UsersFile": "foobar",
|
||||
"traefik.Middlewares.Middleware6.Errors.Query": "foobar",
|
||||
"traefik.Middlewares.Middleware6.Errors.Service": "foobar",
|
||||
"traefik.Middlewares.Middleware6.Errors.Status": "foobar, fiibar",
|
||||
"traefik.Middlewares.Middleware7.ForwardAuth.Address": "foobar",
|
||||
"traefik.Middlewares.Middleware7.ForwardAuth.AuthResponseHeaders": "foobar, fiibar",
|
||||
"traefik.Middlewares.Middleware7.ForwardAuth.TLS.CA": "foobar",
|
||||
"traefik.Middlewares.Middleware7.ForwardAuth.TLS.CAOptional": "true",
|
||||
"traefik.Middlewares.Middleware7.ForwardAuth.TLS.Cert": "foobar",
|
||||
"traefik.Middlewares.Middleware7.ForwardAuth.TLS.InsecureSkipVerify": "true",
|
||||
"traefik.Middlewares.Middleware7.ForwardAuth.TLS.Key": "foobar",
|
||||
"traefik.Middlewares.Middleware7.ForwardAuth.TrustForwardHeader": "true",
|
||||
"traefik.Middlewares.Middleware8.Headers.AllowedHosts": "foobar, fiibar",
|
||||
"traefik.Middlewares.Middleware8.Headers.BrowserXSSFilter": "true",
|
||||
"traefik.Middlewares.Middleware8.Headers.ContentSecurityPolicy": "foobar",
|
||||
"traefik.Middlewares.Middleware8.Headers.ContentTypeNosniff": "true",
|
||||
"traefik.Middlewares.Middleware8.Headers.CustomBrowserXSSValue": "foobar",
|
||||
"traefik.Middlewares.Middleware8.Headers.CustomFrameOptionsValue": "foobar",
|
||||
"traefik.Middlewares.Middleware8.Headers.CustomRequestHeaders.name0": "foobar",
|
||||
"traefik.Middlewares.Middleware8.Headers.CustomRequestHeaders.name1": "foobar",
|
||||
"traefik.Middlewares.Middleware8.Headers.CustomResponseHeaders.name0": "foobar",
|
||||
"traefik.Middlewares.Middleware8.Headers.CustomResponseHeaders.name1": "foobar",
|
||||
"traefik.Middlewares.Middleware8.Headers.ForceSTSHeader": "true",
|
||||
"traefik.Middlewares.Middleware8.Headers.FrameDeny": "true",
|
||||
"traefik.Middlewares.Middleware8.Headers.HostsProxyHeaders": "foobar, fiibar",
|
||||
"traefik.Middlewares.Middleware8.Headers.IsDevelopment": "true",
|
||||
"traefik.Middlewares.Middleware8.Headers.PublicKey": "foobar",
|
||||
"traefik.Middlewares.Middleware8.Headers.ReferrerPolicy": "foobar",
|
||||
"traefik.Middlewares.Middleware8.Headers.SSLForceHost": "true",
|
||||
"traefik.Middlewares.Middleware8.Headers.SSLHost": "foobar",
|
||||
"traefik.Middlewares.Middleware8.Headers.SSLProxyHeaders.name0": "foobar",
|
||||
"traefik.Middlewares.Middleware8.Headers.SSLProxyHeaders.name1": "foobar",
|
||||
"traefik.Middlewares.Middleware8.Headers.SSLRedirect": "true",
|
||||
"traefik.Middlewares.Middleware8.Headers.SSLTemporaryRedirect": "true",
|
||||
"traefik.Middlewares.Middleware8.Headers.STSIncludeSubdomains": "true",
|
||||
"traefik.Middlewares.Middleware8.Headers.STSPreload": "true",
|
||||
"traefik.Middlewares.Middleware8.Headers.STSSeconds": "42",
|
||||
"traefik.Middlewares.Middleware9.IPWhiteList.IPStrategy.Depth": "42",
|
||||
"traefik.Middlewares.Middleware9.IPWhiteList.IPStrategy.ExcludedIPs": "foobar, fiibar",
|
||||
"traefik.Middlewares.Middleware9.IPWhiteList.SourceRange": "foobar, fiibar",
|
||||
"traefik.Middlewares.Middleware10.MaxConn.Amount": "42",
|
||||
"traefik.Middlewares.Middleware10.MaxConn.ExtractorFunc": "foobar",
|
||||
"traefik.Middlewares.Middleware11.PassTLSClientCert.Infos.NotAfter": "true",
|
||||
"traefik.Middlewares.Middleware11.PassTLSClientCert.Infos.NotBefore": "true",
|
||||
"traefik.Middlewares.Middleware11.PassTLSClientCert.Infos.Sans": "true",
|
||||
"traefik.Middlewares.Middleware11.PassTLSClientCert.Infos.Subject.CommonName": "true",
|
||||
"traefik.Middlewares.Middleware11.PassTLSClientCert.Infos.Subject.Country": "true",
|
||||
"traefik.Middlewares.Middleware11.PassTLSClientCert.Infos.Subject.Locality": "true",
|
||||
"traefik.Middlewares.Middleware11.PassTLSClientCert.Infos.Subject.Organization": "true",
|
||||
"traefik.Middlewares.Middleware11.PassTLSClientCert.Infos.Subject.Province": "true",
|
||||
"traefik.Middlewares.Middleware11.PassTLSClientCert.Infos.Subject.SerialNumber": "true",
|
||||
"traefik.Middlewares.Middleware11.PassTLSClientCert.PEM": "true",
|
||||
"traefik.Middlewares.Middleware12.RateLimit.ExtractorFunc": "foobar",
|
||||
"traefik.Middlewares.Middleware12.RateLimit.RateSet.Rate0.Average": "42",
|
||||
"traefik.Middlewares.Middleware12.RateLimit.RateSet.Rate0.Burst": "42",
|
||||
"traefik.Middlewares.Middleware12.RateLimit.RateSet.Rate0.Period": "42",
|
||||
"traefik.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Average": "42",
|
||||
"traefik.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Burst": "42",
|
||||
"traefik.Middlewares.Middleware12.RateLimit.RateSet.Rate1.Period": "42",
|
||||
"traefik.Middlewares.Middleware13.Redirect.Permanent": "true",
|
||||
"traefik.Middlewares.Middleware13.Redirect.Regex": "foobar",
|
||||
"traefik.Middlewares.Middleware13.Redirect.Replacement": "foobar",
|
||||
"traefik.Middlewares.Middleware14.ReplacePath.Path": "foobar",
|
||||
"traefik.Middlewares.Middleware15.ReplacePathRegex.Regex": "foobar",
|
||||
"traefik.Middlewares.Middleware15.ReplacePathRegex.Replacement": "foobar",
|
||||
"traefik.Middlewares.Middleware16.Retry.Attempts": "42",
|
||||
"traefik.Middlewares.Middleware17.StripPrefix.Prefixes": "foobar, fiibar",
|
||||
"traefik.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar",
|
||||
"traefik.Middlewares.Middleware19.Compress": "true",
|
||||
|
||||
"traefik.Routers.Router0.EntryPoints": "foobar, fiibar",
|
||||
"traefik.Routers.Router0.Middlewares": "foobar, fiibar",
|
||||
"traefik.Routers.Router0.Priority": "42",
|
||||
"traefik.Routers.Router0.Rule": "foobar",
|
||||
"traefik.Routers.Router0.Service": "foobar",
|
||||
"traefik.Routers.Router1.EntryPoints": "foobar, fiibar",
|
||||
"traefik.Routers.Router1.Middlewares": "foobar, fiibar",
|
||||
"traefik.Routers.Router1.Priority": "42",
|
||||
"traefik.Routers.Router1.Rule": "foobar",
|
||||
"traefik.Routers.Router1.Service": "foobar",
|
||||
|
||||
"traefik.Services.Service0.LoadBalancer.HealthCheck.Headers.name0": "foobar",
|
||||
"traefik.Services.Service0.LoadBalancer.HealthCheck.Headers.name1": "foobar",
|
||||
"traefik.Services.Service0.LoadBalancer.HealthCheck.Hostname": "foobar",
|
||||
"traefik.Services.Service0.LoadBalancer.HealthCheck.Interval": "foobar",
|
||||
"traefik.Services.Service0.LoadBalancer.HealthCheck.Path": "foobar",
|
||||
"traefik.Services.Service0.LoadBalancer.HealthCheck.Port": "42",
|
||||
"traefik.Services.Service0.LoadBalancer.HealthCheck.Scheme": "foobar",
|
||||
"traefik.Services.Service0.LoadBalancer.HealthCheck.Timeout": "foobar",
|
||||
"traefik.Services.Service0.LoadBalancer.Method": "foobar",
|
||||
"traefik.Services.Service0.LoadBalancer.PassHostHeader": "true",
|
||||
"traefik.Services.Service0.LoadBalancer.ResponseForwarding.FlushInterval": "foobar",
|
||||
"traefik.Services.Service0.LoadBalancer.server.URL": "foobar",
|
||||
"traefik.Services.Service0.LoadBalancer.server.Weight": "42",
|
||||
"traefik.Services.Service0.LoadBalancer.Stickiness.CookieName": "foobar",
|
||||
"traefik.Services.Service1.LoadBalancer.HealthCheck.Headers.name0": "foobar",
|
||||
"traefik.Services.Service1.LoadBalancer.HealthCheck.Headers.name1": "foobar",
|
||||
"traefik.Services.Service1.LoadBalancer.HealthCheck.Hostname": "foobar",
|
||||
"traefik.Services.Service1.LoadBalancer.HealthCheck.Interval": "foobar",
|
||||
"traefik.Services.Service1.LoadBalancer.HealthCheck.Path": "foobar",
|
||||
"traefik.Services.Service1.LoadBalancer.HealthCheck.Port": "42",
|
||||
"traefik.Services.Service1.LoadBalancer.HealthCheck.Scheme": "foobar",
|
||||
"traefik.Services.Service1.LoadBalancer.HealthCheck.Timeout": "foobar",
|
||||
"traefik.Services.Service1.LoadBalancer.Method": "foobar",
|
||||
"traefik.Services.Service1.LoadBalancer.PassHostHeader": "true",
|
||||
"traefik.Services.Service1.LoadBalancer.ResponseForwarding.FlushInterval": "foobar",
|
||||
"traefik.Services.Service1.LoadBalancer.server.URL": "foobar",
|
||||
"traefik.Services.Service1.LoadBalancer.server.Weight": "42",
|
||||
}
|
||||
|
||||
for key, val := range expected {
|
||||
if _, ok := labels[key]; !ok {
|
||||
fmt.Println("missing in labels:", key, val)
|
||||
}
|
||||
}
|
||||
|
||||
for key, val := range labels {
|
||||
if _, ok := expected[key]; !ok {
|
||||
fmt.Println("missing in expected:", key, val)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, expected, labels)
|
||||
}
|
Loading…
Reference in a new issue