1
0
Fork 0

New static configuration loading system.

Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
This commit is contained in:
Ludovic Fernandez 2019-06-17 11:48:05 +02:00 committed by Traefiker Bot
parent d18edd6f77
commit 8d7eccad5d
165 changed files with 10894 additions and 6076 deletions

44
pkg/config/flag/flag.go Normal file
View file

@ -0,0 +1,44 @@
// Package flag implements encoding and decoding between flag arguments and a typed Configuration.
package flag
import (
"github.com/containous/traefik/pkg/config/parser"
)
// Decode decodes the given flag arguments into the given element.
// The operation goes through four stages roughly summarized as:
// flag arguments -> parsed map of flags
// map -> tree of untyped nodes
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
// "typed" nodes -> typed element
func Decode(args []string, element interface{}) error {
ref, err := Parse(args, element)
if err != nil {
return err
}
return parser.Decode(ref, element)
}
// Encode encodes the configuration in element into the flags represented in the returned Flats.
// The operation goes through three stages roughly summarized as:
// typed configuration in element -> tree of untyped nodes
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
// "typed" nodes -> flags with default values (determined by type/kind)
func Encode(element interface{}) ([]parser.Flat, error) {
if element == nil {
return nil, nil
}
node, err := parser.EncodeToNode(element, false)
if err != nil {
return nil, err
}
err = parser.AddMetadata(element, node)
if err != nil {
return nil, err
}
return parser.EncodeToFlat(element, node, parser.FlatOpts{Separator: ".", SkipRoot: true})
}

View file

