1
0
Fork 0

Merge branch v2.3 into master

This commit is contained in:
Fernandez Ludovic 2020-08-26 12:22:39 +02:00
commit 483e2c43cf
153 changed files with 2466 additions and 14841 deletions

View file

@ -178,9 +178,9 @@ type HealthCheck struct {
Scheme string `json:"scheme,omitempty" toml:"scheme,omitempty" yaml:"scheme,omitempty"`
Path string `json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty"`
Port int `json:"port,omitempty" toml:"port,omitempty,omitzero" yaml:"port,omitempty"`
// FIXME change string to types.Duration
// FIXME change string to ptypes.Duration
Interval string `json:"interval,omitempty" toml:"interval,omitempty" yaml:"interval,omitempty"`
// FIXME change string to types.Duration
// FIXME change string to ptypes.Duration
Timeout string `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty"`
Hostname string `json:"hostname,omitempty" toml:"hostname,omitempty" yaml:"hostname,omitempty"`
FollowRedirects *bool `json:"followRedirects" toml:"followRedirects" yaml:"followRedirects"`

View file

@ -9,7 +9,7 @@ import (
"time"
"github.com/containous/traefik/v2/pkg/ip"
"github.com/containous/traefik/v2/pkg/types"
ptypes "github.com/traefik/paerser/types"
)
// +k8s:deepcopy-gen=true
@ -319,7 +319,7 @@ type RateLimit struct {
// Period, in combination with Average, defines the actual maximum rate, such as:
// r = Average / Period. It defaults to a second.
Period types.Duration `json:"period,omitempty" toml:"period,omitempty" yaml:"period,omitempty"`
Period ptypes.Duration `json:"period,omitempty" toml:"period,omitempty" yaml:"period,omitempty"`
// Burst is the maximum number of requests allowed to arrive in the same arbitrarily small period of time.
// It defaults to 1.
@ -331,7 +331,7 @@ type RateLimit struct {
// SetDefaults sets the default values on a RateLimit.
func (r *RateLimit) SetDefaults() {
r.Burst = 1
r.Period = types.Duration(time.Second)
r.Period = ptypes.Duration(time.Second)
}
// +k8s:deepcopy-gen=true

77
pkg/config/env/env.go vendored
View file

@ -1,77 +0,0 @@
// Package env implements encoding and decoding between environment variable and a typed Configuration.
package env
import (
"fmt"
"regexp"
"strings"
"github.com/containous/traefik/v2/pkg/config/parser"
)
// DefaultNamePrefix is the default prefix for environment variable names.
const DefaultNamePrefix = "TRAEFIK_"
// Decode decodes the given environment variables into the given element.
// The operation goes through four stages roughly summarized as:
// env vars -> map
// map -> tree of untyped nodes
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
// "typed" nodes -> typed element.
func Decode(environ []string, prefix string, element interface{}) error {
if err := checkPrefix(prefix); err != nil {
return err
}
vars := make(map[string]string)
for _, evr := range environ {
n := strings.SplitN(evr, "=", 2)
if strings.HasPrefix(strings.ToUpper(n[0]), prefix) {
key := strings.ReplaceAll(strings.ToLower(n[0]), "_", ".")
vars[key] = n[1]
}
}
rootName := strings.ToLower(prefix[:len(prefix)-1])
return parser.Decode(vars, element, rootName)
}
// Encode encodes the configuration in element into the environment variables 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 -> environment variables with default values (determined by type/kind).
func Encode(element interface{}) ([]parser.Flat, error) {
if element == nil {
return nil, nil
}
etnOpts := parser.EncoderToNodeOpts{OmitEmpty: false, TagName: parser.TagLabel, AllowSliceAsStruct: true}
node, err := parser.EncodeToNode(element, parser.DefaultRootName, etnOpts)
if err != nil {
return nil, err
}
metaOpts := parser.MetadataOpts{TagName: parser.TagLabel, AllowSliceAsStruct: true}
err = parser.AddMetadata(element, node, metaOpts)
if err != nil {
return nil, err
}
flatOpts := parser.FlatOpts{Case: "upper", Separator: "_", TagName: parser.TagLabel}
return parser.EncodeToFlat(element, node, flatOpts)
}
func checkPrefix(prefix string) error {
prefixPattern := `[a-zA-Z0-9]+_`
matched, err := regexp.MatchString(prefixPattern, prefix)
if err != nil {
return err
}
if !matched {
return fmt.Errorf("invalid prefix %q, the prefix pattern must match the following pattern: %s", prefix, prefixPattern)
}
return nil
}

View file

@ -1,462 +0,0 @@
package env
import (
"testing"
"github.com/containous/traefik/v2/pkg/config/generator"
"github.com/containous/traefik/v2/pkg/config/parser"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDecode(t *testing.T) {
testCases := []struct {
desc string
environ []string
element interface{}
expected interface{}
}{
{
desc: "no env vars",
environ: nil,
expected: nil,
},
{
desc: "bool value",
environ: []string{"TRAEFIK_FOO=true"},
element: &struct {
Foo bool
}{},
expected: &struct {
Foo bool
}{
Foo: true,
},
},
{
desc: "equal",
environ: []string{"TRAEFIK_FOO=bar"},
element: &struct {
Foo string
}{},
expected: &struct {
Foo string
}{
Foo: "bar",
},
},
{
desc: "multiple bool flags without value",
environ: []string{"TRAEFIK_FOO=true", "TRAEFIK_BAR=true"},
element: &struct {
Foo bool
Bar bool
}{},
expected: &struct {
Foo bool
Bar bool
}{
Foo: true,
Bar: true,
},
},
{
desc: "map string",
environ: []string{"TRAEFIK_FOO_NAME=bar"},
element: &struct {
Foo map[string]string
}{},
expected: &struct {
Foo map[string]string
}{
Foo: map[string]string{
"name": "bar",
},
},
},
{
desc: "map struct",
environ: []string{"TRAEFIK_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",
environ: []string{"TRAEFIK_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",
environ: []string{"TRAEFIK_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",
environ: []string{"TRAEFIK_FOO=bar,baz"},
element: &struct {
Foo []string
}{},
expected: &struct {
Foo []string
}{
Foo: []string{"bar", "baz"},
},
},
{
desc: "struct pointer value",
environ: []string{"TRAEFIK_FOO=true"},
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.environ, DefaultNamePrefix, test.element)
require.NoError(t, err)
assert.Equal(t, test.expected, test.element)
})
}
}
func TestEncode(t *testing.T) {
element := &Ya{
Foo: &Yaa{
FieldIn1: "bar",
FieldIn2: false,
FieldIn3: 1,
FieldIn4: map[string]string{
parser.MapNamePlaceholder: "",
},
FieldIn5: map[string]int{
parser.MapNamePlaceholder: 0,
},
FieldIn6: map[string]struct{ Field string }{
parser.MapNamePlaceholder: {},
},
FieldIn7: map[string]struct{ Field map[string]string }{
parser.MapNamePlaceholder: {
Field: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
FieldIn8: map[string]*struct{ Field string }{
parser.MapNamePlaceholder: {},
},
FieldIn9: map[string]*struct{ Field map[string]string }{
parser.MapNamePlaceholder: {
Field: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
FieldIn10: struct{ Field string }{},
FieldIn11: &struct{ Field string }{},
FieldIn12: func(v string) *string { return &v }(""),
FieldIn13: func(v bool) *bool { return &v }(false),
FieldIn14: func(v int) *int { return &v }(0),
},
Field1: "bir",
Field2: true,
Field3: 0,
Field4: map[string]string{
parser.MapNamePlaceholder: "",
},
Field5: map[string]int{
parser.MapNamePlaceholder: 0,
},
Field6: map[string]struct{ Field string }{
parser.MapNamePlaceholder: {},
},
Field7: map[string]struct{ Field map[string]string }{
parser.MapNamePlaceholder: {
Field: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
Field8: map[string]*struct{ Field string }{
parser.MapNamePlaceholder: {},
},
Field9: map[string]*struct{ Field map[string]string }{
parser.MapNamePlaceholder: {
Field: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
Field10: struct{ Field string }{},
Field11: &struct{ Field string }{},
Field12: func(v string) *string { return &v }(""),
Field13: func(v bool) *bool { return &v }(false),
Field14: func(v int) *int { return &v }(0),
Field15: []int{7},
}
generator.Generate(element)
flats, err := Encode(element)
require.NoError(t, err)
expected := []parser.Flat{
{
Name: "TRAEFIK_FIELD1",
Description: "",
Default: "bir",
},
{
Name: "TRAEFIK_FIELD10",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FIELD10_FIELD",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FIELD11_FIELD",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FIELD12",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FIELD13",
Description: "",
Default: "false",
},
{
Name: "TRAEFIK_FIELD14",
Description: "",
Default: "0",
},
{
Name: "TRAEFIK_FIELD15",
Description: "",
Default: "7",
},
{
Name: "TRAEFIK_FIELD2",
Description: "",
Default: "true",
},
{
Name: "TRAEFIK_FIELD3",
Description: "",
Default: "0",
},
{
Name: "TRAEFIK_FIELD4_\u003cNAME\u003e",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FIELD5_\u003cNAME\u003e",
Description: "",
Default: "0",
},
{
Name: "TRAEFIK_FIELD6_\u003cNAME\u003e",
Description: "",
Default: "false",
},
{
Name: "TRAEFIK_FIELD6_\u003cNAME\u003e_FIELD",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FIELD7_\u003cNAME\u003e",
Description: "",
Default: "false",
},
{
Name: "TRAEFIK_FIELD7_\u003cNAME\u003e_FIELD_\u003cNAME\u003e",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FIELD8_\u003cNAME\u003e",
Description: "",
Default: "false",
},
{
Name: "TRAEFIK_FIELD8_\u003cNAME\u003e_FIELD",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FIELD9_\u003cNAME\u003e",
Description: "",
Default: "false",
},
{
Name: "TRAEFIK_FIELD9_\u003cNAME\u003e_FIELD_\u003cNAME\u003e",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FOO_FIELDIN1",
Description: "",
Default: "bar",
},
{
Name: "TRAEFIK_FOO_FIELDIN10",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FOO_FIELDIN10_FIELD",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FOO_FIELDIN11_FIELD",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FOO_FIELDIN12",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FOO_FIELDIN13",
Description: "",
Default: "false",
},
{
Name: "TRAEFIK_FOO_FIELDIN14",
Description: "",
Default: "0",
},
{
Name: "TRAEFIK_FOO_FIELDIN2",
Description: "",
Default: "false",
},
{
Name: "TRAEFIK_FOO_FIELDIN3",
Description: "",
Default: "1",
},
{
Name: "TRAEFIK_FOO_FIELDIN4_\u003cNAME\u003e",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FOO_FIELDIN5_\u003cNAME\u003e",
Description: "",
Default: "0",
},
{
Name: "TRAEFIK_FOO_FIELDIN6_\u003cNAME\u003e",
Description: "",
Default: "false",
},
{
Name: "TRAEFIK_FOO_FIELDIN6_\u003cNAME\u003e_FIELD",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FOO_FIELDIN7_\u003cNAME\u003e",
Description: "",
Default: "false",
},
{
Name: "TRAEFIK_FOO_FIELDIN7_\u003cNAME\u003e_FIELD_\u003cNAME\u003e",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FOO_FIELDIN8_\u003cNAME\u003e",
Description: "",
Default: "false",
},
{
Name: "TRAEFIK_FOO_FIELDIN8_\u003cNAME\u003e_FIELD",
Description: "",
Default: "",
},
{
Name: "TRAEFIK_FOO_FIELDIN9_\u003cNAME\u003e",
Description: "",
Default: "false",
},
{
Name: "TRAEFIK_FOO_FIELDIN9_\u003cNAME\u003e_FIELD_\u003cNAME\u003e",
Description: "",
Default: "",
},
}
assert.Equal(t, expected, flats)
}

View file

@ -1,64 +0,0 @@
package env
import (
"reflect"
"strings"
"github.com/containous/traefik/v2/pkg/config/parser"
)
// FindPrefixedEnvVars finds prefixed environment variables.
func FindPrefixedEnvVars(environ []string, prefix string, element interface{}) []string {
prefixes := getRootPrefixes(element, prefix)
var values []string
for _, px := range prefixes {
for _, value := range environ {
if strings.HasPrefix(value, px) {
values = append(values, value)
}
}
}
return values
}
func getRootPrefixes(element interface{}, prefix string) []string {
if element == nil {
return nil
}
rootType := reflect.TypeOf(element)
return getPrefixes(prefix, rootType)
}
func getPrefixes(prefix string, rootType reflect.Type) []string {
var names []string
if rootType.Kind() == reflect.Ptr {
rootType = rootType.Elem()
}
if rootType.Kind() != reflect.Struct {
return nil
}
for i := 0; i < rootType.NumField(); i++ {
field := rootType.Field(i)
if !parser.IsExported(field) {
continue
}
if field.Anonymous &&
(field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct || field.Type.Kind() == reflect.Struct) {
names = append(names, getPrefixes(prefix, field.Type)...)
continue
}
names = append(names, prefix+strings.ToUpper(field.Name))
}
return names
}

View file

@ -1,87 +0,0 @@
package env
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFindPrefixedEnvVars(t *testing.T) {
testCases := []struct {
desc string
environ []string
element interface{}
expected []string
}{
{
desc: "exact name",
environ: []string{"TRAEFIK_FOO"},
element: &Yo{},
expected: []string{"TRAEFIK_FOO"},
},
{
desc: "prefixed name",
environ: []string{"TRAEFIK_FII01"},
element: &Yo{},
expected: []string{"TRAEFIK_FII01"},
},
{
desc: "excluded env vars",
environ: []string{"TRAEFIK_NOPE", "TRAEFIK_NO"},
element: &Yo{},
expected: nil,
},
{
desc: "filter",
environ: []string{"TRAEFIK_NOPE", "TRAEFIK_NO", "TRAEFIK_FOO", "TRAEFIK_FII01"},
element: &Yo{},
expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII01"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
vars := FindPrefixedEnvVars(test.environ, DefaultNamePrefix, test.element)
assert.Equal(t, test.expected, vars)
})
}
}
func Test_getRootFieldNames(t *testing.T) {
testCases := []struct {
desc string
element interface{}
expected []string
}{
{
desc: "simple fields",
element: &Yo{},
expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII", "TRAEFIK_FUU", "TRAEFIK_YI", "TRAEFIK_YU"},
},
{
desc: "embedded struct",
element: &Yu{},
expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII", "TRAEFIK_FUU"},
},
{
desc: "embedded struct pointer",
element: &Ye{},
expected: []string{"TRAEFIK_FOO", "TRAEFIK_FII", "TRAEFIK_FUU"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
names := getRootPrefixes(test.element, DefaultNamePrefix)
assert.Equal(t, test.expected, names)
})
}
}

View file

@ -1,69 +0,0 @@
package env
type Ya struct {
Foo *Yaa
Field1 string
Field2 bool
Field3 int
Field4 map[string]string
Field5 map[string]int
Field6 map[string]struct{ Field string }
Field7 map[string]struct{ Field map[string]string }
Field8 map[string]*struct{ Field string }
Field9 map[string]*struct{ Field map[string]string }
Field10 struct{ Field string }
Field11 *struct{ Field string }
Field12 *string
Field13 *bool
Field14 *int
Field15 []int
}
type Yaa struct {
FieldIn1 string
FieldIn2 bool
FieldIn3 int
FieldIn4 map[string]string
FieldIn5 map[string]int
FieldIn6 map[string]struct{ Field string }
FieldIn7 map[string]struct{ Field map[string]string }
FieldIn8 map[string]*struct{ Field string }
FieldIn9 map[string]*struct{ Field map[string]string }
FieldIn10 struct{ Field string }
FieldIn11 *struct{ Field string }
FieldIn12 *string
FieldIn13 *bool
FieldIn14 *int
}
type Yo struct {
Foo string `description:"Foo description"`
Fii string `description:"Fii description"`
Fuu string `description:"Fuu description"`
Yi *Yi `label:"allowEmpty"`
Yu *Yi
}
func (y *Yo) SetDefaults() {
y.Foo = "foo"
y.Fii = "fii"
}
type Yi struct {
Foo string
Fii string
Fuu string
}
func (y *Yi) SetDefaults() {
y.Foo = "foo"
y.Fii = "fii"
}
type Yu struct {
Yi
}
type Ye struct {
*Yi
}

View file

@ -1,82 +0,0 @@
// Package file implements decoding between configuration in a file and a typed Configuration.
package file
import (
"fmt"
"github.com/BurntSushi/toml"
"github.com/containous/traefik/v2/pkg/config/parser"
"gopkg.in/yaml.v2"
)
// Decode decodes the given configuration file into the given element.
// The operation goes through three stages roughly summarized as:
// file contents -> tree of untyped nodes
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
// "typed" nodes -> typed element.
func Decode(filePath string, element interface{}) error {
if element == nil {
return nil
}
filters := getRootFieldNames(element)
root, err := decodeFileToNode(filePath, filters...)
if err != nil {
return err
}
metaOpts := parser.MetadataOpts{TagName: parser.TagFile, AllowSliceAsStruct: false}
err = parser.AddMetadata(element, root, metaOpts)
if err != nil {
return err
}
return parser.Fill(element, root, parser.FillerOpts{AllowSliceAsStruct: false})
}
// DecodeContent decodes the given configuration file content into the given element.
// The operation goes through three stages roughly summarized as:
// file contents -> tree of untyped nodes
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
// "typed" nodes -> typed element.
func DecodeContent(content, extension string, element interface{}) error {
data := make(map[string]interface{})
switch extension {
case ".toml":
_, err := toml.Decode(content, &data)
if err != nil {
return err
}
case ".yml", ".yaml":
var err error
err = yaml.Unmarshal([]byte(content), &data)
if err != nil {
return err
}
default:
return fmt.Errorf("unsupported file extension: %s", extension)
}
filters := getRootFieldNames(element)
node, err := decodeRawToNode(data, parser.DefaultRootName, filters...)
if err != nil {
return err
}
if len(node.Children) == 0 {
return nil
}
metaOpts := parser.MetadataOpts{TagName: parser.TagFile, AllowSliceAsStruct: false}
err = parser.AddMetadata(element, node, metaOpts)
if err != nil {
return err
}
return parser.Fill(element, node, parser.FillerOpts{AllowSliceAsStruct: false})
}

View file

@ -1,96 +0,0 @@
package file
import (
"fmt"
"io/ioutil"
"path/filepath"
"reflect"
"strings"
"github.com/BurntSushi/toml"
"github.com/containous/traefik/v2/pkg/config/parser"
"gopkg.in/yaml.v2"
)
// decodeFileToNode decodes the configuration in filePath in a tree of untyped nodes.
// If filters is not empty, it skips any configuration element whose name is not among filters.
func decodeFileToNode(filePath string, filters ...string) (*parser.Node, error) {
content, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
data := make(map[string]interface{})
switch strings.ToLower(filepath.Ext(filePath)) {
case ".toml":
err = toml.Unmarshal(content, &data)
if err != nil {
return nil, err
}
case ".yml", ".yaml":
err = yaml.Unmarshal(content, data)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported file extension: %s", filePath)
}
if len(data) == 0 {
return nil, fmt.Errorf("no configuration found in file: %s", filePath)
}
node, err := decodeRawToNode(data, parser.DefaultRootName, filters...)
if err != nil {
return nil, err
}
if len(node.Children) == 0 {
return nil, fmt.Errorf("no valid configuration found in file: %s", filePath)
}
return node, nil
}
func getRootFieldNames(element interface{}) []string {
if element == nil {
return nil
}
rootType := reflect.TypeOf(element)
return getFieldNames(rootType)
}
func getFieldNames(rootType reflect.Type) []string {
var names []string
if rootType.Kind() == reflect.Ptr {
rootType = rootType.Elem()
}
if rootType.Kind() != reflect.Struct {
return nil
}
for i := 0; i < rootType.NumField(); i++ {
field := rootType.Field(i)
if !parser.IsExported(field) {
continue
}
if field.Anonymous &&
(field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct || field.Type.Kind() == reflect.Struct) {
names = append(names, getFieldNames(field.Type)...)
continue
}
names = append(names, field.Name)
}
return names
}

View file

@ -1,646 +0,0 @@
package file
import (
"testing"
"github.com/containous/traefik/v2/pkg/config/parser"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_getRootFieldNames(t *testing.T) {
testCases := []struct {
desc string
element interface{}
expected []string
}{
{
desc: "simple fields",
element: &Yo{},
expected: []string{"Foo", "Fii", "Fuu", "Yi"},
},
{
desc: "embedded struct",
element: &Yu{},
expected: []string{"Foo", "Fii", "Fuu"},
},
{
desc: "embedded struct pointer",
element: &Ye{},
expected: []string{"Foo", "Fii", "Fuu"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
names := getRootFieldNames(test.element)
assert.Equal(t, test.expected, names)
})
}
}
func Test_decodeFileToNode_errors(t *testing.T) {
testCases := []struct {
desc string
confFile string
}{
{
desc: "non existing file",
confFile: "./fixtures/not_existing.toml",
},
{
desc: "file without content",
confFile: "./fixtures/empty.toml",
},
{
desc: "file without any valid configuration",
confFile: "./fixtures/no_conf.toml",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
node, err := decodeFileToNode(test.confFile,
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "CertificatesResolvers")
require.Error(t, err)
assert.Nil(t, node)
})
}
}
func Test_decodeFileToNode_compare(t *testing.T) {
nodeToml, err := decodeFileToNode("./fixtures/sample.toml",
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "CertificatesResolvers")
require.NoError(t, err)
nodeYaml, err := decodeFileToNode("./fixtures/sample.yml")
require.NoError(t, err)
assert.Equal(t, nodeToml, nodeYaml)
}
func Test_decodeFileToNode_Toml(t *testing.T) {
node, err := decodeFileToNode("./fixtures/sample.toml",
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "CertificatesResolvers")
require.NoError(t, err)
expected := &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "accessLog", Children: []*parser.Node{
{Name: "bufferingSize", Value: "42"},
{Name: "fields", Children: []*parser.Node{
{Name: "defaultMode", Value: "foobar"},
{Name: "headers", Children: []*parser.Node{
{Name: "defaultMode", Value: "foobar"},
{Name: "names", Children: []*parser.Node{
{Name: "name0", Value: "foobar"},
{Name: "name1", Value: "foobar"},
}},
}},
{Name: "names", Children: []*parser.Node{
{Name: "name0", Value: "foobar"},
{Name: "name1", Value: "foobar"},
}},
}},
{Name: "filePath", Value: "foobar"},
{Name: "filters", Children: []*parser.Node{
{Name: "minDuration", Value: "42"},
{Name: "retryAttempts", Value: "true"},
{Name: "statusCodes", Value: "foobar,foobar"},
}},
{Name: "format", Value: "foobar"},
}},
{Name: "api", Children: []*parser.Node{
{Name: "dashboard", Value: "true"},
{Name: "entryPoint", Value: "foobar"},
{Name: "middlewares", Value: "foobar,foobar"},
{Name: "statistics", Children: []*parser.Node{
{Name: "recentErrors", Value: "42"},
}},
}},
{Name: "certificatesResolvers", Children: []*parser.Node{
{Name: "default", Children: []*parser.Node{
{
Name: "acme",
Children: []*parser.Node{
{Name: "acmeLogging", Value: "true"},
{Name: "caServer", Value: "foobar"},
{Name: "dnsChallenge", Children: []*parser.Node{
{Name: "delayBeforeCheck", Value: "42"},
{Name: "disablePropagationCheck", Value: "true"},
{Name: "provider", Value: "foobar"},
{Name: "resolvers", Value: "foobar,foobar"},
}},
{Name: "email", Value: "foobar"},
{Name: "entryPoint", Value: "foobar"},
{Name: "httpChallenge", Children: []*parser.Node{
{Name: "entryPoint", Value: "foobar"},
}},
{Name: "keyType", Value: "foobar"},
{Name: "storage", Value: "foobar"},
{Name: "tlsChallenge"},
},
},
}},
}},
{Name: "entryPoints", Children: []*parser.Node{
{Name: "EntryPoint0", Children: []*parser.Node{
{Name: "address", Value: "foobar"},
{Name: "forwardedHeaders", Children: []*parser.Node{
{Name: "insecure", Value: "true"},
{Name: "trustedIPs", Value: "foobar,foobar"},
}},
{Name: "proxyProtocol", Children: []*parser.Node{
{Name: "insecure", Value: "true"},
{Name: "trustedIPs", Value: "foobar,foobar"},
}},
{Name: "transport", Children: []*parser.Node{
{Name: "lifeCycle", Children: []*parser.Node{
{Name: "graceTimeOut", Value: "42"},
{Name: "requestAcceptGraceTimeout", Value: "42"},
}},
{Name: "respondingTimeouts", Children: []*parser.Node{
{Name: "idleTimeout", Value: "42"},
{Name: "readTimeout", Value: "42"},
{Name: "writeTimeout", Value: "42"},
}},
}},
}},
}},
{Name: "global", Children: []*parser.Node{
{Name: "checkNewVersion", Value: "true"},
{Name: "sendAnonymousUsage", Value: "true"},
}},
{Name: "hostResolver", Children: []*parser.Node{
{Name: "cnameFlattening", Value: "true"},
{Name: "resolvConfig", Value: "foobar"},
{Name: "resolvDepth", Value: "42"},
}},
{Name: "log", Children: []*parser.Node{
{Name: "filePath", Value: "foobar"},
{Name: "format", Value: "foobar"},
{Name: "level", Value: "foobar"},
}},
{Name: "metrics", Children: []*parser.Node{
{Name: "datadog", Children: []*parser.Node{
{Name: "address", Value: "foobar"},
{Name: "pushInterval", Value: "10s"},
}},
{Name: "influxDB", Children: []*parser.Node{
{Name: "address", Value: "foobar"},
{Name: "database", Value: "foobar"},
{Name: "password", Value: "foobar"},
{Name: "protocol", Value: "foobar"},
{Name: "pushInterval", Value: "10s"},
{Name: "retentionPolicy", Value: "foobar"},
{Name: "username", Value: "foobar"},
}},
{Name: "prometheus", Children: []*parser.Node{
{Name: "buckets", Value: "42,42"},
{Name: "entryPoint", Value: "foobar"},
{Name: "middlewares", Value: "foobar,foobar"},
}},
{Name: "statsD", Children: []*parser.Node{
{Name: "address", Value: "foobar"},
{Name: "pushInterval", Value: "10s"},
}},
}},
{Name: "ping", Children: []*parser.Node{
{Name: "entryPoint", Value: "foobar"},
{Name: "middlewares", Value: "foobar,foobar"},
}},
{Name: "providers", Children: []*parser.Node{
{Name: "docker", Children: []*parser.Node{
{Name: "constraints", Value: "foobar"},
{Name: "defaultRule", Value: "foobar"},
{Name: "endpoint", Value: "foobar"},
{Name: "exposedByDefault", Value: "true"},
{Name: "network", Value: "foobar"},
{Name: "swarmMode", Value: "true"},
{Name: "swarmModeRefreshSeconds", Value: "42"},
{Name: "tls", Children: []*parser.Node{
{Name: "ca", Value: "foobar"},
{Name: "caOptional", Value: "true"},
{Name: "cert", Value: "foobar"},
{Name: "insecureSkipVerify", Value: "true"},
{Name: "key", Value: "foobar"},
}},
{Name: "useBindPortIP", Value: "true"},
{Name: "watch", Value: "true"},
}},
{Name: "file", Children: []*parser.Node{
{Name: "debugLogGeneratedTemplate", Value: "true"},
{Name: "directory", Value: "foobar"},
{Name: "filename", Value: "foobar"},
{Name: "watch", Value: "true"},
}},
{
Name: "kubernetesCRD",
Children: []*parser.Node{
{Name: "certAuthFilePath", Value: "foobar"},
{Name: "disablePassHostHeaders", Value: "true"},
{Name: "endpoint", Value: "foobar"},
{Name: "ingressClass", Value: "foobar"},
{Name: "labelSelector", Value: "foobar"},
{Name: "namespaces", Value: "foobar,foobar"},
{Name: "token", Value: "foobar"},
},
},
{Name: "kubernetesIngress", Children: []*parser.Node{
{Name: "certAuthFilePath", Value: "foobar"},
{Name: "disablePassHostHeaders", Value: "true"},
{Name: "endpoint", Value: "foobar"},
{Name: "ingressClass", Value: "foobar"},
{Name: "ingressEndpoint", Children: []*parser.Node{
{Name: "hostname", Value: "foobar"},
{Name: "ip", Value: "foobar"},
{Name: "publishedService", Value: "foobar"},
}},
{Name: "labelSelector", Value: "foobar"},
{Name: "namespaces", Value: "foobar,foobar"},
{Name: "token", Value: "foobar"},
}},
{Name: "marathon", Children: []*parser.Node{
{Name: "basic", Children: []*parser.Node{
{Name: "httpBasicAuthUser", Value: "foobar"},
{Name: "httpBasicPassword", Value: "foobar"},
}},
{Name: "constraints", Value: "foobar"},
{Name: "dcosToken", Value: "foobar"},
{Name: "defaultRule", Value: "foobar"},
{Name: "dialerTimeout", Value: "42"},
{Name: "endpoint", Value: "foobar"},
{Name: "exposedByDefault", Value: "true"},
{Name: "forceTaskHostname", Value: "true"},
{Name: "keepAlive", Value: "42"},
{Name: "respectReadinessChecks", Value: "true"},
{Name: "responseHeaderTimeout", Value: "42"},
{Name: "tls", Children: []*parser.Node{
{Name: "ca", Value: "foobar"},
{Name: "caOptional", Value: "true"},
{Name: "cert", Value: "foobar"},
{Name: "insecureSkipVerify", Value: "true"},
{Name: "key", Value: "foobar"},
}},
{Name: "tlsHandshakeTimeout", Value: "42"},
{Name: "trace", Value: "true"},
{Name: "watch", Value: "true"},
}},
{Name: "providersThrottleDuration", Value: "42"},
{Name: "rancher", Children: []*parser.Node{
{Name: "constraints", Value: "foobar"},
{Name: "defaultRule", Value: "foobar"},
{Name: "enableServiceHealthFilter", Value: "true"},
{Name: "exposedByDefault", Value: "true"},
{Name: "intervalPoll", Value: "true"},
{Name: "prefix", Value: "foobar"},
{Name: "refreshSeconds", Value: "42"},
{Name: "watch", Value: "true"},
}},
{Name: "rest", Children: []*parser.Node{
{Name: "entryPoint", Value: "foobar"},
}},
}},
{Name: "serversTransport", Children: []*parser.Node{
{Name: "forwardingTimeouts", Children: []*parser.Node{
{Name: "dialTimeout", Value: "42"},
{Name: "idleConnTimeout", Value: "42"},
{Name: "responseHeaderTimeout", Value: "42"},
}},
{Name: "insecureSkipVerify", Value: "true"},
{Name: "maxIdleConnsPerHost", Value: "42"},
{Name: "rootCAs", Value: "foobar,foobar"},
}},
{Name: "tracing", Children: []*parser.Node{
{Name: "datadog", Children: []*parser.Node{
{Name: "bagagePrefixHeaderName", Value: "foobar"},
{Name: "debug", Value: "true"},
{Name: "globalTag", Value: "foobar"},
{Name: "localAgentHostPort", Value: "foobar"},
{Name: "parentIDHeaderName", Value: "foobar"},
{Name: "prioritySampling", Value: "true"},
{Name: "samplingPriorityHeaderName", Value: "foobar"},
{Name: "traceIDHeaderName", Value: "foobar"},
}},
{Name: "haystack", Children: []*parser.Node{
{Name: "globalTag", Value: "foobar"},
{Name: "localAgentHost", Value: "foobar"},
{Name: "localAgentPort", Value: "42"},
{Name: "parentIDHeaderName", Value: "foobar"},
{Name: "spanIDHeaderName", Value: "foobar"},
{Name: "traceIDHeaderName", Value: "foobar"},
}},
{Name: "instana", Children: []*parser.Node{
{Name: "localAgentHost", Value: "foobar"},
{Name: "localAgentPort", Value: "42"},
{Name: "logLevel", Value: "foobar"},
}},
{Name: "jaeger", Children: []*parser.Node{
{Name: "gen128Bit", Value: "true"},
{Name: "localAgentHostPort", Value: "foobar"},
{Name: "propagation", Value: "foobar"},
{Name: "samplingParam", Value: "42"},
{Name: "samplingServerURL", Value: "foobar"},
{Name: "samplingType", Value: "foobar"},
{Name: "traceContextHeaderName", Value: "foobar"},
}},
{Name: "serviceName", Value: "foobar"},
{Name: "spanNameLimit", Value: "42"},
{Name: "zipkin", Children: []*parser.Node{
{Name: "httpEndpoint", Value: "foobar"},
{Name: "id128Bit", Value: "true"},
{Name: "sameSpan", Value: "true"},
{Name: "sampleRate", Value: "42"},
}},
}},
},
}
assert.Equal(t, expected, node)
}
func Test_decodeFileToNode_Yaml(t *testing.T) {
node, err := decodeFileToNode("./fixtures/sample.yml")
require.NoError(t, err)
expected := &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "accessLog", Children: []*parser.Node{
{Name: "bufferingSize", Value: "42"},
{Name: "fields", Children: []*parser.Node{
{Name: "defaultMode", Value: "foobar"},
{Name: "headers", Children: []*parser.Node{
{Name: "defaultMode", Value: "foobar"},
{Name: "names", Children: []*parser.Node{
{Name: "name0", Value: "foobar"},
{Name: "name1", Value: "foobar"},
}},
}},
{Name: "names", Children: []*parser.Node{
{Name: "name0", Value: "foobar"},
{Name: "name1", Value: "foobar"},
}},
}},
{Name: "filePath", Value: "foobar"},
{Name: "filters", Children: []*parser.Node{
{Name: "minDuration", Value: "42"},
{Name: "retryAttempts", Value: "true"},
{Name: "statusCodes", Value: "foobar,foobar"},
}},
{Name: "format", Value: "foobar"},
}},
{Name: "api", Children: []*parser.Node{
{Name: "dashboard", Value: "true"},
{Name: "entryPoint", Value: "foobar"},
{Name: "middlewares", Value: "foobar,foobar"},
{Name: "statistics", Children: []*parser.Node{
{Name: "recentErrors", Value: "42"},
}},
}},
{Name: "certificatesResolvers", Children: []*parser.Node{
{Name: "default", Children: []*parser.Node{
{
Name: "acme",
Children: []*parser.Node{
{Name: "acmeLogging", Value: "true"},
{Name: "caServer", Value: "foobar"},
{Name: "dnsChallenge", Children: []*parser.Node{
{Name: "delayBeforeCheck", Value: "42"},
{Name: "disablePropagationCheck", Value: "true"},
{Name: "provider", Value: "foobar"},
{Name: "resolvers", Value: "foobar,foobar"},
}},
{Name: "email", Value: "foobar"},
{Name: "entryPoint", Value: "foobar"},
{Name: "httpChallenge", Children: []*parser.Node{
{Name: "entryPoint", Value: "foobar"},
}},
{Name: "keyType", Value: "foobar"},
{Name: "storage", Value: "foobar"},
{Name: "tlsChallenge"},
},
},
}},
}},
{Name: "entryPoints", Children: []*parser.Node{
{Name: "EntryPoint0", Children: []*parser.Node{
{Name: "address", Value: "foobar"},
{Name: "forwardedHeaders", Children: []*parser.Node{
{Name: "insecure", Value: "true"},
{Name: "trustedIPs", Value: "foobar,foobar"},
}},
{Name: "proxyProtocol", Children: []*parser.Node{
{Name: "insecure", Value: "true"},
{Name: "trustedIPs", Value: "foobar,foobar"},
}},
{Name: "transport", Children: []*parser.Node{
{Name: "lifeCycle", Children: []*parser.Node{
{Name: "graceTimeOut", Value: "42"},
{Name: "requestAcceptGraceTimeout", Value: "42"},
}},
{Name: "respondingTimeouts", Children: []*parser.Node{
{Name: "idleTimeout", Value: "42"},
{Name: "readTimeout", Value: "42"},
{Name: "writeTimeout", Value: "42"},
}},
}},
}},
}},
{Name: "global", Children: []*parser.Node{
{Name: "checkNewVersion", Value: "true"},
{Name: "sendAnonymousUsage", Value: "true"},
}},
{Name: "hostResolver", Children: []*parser.Node{
{Name: "cnameFlattening", Value: "true"},
{Name: "resolvConfig", Value: "foobar"},
{Name: "resolvDepth", Value: "42"},
}},
{Name: "log", Children: []*parser.Node{
{Name: "filePath", Value: "foobar"},
{Name: "format", Value: "foobar"},
{Name: "level", Value: "foobar"},
}},
{Name: "metrics", Children: []*parser.Node{
{Name: "datadog", Children: []*parser.Node{
{Name: "address", Value: "foobar"},
{Name: "pushInterval", Value: "10s"},
}},
{Name: "influxDB", Children: []*parser.Node{
{Name: "address", Value: "foobar"},
{Name: "database", Value: "foobar"},
{Name: "password", Value: "foobar"},
{Name: "protocol", Value: "foobar"},
{Name: "pushInterval", Value: "10s"},
{Name: "retentionPolicy", Value: "foobar"},
{Name: "username", Value: "foobar"},
}},
{Name: "prometheus", Children: []*parser.Node{
{Name: "buckets", Value: "42,42"},
{Name: "entryPoint", Value: "foobar"},
{Name: "middlewares", Value: "foobar,foobar"},
}},
{Name: "statsD", Children: []*parser.Node{
{Name: "address", Value: "foobar"},
{Name: "pushInterval", Value: "10s"},
}},
}},
{Name: "ping", Children: []*parser.Node{
{Name: "entryPoint", Value: "foobar"},
{Name: "middlewares", Value: "foobar,foobar"},
}},
{Name: "providers", Children: []*parser.Node{
{Name: "docker", Children: []*parser.Node{
{Name: "constraints", Value: "foobar"},
{Name: "defaultRule", Value: "foobar"},
{Name: "endpoint", Value: "foobar"},
{Name: "exposedByDefault", Value: "true"},
{Name: "network", Value: "foobar"},
{Name: "swarmMode", Value: "true"},
{Name: "swarmModeRefreshSeconds", Value: "42"},
{Name: "tls", Children: []*parser.Node{
{Name: "ca", Value: "foobar"},
{Name: "caOptional", Value: "true"},
{Name: "cert", Value: "foobar"},
{Name: "insecureSkipVerify", Value: "true"},
{Name: "key", Value: "foobar"},
}},
{Name: "useBindPortIP", Value: "true"},
{Name: "watch", Value: "true"},
}},
{Name: "file", Children: []*parser.Node{
{Name: "debugLogGeneratedTemplate", Value: "true"},
{Name: "directory", Value: "foobar"},
{Name: "filename", Value: "foobar"},
{Name: "watch", Value: "true"},
}},
{
Name: "kubernetesCRD",
Children: []*parser.Node{
{Name: "certAuthFilePath", Value: "foobar"},
{Name: "disablePassHostHeaders", Value: "true"},
{Name: "endpoint", Value: "foobar"},
{Name: "ingressClass", Value: "foobar"},
{Name: "labelSelector", Value: "foobar"},
{Name: "namespaces", Value: "foobar,foobar"},
{Name: "token", Value: "foobar"},
},
},
{Name: "kubernetesIngress", Children: []*parser.Node{
{Name: "certAuthFilePath", Value: "foobar"},
{Name: "disablePassHostHeaders", Value: "true"},
{Name: "endpoint", Value: "foobar"},
{Name: "ingressClass", Value: "foobar"},
{Name: "ingressEndpoint", Children: []*parser.Node{
{Name: "hostname", Value: "foobar"},
{Name: "ip", Value: "foobar"},
{Name: "publishedService", Value: "foobar"},
}},
{Name: "labelSelector", Value: "foobar"},
{Name: "namespaces", Value: "foobar,foobar"},
{Name: "token", Value: "foobar"},
}},
{Name: "marathon", Children: []*parser.Node{
{Name: "basic", Children: []*parser.Node{
{Name: "httpBasicAuthUser", Value: "foobar"},
{Name: "httpBasicPassword", Value: "foobar"},
}},
{Name: "constraints", Value: "foobar"},
{Name: "dcosToken", Value: "foobar"},
{Name: "defaultRule", Value: "foobar"},
{Name: "dialerTimeout", Value: "42"},
{Name: "endpoint", Value: "foobar"},
{Name: "exposedByDefault", Value: "true"},
{Name: "forceTaskHostname", Value: "true"},
{Name: "keepAlive", Value: "42"},
{Name: "respectReadinessChecks", Value: "true"},
{Name: "responseHeaderTimeout", Value: "42"},
{Name: "tls", Children: []*parser.Node{
{Name: "ca", Value: "foobar"},
{Name: "caOptional", Value: "true"},
{Name: "cert", Value: "foobar"},
{Name: "insecureSkipVerify", Value: "true"},
{Name: "key", Value: "foobar"},
}},
{Name: "tlsHandshakeTimeout", Value: "42"},
{Name: "trace", Value: "true"},
{Name: "watch", Value: "true"},
}},
{Name: "providersThrottleDuration", Value: "42"},
{Name: "rancher", Children: []*parser.Node{
{Name: "constraints", Value: "foobar"},
{Name: "defaultRule", Value: "foobar"},
{Name: "enableServiceHealthFilter", Value: "true"},
{Name: "exposedByDefault", Value: "true"},
{Name: "intervalPoll", Value: "true"},
{Name: "prefix", Value: "foobar"},
{Name: "refreshSeconds", Value: "42"},
{Name: "watch", Value: "true"},
}},
{Name: "rest", Children: []*parser.Node{
{Name: "entryPoint", Value: "foobar"},
}},
}},
{Name: "serversTransport", Children: []*parser.Node{
{Name: "forwardingTimeouts", Children: []*parser.Node{
{Name: "dialTimeout", Value: "42"},
{Name: "idleConnTimeout", Value: "42"},
{Name: "responseHeaderTimeout", Value: "42"},
}},
{Name: "insecureSkipVerify", Value: "true"},
{Name: "maxIdleConnsPerHost", Value: "42"},
{Name: "rootCAs", Value: "foobar,foobar"},
}},
{Name: "tracing", Children: []*parser.Node{
{Name: "datadog", Children: []*parser.Node{
{Name: "bagagePrefixHeaderName", Value: "foobar"},
{Name: "debug", Value: "true"},
{Name: "globalTag", Value: "foobar"},
{Name: "localAgentHostPort", Value: "foobar"},
{Name: "parentIDHeaderName", Value: "foobar"},
{Name: "prioritySampling", Value: "true"},
{Name: "samplingPriorityHeaderName", Value: "foobar"},
{Name: "traceIDHeaderName", Value: "foobar"},
}},
{Name: "haystack", Children: []*parser.Node{
{Name: "globalTag", Value: "foobar"},
{Name: "localAgentHost", Value: "foobar"},
{Name: "localAgentPort", Value: "42"},
{Name: "parentIDHeaderName", Value: "foobar"},
{Name: "spanIDHeaderName", Value: "foobar"},
{Name: "traceIDHeaderName", Value: "foobar"},
}},
{Name: "instana", Children: []*parser.Node{
{Name: "localAgentHost", Value: "foobar"},
{Name: "localAgentPort", Value: "42"},
{Name: "logLevel", Value: "foobar"},
}},
{Name: "jaeger", Children: []*parser.Node{
{Name: "gen128Bit", Value: "true"},
{Name: "localAgentHostPort", Value: "foobar"},
{Name: "propagation", Value: "foobar"},
{Name: "samplingParam", Value: "42"},
{Name: "samplingServerURL", Value: "foobar"},
{Name: "samplingType", Value: "foobar"},
{Name: "traceContextHeaderName", Value: "foobar"},
}},
{Name: "serviceName", Value: "foobar"},
{Name: "spanNameLimit", Value: "42"},
{Name: "zipkin", Children: []*parser.Node{
{Name: "httpEndpoint", Value: "foobar"},
{Name: "id128Bit", Value: "true"},
{Name: "sameSpan", Value: "true"},
{Name: "sampleRate", Value: "42"},
}},
}},
},
}
assert.Equal(t, expected, node)
}

View file

@ -1,177 +0,0 @@
package file
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDecode_TOML(t *testing.T) {
f, err := ioutil.TempFile("", "traefik-config-*.toml")
require.NoError(t, err)
defer func() {
_ = os.Remove(f.Name())
}()
_, err = f.Write([]byte(`
foo = "bar"
fii = "bir"
[yi]
`))
require.NoError(t, err)
element := &Yo{
Fuu: "test",
}
err = Decode(f.Name(), element)
require.NoError(t, err)
expected := &Yo{
Foo: "bar",
Fii: "bir",
Fuu: "test",
Yi: &Yi{
Foo: "foo",
Fii: "fii",
},
}
assert.Equal(t, expected, element)
}
func TestDecodeContent_TOML(t *testing.T) {
content := `
foo = "bar"
fii = "bir"
[yi]
`
element := &Yo{
Fuu: "test",
}
err := DecodeContent(content, ".toml", element)
require.NoError(t, err)
expected := &Yo{
Foo: "bar",
Fii: "bir",
Fuu: "test",
Yi: &Yi{
Foo: "foo",
Fii: "fii",
},
}
assert.Equal(t, expected, element)
}
func TestDecodeContent_TOML_rawValue(t *testing.T) {
content := `
name = "test"
[[meta.aaa]]
bbb = 1
`
type Foo struct {
Name string
Meta map[string]interface{}
}
element := &Foo{}
err := DecodeContent(content, ".toml", element)
require.NoError(t, err)
expected := &Foo{
Name: "test",
Meta: map[string]interface{}{"aaa": []interface{}{map[string]interface{}{"bbb": "1"}}},
}
assert.Equal(t, expected, element)
}
func TestDecode_YAML(t *testing.T) {
f, err := ioutil.TempFile("", "traefik-config-*.yaml")
require.NoError(t, err)
defer func() {
_ = os.Remove(f.Name())
}()
_, err = f.Write([]byte(`
foo: bar
fii: bir
yi: {}
`))
require.NoError(t, err)
element := &Yo{
Fuu: "test",
}
err = Decode(f.Name(), element)
require.NoError(t, err)
expected := &Yo{
Foo: "bar",
Fii: "bir",
Fuu: "test",
Yi: &Yi{
Foo: "foo",
Fii: "fii",
},
}
assert.Equal(t, expected, element)
}
func TestDecodeContent_YAML(t *testing.T) {
content := `
foo: bar
fii: bir
yi: {}
`
element := &Yo{
Fuu: "test",
}
err := DecodeContent(content, ".yaml", element)
require.NoError(t, err)
expected := &Yo{
Foo: "bar",
Fii: "bir",
Fuu: "test",
Yi: &Yi{
Foo: "foo",
Fii: "fii",
},
}
assert.Equal(t, expected, element)
}
func TestDecodeContent_YAML_rawValue(t *testing.T) {
content := `
name: test
meta:
aaa:
- bbb: 1
`
type Foo struct {
Name string
Meta map[string]interface{}
}
element := &Foo{}
err := DecodeContent(content, ".yaml", element)
require.NoError(t, err)
expected := &Foo{
Name: "test",
Meta: map[string]interface{}{"aaa": []interface{}{map[string]interface{}{"bbb": "1"}}},
}
assert.Equal(t, expected, element)
}

View file

@ -1,2 +0,0 @@
[foo]
bar = "test"

View file

@ -1,474 +0,0 @@
[global]
checkNewVersion = true
sendAnonymousUsage = true
[serversTransport]
insecureSkipVerify = true
rootCAs = ["foobar", "foobar"]
maxIdleConnsPerHost = 42
[serversTransport.forwardingTimeouts]
dialTimeout = 42
responseHeaderTimeout = 42
idleConnTimeout = 42
[entryPoints]
[entryPoints.EntryPoint0]
address = "foobar"
[entryPoints.EntryPoint0.transport]
[entryPoints.EntryPoint0.transport.lifeCycle]
requestAcceptGraceTimeout = 42
graceTimeOut = 42
[entryPoints.EntryPoint0.transport.respondingTimeouts]
readTimeout = 42
writeTimeout = 42
idleTimeout = 42
[entryPoints.EntryPoint0.proxyProtocol]
insecure = true
trustedIPs = ["foobar", "foobar"]
[entryPoints.EntryPoint0.forwardedHeaders]
insecure = true
trustedIPs = ["foobar", "foobar"]
[providers]
providersThrottleDuration = 42
[providers.docker]
constraints = "foobar"
watch = true
endpoint = "foobar"
defaultRule = "foobar"
exposedByDefault = true
useBindPortIP = true
swarmMode = true
network = "foobar"
swarmModeRefreshSeconds = 42
[providers.docker.tls]
ca = "foobar"
caOptional = true
cert = "foobar"
key = "foobar"
insecureSkipVerify = true
[providers.file]
directory = "foobar"
watch = true
filename = "foobar"
debugLogGeneratedTemplate = true
[providers.marathon]
constraints = "foobar"
trace = true
watch = true
endpoint = "foobar"
defaultRule = "foobar"
exposedByDefault = true
dcosToken = "foobar"
dialerTimeout = 42
responseHeaderTimeout = 42
tlsHandshakeTimeout = 42
keepAlive = 42
forceTaskHostname = true
respectReadinessChecks = true
[providers.marathon.tls]
ca = "foobar"
caOptional = true
cert = "foobar"
key = "foobar"
insecureSkipVerify = true
[providers.marathon.basic]
httpBasicAuthUser = "foobar"
httpBasicPassword = "foobar"
[providers.kubernetesIngress]
endpoint = "foobar"
token = "foobar"
certAuthFilePath = "foobar"
disablePassHostHeaders = true
namespaces = ["foobar", "foobar"]
labelSelector = "foobar"
ingressClass = "foobar"
[providers.kubernetesIngress.ingressEndpoint]
ip = "foobar"
hostname = "foobar"
publishedService = "foobar"
[providers.kubernetesCRD]
endpoint = "foobar"
token = "foobar"
certAuthFilePath = "foobar"
disablePassHostHeaders = true
namespaces = ["foobar", "foobar"]
labelSelector = "foobar"
ingressClass = "foobar"
[providers.rest]
entryPoint = "foobar"
[providers.rancher]
constraints = "foobar"
watch = true
defaultRule = "foobar"
exposedByDefault = true
enableServiceHealthFilter = true
refreshSeconds = 42
intervalPoll = true
prefix = "foobar"
[api]
entryPoint = "foobar"
dashboard = true
middlewares = ["foobar", "foobar"]
[api.statistics]
recentErrors = 42
[metrics]
[metrics.prometheus]
buckets = [42.0, 42.0]
entryPoint = "foobar"
middlewares = ["foobar", "foobar"]
[metrics.datadog]
address = "foobar"
pushInterval = "10s"
[metrics.statsD]
address = "foobar"
pushInterval = "10s"
[metrics.influxDB]
address = "foobar"
protocol = "foobar"
pushInterval = "10s"
database = "foobar"
retentionPolicy = "foobar"
username = "foobar"
password = "foobar"
[ping]
entryPoint = "foobar"
middlewares = ["foobar", "foobar"]
[log]
level = "foobar"
filePath = "foobar"
format = "foobar"
[accessLog]
filePath = "foobar"
format = "foobar"
bufferingSize = 42
[accessLog.filters]
statusCodes = ["foobar", "foobar"]
retryAttempts = true
minDuration = 42
[accessLog.fields]
defaultMode = "foobar"
[accessLog.fields.names]
name0 = "foobar"
name1 = "foobar"
[accessLog.fields.headers]
defaultMode = "foobar"
[accessLog.fields.headers.names]
name0 = "foobar"
name1 = "foobar"
[tracing]
serviceName = "foobar"
spanNameLimit = 42
[tracing.jaeger]
samplingServerURL = "foobar"
samplingType = "foobar"
samplingParam = 42.0
localAgentHostPort = "foobar"
gen128Bit = true
propagation = "foobar"
traceContextHeaderName = "foobar"
[tracing.zipkin]
httpEndpoint = "foobar"
sameSpan = true
id128Bit = true
sampleRate = 42.0
[tracing.datadog]
localAgentHostPort = "foobar"
globalTag = "foobar"
debug = true
prioritySampling = true
traceIDHeaderName = "foobar"
parentIDHeaderName = "foobar"
samplingPriorityHeaderName = "foobar"
bagagePrefixHeaderName = "foobar"
[tracing.instana]
localAgentHost = "foobar"
localAgentPort = 42
logLevel = "foobar"
[tracing.haystack]
localAgentHost = "foobar"
localAgentPort = 42
globalTag = "foobar"
traceIDHeaderName = "foobar"
parentIDHeaderName = "foobar"
spanIDHeaderName = "foobar"
[hostResolver]
cnameFlattening = true
resolvConfig = "foobar"
resolvDepth = 42
[certificatesResolvers.default.acme]
email = "foobar"
acmeLogging = true
caServer = "foobar"
storage = "foobar"
entryPoint = "foobar"
keyType = "foobar"
[certificatesResolvers.default.acme.dnsChallenge]
provider = "foobar"
delayBeforeCheck = 42
resolvers = ["foobar", "foobar"]
disablePropagationCheck = true
[certificatesResolvers.default.acme.httpChallenge]
entryPoint = "foobar"
[certificatesResolvers.default.acme.tlsChallenge]
## Dynamic configuration
[http]
[http.routers]
[http.routers.Router0]
entryPoints = ["foobar", "foobar"]
middlewares = ["foobar", "foobar"]
service = "foobar"
rule = "foobar"
priority = 42
[http.routers.Router0.tls]
[http.middlewares]
[http.middlewares.Middleware0]
[http.middlewares.Middleware0.addPrefix]
prefix = "foobar"
[http.middlewares.Middleware1]
[http.middlewares.Middleware1.stripPrefix]
prefixes = ["foobar", "foobar"]
[http.middlewares.Middleware10]
[http.middlewares.Middleware10.rateLimit]
average = 42
burst = 42
[http.middlewares.Middleware10.rateLimit.sourceCriterion]
requestHeaderName = "foobar"
requestHost = true
[http.middlewares.Middleware10.rateLimit.sourceCriterion.ipStrategy]
depth = 42
excludedIPs = ["foobar", "foobar"]
[http.middlewares.Middleware11]
[http.middlewares.Middleware11.redirectRegex]
regex = "foobar"
replacement = "foobar"
permanent = true
[http.middlewares.Middleware12]
[http.middlewares.Middleware12.redirectScheme]
scheme = "foobar"
port = "foobar"
permanent = true
[http.middlewares.Middleware13]
[http.middlewares.Middleware13.basicAuth]
users = ["foobar", "foobar"]
usersFile = "foobar"
realm = "foobar"
removeHeader = true
headerField = "foobar"
[http.middlewares.Middleware14]
[http.middlewares.Middleware14.digestAuth]
users = ["foobar", "foobar"]
usersFile = "foobar"
removeHeader = true
realm = "foobar"
headerField = "foobar"
[http.middlewares.Middleware15]
[http.middlewares.Middleware15.forwardAuth]
address = "foobar"
trustForwardHeader = true
authResponseHeaders = ["foobar", "foobar"]
[http.middlewares.Middleware15.forwardAuth.tls]
ca = "foobar"
caOptional = true
cert = "foobar"
key = "foobar"
insecureSkipVerify = true
[http.middlewares.Middleware16]
[http.middlewares.Middleware16.inFlightReq]
amount = 42
[http.middlewares.Middleware16.inFlightReq.sourceCriterion]
requestHeaderName = "foobar"
requestHost = true
[http.middlewares.Middleware16.inFlightReq.sourceCriterion.ipStrategy]
depth = 42
excludedIPs = ["foobar", "foobar"]
[http.middlewares.Middleware17]
[http.middlewares.Middleware17.buffering]
maxRequestBodyBytes = 42
memRequestBodyBytes = 42
maxResponseBodyBytes = 42
memResponseBodyBytes = 42
retryExpression = "foobar"
[http.middlewares.Middleware18]
[http.middlewares.Middleware18.circuitBreaker]
expression = "foobar"
[http.middlewares.Middleware19]
[http.middlewares.Middleware19.compress]
[http.middlewares.Middleware2]
[http.middlewares.Middleware2.stripPrefixRegex]
regex = ["foobar", "foobar"]
[http.middlewares.Middleware20]
[http.middlewares.Middleware20.passTLSClientCert]
pem = true
[http.middlewares.Middleware20.passTLSClientCert.info]
notAfter = true
notBefore = true
sans = true
[http.middlewares.Middleware20.passTLSClientCert.info.subject]
country = true
province = true
locality = true
organization = true
commonName = true
serialNumber = true
domainComponent = true
[http.middlewares.Middleware20.passTLSClientCert.info.issuer]
country = true
province = true
locality = true
organization = true
commonName = true
serialNumber = true
domainComponent = true
[http.middlewares.Middleware21]
[http.middlewares.Middleware21.retry]
regex = 0
[http.middlewares.Middleware3]
[http.middlewares.Middleware3.replacePath]
path = "foobar"
[http.middlewares.Middleware4]
[http.middlewares.Middleware4.replacePathRegex]
regex = "foobar"
replacement = "foobar"
[http.middlewares.Middleware5]
[http.middlewares.Middleware5.chain]
middlewares = ["foobar", "foobar"]
[http.middlewares.Middleware6]
[http.middlewares.Middleware6.ipWhiteList]
sourceRange = ["foobar", "foobar"]
[http.middlewares.Middleware7]
[http.middlewares.Middleware7.ipWhiteList]
[http.middlewares.Middleware7.ipWhiteList.ipStrategy]
depth = 42
excludedIPs = ["foobar", "foobar"]
[http.middlewares.Middleware8]
[http.middlewares.Middleware8.headers]
accessControlAllowCredentials = true
accessControlAllowHeaders = ["foobar", "foobar"]
accessControlAllowMethods = ["foobar", "foobar"]
accessControlAllowOrigin = "foobar"
accessControlExposeHeaders = ["foobar", "foobar"]
accessControlMaxAge = 42
addVaryHeader = true
allowedHosts = ["foobar", "foobar"]
hostsProxyHeaders = ["foobar", "foobar"]
sslRedirect = true
sslTemporaryRedirect = true
sslHost = "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"
featurePolicy = "foobar"
isDevelopment = true
[http.middlewares.Middleware8.headers.customRequestHeaders]
name0 = "foobar"
name1 = "foobar"
[http.middlewares.Middleware8.headers.customResponseHeaders]
name0 = "foobar"
name1 = "foobar"
[http.middlewares.Middleware8.headers.sslProxyHeaders]
name0 = "foobar"
name1 = "foobar"
[http.middlewares.Middleware9]
[http.middlewares.Middleware9.errors]
status = ["foobar", "foobar"]
service = "foobar"
query = "foobar"
[http.services]
[http.services.Service0]
[http.services.Service0.loadBalancer]
passHostHeader = true
[http.services.Service0.loadBalancer.sticky.cookie]
name = "foobar"
[[http.services.Service0.loadBalancer.servers]]
url = "foobar"
[[http.services.Service0.loadBalancer.servers]]
url = "foobar"
[http.services.Service0.loadBalancer.healthCheck]
scheme = "foobar"
path = "foobar"
port = 42
interval = "foobar"
timeout = "foobar"
hostname = "foobar"
[http.services.Service0.loadBalancer.healthCheck.headers]
name0 = "foobar"
name1 = "foobar"
[http.services.Service0.loadBalancer.responseForwarding]
flushInterval = "foobar"
[tcp]
[tcp.routers]
[tcp.routers.TCPRouter0]
entryPoints = ["foobar", "foobar"]
service = "foobar"
rule = "foobar"
[tcp.routers.TCPRouter0.tls]
passthrough = true
[tcp.services]
[tcp.services.TCPService0]
[tcp.services.TCPService0.loadBalancer]
[[tcp.services.TCPService0.loadBalancer.servers]]
address = "foobar"
[[tcp.services.TCPService0.loadBalancer.servers]]
address = "foobar"
[tls]
[[tls.Certificates]]
certFile = "foobar"
keyFile = "foobar"
stores = ["foobar", "foobar"]
[[tls.Certificates]]
certFile = "foobar"
keyFile = "foobar"
stores = ["foobar", "foobar"]
[tls.options]
[tls.options.TLS0]
minVersion = "foobar"
cipherSuites = ["foobar", "foobar"]
sniStrict = true
[tls.options.TLS0.clientAuth]
caFiles = ["foobar", "foobar"]
clientAuthType = "VerifyClientCertIfGiven"
[tls.options.TLS1]
minVersion = "foobar"
cipherSuites = ["foobar", "foobar"]
sniStrict = true
[tls.options.TLS1.clientAuth]
caFiles = ["foobar", "foobar"]
clientAuthType = "VerifyClientCertIfGiven"
[tls.stores]
[tls.stores.Store0]
[tls.stores.Store0.defaultCertificate]
certFile = "foobar"
keyFile = "foobar"
[tls.stores.Store1]
[tls.stores.Store1.defaultCertificate]
certFile = "foobar"
keyFile = "foobar"

View file

@ -1,235 +0,0 @@
global:
checkNewVersion: true
sendAnonymousUsage: true
serversTransport:
insecureSkipVerify: true
rootCAs:
- foobar
- foobar
maxIdleConnsPerHost: 42
forwardingTimeouts:
dialTimeout: 42
responseHeaderTimeout: 42
idleConnTimeout: 42
entryPoints:
EntryPoint0:
address: foobar
transport:
lifeCycle:
requestAcceptGraceTimeout: 42
graceTimeOut: 42
respondingTimeouts:
readTimeout: 42
writeTimeout: 42
idleTimeout: 42
proxyProtocol:
insecure: true
trustedIPs:
- foobar
- foobar
forwardedHeaders:
insecure: true
trustedIPs:
- foobar
- foobar
providers:
providersThrottleDuration: 42
docker:
constraints: foobar
watch: true
endpoint: foobar
defaultRule: foobar
tls:
ca: foobar
caOptional: true
cert: foobar
key: foobar
insecureSkipVerify: true
exposedByDefault: true
useBindPortIP: true
swarmMode: true
network: foobar
swarmModeRefreshSeconds: 42
file:
directory: foobar
watch: true
filename: foobar
debugLogGeneratedTemplate: true
marathon:
constraints: foobar
trace: true
watch: true
endpoint: foobar
defaultRule: foobar
exposedByDefault: true
dcosToken: foobar
tls:
ca: foobar
caOptional: true
cert: foobar
key: foobar
insecureSkipVerify: true
dialerTimeout: 42
responseHeaderTimeout: 42
tlsHandshakeTimeout: 42
keepAlive: 42
forceTaskHostname: true
basic:
httpBasicAuthUser: foobar
httpBasicPassword: foobar
respectReadinessChecks: true
kubernetesIngress:
endpoint: foobar
token: foobar
certAuthFilePath: foobar
disablePassHostHeaders: true
namespaces:
- foobar
- foobar
labelSelector: foobar
ingressClass: foobar
ingressEndpoint:
ip: foobar
hostname: foobar
publishedService: foobar
kubernetesCRD:
endpoint: foobar
token: foobar
certAuthFilePath: foobar
disablePassHostHeaders: true
namespaces:
- foobar
- foobar
labelSelector: foobar
ingressClass: foobar
rest:
entryPoint: foobar
rancher:
constraints: foobar
watch: true
defaultRule: foobar
exposedByDefault: true
enableServiceHealthFilter: true
refreshSeconds: 42
intervalPoll: true
prefix: foobar
api:
entryPoint: foobar
dashboard: true
statistics:
recentErrors: 42
middlewares:
- foobar
- foobar
metrics:
prometheus:
buckets:
- 42
- 42
entryPoint: foobar
middlewares:
- foobar
- foobar
datadog:
address: foobar
pushInterval: 10s
statsD:
address: foobar
pushInterval: 10s
influxDB:
address: foobar
protocol: foobar
pushInterval: 10s
database: foobar
retentionPolicy: foobar
username: foobar
password: foobar
ping:
entryPoint: foobar
middlewares:
- foobar
- foobar
log:
level: foobar
filePath: foobar
format: foobar
accessLog:
filePath: foobar
format: foobar
filters:
statusCodes:
- foobar
- foobar
retryAttempts: true
minDuration: 42
fields:
defaultMode: foobar
names:
name0: foobar
name1: foobar
headers:
defaultMode: foobar
names:
name0: foobar
name1: foobar
bufferingSize: 42
tracing:
serviceName: foobar
spanNameLimit: 42
jaeger:
samplingServerURL: foobar
samplingType: foobar
samplingParam: 42
localAgentHostPort: foobar
gen128Bit: true
propagation: foobar
traceContextHeaderName: foobar
zipkin:
httpEndpoint: foobar
sameSpan: true
id128Bit: true
sampleRate: 42
datadog:
localAgentHostPort: foobar
globalTag: foobar
debug: true
prioritySampling: true
traceIDHeaderName: foobar
parentIDHeaderName: foobar
samplingPriorityHeaderName: foobar
bagagePrefixHeaderName: foobar
instana:
localAgentHost: foobar
localAgentPort: 42
logLevel: foobar
haystack:
localAgentHost: foobar
localAgentPort: 42
globalTag: foobar
traceIDHeaderName: foobar
parentIDHeaderName: foobar
spanIDHeaderName: foobar
hostResolver:
cnameFlattening: true
resolvConfig: foobar
resolvDepth: 42
certificatesResolvers:
default:
acme:
email: foobar
acmeLogging: true
caServer: foobar
storage: foobar
entryPoint: foobar
keyType: foobar
dnsChallenge:
provider: foobar
delayBeforeCheck: 42
resolvers:
- foobar
- foobar
disablePropagationCheck: true
httpChallenge:
entryPoint: foobar
tlsChallenge: {}

View file

@ -1,34 +0,0 @@
package file
type bar string
type Yo struct {
Foo string
Fii string
Fuu string
Yi *Yi `file:"allowEmpty"`
}
func (y *Yo) SetDefaults() {
y.Foo = "foo"
y.Fii = "fii"
}
type Yi struct {
Foo string
Fii string
Fuu string
}
func (y *Yi) SetDefaults() {
y.Foo = "foo"
y.Fii = "fii"
}
type Yu struct {
Yi
}
type Ye struct {
*Yi
}

View file

@ -1,152 +0,0 @@
package file
import (
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"github.com/containous/traefik/v2/pkg/config/parser"
)
func decodeRawToNode(data map[string]interface{}, rootName string, filters ...string) (*parser.Node, error) {
root := &parser.Node{
Name: rootName,
}
vData := reflect.ValueOf(data)
err := decodeRaw(root, vData, filters...)
if err != nil {
return nil, err
}
return root, nil
}
func decodeRaw(node *parser.Node, vData reflect.Value, filters ...string) error {
sortedKeys := sortKeys(vData, filters)
for _, key := range sortedKeys {
if vData.MapIndex(key).IsNil() {
continue
}
value := reflect.ValueOf(vData.MapIndex(key).Interface())
child := &parser.Node{Name: key.String()}
switch value.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fallthrough
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
fallthrough
case reflect.Float32, reflect.Float64:
fallthrough
case reflect.Bool:
fallthrough
case reflect.String:
value, err := getSimpleValue(value)
if err != nil {
return err
}
child.Value = value
case reflect.Slice:
var values []string
for i := 0; i < value.Len(); i++ {
item := value.Index(i)
switch item.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fallthrough
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
fallthrough
case reflect.Bool:
fallthrough
case reflect.String:
fallthrough
case reflect.Map:
fallthrough
case reflect.Interface:
sValue := reflect.ValueOf(item.Interface())
if sValue.Kind() == reflect.Map {
ch := &parser.Node{
Name: "[" + strconv.Itoa(i) + "]",
}
child.Children = append(child.Children, ch)
err := decodeRaw(ch, sValue)
if err != nil {
return err
}
} else {
val, err := getSimpleValue(sValue)
if err != nil {
return err
}
values = append(values, val)
}
default:
return fmt.Errorf("field %s uses unsupported slice type: %s", child.Name, item.Kind().String())
}
}
child.Value = strings.Join(values, ",")
case reflect.Map:
err := decodeRaw(child, value)
if err != nil {
return err
}
default:
return fmt.Errorf("field %s uses unsupported type: %s", child.Name, value.Kind().String())
}
node.Children = append(node.Children, child)
}
return nil
}
func getSimpleValue(item reflect.Value) (string, error) {
switch item.Kind() {
case reflect.String:
return item.String(), nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(item.Int(), 10), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(item.Uint(), 10), nil
case reflect.Float32, reflect.Float64:
return strings.TrimSuffix(strconv.FormatFloat(item.Float(), 'f', 6, 64), ".000000"), nil
case reflect.Bool:
return strconv.FormatBool(item.Bool()), nil
default:
return "", fmt.Errorf("unsupported simple value type: %s", item.Kind().String())
}
}
func sortKeys(vData reflect.Value, filters []string) []reflect.Value {
var sortedKeys []reflect.Value
for _, v := range vData.MapKeys() {
rValue := reflect.ValueOf(v.Interface())
key := rValue.String()
if len(filters) == 0 {
sortedKeys = append(sortedKeys, rValue)
continue
}
for _, filter := range filters {
if strings.EqualFold(key, filter) {
sortedKeys = append(sortedKeys, rValue)
continue
}
}
}
sort.Slice(sortedKeys, func(i, j int) bool {
return sortedKeys[i].String() < sortedKeys[j].String()
})
return sortedKeys
}

View file

@ -1,578 +0,0 @@
package file
import (
"testing"
"github.com/containous/traefik/v2/pkg/config/parser"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_decodeRawToNode(t *testing.T) {
testCases := []struct {
desc string
data map[string]interface{}
expected *parser.Node
}{
{
desc: "empty",
data: map[string]interface{}{},
expected: &parser.Node{
Name: "traefik",
},
},
{
desc: "string",
data: map[string]interface{}{
"foo": "bar",
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "bar"},
},
},
},
{
desc: "string named type",
data: map[string]interface{}{
"foo": bar("bar"),
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "bar"},
},
},
},
{
desc: "bool",
data: map[string]interface{}{
"foo": true,
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "true"},
},
},
},
{
desc: "int",
data: map[string]interface{}{
"foo": 1,
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1"},
},
},
},
{
desc: "int8",
data: map[string]interface{}{
"foo": int8(1),
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1"},
},
},
},
{
desc: "int16",
data: map[string]interface{}{
"foo": int16(1),
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1"},
},
},
},
{
desc: "int32",
data: map[string]interface{}{
"foo": int32(1),
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1"},
},
},
},
{
desc: "int64",
data: map[string]interface{}{
"foo": int64(1),
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1"},
},
},
},
{
desc: "uint",
data: map[string]interface{}{
"foo": uint(1),
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1"},
},
},
},
{
desc: "uint8",
data: map[string]interface{}{
"foo": uint8(1),
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1"},
},
},
},
{
desc: "uint16",
data: map[string]interface{}{
"foo": uint16(1),
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1"},
},
},
},
{
desc: "uint32",
data: map[string]interface{}{
"foo": uint32(1),
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1"},
},
},
},
{
desc: "uint64",
data: map[string]interface{}{
"foo": uint64(1),
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1"},
},
},
},
{
desc: "float32",
data: map[string]interface{}{
"foo": float32(1),
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1"},
},
},
},
{
desc: "float64",
data: map[string]interface{}{
"foo": float64(1),
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1"},
},
},
},
{
desc: "string slice",
data: map[string]interface{}{
"foo": []string{"A", "B"},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "A,B"},
},
},
},
{
desc: "int slice",
data: map[string]interface{}{
"foo": []int{1, 2},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1,2"},
},
},
},
{
desc: "int8 slice",
data: map[string]interface{}{
"foo": []int8{1, 2},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1,2"},
},
},
},
{
desc: "int16 slice",
data: map[string]interface{}{
"foo": []int16{1, 2},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1,2"},
},
},
},
{
desc: "int32 slice",
data: map[string]interface{}{
"foo": []int32{1, 2},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1,2"},
},
},
},
{
desc: "int64 slice",
data: map[string]interface{}{
"foo": []int64{1, 2},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1,2"},
},
},
},
{
desc: "bool slice",
data: map[string]interface{}{
"foo": []bool{true, false},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "true,false"},
},
},
},
{
desc: "interface (string) slice",
data: map[string]interface{}{
"foo": []interface{}{"A", "B"},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "A,B"},
},
},
},
{
desc: "interface (int) slice",
data: map[string]interface{}{
"foo": []interface{}{1, 2},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Value: "1,2"},
},
},
},
{
desc: "2 strings",
data: map[string]interface{}{
"foo": "bar",
"fii": "bir",
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "fii", Value: "bir"},
{Name: "foo", Value: "bar"},
},
},
},
{
desc: "string, level 2",
data: map[string]interface{}{
"fii": map[interface{}]interface{}{
"fuu": "bur",
},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "bur"}}},
},
},
},
{
desc: "int, level 2",
data: map[string]interface{}{
"fii": map[interface{}]interface{}{
"fuu": 1,
},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "1"}}},
},
},
},
{
desc: "uint, level 2",
data: map[string]interface{}{
"fii": map[interface{}]interface{}{
"fuu": uint(1),
},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "1"}}},
},
},
},
{
desc: "bool, level 2",
data: map[string]interface{}{
"fii": map[interface{}]interface{}{
"fuu": true,
},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "true"}}},
},
},
},
{
desc: "string, level 3",
data: map[string]interface{}{
"foo": map[interface{}]interface{}{
"fii": map[interface{}]interface{}{
"fuu": "bur",
},
},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Children: []*parser.Node{
{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "bur"}}},
}},
},
},
},
{
desc: "int, level 3",
data: map[string]interface{}{
"fii": map[interface{}]interface{}{
"fuu": 1,
},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "1"}}},
},
},
},
{
desc: "uint, level 3",
data: map[string]interface{}{
"fii": map[interface{}]interface{}{
"fuu": uint(1),
},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "1"}}},
},
},
},
{
desc: "bool, level 3",
data: map[string]interface{}{
"fii": map[interface{}]interface{}{
"fuu": true,
},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "true"}}},
},
},
},
{
desc: "struct",
data: map[string]interface{}{
"foo": map[interface{}]interface{}{
"field1": "C",
"field2": "C",
},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Children: []*parser.Node{
{Name: "field1", Value: "C"},
{Name: "field2", Value: "C"},
}},
},
},
},
{
desc: "slice struct 1",
data: map[string]interface{}{
"foo": []map[string]interface{}{
{"field1": "A", "field2": "A"},
{"field1": "B", "field2": "B"},
{"field2": "C", "field1": "C"},
},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Children: []*parser.Node{
{Name: "[0]", Children: []*parser.Node{
{Name: "field1", Value: "A"},
{Name: "field2", Value: "A"},
}},
{Name: "[1]", Children: []*parser.Node{
{Name: "field1", Value: "B"},
{Name: "field2", Value: "B"},
}},
{Name: "[2]", Children: []*parser.Node{
{Name: "field1", Value: "C"},
{Name: "field2", Value: "C"},
}},
}},
},
},
},
{
desc: "slice struct 2",
data: map[string]interface{}{
"foo": []interface{}{
map[interface{}]interface{}{
"field2": "A",
"field1": "A",
},
map[interface{}]interface{}{
"field1": "B",
"field2": "B",
},
map[interface{}]interface{}{
"field1": "C",
"field2": "C",
},
},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "foo", Children: []*parser.Node{
{Name: "[0]", Children: []*parser.Node{
{Name: "field1", Value: "A"},
{Name: "field2", Value: "A"},
}},
{Name: "[1]", Children: []*parser.Node{
{Name: "field1", Value: "B"},
{Name: "field2", Value: "B"},
}},
{Name: "[2]", Children: []*parser.Node{
{Name: "field1", Value: "C"},
{Name: "field2", Value: "C"},
}},
}},
},
},
},
{
desc: "nil value",
data: map[string]interface{}{
"fii": map[interface{}]interface{}{
"fuu": nil,
},
},
expected: &parser.Node{
Name: "traefik",
Children: []*parser.Node{
{Name: "fii"},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
node, err := decodeRawToNode(test.data, parser.DefaultRootName)
require.NoError(t, err)
assert.Equal(t, test.expected, node)
})
}
}
func Test_decodeRawToNode_errors(t *testing.T) {
testCases := []struct {
desc string
data map[string]interface{}
}{
{
desc: "invalid type",
data: map[string]interface{}{
"foo": struct{}{},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
_, err := decodeRawToNode(test.data, parser.DefaultRootName)
require.Error(t, err)
})
}
}

View file

@ -1,47 +0,0 @@
// Package flag implements encoding and decoding between flag arguments and a typed Configuration.
package flag
import (
"github.com/containous/traefik/v2/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, parser.DefaultRootName)
}
// 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
}
etnOpts := parser.EncoderToNodeOpts{OmitEmpty: false, TagName: parser.TagLabel, AllowSliceAsStruct: true}
node, err := parser.EncodeToNode(element, parser.DefaultRootName, etnOpts)
if err != nil {
return nil, err
}
metaOpts := parser.MetadataOpts{TagName: parser.TagLabel, AllowSliceAsStruct: true}
err = parser.AddMetadata(element, node, metaOpts)
if err != nil {
return nil, err
}
flatOpts := parser.FlatOpts{Separator: ".", SkipRoot: true, TagName: parser.TagLabel}
return parser.EncodeToFlat(element, node, flatOpts)
}

View file

@ -1,940 +0,0 @@
package flag
import (
"testing"
"time"
"github.com/containous/traefik/v2/pkg/config/generator"
"github.com/containous/traefik/v2/pkg/config/parser"
"github.com/containous/traefik/v2/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 string case sensitive",
args: []string{"--foo.caseSensitiveName=barBoo"},
element: &struct {
Foo map[string]string
}{},
expected: &struct {
Foo map[string]string
}{
Foo: map[string]string{
"caseSensitiveName": "barBoo",
},
},
},
{
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

@ -1,141 +0,0 @@
package flag
import (
"fmt"
"reflect"
"regexp"
"strings"
"github.com/containous/traefik/v2/pkg/config/parser"
)
// 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),
keys: 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
keys 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
}
flagType := f.getFlagType(name)
if flagType == reflect.Bool || flagType == 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, value string) {
srcKey := parser.DefaultRootName + "." + name
neutralKey := strings.ToLower(srcKey)
key, ok := f.keys[neutralKey]
if !ok {
f.keys[neutralKey] = srcKey
key = srcKey
}
v, ok := f.values[key]
if ok && f.getFlagType(name) == reflect.Slice {
f.values[key] = v + "," + value
return
}
f.values[key] = value
}
func (f *flagSet) getFlagType(name string) reflect.Kind {
neutral := strings.ToLower(name)
kind, ok := f.flagTypes[neutral]
if ok {
return kind
}
for n, k := range f.flagTypes {
if strings.Contains(n, parser.MapNamePlaceholder) {
p := strings.NewReplacer(".", `\.`, parser.MapNamePlaceholder, `([^.]+)`).Replace(n)
if regexp.MustCompile(p).MatchString(neutral) {
return k
}
}
}
return reflect.Invalid
}