@ -0,0 +1,926 @@
package flag
import (
"testing"
"time"
"github.com/containous/traefik/pkg/config/generator"
"github.com/containous/traefik/pkg/config/parser"
"github.com/containous/traefik/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDecode(t *testing.T) {
testCases := []struct {
desc string
args []string
element interface{}
expected interface{}
}{
{
desc: "no args",
args: nil,
expected: nil,
},
{
desc: "types.Duration value",
args: []string{"--foo=1"},
element: &struct {
Foo types.Duration
}{},
expected: &struct {
Foo types.Duration
}{
Foo: types.Duration(1 * time.Second),
},
},
{
desc: "time.Duration value",
args: []string{"--foo=1"},
element: &struct {
Foo time.Duration
}{},
expected: &struct {
Foo time.Duration
}{
Foo: 1 * time.Nanosecond,
},
},
{
desc: "bool value",
args: []string{"--foo"},
element: &struct {
Foo bool
}{},
expected: &struct {
Foo bool
}{
Foo: true,
},
},
{
desc: "equal",
args: []string{"--foo=bar"},
element: &struct {
Foo string
}{},
expected: &struct {
Foo string
}{
Foo: "bar",
},
},
{
desc: "space separated",
args: []string{"--foo", "bar"},
element: &struct {
Foo string
}{},
expected: &struct {
Foo string
}{
Foo: "bar",
},
},
{
desc: "space separated with end of parameter",
args: []string{"--foo=bir", "--", "--bar"},
element: &struct {
Foo string
}{},
expected: &struct {
Foo string
}{
Foo: "bir",
},
},
{
desc: "multiple bool flags without value",
args: []string{"--foo", "--bar"},
element: &struct {
Foo bool
Bar bool
}{},
expected: &struct {
Foo bool
Bar bool
}{
Foo: true,
Bar: true,
},
},
{
desc: "slice with several flags",
args: []string{"--foo=bar", "--foo=baz"},
element: &struct {
Foo []string
}{},
expected: &struct {
Foo []string
}{
Foo: []string{"bar", "baz"},
},
},
{
desc: "map string",
args: []string{"--foo.name=bar"},
element: &struct {
Foo map[string]string
}{},
expected: &struct {
Foo map[string]string
}{
Foo: map[string]string{
"name": "bar",
},
},
},
{
desc: "map struct",
args: []string{"--foo.name.value=bar"},
element: &struct {
Foo map[string]struct{ Value string }
}{},
expected: &struct {
Foo map[string]struct{ Value string }
}{
Foo: map[string]struct{ Value string }{
"name": {
Value: "bar",
},
},
},
},
{
desc: "map struct with sub-struct",
args: []string{"--foo.name.bar.value=bar"},
element: &struct {
Foo map[string]struct {
Bar *struct{ Value string }
}
}{},
expected: &struct {
Foo map[string]struct {
Bar *struct{ Value string }
}
}{
Foo: map[string]struct {
Bar *struct{ Value string }
}{
"name": {
Bar: &struct {
Value string
}{
Value: "bar",
},
},
},
},
},
{
desc: "map struct with sub-map",
args: []string{"--foo.name1.bar.name2.value=bar"},
element: &struct {
Foo map[string]struct {
Bar map[string]struct{ Value string }
}
}{},
expected: &struct {
Foo map[string]struct {
Bar map[string]struct{ Value string }
}
}{
Foo: map[string]struct {
Bar map[string]struct{ Value string }
}{
"name1": {
Bar: map[string]struct{ Value string }{
"name2": {
Value: "bar",
},
},
},
},
},
},
{
desc: "slice with several flags 2",
args: []string{"--foo", "bar", "--foo", "baz"},
element: &struct {
Foo []string
}{},
expected: &struct {
Foo []string
}{
Foo: []string{"bar", "baz"},
},
},
{
desc: "slice with several flags 3",
args: []string{"--foo", "bar", "--foo=", "--baz"},
element: &struct {
Foo []string
Baz bool
}{},
expected: &struct {
Foo []string
Baz bool
}{
Foo: []string{"bar", ""},
Baz: true,
},
},
{
desc: "slice with several flags 4",
args: []string{"--foo", "bar", "--foo", "--baz"},
element: &struct {
Foo []string
Baz bool
}{},
expected: &struct {
Foo []string
Baz bool
}{
Foo: []string{"bar", "--baz"},
},
},
{
desc: "slice of struct",
args: []string{
"--foo[0].Field1", "bar", "--foo[0].Field2", "6",
"--foo[1].Field1", "bur", "--foo[1].Field2", "2",
},
element: &struct {
Foo []struct {
Field1 string
Field2 int
}
}{},
expected: &struct {
Foo []struct {
Field1 string
Field2 int
}
}{
Foo: []struct {
Field1 string
Field2 int
}{
{
Field1: "bar",
Field2: 6,
},
{
Field1: "bur",
Field2: 2,
},
},
},
},
{
desc: "slice of pointer of struct",
args: []string{
"--foo[0].Field1", "bar", "--foo[0].Field2", "6",
"--foo[1].Field1", "bur", "--foo[1].Field2", "2",
},
element: &struct {
Foo []*struct {
Field1 string
Field2 int
}
}{},
expected: &struct {
Foo []*struct {
Field1 string
Field2 int
}
}{
Foo: []*struct {
Field1 string
Field2 int
}{
{
Field1: "bar",
Field2: 6,
},
{
Field1: "bur",
Field2: 2,
},
},
},
},
{
desc: "multiple string flag",
element: &struct {
Foo string
}{},
args: []string{"--foo=bar", "--foo=baz"},
expected: &struct {
Foo string
}{
Foo: "baz",
},
},
{
desc: "multiple string flag 2",
element: &struct {
Foo string
}{},
args: []string{"--foo", "bar", "--foo", "baz"},
expected: &struct {
Foo string
}{
Foo: "baz",
},
},
{
desc: "string without value",
element: &struct {
Foo string
Bar bool
}{},
args: []string{"--foo", "--bar"},
expected: &struct {
Foo string
Bar bool
}{
Foo: "--bar",
},
},
{
desc: "struct pointer value",
args: []string{"--foo"},
element: &struct {
Foo *struct{ Field string } `label:"allowEmpty"`
}{},
expected: &struct {
Foo *struct{ Field string } `label:"allowEmpty"`
}{
Foo: &struct{ Field string }{},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
err := Decode(test.args, test.element)
require.NoError(t, err)
assert.Equal(t, test.expected, test.element)
})
}
}
func TestEncode(t *testing.T) {
testCases := []struct {
desc string
element interface{}
expected []parser.Flat
}{
{
desc: "string field",
element: &struct {
Field string `description:"field description"`
}{
Field: "test",
},
expected: []parser.Flat{{
Name: "field",
Description: "field description",
Default: "test",
}},
},
{
desc: "int field",
element: &struct {
Field int `description:"field description"`
}{
Field: 6,
},
expected: []parser.Flat{{
Name: "field",
Description: "field description",
Default: "6",
}},
},
{
desc: "bool field",
element: &struct {
Field bool `description:"field description"`
}{
Field: true,
},
expected: []parser.Flat{{
Name: "field",
Description: "field description",
Default: "true",
}},
},
{
desc: "string pointer field",
element: &struct {
Field *string `description:"field description"`
}{
Field: func(v string) *string { return &v }("test"),
},
expected: []parser.Flat{{
Name: "field",
Description: "field description",
Default: "test",
}},
},
{
desc: "int pointer field",
element: &struct {
Field *int `description:"field description"`
}{
Field: func(v int) *int { return &v }(6),
},
expected: []parser.Flat{{
Name: "field",
Description: "field description",
Default: "6",
}},
},
{
desc: "bool pointer field",
element: &struct {
Field *bool `description:"field description"`
}{
Field: func(v bool) *bool { return &v }(true),
},
expected: []parser.Flat{{
Name: "field",
Description: "field description",
Default: "true",
}},
},
{
desc: "slice of string field, no initial value",
element: &struct {
Field []string `description:"field description"`
}{},
expected: []parser.Flat{{
Name: "field",
Description: "field description",
Default: "",
}},
},
{
desc: "slice of string field, with initial value",
element: &struct {
Field []string `description:"field description"`
}{
Field: []string{"foo", "bar"},
},
expected: []parser.Flat{{
Name: "field",
Description: "field description",
Default: "foo, bar",
}},
},
{
desc: "slice of int field, no initial value",
element: &struct {
Field []int `description:"field description"`
}{},
expected: []parser.Flat{{
Name: "field",
Description: "field description",
Default: "",
}},
},
{
desc: "slice of int field, with initial value",
element: &struct {
Field []int `description:"field description"`
}{
Field: []int{6, 3},
},
expected: []parser.Flat{{
Name: "field",
Description: "field description",
Default: "6, 3",
}},
},
{
desc: "map string field",
element: &struct {
Field map[string]string `description:"field description"`
}{
Field: map[string]string{
parser.MapNamePlaceholder: "",
},
},
expected: []parser.Flat{{
Name: "field.<name>",
Description: "field description",
Default: "",
}},
},
{
desc: "struct pointer field",
element: &struct {
Foo *struct {
Field string `description:"field description"`
} `description:"foo description"`
}{
Foo: &struct {
Field string `description:"field description"`
}{
Field: "test",
},
},
expected: []parser.Flat{
{
Name: "foo.field",
Description: "field description",
Default: "test",
},
},
},
{
desc: "struct pointer field, allow empty",
element: &struct {
Foo *struct {
Field string `description:"field description"`
} `description:"foo description" label:"allowEmpty"`
}{
Foo: &struct {
Field string `description:"field description"`
}{
Field: "test",
},
},
expected: []parser.Flat{
{
Name: "foo",
Description: "foo description",
Default: "false",
},
{
Name: "foo.field",
Description: "field description",
Default: "test",
},
},
},
{
desc: "struct pointer field level 2",
element: &struct {
Foo *struct {
Fii *struct {
Field string `description:"field description"`
} `description:"fii description"`
} `description:"foo description"`
}{
Foo: &struct {
Fii *struct {
Field string `description:"field description"`
} `description:"fii description"`
}{
Fii: &struct {
Field string `description:"field description"`
}{
Field: "test",
},
},
},
expected: []parser.Flat{
{
Name: "foo.fii.field",
Description: "field description",
Default: "test",
},
},
},
{
desc: "struct pointer field level 2, allow empty",
element: &struct {
Foo *struct {
Fii *struct {
Field string `description:"field description"`
} `description:"fii description" label:"allowEmpty"`
} `description:"foo description" label:"allowEmpty"`
}{
Foo: &struct {
Fii *struct {
Field string `description:"field description"`
} `description:"fii description" label:"allowEmpty"`
}{
Fii: &struct {
Field string `description:"field description"`
}{
Field: "test",
},
},
},
expected: []parser.Flat{
{
Name: "foo",
Description: "foo description",
Default: "false",
},
{
Name: "foo.fii",
Description: "fii description",
Default: "false",
},
{
Name: "foo.fii.field",
Description: "field description",
Default: "test",
},
},
},
{
desc: "map string field level 2",
element: &struct {
Foo *struct {
Fii map[string]string `description:"fii description"`
} `description:"foo description"`
}{
Foo: &struct {
Fii map[string]string `description:"fii description"`
}{
Fii: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
expected: []parser.Flat{
{
Name: "foo.fii.<name>",
Description: "fii description",
Default: "",
},
},
},
{
desc: "map string pointer field level 2",
element: &struct {
Foo *struct {
Fii map[string]*string `description:"fii description"`
} `description:"foo description"`
}{
Foo: &struct {
Fii map[string]*string `description:"fii description"`
}{
Fii: map[string]*string{
parser.MapNamePlaceholder: func(v string) *string { return &v }(""),
},
},
},
expected: []parser.Flat{
{
Name: "foo.fii.<name>",
Description: "fii description",
Default: "",
},
},
},
{
desc: "map struct level 1",
element: &struct {
Foo map[string]struct {
Field string `description:"field description"`
Yo int `description:"yo description"`
} `description:"foo description"`
}{},
expected: []parser.Flat{
{
Name: "foo.<name>",
Description: "foo description",
Default: "false",
},
{
Name: "foo.<name>.field",
Description: "field description",
Default: "",
},
{
Name: "foo.<name>.yo",
Description: "yo description",
Default: "0",
},
},
},
{
desc: "map struct pointer level 1",
element: &struct {
Foo map[string]*struct {
Field string `description:"field description"`
Yo string `description:"yo description"`
} `description:"foo description"`
}{},
expected: []parser.Flat{
{
Name: "foo.<name>",
Description: "foo description",
Default: "false",
},
{
Name: "foo.<name>.field",
Description: "field description",
Default: "",
},
{
Name: "foo.<name>.yo",
Description: "yo description",
Default: "",
},
},
},
{
desc: "time duration field",
element: &struct {
Field time.Duration `description:"field description"`
}{
Field: 1 * time.Second,
},
expected: []parser.Flat{{
Name: "field",
Description: "field description",
Default: "1s",
}},
},
{
desc: "time duration field map",
element: &struct {
Foo map[string]*struct {
Field time.Duration `description:"field description"`
} `description:"foo description"`
}{
Foo: map[string]*struct {
Field time.Duration `description:"field description"`
}{},
},
expected: []parser.Flat{
{
Name: "foo.<name>",
Description: "foo description",
Default: "false",
},
{
Name: "foo.<name>.field",
Description: "field description",
Default: "0s",
},
},
},
{
desc: "time duration field map 2",
element: &struct {
Foo map[string]*struct {
Fii *struct {
Field time.Duration `description:"field description"`
}
} `description:"foo description"`
}{
Foo: map[string]*struct {
Fii *struct {
Field time.Duration `description:"field description"`
}
}{},
},
expected: []parser.Flat{
{
Name: "foo.<name>",
Description: "foo description",
Default: "false",
},
{
Name: "foo.<name>.fii.field",
Description: "field description",
Default: "0s",
},
},
},
{
desc: "time duration field 2",
element: &struct {
Foo *struct {
Field time.Duration `description:"field description"`
}
}{
Foo: &struct {
Field time.Duration `description:"field description"`
}{
Field: 1 * time.Second,
},
},
expected: []parser.Flat{{
Name: "foo.field",
Description: "field description",
Default: "1s",
}},
},
{
desc: "time duration field 3",
element: &struct {
Foo *struct {
Fii *struct {
Field time.Duration `description:"field description"`
}
}
}{
Foo: &struct {
Fii *struct {
Field time.Duration `description:"field description"`
}
}{
Fii: &struct {
Field time.Duration `description:"field description"`
}{
Field: 1 * time.Second,
},
},
},
expected: []parser.Flat{{
Name: "foo.fii.field",
Description: "field description",
Default: "1s",
}},
},
{
desc: "time duration field",
element: &struct {
Field types.Duration `description:"field description"`
}{
Field: types.Duration(180 * time.Second),
},
expected: []parser.Flat{{
Name: "field",
Description: "field description",
Default: "180",
}},
},
{
desc: "slice of struct",
element: &struct {
Foo *struct {
Fii []struct {
Field1 string `description:"field1 description"`
Field2 int `description:"field2 description"`
} `description:"fii description"`
} `description:"foo description"`
}{},
expected: []parser.Flat{
{
Name: "foo.fii",
Description: "fii description",
Default: "",
},
{
Name: "foo.fii[0].field1",
Description: "field1 description",
Default: "",
},
{
Name: "foo.fii[0].field2",
Description: "field2 description",
Default: "0",
},
},
},
// Skipped: because realistically not needed in Traefik for now.
// {
// desc: "map of map field level 2",
// element: &struct {
// Foo *struct {
// Fii map[string]map[string]string `description:"fii description"`
// } `description:"foo description"`
// }{
// Foo: &struct {
// Fii map[string]map[string]string `description:"fii description"`
// }{
// Fii: map[string]map[string]string{
// parser.MapNamePlaceholder: {
// parser.MapNamePlaceholder: "test",
// },
// },
// },
// },
// expected: `XXX`,
// },
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
generator.Generate(test.element)
entries, err := Encode(test.element)
require.NoError(t, err)
assert.Equal(t, test.expected, entries)
})
}
}