View file

@ -1,353 +0,0 @@
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: "bool value capitalized",
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: "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 capitalized",
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 string capitalized",
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",
},
},
{
desc: "map string case sensitive",
args: []string{"--foo.caseSensitiveName=barBoo"},
element: &struct {
Foo map[string]string
}{},
expected: map[string]string{
"traefik.foo.caseSensitiveName": "barBoo",
},
},
{
desc: "map struct with sub-map case sensitive",
args: []string{"--foo.Name1.bar.name2.value=firstValue", "--foo.naMe1.bar.name2.value=secondValue"},
element: &struct {
Foo map[string]struct {
Bar map[string]struct{ Value string }
}
}{},
expected: map[string]string{
"traefik.foo.Name1.bar.name2.value": "secondValue",
},
},
{
desc: "map struct with sub-map and different case",
args: []string{"--foo.Name1.bar.name2.value=firstValue", "--foo.naMe1.bar.name2.value=secondValue"},
element: &struct {
Foo map[string]struct {
Bar map[string]struct{ Value string }
}
}{},
expected: map[string]string{
"traefik.foo.Name1.bar.name2.value": "secondValue",
},
},
{
desc: "pointer of struct and map without explicit value",
args: []string{"--foo.default.bar.fuu"},
element: &struct {
Foo map[string]struct {
Bar *struct {
Fuu *struct{ Value string }
}
}
}{},
expected: map[string]string{
"traefik.foo.default.bar.fuu": "true",
},
},
{
desc: "slice with several flags 2 and different cases.",
args: []string{"--foo", "bar", "--Foo", "baz"},
element: &struct {
Foo []string
}{},
expected: map[string]string{
"traefik.foo": "bar,baz",
},
},
}
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

@ -1,60 +0,0 @@
package flag
import (
"reflect"
"strings"
"github.com/containous/traefik/v2/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

@ -1,226 +0,0 @@
package flag
import (
"reflect"
"testing"
"github.com/containous/traefik/v2/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
}

View file

@ -1,97 +0,0 @@
// Package generator implements the custom initialization of all the fields of an empty interface.
package generator
import (
"reflect"
"github.com/containous/traefik/v2/pkg/config/parser"
)
type initializer interface {
SetDefaults()
}
// Generate recursively initializes an empty structure, calling SetDefaults on each field, when it applies.
func Generate(element interface{}) {
if element == nil {
return
}
generate(element)
}
func generate(element interface{}) {
field := reflect.ValueOf(element)
fill(field)
}
func fill(field reflect.Value) {
switch field.Kind() {
case reflect.Ptr:
setPtr(field)
case reflect.Struct:
setStruct(field)
case reflect.Map:
setMap(field)
case reflect.Slice:
if field.Type().Elem().Kind() == reflect.Struct ||
field.Type().Elem().Kind() == reflect.Ptr && field.Type().Elem().Elem().Kind() == reflect.Struct {
slice := reflect.MakeSlice(field.Type(), 1, 1)
field.Set(slice)
// use Ptr to allow "SetDefaults"
value := reflect.New(reflect.PtrTo(field.Type().Elem()))
setPtr(value)
elem := value.Elem().Elem()
field.Index(0).Set(elem)
} else if field.Len() == 0 {
slice := reflect.MakeSlice(field.Type(), 0, 0)
field.Set(slice)
}
}
}
func setPtr(field reflect.Value) {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
}
if field.Type().Implements(reflect.TypeOf((*initializer)(nil)).Elem()) {
method := field.MethodByName("SetDefaults")
if method.IsValid() {
method.Call([]reflect.Value{})
}
}
fill(field.Elem())
}
func setStruct(field reflect.Value) {
for i := 0; i < field.NumField(); i++ {
fd := field.Field(i)
structField := field.Type().Field(i)
if structField.Tag.Get(parser.TagLabel) == "-" {
continue
}
if parser.IsExported(structField) {
fill(fd)
}
}
}
func setMap(field reflect.Value) {
if field.IsNil() {
field.Set(reflect.MakeMap(field.Type()))
}
ptrValue := reflect.New(reflect.PtrTo(field.Type().Elem()))
fill(ptrValue)
value := ptrValue.Elem().Elem()
key := reflect.ValueOf(parser.MapNamePlaceholder)
field.SetMapIndex(key, value)
}