View file

@ -0,0 +1,108 @@
package flag
import (
"fmt"
"reflect"
"strings"
)
// Parse parses the command-line flag arguments into a map,
// using the type information in element to discriminate whether a flag is supposed to be a bool,
// and other such ambiguities.
func Parse(args []string, element interface{}) (map[string]string, error) {
f := flagSet{
flagTypes: getFlagTypes(element),
args: args,
values: make(map[string]string),
}
for {
seen, err := f.parseOne()
if seen {
continue
}
if err == nil {
break
}
return nil, err
}
return f.values, nil
}
type flagSet struct {
flagTypes map[string]reflect.Kind
args []string
values map[string]string
}
func (f *flagSet) parseOne() (bool, error) {
if len(f.args) == 0 {
return false, nil
}
s := f.args[0]
if len(s) < 2 || s[0] != '-' {
return false, nil
}
numMinuses := 1
if s[1] == '-' {
numMinuses++
if len(s) == 2 { // "--" terminates the flags
f.args = f.args[1:]
return false, nil
}
}
name := s[numMinuses:]
if len(name) == 0 || name[0] == '-' || name[0] == '=' {
return false, fmt.Errorf("bad flag syntax: %s", s)
}
// it's a flag. does it have an argument?
f.args = f.args[1:]
hasValue := false
value := ""
for i := 1; i < len(name); i++ { // equals cannot be first
if name[i] == '=' {
value = name[i+1:]
hasValue = true
name = name[0:i]
break
}
}
if hasValue {
f.setValue(name, value)
return true, nil
}
if f.flagTypes[name] == reflect.Bool || f.flagTypes[name] == reflect.Ptr {
f.setValue(name, "true")
return true, nil
}
if len(f.args) > 0 {
// value is the next arg
hasValue = true
value, f.args = f.args[0], f.args[1:]
}
if !hasValue {
return false, fmt.Errorf("flag needs an argument: -%s", name)
}
f.setValue(name, value)
return true, nil
}
func (f *flagSet) setValue(name string, value string) {
n := strings.ToLower("traefik." + name)
v, ok := f.values[n]
if ok && f.flagTypes[name] == reflect.Slice {
f.values[n] = v + "," + value
return
}
f.values[n] = value
}

View file

@ -0,0 +1,255 @@
package flag
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParse(t *testing.T) {
testCases := []struct {
desc string
args []string
element interface{}
expected map[string]string
}{
{
desc: "no args",
args: nil,
expected: map[string]string{},
},
{
desc: "bool value",
args: []string{"--foo"},
element: &struct {
Foo bool
}{},
expected: map[string]string{
"traefik.foo": "true",
},
},
{
desc: "equal",
args: []string{"--foo=bar"},
element: &struct {
Foo string
}{},
expected: map[string]string{
"traefik.foo": "bar",
},
},
{
desc: "space separated",
args: []string{"--foo", "bar"},
element: &struct {
Foo string
}{},
expected: map[string]string{
"traefik.foo": "bar",
},
},
{
desc: "space separated with end of parameter",
args: []string{"--foo=bir", "--", "--bar"},
element: &struct {
Foo string
}{},
expected: map[string]string{
"traefik.foo": "bir",
},
},
{
desc: "multiple bool flags without value",
args: []string{"--foo", "--bar"},
element: &struct {
Foo bool
Bar bool
}{},
expected: map[string]string{
"traefik.foo": "true",
"traefik.bar": "true",
},
},
{
desc: "slice with several flags",
args: []string{"--foo=bar", "--foo=baz"},
element: &struct {
Foo []string
}{},
expected: map[string]string{
"traefik.foo": "bar,baz",
},
},
{
desc: "map string",
args: []string{"--foo.name=bar"},
element: &struct {
Foo map[string]string
}{},
expected: map[string]string{
"traefik.foo.name": "bar",
},
},
{
desc: "map struct",
args: []string{"--foo.name.value=bar"},
element: &struct {
Foo map[string]struct{ Value string }
}{},
expected: map[string]string{
"traefik.foo.name.value": "bar",
},
},
{
desc: "map struct with sub-struct",
args: []string{"--foo.name.bar.value=bar"},
element: &struct {
Foo map[string]struct {
Bar *struct{ Value string }
}
}{},
expected: map[string]string{
"traefik.foo.name.bar.value": "bar",
},
},
{
desc: "map struct with sub-map",
args: []string{"--foo.name1.bar.name2.value=bar"},
element: &struct {
Foo map[string]struct {
Bar map[string]struct{ Value string }
}
}{},
expected: map[string]string{
"traefik.foo.name1.bar.name2.value": "bar",
},
},
{
desc: "slice with several flags 2",
args: []string{"--foo", "bar", "--foo", "baz"},
element: &struct {
Foo []string
}{},
expected: map[string]string{
"traefik.foo": "bar,baz",
},
},
{
desc: "slice with several flags 3",
args: []string{"--foo", "bar", "--foo=", "--baz"},
element: &struct {
Foo []string
Baz bool
}{},
expected: map[string]string{
"traefik.foo": "bar,",
"traefik.baz": "true",
},
},
{
desc: "slice with several flags 4",
args: []string{"--foo", "bar", "--foo", "--baz"},
element: &struct {
Foo []string
Baz bool
}{},
expected: map[string]string{
"traefik.foo": "bar,--baz",
},
},
{
desc: "multiple string flag",
element: &struct {
Foo string
}{},
args: []string{"--foo=bar", "--foo=baz"},
expected: map[string]string{
"traefik.foo": "baz",
},
},
{
desc: "multiple string flag 2",
element: &struct {
Foo string
}{},
args: []string{"--foo", "bar", "--foo", "baz"},
expected: map[string]string{
"traefik.foo": "baz",
},
},
{
desc: "string without value",
element: &struct {
Foo string
Bar bool
}{},
args: []string{"--foo", "--bar"},
expected: map[string]string{
"traefik.foo": "--bar",
},
},
{
desc: "struct pointer value",
args: []string{"--foo"},
element: &struct {
Foo *struct{ Field string }
}{},
expected: map[string]string{
"traefik.foo": "true",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
fl, err := Parse(test.args, test.element)
require.NoError(t, err)
assert.Equal(t, test.expected, fl)
})
}
}
func TestParse_Errors(t *testing.T) {
testCases := []struct {
desc string
args []string
element interface{}
}{
{
desc: "triple hyphen",
args: []string{"---foo"},
element: &struct {
Foo bool
}{},
},
{
desc: "equal",
args: []string{"--=foo"},
element: &struct {
Foo bool
}{},
},
{
desc: "string without value",
element: &struct {
Foo string
Bar bool
}{},
args: []string{"--foo"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
_, err := Parse(test.args, test.element)
require.Error(t, err)
})
}
}