View file

@ -1,439 +0,0 @@
package generator
import (
"testing"
"github.com/containous/traefik/v2/pkg/config/parser"
"github.com/stretchr/testify/assert"
)
func TestGenerate(t *testing.T) {
testCases := []struct {
desc string
element interface{}
expected interface{}
}{
{
desc: "nil",
},
{
desc: "simple",
element: &Ya{},
expected: &Ya{
Foo: &Yaa{
FieldIn1: "",
FieldIn2: false,
FieldIn3: 0,
FieldIn4: map[string]string{
parser.MapNamePlaceholder: "",
},
FieldIn5: map[string]int{
parser.MapNamePlaceholder: 0,
},
FieldIn6: map[string]struct{ Field string }{
parser.MapNamePlaceholder: {},
},
FieldIn7: map[string]struct{ Field map[string]string }{
parser.MapNamePlaceholder: {
Field: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
FieldIn8: map[string]*struct{ Field string }{
parser.MapNamePlaceholder: {},
},
FieldIn9: map[string]*struct{ Field map[string]string }{
parser.MapNamePlaceholder: {
Field: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
FieldIn10: struct{ Field string }{},
FieldIn11: &struct{ Field string }{},
FieldIn12: func(v string) *string { return &v }(""),
FieldIn13: func(v bool) *bool { return &v }(false),
FieldIn14: func(v int) *int { return &v }(0),
},
Field1: "",
Field2: false,
Field3: 0,
Field4: map[string]string{
parser.MapNamePlaceholder: "",
},
Field5: map[string]int{
parser.MapNamePlaceholder: 0,
},
Field6: map[string]struct{ Field string }{
parser.MapNamePlaceholder: {},
},
Field7: map[string]struct{ Field map[string]string }{
parser.MapNamePlaceholder: {
Field: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
Field8: map[string]*struct{ Field string }{
parser.MapNamePlaceholder: {},
},
Field9: map[string]*struct{ Field map[string]string }{
parser.MapNamePlaceholder: {
Field: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
Field10: struct{ Field string }{},
Field11: &struct{ Field string }{},
Field12: func(v string) *string { return &v }(""),
Field13: func(v bool) *bool { return &v }(false),
Field14: func(v int) *int { return &v }(0),
Field15: []int{},
},
},
{
desc: "with initial state",
element: &Ya{
Foo: &Yaa{
FieldIn1: "bar",
FieldIn2: false,
FieldIn3: 1,
FieldIn4: nil,
FieldIn5: nil,
FieldIn6: nil,
FieldIn7: nil,
FieldIn8: nil,
FieldIn9: nil,
FieldIn10: struct{ Field string }{},
FieldIn11: nil,
FieldIn12: nil,
FieldIn13: nil,
FieldIn14: nil,
},
Field1: "bir",
Field2: true,
Field3: 0,
Field4: nil,
Field5: nil,
Field6: nil,
Field7: nil,
Field8: nil,
Field9: nil,
Field10: struct{ Field string }{},
Field11: nil,
Field12: nil,
Field13: nil,
Field14: nil,
Field15: []int{7},
},
expected: &Ya{
Foo: &Yaa{
FieldIn1: "bar",
FieldIn2: false,
FieldIn3: 1,
FieldIn4: map[string]string{
parser.MapNamePlaceholder: "",
},
FieldIn5: map[string]int{
parser.MapNamePlaceholder: 0,
},
FieldIn6: map[string]struct{ Field string }{
parser.MapNamePlaceholder: {},
},
FieldIn7: map[string]struct{ Field map[string]string }{
parser.MapNamePlaceholder: {
Field: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
FieldIn8: map[string]*struct{ Field string }{
parser.MapNamePlaceholder: {},
},
FieldIn9: map[string]*struct{ Field map[string]string }{
parser.MapNamePlaceholder: {
Field: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
FieldIn10: struct{ Field string }{},
FieldIn11: &struct{ Field string }{},
FieldIn12: func(v string) *string { return &v }(""),
FieldIn13: func(v bool) *bool { return &v }(false),
FieldIn14: func(v int) *int { return &v }(0),
},
Field1: "bir",
Field2: true,
Field3: 0,
Field4: map[string]string{
parser.MapNamePlaceholder: "",
},
Field5: map[string]int{
parser.MapNamePlaceholder: 0,
},
Field6: map[string]struct{ Field string }{
parser.MapNamePlaceholder: {},
},
Field7: map[string]struct{ Field map[string]string }{
parser.MapNamePlaceholder: {
Field: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
Field8: map[string]*struct{ Field string }{
parser.MapNamePlaceholder: {},
},
Field9: map[string]*struct{ Field map[string]string }{
parser.MapNamePlaceholder: {
Field: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
Field10: struct{ Field string }{},
Field11: &struct{ Field string }{},
Field12: func(v string) *string { return &v }(""),
Field13: func(v bool) *bool { return &v }(false),
Field14: func(v int) *int { return &v }(0),
Field15: []int{7},
},
},
{
desc: "setDefault",
element: &Hu{},
expected: &Hu{
Foo: "hu",
Fii: &Hi{
Field: "hi",
},
Fuu: map[string]string{"<name>": ""},
Fee: map[string]Hi{"<name>": {Field: "hi"}},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
Generate(test.element)
assert.Equal(t, test.expected, test.element)
})
}
}
func Test_generate(t *testing.T) {
testCases := []struct {
desc string
element interface{}
expected interface{}
}{
{
desc: "struct pointer",
element: &struct {
Foo string
Fii *struct{ Field string }
}{},
expected: &struct {
Foo string
Fii *struct{ Field string }
}{
Foo: "",
Fii: &struct{ Field string }{
Field: "",
},
},
},
{
desc: "string slice",
element: &struct {
Foo []string
}{},
expected: &struct {
Foo []string
}{
Foo: []string{},
},
},
{
desc: "int slice",
element: &struct {
Foo []int
}{},
expected: &struct {
Foo []int
}{
Foo: []int{},
},
},
{
desc: "struct slice",
element: &struct {
Foo []struct {
Field string
}
}{},
expected: &struct {
Foo []struct {
Field string
}
}{
Foo: []struct {
Field string
}{
{Field: ""},
},
},
},
{
desc: "map string",
element: &struct {
Foo string
Fii map[string]string
}{},
expected: &struct {
Foo string
Fii map[string]string
}{
Foo: "",
Fii: map[string]string{
parser.MapNamePlaceholder: "",
},
},
},
{
desc: "map struct",
element: &struct {
Foo string
Fii map[string]struct{ Field string }
}{},
expected: &struct {
Foo string
Fii map[string]struct{ Field string }
}{
Foo: "",
Fii: map[string]struct{ Field string }{
parser.MapNamePlaceholder: {},
},
},
},
{
desc: "map struct pointer level 2",
element: &struct {
Foo string
Fuu *struct {
Fii map[string]*struct{ Field string }
}
}{},
expected: &struct {
Foo string
Fuu *struct {
Fii map[string]*struct{ Field string }
}
}{
Foo: "",
Fuu: &struct {
Fii map[string]*struct {
Field string
}
}{
Fii: map[string]*struct{ Field string }{
parser.MapNamePlaceholder: {
Field: "",
},
},
},
},
},
{
desc: "SetDefaults",
element: &Hu{},
expected: &Hu{
Foo: "hu",
Fii: &Hi{
Field: "hi",
},
Fuu: map[string]string{
parser.MapNamePlaceholder: "",
},
Fee: map[string]Hi{
parser.MapNamePlaceholder: {
Field: "hi",
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
generate(test.element)
assert.Equal(t, test.expected, test.element)
})
}
}
type Hu struct {
Foo string
Fii *Hi
Fuu map[string]string
Fee map[string]Hi
}
func (h *Hu) SetDefaults() {
h.Foo = "hu"
}
type Hi struct {
Field string
}
func (h *Hi) SetDefaults() {
h.Field = "hi"
}
type Ya struct {
Foo *Yaa
Field1 string
Field2 bool
Field3 int
Field4 map[string]string
Field5 map[string]int
Field6 map[string]struct{ Field string }
Field7 map[string]struct{ Field map[string]string }
Field8 map[string]*struct{ Field string }
Field9 map[string]*struct{ Field map[string]string }
Field10 struct{ Field string }
Field11 *struct{ Field string }
Field12 *string
Field13 *bool
Field14 *int
Field15 []int
}
type Yaa struct {
FieldIn1 string
FieldIn2 bool
FieldIn3 int
FieldIn4 map[string]string
FieldIn5 map[string]int
FieldIn6 map[string]struct{ Field string }
FieldIn7 map[string]struct{ Field map[string]string }
FieldIn8 map[string]*struct{ Field string }
FieldIn9 map[string]*struct{ Field map[string]string }
FieldIn10 struct{ Field string }
FieldIn11 *struct{ Field string }
FieldIn12 *string
FieldIn13 *bool
FieldIn14 *int
}

View file

@ -5,7 +5,7 @@ import (
"reflect"
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/config/parser"
"github.com/traefik/paerser/parser"
)
// Decode decodes the given KV pairs into the given element.

View file

@ -7,7 +7,7 @@ import (
"strings"
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/config/parser"
"github.com/traefik/paerser/parser"
)
// DecodeToNode converts the labels to a tree of nodes.

View file

@ -6,9 +6,9 @@ import (
"testing"
"github.com/abronan/valkeyrie/store"
"github.com/containous/traefik/v2/pkg/config/parser"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/paerser/parser"
)
func TestDecodeToNode(t *testing.T) {

View file

@ -3,7 +3,7 @@ package label
import (
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/parser"
"github.com/traefik/paerser/parser"
)
// DecodeConfiguration converts the labels to a configuration.

View file

@ -6,9 +6,9 @@ import (
"time"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types"
)
func TestDecodeConfiguration(t *testing.T) {
@ -376,7 +376,7 @@ func TestDecodeConfiguration(t *testing.T) {
RateLimit: &dynamic.RateLimit{
Average: 42,
Burst: 42,
Period: types.Duration(time.Second),
Period: ptypes.Duration(time.Second),
SourceCriterion: &dynamic.SourceCriterion{
IPStrategy: &dynamic.IPStrategy{
Depth: 42,
@ -836,7 +836,7 @@ func TestEncodeConfiguration(t *testing.T) {
RateLimit: &dynamic.RateLimit{
Average: 42,
Burst: 42,
Period: types.Duration(time.Second),
Period: ptypes.Duration(time.Second),
SourceCriterion: &dynamic.SourceCriterion{
IPStrategy: &dynamic.IPStrategy{
Depth: 42,

View file

@ -1,372 +0,0 @@
package parser
import (
"fmt"
"reflect"
"strconv"
"strings"
"time"
"github.com/containous/traefik/v2/pkg/types"
)
type initializer interface {
SetDefaults()
}
// FillerOpts Options for the filler.
type FillerOpts struct {
AllowSliceAsStruct bool
}
// Fill populates the fields of the element using the information in node.
func Fill(element interface{}, node *Node, opts FillerOpts) error {
return filler{FillerOpts: opts}.Fill(element, node)
}
type filler struct {
FillerOpts
}
// Fill populates the fields of the element using the information in node.
func (f filler) 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)
}
root := reflect.ValueOf(element)
if root.Kind() == reflect.Struct {
return fmt.Errorf("struct are not supported, use pointer instead")
}
return f.fill(root.Elem(), node)
}
func (f filler) 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 f.setStruct(field, node)
case reflect.Ptr:
return f.setPtr(field, node)
case reflect.Map:
return f.setMap(field, node)
case reflect.Slice:
return f.setSlice(field, node)
default:
return nil
}
}
func (f filler) setPtr(field reflect.Value, node *Node) error {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
if field.Type().Implements(reflect.TypeOf((*initializer)(nil)).Elem()) {
method := field.MethodByName("SetDefaults")
if method.IsValid() {
method.Call([]reflect.Value{})
}
}
}
return f.fill(field.Elem(), node)
}
func (f filler) 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 := f.fill(fd, child)
if err != nil {
return err
}
}
return nil
}
func (f filler) setSlice(field reflect.Value, node *Node) error {
if field.Type().Elem().Kind() == reflect.Struct ||
field.Type().Elem().Kind() == reflect.Ptr && field.Type().Elem().Elem().Kind() == reflect.Struct {
return f.setSliceStruct(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).SetString(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 (f filler) setSliceStruct(field reflect.Value, node *Node) error {
if f.AllowSliceAsStruct && node.Tag.Get(TagLabelSliceAsStruct) != "" {
return f.setSliceAsStruct(field, node)
}
field.Set(reflect.MakeSlice(field.Type(), len(node.Children), len(node.Children)))
for i, child := range node.Children {
// use Ptr to allow "SetDefaults"
value := reflect.New(reflect.PtrTo(field.Type().Elem()))
err := f.setPtr(value, child)
if err != nil {
return err
}
field.Index(i).Set(value.Elem().Elem())
}
return nil
}
func (f filler) setSliceAsStruct(field reflect.Value, node *Node) error {
if len(node.Children) == 0 {
return fmt.Errorf("invalid slice: node %s", node.Name)
}
// use Ptr to allow "SetDefaults"
value := reflect.New(reflect.PtrTo(field.Type().Elem()))
err := f.setPtr(value, node)
if err != nil {
return err
}
elem := value.Elem().Elem()
field.Set(reflect.MakeSlice(field.Type(), 1, 1))
field.Index(0).Set(elem)
return nil
}
func (f filler) setMap(field reflect.Value, node *Node) error {
if field.IsNil() {
field.Set(reflect.MakeMap(field.Type()))
}
if field.Type().Elem().Kind() == reflect.Interface {
fillRawValue(field, node, false)
for _, child := range node.Children {
fillRawValue(field, child, true)
}
return nil
}
for _, child := range node.Children {
ptrValue := reflect.New(reflect.PtrTo(field.Type().Elem()))
err := f.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 {
switch field.Type() {
case reflect.TypeOf(types.Duration(0)):
return setDuration(field, value, bitSize, time.Second)
case reflect.TypeOf(time.Duration(0)):
return setDuration(field, value, bitSize, time.Nanosecond)
default:
val, err := strconv.ParseInt(value, 10, bitSize)
if err != nil {
return err
}
field.Set(reflect.ValueOf(val).Convert(field.Type()))
return nil
}
}
func setDuration(field reflect.Value, value string, bitSize int, defaultUnit time.Duration) error {
val, err := strconv.ParseInt(value, 10, bitSize)
if err == nil {
field.Set(reflect.ValueOf(time.Duration(val) * defaultUnit).Convert(field.Type()))
return nil
}
duration, err := time.ParseDuration(value)
if err != nil {
return err
}
field.Set(reflect.ValueOf(duration).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
}
func fillRawValue(field reflect.Value, node *Node, subMap bool) {
m, ok := node.RawValue.(map[string]interface{})
if !ok {
return
}
if _, self := m[node.Name]; self || !subMap {
for k, v := range m {
field.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
}
return
}
p := map[string]interface{}{node.Name: m}
node.RawValue = p
field.SetMapIndex(reflect.ValueOf(node.Name), reflect.ValueOf(p[node.Name]))
}

File diff suppressed because it is too large Load diff

View file

@ -1,215 +0,0 @@
package parser
import (
"fmt"
"reflect"
"strconv"
"strings"
)
// EncoderToNodeOpts Options for the encoderToNode.
type EncoderToNodeOpts struct {
OmitEmpty bool
TagName string
AllowSliceAsStruct bool
}
// EncodeToNode converts an element to a node.
// element -> nodes.
func EncodeToNode(element interface{}, rootName string, opts EncoderToNodeOpts) (*Node, error) {
rValue := reflect.ValueOf(element)
node := &Node{Name: rootName}
encoder := encoderToNode{EncoderToNodeOpts: opts}
err := encoder.setNodeValue(node, rValue)
if err != nil {
return nil, err
}
return node, nil
}
type encoderToNode struct {
EncoderToNodeOpts
}
func (e encoderToNode) 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 e.setStructValue(node, rValue)
case reflect.Ptr:
return e.setNodeValue(node, rValue.Elem())
case reflect.Map:
return e.setMapValue(node, rValue)
case reflect.Slice:
return e.setSliceValue(node, rValue)
default:
// noop
}
return nil
}
func (e encoderToNode) 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(e.TagName) == "-" {
continue
}
if err := isSupportedType(field); err != nil {
return err
}
if e.isSkippedField(field, fieldValue) {
continue
}
nodeName := field.Name
if e.AllowSliceAsStruct && field.Type.Kind() == reflect.Slice && len(field.Tag.Get(TagLabelSliceAsStruct)) != 0 {
nodeName = field.Tag.Get(TagLabelSliceAsStruct)
}
if field.Anonymous {
if err := e.setNodeValue(node, fieldValue); err != nil {
return err
}
continue
}
child := &Node{Name: nodeName, FieldName: field.Name, Description: field.Tag.Get(TagDescription)}
if err := e.setNodeValue(child, fieldValue); err != nil {
return err
}
if field.Type.Kind() == reflect.Ptr {
if field.Type.Elem().Kind() != reflect.Struct && fieldValue.IsNil() {
continue
}
if field.Type.Elem().Kind() == reflect.Struct && len(child.Children) == 0 {
if field.Tag.Get(e.TagName) != TagLabelAllowEmpty {
continue
}
child.Value = "true"
}
}
node.Children = append(node.Children, child)
}
return nil
}
func (e encoderToNode) setMapValue(node *Node, rValue reflect.Value) error {
if rValue.Type().Elem().Kind() == reflect.Interface {
node.RawValue = rValue.Interface()
return nil
}
for _, key := range rValue.MapKeys() {
child := &Node{Name: key.String(), FieldName: key.String()}
node.Children = append(node.Children, child)
if err := e.setNodeValue(child, rValue.MapIndex(key)); err != nil {
return err
}
}
return nil
}
func (e encoderToNode) 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())
}
return e.setNodeValue(node, rValue.Index(0))
}
if rValue.Type().Elem().Kind() == reflect.Struct ||
rValue.Type().Elem().Kind() == reflect.Ptr && rValue.Type().Elem().Elem().Kind() == reflect.Struct {
for i := 0; i < rValue.Len(); i++ {
child := &Node{Name: "[" + strconv.Itoa(i) + "]"}
eValue := rValue.Index(i)
err := e.setNodeValue(child, eValue)
if err != nil {
return err
}
node.Children = append(node.Children, child)
}
return nil
}
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 (e encoderToNode) isSkippedField(field reflect.StructField, fieldValue reflect.Value) bool {
if e.OmitEmpty && 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 e.OmitEmpty && (field.Type.Kind() == reflect.Slice) &&
(fieldValue.IsNil() || fieldValue.Len() == 0) {
return true
}
if (field.Type.Kind() == reflect.Map) &&
(fieldValue.IsNil() || fieldValue.Len() == 0) {
return true
}
return false
}

View file

@ -1,813 +0,0 @@
package parser
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: "Description",
element: struct {
Foo string `description:"text"`
}{Foo: "bar"},
expected: expected{
node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Value: "bar", Description: "text"},
}},
},
},
{
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{
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 nil pointer",
element: struct {
Foo *struct {
Fii *string
Fuu string
}
}{
Foo: &struct {
Fii *string
Fuu string
}{
Fii: nil,
Fuu: "huu",
},
},
expected: expected{
node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Children: []*Node{
{Name: "Fuu", FieldName: "Fuu", Value: "huu"},
}},
}},
},
},
{
desc: "int pointer",
element: struct {
Foo *struct {
Fii *int
Fuu int
}
}{
Foo: &struct {
Fii *int
Fuu int
}{
Fii: func(v int) *int { return &v }(6),
Fuu: 4,
},
},
expected: expected{
node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Children: []*Node{
{Name: "Fii", FieldName: "Fii", Value: "6"},
{Name: "Fuu", FieldName: "Fuu", Value: "4"},
}},
}},
},
},
{
desc: "bool pointer",
element: struct {
Foo *struct {
Fii *bool
Fuu bool
}
}{
Foo: &struct {
Fii *bool
Fuu bool
}{
Fii: func(v bool) *bool { return &v }(true),
Fuu: true,
},
},
expected: expected{
node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Children: []*Node{
{Name: "Fii", FieldName: "Fii", Value: "true"},
{Name: "Fuu", FieldName: "Fuu", Value: "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: "slice of struct",
element: struct {
Foo []struct {
Field string
}
}{
Foo: []struct {
Field string
}{
{
Field: "bar",
},
{
Field: "bir",
},
},
},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Children: []*Node{
{Name: "[0]", Children: []*Node{
{Name: "Field", FieldName: "Field", Value: "bar"},
}},
{Name: "[1]", Children: []*Node{
{Name: "Field", FieldName: "Field", Value: "bir"},
}},
}},
}}},
},
{
desc: "slice of pointer of struct",
element: struct {
Foo []*struct {
Field string
}
}{
Foo: []*struct {
Field string
}{
{Field: "bar"},
{Field: "bir"},
},
},
expected: expected{node: &Node{Name: "traefik", Children: []*Node{
{Name: "Foo", FieldName: "Foo", Children: []*Node{
{Name: "[0]", Children: []*Node{
{Name: "Field", FieldName: "Field", Value: "bar"},
}},
{Name: "[1]", Children: []*Node{
{Name: "Field", FieldName: "Field", Value: "bir"},
}},
}},
}}},
},
{
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"}},
},
{
desc: "embedded",
element: struct {
Foo struct{ FiiFoo }
}{
Foo: struct{ FiiFoo }{
FiiFoo: FiiFoo{
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: "raw value",
element: struct {
Foo *struct {
Bar map[string]interface{}
}
}{
Foo: &struct {
Bar map[string]interface{}
}{
Bar: map[string]interface{}{
"AAA": "valueA",
"BBB": map[string]interface{}{
"CCC": map[string]interface{}{
"DDD": "valueD",
},
},
},
},
},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "Foo", FieldName: "Foo", Children: []*Node{
{Name: "Bar", FieldName: "Bar", RawValue: map[string]interface{}{
"AAA": "valueA",
"BBB": map[string]interface{}{
"CCC": map[string]interface{}{
"DDD": "valueD",
},
},
}},
}},
},
}},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
etnOpts := EncoderToNodeOpts{OmitEmpty: true, TagName: TagLabel, AllowSliceAsStruct: true}
node, err := EncodeToNode(test.element, DefaultRootName, etnOpts)
if test.expected.error {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, test.expected.node, node)
}
})
}
}

View file

@ -1,167 +0,0 @@
package parser
import (
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/containous/traefik/v2/pkg/types"
)
const defaultPtrValue = "false"
// FlatOpts holds options used when encoding to Flat.
type FlatOpts struct {
Case string // "lower" or "upper", defaults to "lower".
Separator string
SkipRoot bool
TagName string
}
// Flat is a configuration item representation.
type Flat struct {
Name string
Description string
Default string
}
// EncodeToFlat encodes a node to a Flat representation.
// Even though the given node argument should have already been augmented with metadata such as kind,
// the element (and its type information) is still needed to treat remaining edge cases.
func EncodeToFlat(element interface{}, node *Node, opts FlatOpts) ([]Flat, error) {
if element == nil || node == nil {
return nil, nil
}
if node.Kind == 0 {
return nil, fmt.Errorf("missing node type: %s", node.Name)
}
elem := reflect.ValueOf(element)
if elem.Kind() == reflect.Struct {
return nil, fmt.Errorf("structs are not supported, use pointer instead")
}
encoder := encoderToFlat{FlatOpts: opts}
var entries []Flat
if encoder.SkipRoot {
for _, child := range node.Children {
field := encoder.getField(elem.Elem(), child)
entries = append(entries, encoder.createFlat(field, child.Name, child)...)
}
} else {
entries = encoder.createFlat(elem, strings.ToLower(node.Name), node)
}
sort.Slice(entries, func(i, j int) bool { return entries[i].Name < entries[j].Name })
return entries, nil
}
type encoderToFlat struct {
FlatOpts
}
func (e encoderToFlat) createFlat(field reflect.Value, name string, node *Node) []Flat {
var entries []Flat
if node.Kind != reflect.Map && node.Description != "-" {
if !(node.Kind == reflect.Ptr && len(node.Children) > 0) ||
(node.Kind == reflect.Ptr && node.Tag.Get(e.TagName) == TagLabelAllowEmpty) {
if node.Name[0] != '[' {
entries = append(entries, Flat{
Name: e.getName(name),
Description: node.Description,
Default: e.getNodeValue(e.getField(field, node), node),
})
}
}
}
for _, child := range node.Children {
if node.Kind == reflect.Map {
fChild := e.getField(field, child)
var v string
if child.Kind == reflect.Struct {
v = defaultPtrValue
} else {
v = e.getNodeValue(fChild, child)
}
if node.Description != "-" {
entries = append(entries, Flat{
Name: e.getName(name, child.Name),
Description: node.Description,
Default: v,
})
}
if child.Kind == reflect.Struct || child.Kind == reflect.Ptr {
for _, ch := range child.Children {
f := e.getField(fChild, ch)
n := e.getName(name, child.Name, ch.Name)
entries = append(entries, e.createFlat(f, n, ch)...)
}
}
} else {
f := e.getField(field, child)
n := e.getName(name, child.Name)
entries = append(entries, e.createFlat(f, n, child)...)
}
}
return entries
}
func (e encoderToFlat) getField(field reflect.Value, node *Node) reflect.Value {
switch field.Kind() {
case reflect.Struct:
return field.FieldByName(node.FieldName)
case reflect.Ptr:
if field.Elem().Kind() == reflect.Struct {
return field.Elem().FieldByName(node.FieldName)
}
return field.Elem()
case reflect.Map:
return field.MapIndex(reflect.ValueOf(node.FieldName))
default:
return field
}
}
func (e encoderToFlat) getNodeValue(field reflect.Value, node *Node) string {
if node.Kind == reflect.Ptr && len(node.Children) > 0 {
return defaultPtrValue
}
if field.Kind() == reflect.Int64 {
i, _ := strconv.ParseInt(node.Value, 10, 64)
switch field.Type() {
case reflect.TypeOf(types.Duration(time.Second)):
return strconv.Itoa(int(i) / int(time.Second))
case reflect.TypeOf(time.Second):
return time.Duration(i).String()
}
}
return node.Value
}
func (e encoderToFlat) getName(names ...string) string {
var name string
if names[len(names)-1][0] == '[' {
name = strings.Join(names, "")
} else {
name = strings.Join(names, e.Separator)
}
if strings.EqualFold(e.Case, "upper") {
return strings.ToUpper(name)
}
return strings.ToLower(name)
}

File diff suppressed because it is too large Load diff

View file

@ -1,97 +0,0 @@
package parser
import (
"fmt"
"sort"
"strings"
)
// DecodeToNode converts the labels to a tree of nodes.
// If any filters are present, labels which do not match the filters are skipped.
func DecodeToNode(labels map[string]string, rootName string, filters ...string) (*Node, error) {
sortedKeys := sortKeys(labels, filters)
var node *Node
for i, key := range sortedKeys {
split := strings.Split(key, ".")
if split[0] != rootName {
return nil, fmt.Errorf("invalid label root %s", split[0])
}
var parts []string
for _, v := range split {
if v == "" {
return nil, fmt.Errorf("invalid element: %s", key)
}
if v[0] == '[' {
return nil, fmt.Errorf("invalid leading character '[' in field name (bracket is a slice delimiter): %s", v)
}
if strings.HasSuffix(v, "]") && v[0] != '[' {
indexLeft := strings.Index(v, "[")
parts = append(parts, v[:indexLeft], v[indexLeft:])
} else {
parts = append(parts, v)
}
}
if i == 0 {
node = &Node{}
}
decodeToNode(node, parts, 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 strings.EqualFold(name, n.Name) {
return n
}
}
return nil
}
func sortKeys(labels map[string]string, filters []string) []string {
var sortedKeys []string
for key := range labels {
if len(filters) == 0 {
sortedKeys = append(sortedKeys, key)
continue
}
for _, filter := range filters {
if len(key) >= len(filter) && strings.EqualFold(key[:len(filter)], filter) {
sortedKeys = append(sortedKeys, key)
continue
}
}
}
sort.Strings(sortedKeys)
return sortedKeys
}

View file

@ -1,261 +0,0 @@
package parser
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
filters []string
expected expected
}{
{
desc: "no label",
in: map[string]string{},
expected: expected{node: nil},
},
{
desc: "invalid label, ending by a dot",
in: map[string]string{
"traefik.http.": "bar",
},
expected: expected{
error: true,
},
},
{
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, prefix filter",
in: map[string]string{
"traefik.foo": "bar",
"traefik.fii": "bir",
},
filters: []string{"traefik.Foo"},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Value: "bar"},
},
}},
},
{
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, case insensitive",
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: "bbb", Value: "bur"},
{Name: "aaa", Value: "bar"},
}},
},
}},
},
{
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"},
}},
}},
},
}},
},
{
desc: "several entries, slice syntax",
in: map[string]string{
"traefik.foo[0].aaa": "bar0",
"traefik.foo[0].bbb": "bur0",
"traefik.foo[1].aaa": "bar1",
"traefik.foo[1].bbb": "bur1",
},
expected: expected{node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Children: []*Node{
{Name: "[0]", Children: []*Node{
{Name: "aaa", Value: "bar0"},
{Name: "bbb", Value: "bur0"},
}},
{Name: "[1]", Children: []*Node{
{Name: "aaa", Value: "bar1"},
{Name: "bbb", Value: "bur1"},
}},
}},
},
}},
},
{
desc: "several entries, invalid slice syntax",
in: map[string]string{
"traefik.foo.[0].aaa": "bar0",
"traefik.foo.[0].bbb": "bur0",
"traefik.foo.[1].aaa": "bar1",
"traefik.foo.[1].bbb": "bur1",
},
expected: expected{error: true},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
out, err := DecodeToNode(test.in, DefaultRootName, test.filters...)
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))
}
}
})
}
}