View file

@ -0,0 +1,60 @@
package flag
import (
"reflect"
"strings"
"github.com/containous/traefik/pkg/config/parser"
)
func getFlagTypes(element interface{}) map[string]reflect.Kind {
ref := map[string]reflect.Kind{}
if element == nil {
return ref
}
tp := reflect.TypeOf(element).Elem()
addFlagType(ref, "", tp)
return ref
}
func addFlagType(ref map[string]reflect.Kind, name string, typ reflect.Type) {
switch typ.Kind() {
case reflect.Bool, reflect.Slice:
ref[name] = typ.Kind()
case reflect.Map:
addFlagType(ref, getName(name, parser.MapNamePlaceholder), typ.Elem())
case reflect.Ptr:
if typ.Elem().Kind() == reflect.Struct {
ref[name] = typ.Kind()
}
addFlagType(ref, name, typ.Elem())
case reflect.Struct:
for j := 0; j < typ.NumField(); j++ {
subField := typ.Field(j)
if !parser.IsExported(subField) {
continue
}
if subField.Anonymous {
addFlagType(ref, getName(name), subField.Type)
} else {
addFlagType(ref, getName(name, subField.Name), subField.Type)
}
}
default:
// noop
}
}
func getName(names ...string) string {
return strings.TrimPrefix(strings.ToLower(strings.Join(names, ".")), ".")
}