View file

@ -1,67 +0,0 @@
package parser
import (
"fmt"
"reflect"
)
// 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
}
var sep string
if child.Name[0] != '[' {
sep = "."
}
childName := root + sep + child.Name
if child.RawValue != nil {
encodeRawValue(labels, childName, child.RawValue)
continue
}
if len(child.Children) > 0 {
encodeNode(labels, childName, child)
} else if len(child.Name) > 0 {
labels[childName] = child.Value
}
}
}
func encodeRawValue(labels map[string]string, root string, rawValue interface{}) {
if rawValue == nil {
return
}
tValue := reflect.TypeOf(rawValue)
if tValue.Kind() == reflect.Map && tValue.Elem().Kind() == reflect.Interface {
r := reflect.ValueOf(rawValue).
Convert(reflect.TypeOf((map[string]interface{})(nil))).
Interface().(map[string]interface{})
for k, v := range r {
switch tv := v.(type) {
case string:
labels[root+"."+k] = tv
case []interface{}:
for i, e := range tv {
encodeRawValue(labels, fmt.Sprintf("%s.%s[%d]", root, k, i), e)
}
default:
encodeRawValue(labels, root+"."+k, v)
}
}
}
}

View file

@ -1,234 +0,0 @@
package parser
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",
},
},
{
desc: "slice of struct syntax",
node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "foo", Children: []*Node{
{Name: "[0]", Children: []*Node{
{Name: "aaa", Value: "bar0"},
{Name: "bbb", Value: "bur0"},
}},
{Name: "[1]", Children: []*Node{
{Name: "aaa", Value: "bar1"},
{Name: "bbb", Value: "bur1"},
}},
}},
},
},
expected: map[string]string{
"traefik.foo[0].aaa": "bar0",
"traefik.foo[0].bbb": "bur0",
"traefik.foo[1].aaa": "bar1",
"traefik.foo[1].bbb": "bur1",
},
},
{
desc: "raw value, level 1",
node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "aaa", RawValue: map[string]interface{}{
"bbb": "test1",
"ccc": "test2",
}},
},
},
expected: map[string]string{
"traefik.aaa.bbb": "test1",
"traefik.aaa.ccc": "test2",
},
},
{
desc: "raw value, level 2",
node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "aaa", RawValue: map[string]interface{}{
"bbb": "test1",
"ccc": map[string]interface{}{
"ddd": "test2",
},
}},
},
},
expected: map[string]string{
"traefik.aaa.bbb": "test1",
"traefik.aaa.ccc.ddd": "test2",
},
},
{
desc: "raw value, slice of struct",
node: &Node{
Name: "traefik",
Children: []*Node{
{Name: "aaa", RawValue: map[string]interface{}{
"bbb": []interface{}{
map[string]interface{}{
"ccc": "test1",
"ddd": "test2",
},
},
}},
},
},
expected: map[string]string{
"traefik.aaa.bbb[0].ccc": "test1",
"traefik.aaa.bbb[0].ddd": "test2",
},
},
}
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)
})
}
}

View file

@ -1,22 +0,0 @@
package parser
import "reflect"
// DefaultRootName is the default name of the root node and the prefix of element name from the resources.
const DefaultRootName = "traefik"
// MapNamePlaceholder is the placeholder for the map name.
const MapNamePlaceholder = "<name>"
// Node is a label node.
type Node struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
FieldName string `json:"fieldName"`
Value string `json:"value,omitempty"`
RawValue interface{} `json:"rawValue,omitempty"`
Disabled bool `json:"disabled,omitempty"`
Kind reflect.Kind `json:"kind,omitempty"`
Tag reflect.StructTag `json:"tag,omitempty"`
Children []*Node `json:"children,omitempty"`
}

View file

@ -1,278 +0,0 @@
package parser
import (
"errors"
"fmt"
"reflect"
"strings"
)
// MetadataOpts Options for the metadata.
type MetadataOpts struct {
TagName string
AllowSliceAsStruct bool
}
// AddMetadata adds metadata such as type, inferred from element, to a node.
func AddMetadata(element interface{}, node *Node, opts MetadataOpts) error {
return metadata{MetadataOpts: opts}.Add(element, node)
}
type metadata struct {
MetadataOpts
}
// Add adds metadata such as type, inferred from element, to a node.
func (m metadata) Add(element interface{}, node *Node) error {
if node == nil {
return nil
}
if len(node.Children) == 0 {
return fmt.Errorf("invalid node %s: no child", node.Name)
}
if element == nil {
return errors.New("nil structure")
}
rootType := reflect.TypeOf(element)
node.Kind = rootType.Kind()
return m.browseChildren(rootType, node)
}
func (m metadata) browseChildren(fType reflect.Type, node *Node) error {
for _, child := range node.Children {
if err := m.add(fType, child); err != nil {
return err
}
}
return nil
}
func (m metadata) add(rootType reflect.Type, node *Node) error {
rType := rootType
if rootType.Kind() == reflect.Ptr {
rType = rootType.Elem()
}
if rType.Kind() == reflect.Map && rType.Elem().Kind() == reflect.Interface {
addRawValue(node)
return nil
}
field, err := m.findTypedField(rType, node)
if err != nil {
return err
}
if err = isSupportedType(field); err != nil {
return err
}
fType := field.Type
node.Kind = fType.Kind()
node.Tag = field.Tag
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(m.TagName) != TagLabelAllowEmpty {
return fmt.Errorf("%s cannot be a standalone element (type %s)", node.Name, fType)
}
node.Disabled = len(node.Value) > 0 && !strings.EqualFold(node.Value, "true") && field.Tag.Get(m.TagName) == TagLabelAllowEmpty
}
if len(node.Children) == 0 {
return nil
}
if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Ptr && fType.Elem().Kind() == reflect.Struct {
return m.browseChildren(fType, node)
}
if fType.Kind() == reflect.Map {
if fType.Elem().Kind() == reflect.Interface {
addRawValue(node)
return nil
}
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 = m.browseChildren(elem, child); err != nil {
return err
}
}
}
return nil
}
if fType.Kind() == reflect.Slice {
if m.AllowSliceAsStruct && field.Tag.Get(TagLabelSliceAsStruct) != "" {
return m.browseChildren(fType.Elem(), node)
}
for _, ch := range node.Children {
ch.Kind = fType.Elem().Kind()
if err = m.browseChildren(fType.Elem(), ch); err != nil {
return err
}
}
return nil
}
return fmt.Errorf("invalid node %s: %v", node.Name, fType.Kind())
}
func (m metadata) 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 !m.AllowSliceAsStruct || len(fieldName) == 0 {
fieldName = cField.Name
}
if IsExported(cField) {
if cField.Anonymous {
if cField.Type.Kind() == reflect.Struct {
structField, err := m.findTypedField(cField.Type, node)
if err != nil {
continue
}
return structField, nil
}
}
if strings.EqualFold(fieldName, node.Name) {
node.FieldName = cField.Name
return cField, nil
}
}
}
return reflect.StructField{}, fmt.Errorf("field not found, node: %s", node.Name)
}
// IsExported reports whether f is exported.
// https://golang.org/pkg/reflect/#StructField
func IsExported(f reflect.StructField) bool {
return f.PkgPath == ""
}
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,
reflect.Struct,
reflect.Ptr:
return nil
default:
return fmt.Errorf("unsupported slice type: %v", fType)
}
}
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
}
/*
RawMap section
*/
func addRawValue(node *Node) {
if node.RawValue == nil {
node.RawValue = nodeToRawMap(node)
}
node.Children = nil
}
func nodeToRawMap(node *Node) map[string]interface{} {
result := map[string]interface{}{}
squashNode(node, result, true)
return result
}
func squashNode(node *Node, acc map[string]interface{}, root bool) {
if len(node.Children) == 0 {
acc[node.Name] = node.Value
return
}
// slice
if isArrayKey(node.Children[0].Name) {
var accChild []interface{}
for _, child := range node.Children {
tmp := map[string]interface{}{}
squashNode(child, tmp, false)
accChild = append(accChild, tmp[child.Name])
}
acc[node.Name] = accChild
return
}
// map
var accChild map[string]interface{}
if root {
accChild = acc
} else {
accChild = typedRawMap(acc, node.Name)
}
for _, child := range node.Children {
squashNode(child, accChild, false)
}
}
func typedRawMap(m map[string]interface{}, k string) map[string]interface{} {
if m[k] == nil {
m[k] = map[string]interface{}{}
}
r, ok := m[k].(map[string]interface{})
if !ok {
panic(fmt.Sprintf("unsupported value (key: %s): %T", k, m[k]))
}
return r
}
func isArrayKey(name string) bool {
return name[0] == '[' && name[len(name)-1] == ']'
}