View file

@ -0,0 +1,226 @@
package flag
import (
"reflect"
"testing"
"github.com/containous/traefik/pkg/config/parser"
"github.com/stretchr/testify/assert"
)
func Test_getFlagTypes(t *testing.T) {
testCases := []struct {
desc string
element interface{}
expected map[string]reflect.Kind
}{
{
desc: "nil",
element: nil,
expected: map[string]reflect.Kind{},
},
{
desc: "no fields",
element: &struct {
}{},
expected: map[string]reflect.Kind{},
},
{
desc: "string field",
element: &struct {
Foo string
}{},
expected: map[string]reflect.Kind{},
},
{
desc: "bool field level 0",
element: &struct {
Foo bool
fii bool
}{},
expected: map[string]reflect.Kind{
"foo": reflect.Bool,
},
},
{
desc: "bool field level 1",
element: &struct {
Foo struct {
Field bool
}
}{},
expected: map[string]reflect.Kind{
"foo.field": reflect.Bool,
},
},
{
desc: "bool field level 2",
element: &struct {
Foo *struct {
Fii *struct {
Field bool
}
}
}{},
expected: map[string]reflect.Kind{
"foo": reflect.Ptr,
"foo.fii": reflect.Ptr,
"foo.fii.field": reflect.Bool,
},
},
{
desc: "pointer field",
element: &struct {
Foo *struct {
Field string
}
}{},
expected: map[string]reflect.Kind{
"foo": reflect.Ptr,
},
},
{
desc: "bool field level 3",
element: &struct {
Foo *struct {
Fii *struct {
Fuu *struct {
Field bool
}
}
}
}{},
expected: map[string]reflect.Kind{
"foo": reflect.Ptr,
"foo.fii": reflect.Ptr,
"foo.fii.fuu": reflect.Ptr,
"foo.fii.fuu.field": reflect.Bool,
},
},
{
desc: "map string",
element: &struct {
Foo map[string]string
}{},
expected: map[string]reflect.Kind{},
},
{
desc: "map bool",
element: &struct {
Foo map[string]bool
Fii struct{}
}{},
expected: map[string]reflect.Kind{
"foo." + parser.MapNamePlaceholder: reflect.Bool,
},
},
{
desc: "map struct",
element: &struct {
Foo map[string]struct {
Field bool
}
}{},
expected: map[string]reflect.Kind{
"foo." + parser.MapNamePlaceholder + ".field": reflect.Bool,
},
},
{
desc: "map map bool",
element: &struct {
Foo map[string]map[string]bool
}{},
expected: map[string]reflect.Kind{
"foo." + parser.MapNamePlaceholder + "." + parser.MapNamePlaceholder: reflect.Bool,
},
},
{
desc: "map struct map",
element: &struct {
Foo map[string]struct {
Fii map[string]bool
}
}{},
expected: map[string]reflect.Kind{
"foo." + parser.MapNamePlaceholder + ".fii." + parser.MapNamePlaceholder: reflect.Bool,
},
},
{
desc: "pointer bool field level 0",
element: &struct {
Foo *bool
}{},
expected: map[string]reflect.Kind{
"foo": reflect.Bool,
},
},
{
desc: "pointer int field level 0",
element: &struct {
Foo *int
}{},
expected: map[string]reflect.Kind{},
},
{
desc: "bool slice field level 0",
element: &struct {
Foo []bool
}{},
expected: map[string]reflect.Kind{
"foo": reflect.Slice,
},
},
{
desc: "string slice field level 0",
element: &struct {
Foo []string
}{},
expected: map[string]reflect.Kind{
"foo": reflect.Slice,
},
},
{
desc: "slice field level 1",
element: &struct {
Foo struct {
Field []string
}
}{},
expected: map[string]reflect.Kind{
"foo.field": reflect.Slice,
},
},
{
desc: "map slice string",
element: &struct {
Foo map[string][]string
}{},
expected: map[string]reflect.Kind{
"foo." + parser.MapNamePlaceholder: reflect.Slice,
},
},
{
desc: "embedded struct",
element: &struct {
Yo
}{},
expected: map[string]reflect.Kind{
"foo": reflect.Bool,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := getFlagTypes(test.element)
assert.Equal(t, test.expected, actual)
})
}
}
type Yo struct {
Foo bool
}