File diff suppressed because it is too large Load diff

View file

@ -1,35 +0,0 @@
// Package parser implements decoding and encoding between a flat map of labels and a typed Configuration.
package parser
// Decode decodes the given map of labels into the given element.
// If any filters are present, labels which do not match the filters are skipped.
// The operation goes through three stages roughly summarized as:
// labels -> tree of untyped nodes
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
// "typed" nodes -> typed element.
func Decode(labels map[string]string, element interface{}, rootName string, filters ...string) error {
node, err := DecodeToNode(labels, rootName, filters...)
if err != nil {
return err
}
metaOpts := MetadataOpts{TagName: TagLabel, AllowSliceAsStruct: true}
err = AddMetadata(element, node, metaOpts)
if err != nil {
return err
}
return Fill(element, node, FillerOpts{AllowSliceAsStruct: true})
}
// Encode converts an element to labels.
// element -> node (value) -> label (node).
func Encode(element interface{}, rootName string) (map[string]string, error) {
etnOpts := EncoderToNodeOpts{OmitEmpty: true, TagName: TagLabel, AllowSliceAsStruct: true}
node, err := EncodeToNode(element, rootName, etnOpts)
if err != nil {
return nil, err
}
return EncodeNode(node), nil
}

View file

@ -1,346 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type Tomato struct {
Name string
Meta map[string]interface{}
}
type Potato struct {
Name string
Meta map[string]map[string]interface{}
}
func TestDecode_RawValue(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
elt interface{}
expected interface{}
}{
{
desc: "level 1",
elt: &Tomato{},
labels: map[string]string{
"traefik.name": "test",
"traefik.meta.aaa": "test",
},
expected: &Tomato{
Name: "test",
Meta: map[string]interface{}{
"aaa": "test",
},
},
},
{
desc: "level 2",
labels: map[string]string{
"traefik.name": "test",
"traefik.meta.aaa": "test",
"traefik.meta.bbb.ccc": "test",
},
elt: &Tomato{},
expected: &Tomato{
Name: "test",
Meta: map[string]interface{}{
"aaa": "test",
"bbb": map[string]interface{}{
"ccc": "test",
},
},
},
},
{
desc: "level 3",
labels: map[string]string{
"traefik.name": "test",
"traefik.meta.aaa": "test",
"traefik.meta.bbb.ccc": "test",
"traefik.meta.bbb.ddd.eee": "test",
},
elt: &Tomato{},
expected: &Tomato{
Name: "test",
Meta: map[string]interface{}{
"aaa": "test",
"bbb": map[string]interface{}{
"ccc": "test",
"ddd": map[string]interface{}{
"eee": "test",
},
},
},
},
},
{
desc: "struct slice, one entry",
elt: &Tomato{},
labels: map[string]string{
"traefik.name": "test1",
"traefik.meta.aaa[0].bbb": "test2",
"traefik.meta.aaa[0].ccc": "test3",
},
expected: &Tomato{
Name: "test1",
Meta: map[string]interface{}{
"aaa": []interface{}{
map[string]interface{}{
"bbb": "test2",
"ccc": "test3",
},
},
},
},
},
{
desc: "struct slice, multiple entries",
elt: &Tomato{},
labels: map[string]string{
"traefik.name": "test1",
"traefik.meta.aaa[0].bbb": "test2",
"traefik.meta.aaa[0].ccc": "test3",
"traefik.meta.aaa[1].bbb": "test4",
"traefik.meta.aaa[1].ccc": "test5",
"traefik.meta.aaa[2].bbb": "test6",
"traefik.meta.aaa[2].ccc": "test7",
},
expected: &Tomato{
Name: "test1",
Meta: map[string]interface{}{
"aaa": []interface{}{
map[string]interface{}{
"bbb": "test2",
"ccc": "test3",
},
map[string]interface{}{
"bbb": "test4",
"ccc": "test5",
},
map[string]interface{}{
"bbb": "test6",
"ccc": "test7",
},
},
},
},
},
{
desc: "explicit map of map, level 1",
elt: &Potato{},
labels: map[string]string{
"traefik.name": "test",
"traefik.meta.aaa.bbb": "test1",
},
expected: &Potato{
Name: "test",
Meta: map[string]map[string]interface{}{
"aaa": {
"bbb": "test1",
},
},
},
},
{
desc: "explicit map of map, level 2",
elt: &Potato{},
labels: map[string]string{
"traefik.name": "test",
"traefik.meta.aaa.bbb": "test1",
"traefik.meta.aaa.ccc": "test2",
},
expected: &Potato{
Name: "test",
Meta: map[string]map[string]interface{}{
"aaa": {
"bbb": "test1",
"ccc": "test2",
},
},
},
},
{
desc: "explicit map of map, level 3",
elt: &Potato{},
labels: map[string]string{
"traefik.name": "test",
"traefik.meta.aaa.bbb.ccc": "test1",
"traefik.meta.aaa.bbb.ddd": "test2",
"traefik.meta.aaa.eee": "test3",
},
expected: &Potato{
Name: "test",
Meta: map[string]map[string]interface{}{
"aaa": {
"bbb": map[string]interface{}{
"ccc": "test1",
"ddd": "test2",
},
"eee": "test3",
},
},
},
},
{
desc: "explicit map of map, level 4",
elt: &Potato{},
labels: map[string]string{
"traefik.name": "test",
"traefik.meta.aaa.bbb.ccc.ddd": "test1",
"traefik.meta.aaa.bbb.ccc.eee": "test2",
"traefik.meta.aaa.bbb.fff": "test3",
"traefik.meta.aaa.ggg": "test4",
},
expected: &Potato{
Name: "test",
Meta: map[string]map[string]interface{}{
"aaa": {
"bbb": map[string]interface{}{
"ccc": map[string]interface{}{
"ddd": "test1",
"eee": "test2",
},
"fff": "test3",
},
"ggg": "test4",
},
},
},
},
{
desc: "explicit map of map, struct slice, level 1, one entry",
elt: &Potato{},
labels: map[string]string{
"traefik.name": "test1",
"traefik.meta.aaa.bbb[0].ccc": "test2",
"traefik.meta.aaa.bbb[0].ddd": "test3",
},
expected: &Potato{
Name: "test1",
Meta: map[string]map[string]interface{}{
"aaa": {
"bbb": []interface{}{
map[string]interface{}{
"ccc": "test2",
"ddd": "test3",
},
},
},
},
},
},
{
desc: "explicit map of map, struct slice, level 1, multiple entries",
elt: &Potato{},
labels: map[string]string{
"traefik.name": "test1",
"traefik.meta.aaa.bbb[0].ccc": "test2",
"traefik.meta.aaa.bbb[0].ddd": "test3",
"traefik.meta.aaa.bbb[1].ccc": "test4",
"traefik.meta.aaa.bbb[1].ddd": "test5",
"traefik.meta.aaa.bbb[2].ccc": "test6",
"traefik.meta.aaa.bbb[2].ddd": "test7",
},
expected: &Potato{
Name: "test1",
Meta: map[string]map[string]interface{}{
"aaa": {
"bbb": []interface{}{
map[string]interface{}{
"ccc": "test2",
"ddd": "test3",
},
map[string]interface{}{
"ccc": "test4",
"ddd": "test5",
},
map[string]interface{}{
"ccc": "test6",
"ddd": "test7",
},
},
},
},
},
},
{
desc: "explicit map of map, struct slice, level 2, one entry",
elt: &Potato{},
labels: map[string]string{
"traefik.name": "test1",
"traefik.meta.aaa.bbb.ccc[0].ddd": "test2",
"traefik.meta.aaa.bbb.ccc[0].eee": "test3",
},
expected: &Potato{
Name: "test1",
Meta: map[string]map[string]interface{}{
"aaa": {
"bbb": map[string]interface{}{
"ccc": []interface{}{
map[string]interface{}{
"ddd": "test2",
"eee": "test3",
},
},
},
},
},
},
},
{
desc: "explicit map of map, struct slice, level 2, multiple entries",
elt: &Potato{},
labels: map[string]string{
"traefik.name": "test1",
"traefik.meta.aaa.bbb.ccc[0].ddd": "test2",
"traefik.meta.aaa.bbb.ccc[0].eee": "test3",
"traefik.meta.aaa.bbb.ccc[1].ddd": "test4",
"traefik.meta.aaa.bbb.ccc[1].eee": "test5",
"traefik.meta.aaa.bbb.ccc[2].ddd": "test6",
"traefik.meta.aaa.bbb.ccc[2].eee": "test7",
},
expected: &Potato{
Name: "test1",
Meta: map[string]map[string]interface{}{
"aaa": {
"bbb": map[string]interface{}{
"ccc": []interface{}{
map[string]interface{}{
"ddd": "test2",
"eee": "test3",
},
map[string]interface{}{
"ddd": "test4",
"eee": "test5",
},
map[string]interface{}{
"ddd": "test6",
"eee": "test7",
},
},
},
},
},
},
},
}
for _, test := range testCases {
if test.desc != "level 3" {
continue
}
test := test
t.Run(test.desc, func(t *testing.T) {
err := Decode(test.labels, test.elt, "traefik")
require.NoError(t, err)
assert.Equal(t, test.expected, test.elt)
})
}
}

View file

@ -1,24 +0,0 @@
package parser
const (
// TagLabel allows to apply a custom behavior.
// - "allowEmpty": allows to create an empty struct.
// - "-": ignore the field.
TagLabel = "label"
// TagFile allows to apply a custom behavior.
// - "allowEmpty": allows to create an empty struct.
// - "-": ignore the field.
TagFile = "file"
// TagLabelSliceAsStruct allows to use a slice of struct by creating one entry into the slice.
// The value is the substitution name used in the label to access the slice.
TagLabelSliceAsStruct = "label-slice-as-struct"
// TagDescription is the documentation for the field.
// - "-": ignore the field.
TagDescription = "description"
// TagLabelAllowEmpty is related to TagLabel.
TagLabelAllowEmpty = "allowEmpty"
)

View file

@ -34,10 +34,11 @@ import (
assetfs "github.com/elazarl/go-bindata-assetfs"
legolog "github.com/go-acme/lego/v3/log"
"github.com/sirupsen/logrus"
ptypes "github.com/traefik/paerser/types"
)
const (
// DefaultInternalEntryPointName the name of the default internal entry point
// DefaultInternalEntryPointName the name of the default internal entry point.
DefaultInternalEntryPointName = "traefik"
// DefaultGraceTimeout controls how long Traefik serves pending requests
@ -47,7 +48,7 @@ const (
// DefaultIdleTimeout before closing an idle connection.
DefaultIdleTimeout = 180 * time.Second
// DefaultAcmeCAServer is the default ACME API endpoint
// DefaultAcmeCAServer is the default ACME API endpoint.
DefaultAcmeCAServer = "https://acme-v02.api.letsencrypt.org/directory"
)
@ -110,38 +111,38 @@ func (a *API) SetDefaults() {
// RespondingTimeouts contains timeout configurations for incoming requests to the Traefik instance.
type RespondingTimeouts struct {
ReadTimeout types.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"`
WriteTimeout types.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"`
IdleTimeout types.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"`
ReadTimeout ptypes.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"`
WriteTimeout ptypes.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"`
IdleTimeout ptypes.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"`
}
// SetDefaults sets the default values.
func (a *RespondingTimeouts) SetDefaults() {
a.IdleTimeout = types.Duration(DefaultIdleTimeout)
a.IdleTimeout = ptypes.Duration(DefaultIdleTimeout)
}
// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers.
type ForwardingTimeouts struct {
DialTimeout types.Duration `description:"The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists." json:"dialTimeout,omitempty" toml:"dialTimeout,omitempty" yaml:"dialTimeout,omitempty" export:"true"`
ResponseHeaderTimeout types.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists." json:"responseHeaderTimeout,omitempty" toml:"responseHeaderTimeout,omitempty" yaml:"responseHeaderTimeout,omitempty" export:"true"`
IdleConnTimeout types.Duration `description:"The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself" json:"idleConnTimeout,omitempty" toml:"idleConnTimeout,omitempty" yaml:"idleConnTimeout,omitempty" export:"true"`
DialTimeout ptypes.Duration `description:"The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists." json:"dialTimeout,omitempty" toml:"dialTimeout,omitempty" yaml:"dialTimeout,omitempty" export:"true"`
ResponseHeaderTimeout ptypes.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists." json:"responseHeaderTimeout,omitempty" toml:"responseHeaderTimeout,omitempty" yaml:"responseHeaderTimeout,omitempty" export:"true"`
IdleConnTimeout ptypes.Duration `description:"The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself" json:"idleConnTimeout,omitempty" toml:"idleConnTimeout,omitempty" yaml:"idleConnTimeout,omitempty" export:"true"`
}
// SetDefaults sets the default values.
func (f *ForwardingTimeouts) SetDefaults() {
f.DialTimeout = types.Duration(30 * time.Second)
f.IdleConnTimeout = types.Duration(90 * time.Second)
f.DialTimeout = ptypes.Duration(30 * time.Second)
f.IdleConnTimeout = ptypes.Duration(90 * time.Second)
}
// LifeCycle contains configurations relevant to the lifecycle (such as the shutdown phase) of Traefik.
type LifeCycle struct {
RequestAcceptGraceTimeout types.Duration `description:"Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure." json:"requestAcceptGraceTimeout,omitempty" toml:"requestAcceptGraceTimeout,omitempty" yaml:"requestAcceptGraceTimeout,omitempty" export:"true"`
GraceTimeOut types.Duration `description:"Duration to give active requests a chance to finish before Traefik stops." json:"graceTimeOut,omitempty" toml:"graceTimeOut,omitempty" yaml:"graceTimeOut,omitempty" export:"true"`
RequestAcceptGraceTimeout ptypes.Duration `description:"Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure." json:"requestAcceptGraceTimeout,omitempty" toml:"requestAcceptGraceTimeout,omitempty" yaml:"requestAcceptGraceTimeout,omitempty" export:"true"`
GraceTimeOut ptypes.Duration `description:"Duration to give active requests a chance to finish before Traefik stops." json:"graceTimeOut,omitempty" toml:"graceTimeOut,omitempty" yaml:"graceTimeOut,omitempty" export:"true"`
}
// SetDefaults sets the default values.
func (a *LifeCycle) SetDefaults() {
a.GraceTimeOut = types.Duration(DefaultGraceTimeout)
a.GraceTimeOut = ptypes.Duration(DefaultGraceTimeout)
}
// Tracing holds the tracing configuration.
@ -164,7 +165,7 @@ func (t *Tracing) SetDefaults() {
// Providers contains providers configuration.
type Providers struct {
ProvidersThrottleDuration types.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." json:"providersThrottleDuration,omitempty" toml:"providersThrottleDuration,omitempty" yaml:"providersThrottleDuration,omitempty" export:"true"`
ProvidersThrottleDuration ptypes.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." json:"providersThrottleDuration,omitempty" toml:"providersThrottleDuration,omitempty" yaml:"providersThrottleDuration,omitempty" export:"true"`
Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
File *file.Provider `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"`
@ -207,7 +208,7 @@ func (c *Configuration) SetEffectiveConfiguration() {
if c.Providers.Docker != nil {
if c.Providers.Docker.SwarmModeRefreshSeconds <= 0 {
c.Providers.Docker.SwarmModeRefreshSeconds = types.Duration(15 * time.Second)
c.Providers.Docker.SwarmModeRefreshSeconds = ptypes.Duration(15 * time.Second)
}
}