New static configuration loading system.
Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
This commit is contained in:
parent
d18edd6f77
commit
8d7eccad5d
165 changed files with 10894 additions and 6076 deletions
|
@ -5,7 +5,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/containous/traefik/pkg/ping"
|
||||
"github.com/containous/traefik/pkg/provider"
|
||||
|
@ -38,18 +37,18 @@ func TestDo_globalConfiguration(t *testing.T) {
|
|||
FilePath: "AccessLog FilePath",
|
||||
Format: "AccessLog Format",
|
||||
Filters: &types.AccessLogFilters{
|
||||
StatusCodes: types.StatusCodes{"200", "500"},
|
||||
StatusCodes: []string{"200", "500"},
|
||||
RetryAttempts: true,
|
||||
MinDuration: 10,
|
||||
},
|
||||
Fields: &types.AccessLogFields{
|
||||
DefaultMode: "drop",
|
||||
Names: types.FieldNames{
|
||||
Names: map[string]string{
|
||||
"RequestHost": "keep",
|
||||
},
|
||||
Headers: &types.FieldHeaders{
|
||||
DefaultMode: "drop",
|
||||
Names: types.FieldHeaderNames{
|
||||
Names: map[string]string{
|
||||
"Referer": "keep",
|
||||
},
|
||||
},
|
||||
|
@ -68,9 +67,9 @@ func TestDo_globalConfiguration(t *testing.T) {
|
|||
Address: "foo Address",
|
||||
Transport: &static.EntryPointsTransport{
|
||||
RespondingTimeouts: &static.RespondingTimeouts{
|
||||
ReadTimeout: parse.Duration(111 * time.Second),
|
||||
WriteTimeout: parse.Duration(111 * time.Second),
|
||||
IdleTimeout: parse.Duration(111 * time.Second),
|
||||
ReadTimeout: types.Duration(111 * time.Second),
|
||||
WriteTimeout: types.Duration(111 * time.Second),
|
||||
IdleTimeout: types.Duration(111 * time.Second),
|
||||
},
|
||||
},
|
||||
ProxyProtocol: &static.ProxyProtocol{
|
||||
|
@ -81,9 +80,9 @@ func TestDo_globalConfiguration(t *testing.T) {
|
|||
Address: "fii Address",
|
||||
Transport: &static.EntryPointsTransport{
|
||||
RespondingTimeouts: &static.RespondingTimeouts{
|
||||
ReadTimeout: parse.Duration(111 * time.Second),
|
||||
WriteTimeout: parse.Duration(111 * time.Second),
|
||||
IdleTimeout: parse.Duration(111 * time.Second),
|
||||
ReadTimeout: types.Duration(111 * time.Second),
|
||||
WriteTimeout: types.Duration(111 * time.Second),
|
||||
IdleTimeout: types.Duration(111 * time.Second),
|
||||
},
|
||||
},
|
||||
ProxyProtocol: &static.ProxyProtocol{
|
||||
|
@ -112,16 +111,16 @@ func TestDo_globalConfiguration(t *testing.T) {
|
|||
},
|
||||
}
|
||||
config.Providers = &static.Providers{
|
||||
ProvidersThrottleDuration: parse.Duration(111 * time.Second),
|
||||
ProvidersThrottleDuration: types.Duration(111 * time.Second),
|
||||
}
|
||||
|
||||
config.ServersTransport = &static.ServersTransport{
|
||||
InsecureSkipVerify: true,
|
||||
RootCAs: traefiktls.FilesOrContents{"RootCAs 1", "RootCAs 2", "RootCAs 3"},
|
||||
RootCAs: []traefiktls.FileOrContent{"RootCAs 1", "RootCAs 2", "RootCAs 3"},
|
||||
MaxIdleConnsPerHost: 111,
|
||||
ForwardingTimeouts: &static.ForwardingTimeouts{
|
||||
DialTimeout: parse.Duration(111 * time.Second),
|
||||
ResponseHeaderTimeout: parse.Duration(111 * time.Second),
|
||||
DialTimeout: types.Duration(111 * time.Second),
|
||||
ResponseHeaderTimeout: types.Duration(111 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -156,15 +155,15 @@ func TestDo_globalConfiguration(t *testing.T) {
|
|||
|
||||
config.Providers.Docker = &docker.Provider{
|
||||
Constrainer: provider.Constrainer{
|
||||
Constraints: types.Constraints{
|
||||
Constraints: []*types.Constraint{
|
||||
{
|
||||
Key: "file Constraints Key 1",
|
||||
Regex: "file Constraints Regex 2",
|
||||
Value: "file Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
{
|
||||
Key: "file Constraints Key 1",
|
||||
Regex: "file Constraints Regex 2",
|
||||
Value: "file Constraints Regex 2",
|
||||
MustMatch: true,
|
||||
},
|
||||
},
|
||||
|
@ -210,22 +209,22 @@ func TestDo_globalConfiguration(t *testing.T) {
|
|||
|
||||
config.Metrics = &types.Metrics{
|
||||
Prometheus: &types.Prometheus{
|
||||
Buckets: types.Buckets{0.1, 0.3, 1.2, 5},
|
||||
Buckets: []float64{0.1, 0.3, 1.2, 5},
|
||||
EntryPoint: "MyEntryPoint",
|
||||
Middlewares: []string{"m1", "m2"},
|
||||
},
|
||||
Datadog: &types.Datadog{
|
||||
Address: "localhost:8181",
|
||||
PushInterval: "12",
|
||||
PushInterval: 12,
|
||||
},
|
||||
StatsD: &types.Statsd{
|
||||
Address: "localhost:8182",
|
||||
PushInterval: "42",
|
||||
PushInterval: 42,
|
||||
},
|
||||
InfluxDB: &types.InfluxDB{
|
||||
Address: "localhost:8183",
|
||||
Protocol: "http",
|
||||
PushInterval: "22",
|
||||
PushInterval: 22,
|
||||
Database: "myDB",
|
||||
RetentionPolicy: "12",
|
||||
Username: "a",
|
||||
|
|
115
pkg/cli/commands.go
Normal file
115
pkg/cli/commands.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
// Package cli provides tools to create commands that support advanced configuration features,
|
||||
// sub-commands, and allowing configuration from command-line flags, configuration files, and environment variables.
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Command structure contains program/command information (command name and description).
|
||||
type Command struct {
|
||||
Name string
|
||||
Description string
|
||||
Configuration interface{}
|
||||
Resources []ResourceLoader
|
||||
Run func([]string) error
|
||||
Hidden bool
|
||||
subCommands []*Command
|
||||
}
|
||||
|
||||
// AddCommand Adds a sub command.
|
||||
func (c *Command) AddCommand(cmd *Command) error {
|
||||
if c == nil || cmd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Name == cmd.Name {
|
||||
return fmt.Errorf("child command cannot have the same name as their parent: %s", cmd.Name)
|
||||
}
|
||||
|
||||
c.subCommands = append(c.subCommands, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Execute Executes a command.
|
||||
func Execute(cmd *Command) error {
|
||||
return execute(cmd, os.Args, true)
|
||||
}
|
||||
|
||||
func execute(cmd *Command, args []string, root bool) error {
|
||||
if len(args) == 1 {
|
||||
if err := run(cmd, args); err != nil {
|
||||
return fmt.Errorf("command %s error: %v", args[0], err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if root && cmd.Name != args[1] && !contains(cmd.subCommands, args[1]) {
|
||||
if err := run(cmd, args[1:]); err != nil {
|
||||
return fmt.Errorf("command %s error: %v", filepath.Base(args[0]), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(args) >= 2 && cmd.Name == args[1] {
|
||||
if err := run(cmd, args[2:]); err != nil {
|
||||
return fmt.Errorf("command %s error: %v", cmd.Name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(cmd.subCommands) == 0 {
|
||||
if err := run(cmd, args[1:]); err != nil {
|
||||
return fmt.Errorf("command %s error: %v", cmd.Name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, subCmd := range cmd.subCommands {
|
||||
if len(args) >= 2 && subCmd.Name == args[1] {
|
||||
return execute(subCmd, args[1:], false)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("command not found: %v", args)
|
||||
}
|
||||
|
||||
func run(cmd *Command, args []string) error {
|
||||
if isHelp(args) {
|
||||
return PrintHelp(os.Stdout, cmd)
|
||||
}
|
||||
|
||||
if cmd.Run == nil {
|
||||
_ = PrintHelp(os.Stdout, cmd)
|
||||
return errors.New("command not found")
|
||||
}
|
||||
|
||||
if cmd.Configuration == nil {
|
||||
return cmd.Run(args)
|
||||
}
|
||||
|
||||
for _, resource := range cmd.Resources {
|
||||
done, err := resource.Load(args, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if done {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return cmd.Run(args)
|
||||
}
|
||||
|
||||
func contains(cmds []*Command, name string) bool {
|
||||
for _, cmd := range cmds {
|
||||
if cmd.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
681
pkg/cli/commands_test.go
Normal file
681
pkg/cli/commands_test.go
Normal file
|
@ -0,0 +1,681 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCommand_AddCommand(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
subCommand *Command
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
desc: "sub command nil",
|
||||
subCommand: nil,
|
||||
},
|
||||
{
|
||||
desc: "add a simple command",
|
||||
subCommand: &Command{
|
||||
Name: "sub",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "add a sub command with the same name as their parent",
|
||||
subCommand: &Command{
|
||||
Name: "root",
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
}
|
||||
|
||||
err := rootCmd.AddCommand(test.subCommand)
|
||||
|
||||
if test.expectedError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_execute(t *testing.T) {
|
||||
var called string
|
||||
|
||||
type expected struct {
|
||||
result string
|
||||
error bool
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
command func() *Command
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "root command",
|
||||
args: []string{""},
|
||||
command: func() *Command {
|
||||
return &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called = "root"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
},
|
||||
expected: expected{result: "root"},
|
||||
},
|
||||
{
|
||||
desc: "one sub command",
|
||||
args: []string{"", "sub1"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "test",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "root"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "sub1"
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{result: "sub1"},
|
||||
},
|
||||
{
|
||||
desc: "two sub commands",
|
||||
args: []string{"", "sub2"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "test",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "root"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "sub1"
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub2",
|
||||
Description: "sub2",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "sub2"
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{result: "sub2"},
|
||||
},
|
||||
{
|
||||
desc: "command with sub sub command, call sub command",
|
||||
args: []string{"", "sub1"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "test",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "root"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
sub1 := &Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "sub1"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
_ = rootCmd.AddCommand(sub1)
|
||||
|
||||
_ = sub1.AddCommand(&Command{
|
||||
Name: "sub2",
|
||||
Description: "sub2",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "sub2"
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{result: "sub1"},
|
||||
},
|
||||
{
|
||||
desc: "command with sub sub command, call sub sub command",
|
||||
args: []string{"", "sub1", "sub2"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "test",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "root"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
sub1 := &Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "sub1"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
_ = rootCmd.AddCommand(sub1)
|
||||
|
||||
_ = sub1.AddCommand(&Command{
|
||||
Name: "sub2",
|
||||
Description: "sub2",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "sub2"
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{result: "sub2"},
|
||||
},
|
||||
{
|
||||
desc: "command with sub command, call root command explicitly",
|
||||
args: []string{"", "root"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "root"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "sub1"
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{result: "root"},
|
||||
},
|
||||
{
|
||||
desc: "command with sub command, call root command implicitly",
|
||||
args: []string{""},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "root"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "sub1"
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{result: "root"},
|
||||
},
|
||||
{
|
||||
desc: "command with sub command, call sub command which has no run",
|
||||
args: []string{"", "sub1"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "root"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "command with sub command, call root command which has no run",
|
||||
args: []string{"", "root"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
}
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "sub1"
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "command with sub command, call implicitly root command which has no run",
|
||||
args: []string{""},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
}
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called += "sub1"
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "command with sub command, call sub command with arguments",
|
||||
args: []string{"", "sub1", "foobar.txt"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called = "root"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(args []string) error {
|
||||
called += "sub1-" + strings.Join(args, "-")
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{result: "sub1-foobar.txt"},
|
||||
},
|
||||
{
|
||||
desc: "command with sub command, call root command with arguments",
|
||||
args: []string{"", "foobar.txt"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(args []string) error {
|
||||
called += "root-" + strings.Join(args, "-")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(args []string) error {
|
||||
called += "sub1-" + strings.Join(args, "-")
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{result: "root-foobar.txt"},
|
||||
},
|
||||
{
|
||||
desc: "command with sub command, call sub command with flags",
|
||||
args: []string{"", "sub1", "--foo=bar", "--fii=bir"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
called = "root"
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(args []string) error {
|
||||
called += "sub1-" + strings.Join(args, "")
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{result: "sub1---foo=bar--fii=bir"},
|
||||
},
|
||||
{
|
||||
desc: "command with sub command, call explicitly root command with flags",
|
||||
args: []string{"", "root", "--foo=bar", "--fii=bir"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(args []string) error {
|
||||
called += "root-" + strings.Join(args, "")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(args []string) error {
|
||||
called += "sub1-" + strings.Join(args, "")
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{result: "root---foo=bar--fii=bir"},
|
||||
},
|
||||
{
|
||||
desc: "command with sub command, call implicitly root command with flags",
|
||||
args: []string{"", "--foo=bar", "--fii=bir"},
|
||||
command: func() *Command {
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(args []string) error {
|
||||
called += "root-" + strings.Join(args, "")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
_ = rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: nil,
|
||||
Run: func(args []string) error {
|
||||
called += "sub1-" + strings.Join(args, "")
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return rootCmd
|
||||
},
|
||||
expected: expected{result: "root---foo=bar--fii=bir"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
defer func() {
|
||||
called = ""
|
||||
}()
|
||||
|
||||
err := execute(test.command(), test.args, true)
|
||||
|
||||
if test.expected.error {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected.result, called)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_execute_configuration(t *testing.T) {
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
element := &Yo{
|
||||
Fuu: "test",
|
||||
}
|
||||
|
||||
sub1 := &Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: element,
|
||||
Resources: []ResourceLoader{&FlagLoader{}},
|
||||
Run: func(args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
err := rootCmd.AddCommand(sub1)
|
||||
require.NoError(t, err)
|
||||
|
||||
args := []string{"", "sub1", "--foo=bar", "--fii=bir", "--yi"}
|
||||
|
||||
err = execute(rootCmd, args, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := &Yo{
|
||||
Foo: "bar",
|
||||
Fii: "bir",
|
||||
Fuu: "test",
|
||||
Yi: &Yi{
|
||||
Foo: "foo",
|
||||
Fii: "fii",
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, element)
|
||||
}
|
||||
|
||||
func Test_execute_configuration_file(t *testing.T) {
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "This is a test",
|
||||
Configuration: nil,
|
||||
Run: func(_ []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
element := &Yo{
|
||||
Fuu: "test",
|
||||
}
|
||||
|
||||
sub1 := &Command{
|
||||
Name: "sub1",
|
||||
Description: "sub1",
|
||||
Configuration: element,
|
||||
Resources: []ResourceLoader{&FileLoader{}, &FlagLoader{}},
|
||||
Run: func(args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
err := rootCmd.AddCommand(sub1)
|
||||
require.NoError(t, err)
|
||||
|
||||
args := []string{"", "sub1", "--configFile=./fixtures/config.toml"}
|
||||
|
||||
err = execute(rootCmd, args, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := &Yo{
|
||||
Foo: "bar",
|
||||
Fii: "bir",
|
||||
Fuu: "test",
|
||||
Yi: &Yi{
|
||||
Foo: "foo",
|
||||
Fii: "fii",
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, element)
|
||||
}
|
||||
|
||||
func Test_execute_help(t *testing.T) {
|
||||
element := &Yo{
|
||||
Fuu: "test",
|
||||
}
|
||||
|
||||
rooCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "Description for root",
|
||||
Configuration: element,
|
||||
Run: func(args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{"", "--help", "--foo"}
|
||||
|
||||
backupStdout := os.Stdout
|
||||
defer func() {
|
||||
os.Stdout = backupStdout
|
||||
}()
|
||||
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
err := execute(rooCmd, args, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// read and restore stdout
|
||||
if err = w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
os.Stdout = backupStdout
|
||||
|
||||
assert.Equal(t, `root Description for root
|
||||
|
||||
Usage: root [command] [flags] [arguments]
|
||||
|
||||
Use "root [command] --help" for help on any command.
|
||||
|
||||
Flag's usage: root [--flag=flag_argument] [-f [flag_argument]] # set flag_argument to flag(s)
|
||||
or: root [--flag[=true|false| ]] [-f [true|false| ]] # set true/false to boolean flag(s)
|
||||
|
||||
Flags:
|
||||
--fii (Default: "fii")
|
||||
Fii description
|
||||
|
||||
--foo (Default: "foo")
|
||||
Foo description
|
||||
|
||||
--fuu (Default: "test")
|
||||
Fuu description
|
||||
|
||||
--yi (Default: "false")
|
||||
|
||||
--yi.fii (Default: "fii")
|
||||
|
||||
--yi.foo (Default: "foo")
|
||||
|
||||
--yi.fuu (Default: "")
|
||||
|
||||
--yu.fii (Default: "fii")
|
||||
|
||||
--yu.foo (Default: "foo")
|
||||
|
||||
--yu.fuu (Default: "")
|
||||
|
||||
`, string(out))
|
||||
}
|
50
pkg/cli/file_finder.go
Normal file
50
pkg/cli/file_finder.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Finder holds a list of file paths.
|
||||
type Finder struct {
|
||||
BasePaths []string
|
||||
Extensions []string
|
||||
}
|
||||
|
||||
// Find returns the first valid existing file among configFile
|
||||
// and the paths already registered with Finder.
|
||||
func (f Finder) Find(configFile string) (string, error) {
|
||||
paths := f.getPaths(configFile)
|
||||
|
||||
for _, filePath := range paths {
|
||||
fp := os.ExpandEnv(filePath)
|
||||
|
||||
_, err := os.Stat(fp)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Abs(fp)
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f Finder) getPaths(configFile string) []string {
|
||||
var paths []string
|
||||
if strings.TrimSpace(configFile) != "" {
|
||||
paths = append(paths, configFile)
|
||||
}
|
||||
|
||||
for _, basePath := range f.BasePaths {
|
||||
for _, ext := range f.Extensions {
|
||||
paths = append(paths, basePath+"."+ext)
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
162
pkg/cli/file_finder_test.go
Normal file
162
pkg/cli/file_finder_test.go
Normal file
|
@ -0,0 +1,162 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFinder_Find(t *testing.T) {
|
||||
configFile, err := ioutil.TempFile("", "traefik-file-finder-test-*.toml")
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_ = os.Remove(configFile.Name())
|
||||
}()
|
||||
|
||||
dir, err := ioutil.TempDir("", "traefik-file-finder-test")
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_ = os.RemoveAll(dir)
|
||||
}()
|
||||
|
||||
fooFile, err := os.Create(filepath.Join(dir, "foo.toml"))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = os.Create(filepath.Join(dir, "bar.toml"))
|
||||
require.NoError(t, err)
|
||||
|
||||
type expected struct {
|
||||
error bool
|
||||
path string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
basePaths []string
|
||||
configFile string
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "not found: no config file",
|
||||
configFile: "",
|
||||
expected: expected{path: ""},
|
||||
},
|
||||
{
|
||||
desc: "not found: no config file, no other paths available",
|
||||
configFile: "",
|
||||
basePaths: []string{"/my/path/traefik", "$HOME/my/path/traefik", "./my-traefik"},
|
||||
expected: expected{path: ""},
|
||||
},
|
||||
{
|
||||
desc: "not found: with non existing config file",
|
||||
configFile: "/my/path/config.toml",
|
||||
expected: expected{path: ""},
|
||||
},
|
||||
{
|
||||
desc: "found: with config file",
|
||||
configFile: configFile.Name(),
|
||||
expected: expected{path: configFile.Name()},
|
||||
},
|
||||
{
|
||||
desc: "found: no config file, first base path",
|
||||
configFile: "",
|
||||
basePaths: []string{filepath.Join(dir, "foo"), filepath.Join(dir, "bar")},
|
||||
expected: expected{path: fooFile.Name()},
|
||||
},
|
||||
{
|
||||
desc: "found: no config file, base path",
|
||||
configFile: "",
|
||||
basePaths: []string{"/my/path/traefik", "$HOME/my/path/traefik", filepath.Join(dir, "foo")},
|
||||
expected: expected{path: fooFile.Name()},
|
||||
},
|
||||
{
|
||||
desc: "found: config file over base path",
|
||||
configFile: configFile.Name(),
|
||||
basePaths: []string{filepath.Join(dir, "foo"), filepath.Join(dir, "bar")},
|
||||
expected: expected{path: configFile.Name()},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
|
||||
finder := Finder{
|
||||
BasePaths: test.basePaths,
|
||||
Extensions: []string{"toml", "yaml", "yml"},
|
||||
}
|
||||
|
||||
path, err := finder.Find(test.configFile)
|
||||
|
||||
if test.expected.error {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected.path, path)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinder_getPaths(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
basePaths []string
|
||||
configFile string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
desc: "no config file",
|
||||
basePaths: []string{"/etc/traefik/traefik", "$HOME/.config/traefik", "./traefik"},
|
||||
configFile: "",
|
||||
expected: []string{
|
||||
"/etc/traefik/traefik.toml",
|
||||
"/etc/traefik/traefik.yaml",
|
||||
"/etc/traefik/traefik.yml",
|
||||
"$HOME/.config/traefik.toml",
|
||||
"$HOME/.config/traefik.yaml",
|
||||
"$HOME/.config/traefik.yml",
|
||||
"./traefik.toml",
|
||||
"./traefik.yaml",
|
||||
"./traefik.yml",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with config file",
|
||||
basePaths: []string{"/etc/traefik/traefik", "$HOME/.config/traefik", "./traefik"},
|
||||
configFile: "/my/path/config.toml",
|
||||
expected: []string{
|
||||
"/my/path/config.toml",
|
||||
"/etc/traefik/traefik.toml",
|
||||
"/etc/traefik/traefik.yaml",
|
||||
"/etc/traefik/traefik.yml",
|
||||
"$HOME/.config/traefik.toml",
|
||||
"$HOME/.config/traefik.yaml",
|
||||
"$HOME/.config/traefik.yml",
|
||||
"./traefik.toml",
|
||||
"./traefik.yaml",
|
||||
"./traefik.yml",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
finder := Finder{
|
||||
BasePaths: test.basePaths,
|
||||
Extensions: []string{"toml", "yaml", "yml"},
|
||||
}
|
||||
paths := finder.getPaths(test.configFile)
|
||||
|
||||
assert.Equal(t, test.expected, paths)
|
||||
})
|
||||
}
|
||||
}
|
3
pkg/cli/fixtures/config.toml
Normal file
3
pkg/cli/fixtures/config.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
foo = "bar"
|
||||
fii = "bir"
|
||||
[yi]
|
25
pkg/cli/fixtures_test.go
Normal file
25
pkg/cli/fixtures_test.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package cli
|
||||
|
||||
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"
|
||||
}
|
89
pkg/cli/help.go
Normal file
89
pkg/cli/help.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
"github.com/containous/traefik/pkg/config/flag"
|
||||
"github.com/containous/traefik/pkg/config/generator"
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
const tmplHelp = `{{ .Cmd.Name }} {{ .Cmd.Description }}
|
||||
|
||||
Usage: {{ .Cmd.Name }} [command] [flags] [arguments]
|
||||
|
||||
Use "{{ .Cmd.Name }} [command] --help" for help on any command.
|
||||
{{if .SubCommands }}
|
||||
Commands:
|
||||
{{- range $i, $subCmd := .SubCommands }}
|
||||
{{ if not $subCmd.Hidden }} {{ $subCmd.Name }} {{ $subCmd.Description }}{{end}}{{end}}
|
||||
{{end}}
|
||||
{{- if .Flags }}
|
||||
Flag's usage: {{ .Cmd.Name }} [--flag=flag_argument] [-f [flag_argument]] # set flag_argument to flag(s)
|
||||
or: {{ .Cmd.Name }} [--flag[=true|false| ]] [-f [true|false| ]] # set true/false to boolean flag(s)
|
||||
|
||||
Flags:
|
||||
{{- range $i, $flag := .Flags }}
|
||||
--{{ SliceIndexN $flag.Name }} {{if ne $flag.Name "global.sendanonymoususage"}}(Default: "{{ $flag.Default}}"){{end}}
|
||||
{{if $flag.Description }} {{ wrapWith 80 "\n\t\t" $flag.Description }}
|
||||
{{else}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
`
|
||||
|
||||
func isHelp(args []string) bool {
|
||||
for _, name := range args {
|
||||
if name == "--help" || name == "-help" || name == "-h" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PrintHelp prints the help for the command given as argument.
|
||||
func PrintHelp(w io.Writer, cmd *Command) error {
|
||||
var flags []parser.Flat
|
||||
if cmd.Configuration != nil {
|
||||
generator.Generate(cmd.Configuration)
|
||||
|
||||
var err error
|
||||
flags, err = flag.Encode(cmd.Configuration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
model := map[string]interface{}{
|
||||
"Cmd": cmd,
|
||||
"Flags": flags,
|
||||
"SubCommands": cmd.subCommands,
|
||||
}
|
||||
|
||||
funcs := sprig.TxtFuncMap()
|
||||
funcs["SliceIndexN"] = sliceIndexN
|
||||
|
||||
tmpl, err := template.New("flags").
|
||||
Funcs(funcs).
|
||||
Parse(tmplHelp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tw := tabwriter.NewWriter(w, 4, 0, 4, ' ', 0)
|
||||
|
||||
err = tmpl.Execute(tw, model)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tw.Flush()
|
||||
}
|
||||
|
||||
func sliceIndexN(flag string) string {
|
||||
return strings.ReplaceAll(flag, "[0]", "[n]")
|
||||
}
|
211
pkg/cli/help_test.go
Normal file
211
pkg/cli/help_test.go
Normal file
|
@ -0,0 +1,211 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPrintHelp(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
command *Command
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "no sub-command, with flags",
|
||||
command: func() *Command {
|
||||
element := &Yo{
|
||||
Fuu: "test",
|
||||
}
|
||||
|
||||
return &Command{
|
||||
Name: "root",
|
||||
Description: "Description for root",
|
||||
Configuration: element,
|
||||
Run: func(args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}(),
|
||||
expected: `root Description for root
|
||||
|
||||
Usage: root [command] [flags] [arguments]
|
||||
|
||||
Use "root [command] --help" for help on any command.
|
||||
|
||||
Flag's usage: root [--flag=flag_argument] [-f [flag_argument]] # set flag_argument to flag(s)
|
||||
or: root [--flag[=true|false| ]] [-f [true|false| ]] # set true/false to boolean flag(s)
|
||||
|
||||
Flags:
|
||||
--fii (Default: "fii")
|
||||
Fii description
|
||||
|
||||
--foo (Default: "foo")
|
||||
Foo description
|
||||
|
||||
--fuu (Default: "test")
|
||||
Fuu description
|
||||
|
||||
--yi (Default: "false")
|
||||
|
||||
--yi.fii (Default: "fii")
|
||||
|
||||
--yi.foo (Default: "foo")
|
||||
|
||||
--yi.fuu (Default: "")
|
||||
|
||||
--yu.fii (Default: "fii")
|
||||
|
||||
--yu.foo (Default: "foo")
|
||||
|
||||
--yu.fuu (Default: "")
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
desc: "with sub-commands, with flags, call root help",
|
||||
command: func() *Command {
|
||||
element := &Yo{
|
||||
Fuu: "test",
|
||||
}
|
||||
|
||||
rootCmd := &Command{
|
||||
Name: "root",
|
||||
Description: "Description for root",
|
||||
Configuration: element,
|
||||
Run: func(_ []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err := rootCmd.AddCommand(&Command{
|
||||
Name: "sub1",
|
||||
Description: "Description for sub1",
|
||||
Configuration: element,
|
||||
Run: func(args []string) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = rootCmd.AddCommand(&Command{
|
||||
Name: "sub2",
|
||||
Description: "Description for sub2",
|
||||
Configuration: element,
|
||||
Run: func(args []string) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return rootCmd
|
||||
}(),
|
||||
expected: `root Description for root
|
||||
|
||||
Usage: root [command] [flags] [arguments]
|
||||
|
||||
Use "root [command] --help" for help on any command.
|
||||
|
||||
Commands:
|
||||
sub1 Description for sub1
|
||||
sub2 Description for sub2
|
||||
|
||||
Flag's usage: root [--flag=flag_argument] [-f [flag_argument]] # set flag_argument to flag(s)
|
||||
or: root [--flag[=true|false| ]] [-f [true|false| ]] # set true/false to boolean flag(s)
|
||||
|
||||
Flags:
|
||||
--fii (Default: "fii")
|
||||
Fii description
|
||||
|
||||
--foo (Default: "foo")
|
||||
Foo description
|
||||
|
||||
--fuu (Default: "test")
|
||||
Fuu description
|
||||
|
||||
--yi (Default: "false")
|
||||
|
||||
--yi.fii (Default: "fii")
|
||||
|
||||
--yi.foo (Default: "foo")
|
||||
|
||||
--yi.fuu (Default: "")
|
||||
|
||||
--yu.fii (Default: "fii")
|
||||
|
||||
--yu.foo (Default: "foo")
|
||||
|
||||
--yu.fuu (Default: "")
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
desc: "no sub-command, no flags",
|
||||
command: func() *Command {
|
||||
return &Command{
|
||||
Name: "root",
|
||||
Description: "Description for root",
|
||||
Configuration: nil,
|
||||
Run: func(args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}(),
|
||||
expected: `root Description for root
|
||||
|
||||
Usage: root [command] [flags] [arguments]
|
||||
|
||||
Use "root [command] --help" for help on any command.
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
desc: "no sub-command, slice flags",
|
||||
command: func() *Command {
|
||||
return &Command{
|
||||
Name: "root",
|
||||
Description: "Description for root",
|
||||
Configuration: &struct {
|
||||
Foo []struct {
|
||||
Field string
|
||||
}
|
||||
}{},
|
||||
Run: func(args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}(),
|
||||
expected: `root Description for root
|
||||
|
||||
Usage: root [command] [flags] [arguments]
|
||||
|
||||
Use "root [command] --help" for help on any command.
|
||||
|
||||
Flag's usage: root [--flag=flag_argument] [-f [flag_argument]] # set flag_argument to flag(s)
|
||||
or: root [--flag[=true|false| ]] [-f [true|false| ]] # set true/false to boolean flag(s)
|
||||
|
||||
Flags:
|
||||
--foo (Default: "")
|
||||
|
||||
--foo[n].field (Default: "")
|
||||
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
err := PrintHelp(buffer, test.command)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, buffer.String())
|
||||
})
|
||||
}
|
||||
}
|
21
pkg/cli/loader.go
Normal file
21
pkg/cli/loader.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package cli
|
||||
|
||||
// ResourceLoader is a configuration resource loader.
|
||||
type ResourceLoader interface {
|
||||
// Load populates cmd.Configuration, optionally using args to do so.
|
||||
Load(args []string, cmd *Command) (bool, error)
|
||||
}
|
||||
|
||||
type filenameGetter interface {
|
||||
GetFilename() string
|
||||
}
|
||||
|
||||
// GetConfigFile returns the configuration file corresponding to the first configuration file loader found in ResourceLoader, if any.
|
||||
func GetConfigFile(loaders []ResourceLoader) string {
|
||||
for _, loader := range loaders {
|
||||
if v, ok := loader.(filenameGetter); ok {
|
||||
return v.GetFilename()
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
40
pkg/cli/loader_env.go
Normal file
40
pkg/cli/loader_env.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/env"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
)
|
||||
|
||||
// EnvLoader loads a configuration from all the environment variables prefixed with "TRAEFIK_".
|
||||
type EnvLoader struct{}
|
||||
|
||||
// Load loads the command's configuration from the environment variables.
|
||||
func (e *EnvLoader) Load(_ []string, cmd *Command) (bool, error) {
|
||||
return e.load(os.Environ(), cmd)
|
||||
}
|
||||
|
||||
func (*EnvLoader) load(environ []string, cmd *Command) (bool, error) {
|
||||
var found bool
|
||||
for _, value := range environ {
|
||||
if strings.HasPrefix(value, "TRAEFIK_") {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := env.Decode(environ, cmd.Configuration); err != nil {
|
||||
return false, fmt.Errorf("failed to decode configuration from environment variables: %v", err)
|
||||
}
|
||||
|
||||
log.WithoutContext().Println("Configuration loaded from environment variables.")
|
||||
|
||||
return true, nil
|
||||
}
|
78
pkg/cli/loader_file.go
Normal file
78
pkg/cli/loader_file.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/file"
|
||||
"github.com/containous/traefik/pkg/config/flag"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
)
|
||||
|
||||
// FileLoader loads a configuration from a file.
|
||||
type FileLoader struct {
|
||||
ConfigFileFlag string
|
||||
filename string
|
||||
}
|
||||
|
||||
// GetFilename returns the configuration file if any.
|
||||
func (f *FileLoader) GetFilename() string {
|
||||
return f.filename
|
||||
}
|
||||
|
||||
// Load loads the command's configuration from a file either specified with the -traefik.configfile flag, or from default locations.
|
||||
func (f *FileLoader) Load(args []string, cmd *Command) (bool, error) {
|
||||
ref, err := flag.Parse(args, cmd.Configuration)
|
||||
if err != nil {
|
||||
_ = PrintHelp(os.Stdout, cmd)
|
||||
return false, err
|
||||
}
|
||||
|
||||
configFileFlag := "traefik.configfile"
|
||||
if f.ConfigFileFlag != "" {
|
||||
configFileFlag = "traefik." + strings.ToLower(f.ConfigFileFlag)
|
||||
}
|
||||
|
||||
configFile, err := loadConfigFiles(ref[configFileFlag], cmd.Configuration)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
f.filename = configFile
|
||||
|
||||
if configFile == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
logger := log.WithoutContext()
|
||||
logger.Printf("Configuration loaded from file: %s", configFile)
|
||||
|
||||
content, _ := ioutil.ReadFile(configFile)
|
||||
logger.Debug(string(content))
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// loadConfigFiles tries to decode the given configuration file and all default locations for the configuration file.
|
||||
// It stops as soon as decoding one of them is successful.
|
||||
func loadConfigFiles(configFile string, element interface{}) (string, error) {
|
||||
finder := Finder{
|
||||
BasePaths: []string{"/etc/traefik/traefik", "$XDG_CONFIG_HOME/traefik", "$HOME/.config/traefik", "./traefik"},
|
||||
Extensions: []string{"toml", "yaml", "yml"},
|
||||
}
|
||||
|
||||
filePath, err := finder.Find(configFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(filePath) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if err = file.Decode(filePath, element); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filePath, nil
|
||||
}
|
22
pkg/cli/loader_flag.go
Normal file
22
pkg/cli/loader_flag.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/flag"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
)
|
||||
|
||||
// FlagLoader loads configuration from flags.
|
||||
type FlagLoader struct{}
|
||||
|
||||
// Load loads the command's configuration from flag arguments.
|
||||
func (*FlagLoader) Load(args []string, cmd *Command) (bool, error) {
|
||||
if err := flag.Decode(args, cmd.Configuration); err != nil {
|
||||
return false, fmt.Errorf("failed to decode configuration from flags: %v", err)
|
||||
}
|
||||
|
||||
log.WithoutContext().Println("Configuration loaded from flags.")
|
||||
|
||||
return true, nil
|
||||
}
|
|
@ -56,7 +56,11 @@ func Collect(staticConfiguration *static.Configuration) error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = makeHTTPClient().Post(collectorURL, "application/json; charset=utf-8", buf)
|
||||
resp, err := makeHTTPClient().Post(collectorURL, "application/json; charset=utf-8", buf)
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -125,9 +125,9 @@ type HealthCheck struct {
|
|||
Scheme string `json:"scheme,omitempty" toml:",omitempty"`
|
||||
Path string `json:"path,omitempty" toml:",omitempty"`
|
||||
Port int `json:"port,omitempty" toml:",omitempty,omitzero"`
|
||||
// FIXME change string to parse.Duration
|
||||
// FIXME change string to types.Duration
|
||||
Interval string `json:"interval,omitempty" toml:",omitempty"`
|
||||
// FIXME change string to parse.Duration
|
||||
// FIXME change string to types.Duration
|
||||
Timeout string `json:"timeout,omitempty" toml:",omitempty"`
|
||||
Hostname string `json:"hostname,omitempty" toml:",omitempty"`
|
||||
Headers map[string]string `json:"headers,omitempty" toml:",omitempty"`
|
||||
|
|
50
pkg/config/env/env.go
vendored
Normal file
50
pkg/config/env/env.go
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Package env implements encoding and decoding between environment variable and a typed Configuration.
|
||||
package env
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
// 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, element interface{}) error {
|
||||
vars := make(map[string]string)
|
||||
for _, evr := range environ {
|
||||
n := strings.SplitN(evr, "=", 2)
|
||||
if strings.HasPrefix(strings.ToUpper(n[0]), "TRAEFIK_") {
|
||||
key := strings.ReplaceAll(strings.ToLower(n[0]), "_", ".")
|
||||
vars[key] = n[1]
|
||||
}
|
||||
}
|
||||
|
||||
return parser.Decode(vars, element)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
node, err := parser.EncodeToNode(element, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = parser.AddMetadata(element, node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parser.EncodeToFlat(element, node, parser.FlatOpts{Case: "upper", Separator: "_"})
|
||||
}
|
498
pkg/config/env/env_test.go
vendored
Normal file
498
pkg/config/env/env_test.go
vendored
Normal file
|
@ -0,0 +1,498 @@
|
|||
package env
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/generator"
|
||||
"github.com/containous/traefik/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, 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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
31
pkg/config/file/file.go
Normal file
31
pkg/config/file/file.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Package file implements decoding between configuration in a file and a typed Configuration.
|
||||
package file
|
||||
|
||||
import (
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
err = parser.AddMetadata(element, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return parser.Fill(element, root)
|
||||
}
|
86
pkg/config/file/file_node.go
Normal file
86
pkg/config/file/file_node.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/containous/traefik/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 filepath.Ext(filePath) {
|
||||
case ".toml":
|
||||
err = toml.Unmarshal(content, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case ".yml", ".yaml":
|
||||
var err error
|
||||
err = yaml.Unmarshal(content, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decodeRawToNode(data, filters...)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported file extension: %s", filePath)
|
||||
}
|
||||
|
||||
return decodeRawToNode(data, filters...)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
599
pkg/config/file/file_node_test.go
Normal file
599
pkg/config/file/file_node_test.go
Normal file
|
@ -0,0 +1,599 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
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_compare(t *testing.T) {
|
||||
nodeToml, err := decodeFileToNode("./fixtures/sample.toml",
|
||||
"Global", "ServersTransport", "EntryPoints", "Providers", "API", "Metrics", "Ping", "Log", "AccessLog", "Tracing", "HostResolver", "ACME")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nodeYaml, err := decodeFileToNode("./fixtures/sample.yml")
|
||||
if err != nil {
|
||||
t.Fatal(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", "ACME")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := &parser.Node{
|
||||
Name: "traefik",
|
||||
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: "Domains", Children: []*parser.Node{
|
||||
{Name: "[0]", Children: []*parser.Node{
|
||||
{Name: "Main", Value: "foobar"},
|
||||
{Name: "SANs", Value: "foobar,foobar"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*parser.Node{
|
||||
{Name: "Main", Value: "foobar"},
|
||||
{Name: "SANs", 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: "OnHostRule", Value: "true"},
|
||||
{Name: "Storage", Value: "foobar"},
|
||||
{Name: "TLSChallenge"},
|
||||
},
|
||||
},
|
||||
{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: "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: "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: "Debug", 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", Children: []*parser.Node{
|
||||
{Name: "[0]", Children: []*parser.Node{
|
||||
{Name: "Key", Value: "foobar"},
|
||||
{Name: "MustMatch", Value: "true"},
|
||||
{Name: "Value", Value: "foobar"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*parser.Node{
|
||||
{Name: "Key", Value: "foobar"},
|
||||
{Name: "MustMatch", Value: "true"},
|
||||
{Name: "Value", 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: "TraefikFile", Value: "foobar"},
|
||||
{Name: "Watch", Value: "true"}}},
|
||||
{Name: "Kubernetes", 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: "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: "Marathon", Children: []*parser.Node{
|
||||
{Name: "Basic", Children: []*parser.Node{
|
||||
{Name: "HTTPBasicAuthUser", Value: "foobar"},
|
||||
{Name: "HTTPBasicPassword", Value: "foobar"}}},
|
||||
{Name: "Constraints", Children: []*parser.Node{
|
||||
{Name: "[0]", Children: []*parser.Node{
|
||||
{Name: "Key", Value: "foobar"},
|
||||
{Name: "MustMatch", Value: "true"},
|
||||
{Name: "Value", Value: "foobar"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*parser.Node{
|
||||
{Name: "Key", Value: "foobar"},
|
||||
{Name: "MustMatch", Value: "true"},
|
||||
{Name: "Value", Value: "foobar"},
|
||||
}},
|
||||
}},
|
||||
{Name: "DCOSToken", Value: "foobar"},
|
||||
{Name: "DefaultRule", Value: "foobar"},
|
||||
{Name: "DialerTimeout", Value: "42"},
|
||||
{Name: "Endpoint", Value: "foobar"},
|
||||
{Name: "ExposedByDefault", Value: "true"},
|
||||
{Name: "FilterMarathonConstraints", 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", Children: []*parser.Node{
|
||||
{Name: "[0]", Children: []*parser.Node{
|
||||
{Name: "Key", Value: "foobar"},
|
||||
{Name: "MustMatch", Value: "true"},
|
||||
{Name: "Value", Value: "foobar"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*parser.Node{
|
||||
{Name: "Key", Value: "foobar"},
|
||||
{Name: "MustMatch", Value: "true"},
|
||||
{Name: "Value", 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: "ResponseHeaderTimeout", Value: "42"}}},
|
||||
{Name: "InsecureSkipVerify", Value: "true"},
|
||||
{Name: "MaxIdleConnsPerHost", Value: "42"},
|
||||
{Name: "RootCAs", Value: "foobar,foobar"}}},
|
||||
{Name: "Tracing", Children: []*parser.Node{
|
||||
{Name: "Backend", Value: "foobar"},
|
||||
{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: "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: "Debug", Value: "true"},
|
||||
{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")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := &parser.Node{
|
||||
Name: "traefik",
|
||||
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: "Domains", Children: []*parser.Node{
|
||||
{Name: "[0]", Children: []*parser.Node{
|
||||
{Name: "Main", Value: "foobar"},
|
||||
{Name: "SANs", Value: "foobar,foobar"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*parser.Node{
|
||||
{Name: "Main", Value: "foobar"},
|
||||
{Name: "SANs", 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: "OnHostRule", Value: "true"},
|
||||
{Name: "Storage", Value: "foobar"},
|
||||
{Name: "TLSChallenge"},
|
||||
},
|
||||
},
|
||||
{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: "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: "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: "Debug", 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", Children: []*parser.Node{
|
||||
{Name: "[0]", Children: []*parser.Node{
|
||||
{Name: "Key", Value: "foobar"},
|
||||
{Name: "MustMatch", Value: "true"},
|
||||
{Name: "Value", Value: "foobar"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*parser.Node{
|
||||
{Name: "Key", Value: "foobar"},
|
||||
{Name: "MustMatch", Value: "true"},
|
||||
{Name: "Value", 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: "TraefikFile", Value: "foobar"},
|
||||
{Name: "Watch", Value: "true"}}},
|
||||
{Name: "Kubernetes", 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: "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: "Marathon", Children: []*parser.Node{
|
||||
{Name: "Basic", Children: []*parser.Node{
|
||||
{Name: "HTTPBasicAuthUser", Value: "foobar"},
|
||||
{Name: "HTTPBasicPassword", Value: "foobar"}}},
|
||||
{Name: "Constraints", Children: []*parser.Node{
|
||||
{Name: "[0]", Children: []*parser.Node{
|
||||
{Name: "Key", Value: "foobar"},
|
||||
{Name: "MustMatch", Value: "true"},
|
||||
{Name: "Value", Value: "foobar"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*parser.Node{
|
||||
{Name: "Key", Value: "foobar"},
|
||||
{Name: "MustMatch", Value: "true"},
|
||||
{Name: "Value", Value: "foobar"},
|
||||
}},
|
||||
}},
|
||||
{Name: "DCOSToken", Value: "foobar"},
|
||||
{Name: "DefaultRule", Value: "foobar"},
|
||||
{Name: "DialerTimeout", Value: "42"},
|
||||
{Name: "Endpoint", Value: "foobar"},
|
||||
{Name: "ExposedByDefault", Value: "true"},
|
||||
{Name: "FilterMarathonConstraints", 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", Children: []*parser.Node{
|
||||
{Name: "[0]", Children: []*parser.Node{
|
||||
{Name: "Key", Value: "foobar"},
|
||||
{Name: "MustMatch", Value: "true"},
|
||||
{Name: "Value", Value: "foobar"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*parser.Node{
|
||||
{Name: "Key", Value: "foobar"},
|
||||
{Name: "MustMatch", Value: "true"},
|
||||
{Name: "Value", 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: "ResponseHeaderTimeout", Value: "42"}}},
|
||||
{Name: "InsecureSkipVerify", Value: "true"},
|
||||
{Name: "MaxIdleConnsPerHost", Value: "42"},
|
||||
{Name: "RootCAs", Value: "foobar,foobar"}}},
|
||||
{Name: "Tracing", Children: []*parser.Node{
|
||||
{Name: "Backend", Value: "foobar"},
|
||||
{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: "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: "Debug", Value: "true"},
|
||||
{Name: "HTTPEndpoint", Value: "foobar"},
|
||||
{Name: "ID128Bit", Value: "true"},
|
||||
{Name: "SameSpan", Value: "true"},
|
||||
{Name: "SampleRate", Value: "42"}}}}}},
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, node)
|
||||
}
|
76
pkg/config/file/file_test.go
Normal file
76
pkg/config/file/file_test.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
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 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)
|
||||
}
|
539
pkg/config/file/fixtures/sample.toml
Normal file
539
pkg/config/file/fixtures/sample.toml
Normal file
|
@ -0,0 +1,539 @@
|
|||
[Global]
|
||||
Debug = true
|
||||
CheckNewVersion = true
|
||||
SendAnonymousUsage = true
|
||||
|
||||
[ServersTransport]
|
||||
InsecureSkipVerify = true
|
||||
RootCAs = ["foobar", "foobar"]
|
||||
MaxIdleConnsPerHost = 42
|
||||
[ServersTransport.ForwardingTimeouts]
|
||||
DialTimeout = 42
|
||||
ResponseHeaderTimeout = 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]
|
||||
Watch = true
|
||||
Endpoint = "foobar"
|
||||
DefaultRule = "foobar"
|
||||
ExposedByDefault = true
|
||||
UseBindPortIP = true
|
||||
SwarmMode = true
|
||||
Network = "foobar"
|
||||
SwarmModeRefreshSeconds = 42
|
||||
|
||||
[[Providers.Docker.Constraints]]
|
||||
Key = "foobar"
|
||||
MustMatch = true
|
||||
Value = "foobar"
|
||||
|
||||
[[Providers.Docker.Constraints]]
|
||||
Key = "foobar"
|
||||
MustMatch = true
|
||||
Value = "foobar"
|
||||
|
||||
[Providers.Docker.TLS]
|
||||
CA = "foobar"
|
||||
CAOptional = true
|
||||
Cert = "foobar"
|
||||
Key = "foobar"
|
||||
InsecureSkipVerify = true
|
||||
|
||||
[Providers.File]
|
||||
Directory = "foobar"
|
||||
Watch = true
|
||||
Filename = "foobar"
|
||||
DebugLogGeneratedTemplate = true
|
||||
TraefikFile = "foobar"
|
||||
|
||||
[Providers.Marathon]
|
||||
Trace = true
|
||||
Watch = true
|
||||
Endpoint = "foobar"
|
||||
DefaultRule = "foobar"
|
||||
ExposedByDefault = true
|
||||
DCOSToken = "foobar"
|
||||
FilterMarathonConstraints = true
|
||||
DialerTimeout = 42
|
||||
ResponseHeaderTimeout = 42
|
||||
TLSHandshakeTimeout = 42
|
||||
KeepAlive = 42
|
||||
ForceTaskHostname = true
|
||||
RespectReadinessChecks = true
|
||||
|
||||
[[Providers.Marathon.Constraints]]
|
||||
Key = "foobar"
|
||||
MustMatch = true
|
||||
Value = "foobar"
|
||||
|
||||
[[Providers.Marathon.Constraints]]
|
||||
Key = "foobar"
|
||||
MustMatch = true
|
||||
Value = "foobar"
|
||||
|
||||
[Providers.Marathon.TLS]
|
||||
CA = "foobar"
|
||||
CAOptional = true
|
||||
Cert = "foobar"
|
||||
Key = "foobar"
|
||||
InsecureSkipVerify = true
|
||||
[Providers.Marathon.Basic]
|
||||
HTTPBasicAuthUser = "foobar"
|
||||
HTTPBasicPassword = "foobar"
|
||||
|
||||
[Providers.Kubernetes]
|
||||
Endpoint = "foobar"
|
||||
Token = "foobar"
|
||||
CertAuthFilePath = "foobar"
|
||||
DisablePassHostHeaders = true
|
||||
Namespaces = ["foobar", "foobar"]
|
||||
LabelSelector = "foobar"
|
||||
IngressClass = "foobar"
|
||||
[Providers.Kubernetes.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]
|
||||
Watch = true
|
||||
DefaultRule = "foobar"
|
||||
ExposedByDefault = true
|
||||
EnableServiceHealthFilter = true
|
||||
RefreshSeconds = 42
|
||||
IntervalPoll = true
|
||||
Prefix = "foobar"
|
||||
|
||||
[[Providers.Rancher.Constraints]]
|
||||
Key = "foobar"
|
||||
MustMatch = true
|
||||
Value = "foobar"
|
||||
|
||||
[[Providers.Rancher.Constraints]]
|
||||
Key = "foobar"
|
||||
MustMatch = true
|
||||
Value = "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]
|
||||
Backend = "foobar"
|
||||
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
|
||||
Debug = 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"
|
||||
|
||||
[HostResolver]
|
||||
CnameFlattening = true
|
||||
ResolvConfig = "foobar"
|
||||
ResolvDepth = 42
|
||||
|
||||
[ACME]
|
||||
Email = "foobar"
|
||||
ACMELogging = true
|
||||
CAServer = "foobar"
|
||||
Storage = "foobar"
|
||||
EntryPoint = "foobar"
|
||||
KeyType = "foobar"
|
||||
OnHostRule = true
|
||||
|
||||
[ACME.DNSChallenge]
|
||||
Provider = "foobar"
|
||||
DelayBeforeCheck = 42
|
||||
Resolvers = ["foobar", "foobar"]
|
||||
DisablePropagationCheck = true
|
||||
|
||||
[ACME.HTTPChallenge]
|
||||
EntryPoint = "foobar"
|
||||
|
||||
[ACME.TLSChallenge]
|
||||
|
||||
[[ACME.Domains]]
|
||||
Main = "foobar"
|
||||
SANs = ["foobar", "foobar"]
|
||||
|
||||
[[ACME.Domains]]
|
||||
Main = "foobar"
|
||||
SANs = ["foobar", "foobar"]
|
||||
|
||||
#### 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.AddPrefix]
|
||||
Prefix = "foobar"
|
||||
|
||||
[HTTP.Middlewares.Middleware1.StripPrefix]
|
||||
Prefixes = ["foobar", "foobar"]
|
||||
|
||||
[HTTP.Middlewares.Middleware2.StripPrefixRegex]
|
||||
Regex = ["foobar", "foobar"]
|
||||
|
||||
[HTTP.Middlewares.Middleware3.ReplacePath]
|
||||
Path = "foobar"
|
||||
|
||||
[HTTP.Middlewares.Middleware4.ReplacePathRegex]
|
||||
Regex = "foobar"
|
||||
Replacement = "foobar"
|
||||
|
||||
[HTTP.Middlewares.Middleware5.Chain]
|
||||
Middlewares = ["foobar", "foobar"]
|
||||
|
||||
[HTTP.Middlewares.Middleware6.IPWhiteList]
|
||||
SourceRange = ["foobar", "foobar"]
|
||||
|
||||
[HTTP.Middlewares.Middleware7.IPWhiteList.IPStrategy]
|
||||
Depth = 42
|
||||
ExcludedIPs = ["foobar", "foobar"]
|
||||
|
||||
[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"
|
||||
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.Errors]
|
||||
Status = ["foobar", "foobar"]
|
||||
Service = "foobar"
|
||||
Query = "foobar"
|
||||
|
||||
[HTTP.Middlewares.Middleware10.RateLimit]
|
||||
ExtractorFunc = "foobar"
|
||||
[HTTP.Middlewares.Middleware10.RateLimit.RateSet]
|
||||
[HTTP.Middlewares.Middleware10.RateLimit.RateSet.Rate0]
|
||||
Period = 42
|
||||
Average = 42
|
||||
Burst = 42
|
||||
[HTTP.Middlewares.Middleware10.RateLimit.RateSet.Rate1]
|
||||
Period = 42
|
||||
Average = 42
|
||||
Burst = 42
|
||||
|
||||
[HTTP.Middlewares.Middleware11.RedirectRegex]
|
||||
Regex = "foobar"
|
||||
Replacement = "foobar"
|
||||
Permanent = true
|
||||
|
||||
[HTTP.Middlewares.Middleware12.RedirectScheme]
|
||||
Scheme = "foobar"
|
||||
Port = "foobar"
|
||||
Permanent = true
|
||||
|
||||
[HTTP.Middlewares.Middleware13.BasicAuth]
|
||||
Users = ["foobar", "foobar"]
|
||||
UsersFile = "foobar"
|
||||
Realm = "foobar"
|
||||
RemoveHeader = true
|
||||
HeaderField = "foobar"
|
||||
|
||||
[HTTP.Middlewares.Middleware14.DigestAuth]
|
||||
Users = ["foobar", "foobar"]
|
||||
UsersFile = "foobar"
|
||||
RemoveHeader = true
|
||||
Realm = "foobar"
|
||||
HeaderField = "foobar"
|
||||
|
||||
[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.MaxConn]
|
||||
Amount = 42
|
||||
ExtractorFunc = "foobar"
|
||||
|
||||
[HTTP.Middlewares.Middleware17.Buffering]
|
||||
MaxRequestBodyBytes = 42
|
||||
MemRequestBodyBytes = 42
|
||||
MaxResponseBodyBytes = 42
|
||||
MemResponseBodyBytes = 42
|
||||
RetryExpression = "foobar"
|
||||
|
||||
[HTTP.Middlewares.Middleware18.CircuitBreaker]
|
||||
Expression = "foobar"
|
||||
|
||||
[HTTP.Middlewares.Middleware19.Compress]
|
||||
|
||||
[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.Retry]
|
||||
Attempts = 42
|
||||
|
||||
[HTTP.Services]
|
||||
[HTTP.Services.Service0]
|
||||
[HTTP.Services.Service0.LoadBalancer]
|
||||
Method = "foobar"
|
||||
PassHostHeader = true
|
||||
|
||||
[[HTTP.Services.Service0.LoadBalancer.Servers]]
|
||||
URL = "foobar"
|
||||
|
||||
[HTTP.Services.Service0.LoadBalancer.Stickiness]
|
||||
CookieName = "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]
|
||||
Method = "foobar"
|
||||
|
||||
[[TCP.Services.TCPService0.LoadBalancer.Servers]]
|
||||
Address = "foobar"
|
||||
|
||||
[[TCP.Services.TCPService0.LoadBalancer.Servers]]
|
||||
Address = "foobar"
|
||||
|
||||
[[TLS]]
|
||||
Stores = ["foobar", "foobar"]
|
||||
[TLS.Certificate]
|
||||
CertFile = "foobar"
|
||||
KeyFile = "foobar"
|
||||
|
||||
[[TLS]]
|
||||
Stores = ["foobar", "foobar"]
|
||||
[TLS.Certificate]
|
||||
CertFile = "foobar"
|
||||
KeyFile = "foobar"
|
||||
|
||||
[TLSOptions]
|
||||
|
||||
[TLSOptions.TLS0]
|
||||
MinVersion = "foobar"
|
||||
CipherSuites = ["foobar", "foobar"]
|
||||
SniStrict = true
|
||||
[TLSOptions.TLS0.ClientCA]
|
||||
Files = ["foobar", "foobar"]
|
||||
Optional = true
|
||||
[TLSOptions.TLS1]
|
||||
MinVersion = "foobar"
|
||||
CipherSuites = ["foobar", "foobar"]
|
||||
SniStrict = true
|
||||
[TLSOptions.TLS1.ClientCA]
|
||||
Files = ["foobar", "foobar"]
|
||||
Optional = true
|
||||
|
||||
[TLSStores]
|
||||
|
||||
[TLSStores.Store0]
|
||||
[TLSStores.Store0.DefaultCertificate]
|
||||
CertFile = "foobar"
|
||||
KeyFile = "foobar"
|
||||
[TLSStores.Store1]
|
||||
[TLSStores.Store1.DefaultCertificate]
|
||||
CertFile = "foobar"
|
||||
KeyFile = "foobar"
|
257
pkg/config/file/fixtures/sample.yml
Normal file
257
pkg/config/file/fixtures/sample.yml
Normal file
|
@ -0,0 +1,257 @@
|
|||
Global:
|
||||
Debug: true
|
||||
CheckNewVersion: true
|
||||
SendAnonymousUsage: true
|
||||
ServersTransport:
|
||||
InsecureSkipVerify: true
|
||||
RootCAs:
|
||||
- foobar
|
||||
- foobar
|
||||
MaxIdleConnsPerHost: 42
|
||||
ForwardingTimeouts:
|
||||
DialTimeout: 42
|
||||
ResponseHeaderTimeout: 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:
|
||||
Watch: true
|
||||
Endpoint: foobar
|
||||
DefaultRule: foobar
|
||||
ExposedByDefault: true
|
||||
UseBindPortIP: true
|
||||
SwarmMode: true
|
||||
Network: foobar
|
||||
SwarmModeRefreshSeconds: 42
|
||||
Constraints:
|
||||
- Key: foobar
|
||||
MustMatch: true
|
||||
Value: foobar
|
||||
- Key: foobar
|
||||
MustMatch: true
|
||||
Value: foobar
|
||||
TLS:
|
||||
CA: foobar
|
||||
CAOptional: true
|
||||
Cert: foobar
|
||||
Key: foobar
|
||||
InsecureSkipVerify: true
|
||||
File:
|
||||
Directory: foobar
|
||||
Watch: true
|
||||
Filename: foobar
|
||||
DebugLogGeneratedTemplate: true
|
||||
TraefikFile: foobar
|
||||
Marathon:
|
||||
Trace: true
|
||||
Watch: true
|
||||
Endpoint: foobar
|
||||
DefaultRule: foobar
|
||||
ExposedByDefault: true
|
||||
DCOSToken: foobar
|
||||
FilterMarathonConstraints: true
|
||||
DialerTimeout: 42
|
||||
ResponseHeaderTimeout: 42
|
||||
TLSHandshakeTimeout: 42
|
||||
KeepAlive: 42
|
||||
ForceTaskHostname: true
|
||||
RespectReadinessChecks: true
|
||||
Constraints:
|
||||
- Key: foobar
|
||||
MustMatch: true
|
||||
Value: foobar
|
||||
- Key: foobar
|
||||
MustMatch: true
|
||||
Value: foobar
|
||||
TLS:
|
||||
CA: foobar
|
||||
CAOptional: true
|
||||
Cert: foobar
|
||||
Key: foobar
|
||||
InsecureSkipVerify: true
|
||||
Basic:
|
||||
HTTPBasicAuthUser: foobar
|
||||
HTTPBasicPassword: foobar
|
||||
Kubernetes:
|
||||
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:
|
||||
Watch: true
|
||||
DefaultRule: foobar
|
||||
ExposedByDefault: true
|
||||
EnableServiceHealthFilter: true
|
||||
RefreshSeconds: 42
|
||||
IntervalPoll: true
|
||||
Prefix: foobar
|
||||
Constraints:
|
||||
- Key: foobar
|
||||
MustMatch: true
|
||||
Value: foobar
|
||||
- Key: foobar
|
||||
MustMatch: true
|
||||
Value: foobar
|
||||
API:
|
||||
EntryPoint: foobar
|
||||
Dashboard: true
|
||||
Middlewares:
|
||||
- foobar
|
||||
- foobar
|
||||
Statistics:
|
||||
RecentErrors: 42
|
||||
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
|
||||
BufferingSize: 42
|
||||
Filters:
|
||||
StatusCodes:
|
||||
- foobar
|
||||
- foobar
|
||||
RetryAttempts: true
|
||||
MinDuration: 42
|
||||
Fields:
|
||||
DefaultMode: foobar
|
||||
Names:
|
||||
name0: foobar
|
||||
name1: foobar
|
||||
Headers:
|
||||
DefaultMode: foobar
|
||||
Names:
|
||||
name0: foobar
|
||||
name1: foobar
|
||||
Tracing:
|
||||
Backend: foobar
|
||||
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
|
||||
Debug: 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
|
||||
HostResolver:
|
||||
CnameFlattening: true
|
||||
ResolvConfig: foobar
|
||||
ResolvDepth: 42
|
||||
ACME:
|
||||
Email: foobar
|
||||
ACMELogging: true
|
||||
CAServer: foobar
|
||||
Storage: foobar
|
||||
EntryPoint: foobar
|
||||
KeyType: foobar
|
||||
OnHostRule: true
|
||||
DNSChallenge:
|
||||
Provider: foobar
|
||||
DelayBeforeCheck: 42
|
||||
Resolvers:
|
||||
- foobar
|
||||
- foobar
|
||||
DisablePropagationCheck: true
|
||||
HTTPChallenge:
|
||||
EntryPoint: foobar
|
||||
TLSChallenge: {}
|
||||
Domains:
|
||||
- Main: foobar
|
||||
SANs:
|
||||
- foobar
|
||||
- foobar
|
||||
- Main: foobar
|
||||
SANs:
|
||||
- foobar
|
||||
- foobar
|
34
pkg/config/file/fixtures_test.go
Normal file
34
pkg/config/file/fixtures_test.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package file
|
||||
|
||||
type bar string
|
||||
|
||||
type Yo struct {
|
||||
Foo string
|
||||
Fii string
|
||||
Fuu string
|
||||
Yi *Yi `label:"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
|
||||
}
|
128
pkg/config/file/raw_node.go
Normal file
128
pkg/config/file/raw_node.go
Normal file
|
@ -0,0 +1,128 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
func decodeRawToNode(data map[string]interface{}, filters ...string) (*parser.Node, error) {
|
||||
root := &parser.Node{
|
||||
Name: "traefik",
|
||||
}
|
||||
|
||||
vData := reflect.ValueOf(data)
|
||||
decodeRaw(root, vData, filters...)
|
||||
|
||||
return root, nil
|
||||
}
|
||||
|
||||
func decodeRaw(node *parser.Node, vData reflect.Value, filters ...string) {
|
||||
sortedKeys := sortKeys(vData, filters)
|
||||
|
||||
for _, key := range sortedKeys {
|
||||
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:
|
||||
child.Value = getSimpleValue(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)
|
||||
decodeRaw(ch, sValue)
|
||||
} else {
|
||||
values = append(values, getSimpleValue(sValue))
|
||||
}
|
||||
default:
|
||||
panic("Unsupported slice type: " + item.Kind().String())
|
||||
}
|
||||
}
|
||||
|
||||
child.Value = strings.Join(values, ",")
|
||||
case reflect.Map:
|
||||
decodeRaw(child, value)
|
||||
default:
|
||||
panic("Unsupported type: " + value.Kind().String())
|
||||
}
|
||||
|
||||
node.Children = append(node.Children, child)
|
||||
}
|
||||
}
|
||||
|
||||
func getSimpleValue(item reflect.Value) string {
|
||||
switch item.Kind() {
|
||||
case reflect.String:
|
||||
return item.String()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return strconv.FormatInt(item.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return strconv.FormatUint(item.Uint(), 10)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return strings.TrimSuffix(strconv.FormatFloat(item.Float(), 'f', 6, 64), ".000000")
|
||||
case reflect.Bool:
|
||||
return strconv.FormatBool(item.Bool())
|
||||
default:
|
||||
panic("Unsupported Simple value type: " + 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
|
||||
}
|
540
pkg/config/file/raw_node_test.go
Normal file
540
pkg/config/file/raw_node_test.go
Normal file
|
@ -0,0 +1,540 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/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"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, err := decodeRawToNode(test.data)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, node)
|
||||
})
|
||||
}
|
||||
}
|
44
pkg/config/flag/flag.go
Normal file
44
pkg/config/flag/flag.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Package flag implements encoding and decoding between flag arguments and a typed Configuration.
|
||||
package flag
|
||||
|
||||
import (
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
// Decode decodes the given flag arguments into the given element.
|
||||
// The operation goes through four stages roughly summarized as:
|
||||
// flag arguments -> parsed map of flags
|
||||
// map -> tree of untyped nodes
|
||||
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
|
||||
// "typed" nodes -> typed element
|
||||
func Decode(args []string, element interface{}) error {
|
||||
ref, err := Parse(args, element)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return parser.Decode(ref, element)
|
||||
}
|
||||
|
||||
// Encode encodes the configuration in element into the flags represented in the returned Flats.
|
||||
// The operation goes through three stages roughly summarized as:
|
||||
// typed configuration in element -> tree of untyped nodes
|
||||
// untyped nodes -> nodes augmented with metadata such as kind (inferred from element)
|
||||
// "typed" nodes -> flags with default values (determined by type/kind)
|
||||
func Encode(element interface{}) ([]parser.Flat, error) {
|
||||
if element == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
node, err := parser.EncodeToNode(element, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = parser.AddMetadata(element, node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parser.EncodeToFlat(element, node, parser.FlatOpts{Separator: ".", SkipRoot: true})
|
||||
}
|
926
pkg/config/flag/flag_test.go
Normal file
926
pkg/config/flag/flag_test.go
Normal file
|
@ -0,0 +1,926 @@
|
|||
package flag
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/generator"
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
element interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
desc: "no args",
|
||||
args: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
desc: "types.Duration value",
|
||||
args: []string{"--foo=1"},
|
||||
element: &struct {
|
||||
Foo types.Duration
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo types.Duration
|
||||
}{
|
||||
Foo: types.Duration(1 * time.Second),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "time.Duration value",
|
||||
args: []string{"--foo=1"},
|
||||
element: &struct {
|
||||
Foo time.Duration
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo time.Duration
|
||||
}{
|
||||
Foo: 1 * time.Nanosecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "bool value",
|
||||
args: []string{"--foo"},
|
||||
element: &struct {
|
||||
Foo bool
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo bool
|
||||
}{
|
||||
Foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "equal",
|
||||
args: []string{"--foo=bar"},
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo string
|
||||
}{
|
||||
Foo: "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "space separated",
|
||||
args: []string{"--foo", "bar"},
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo string
|
||||
}{
|
||||
Foo: "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "space separated with end of parameter",
|
||||
args: []string{"--foo=bir", "--", "--bar"},
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo string
|
||||
}{
|
||||
Foo: "bir",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple bool flags without value",
|
||||
args: []string{"--foo", "--bar"},
|
||||
element: &struct {
|
||||
Foo bool
|
||||
Bar bool
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo bool
|
||||
Bar bool
|
||||
}{
|
||||
Foo: true,
|
||||
Bar: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags",
|
||||
args: []string{"--foo=bar", "--foo=baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo []string
|
||||
}{
|
||||
Foo: []string{"bar", "baz"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map string",
|
||||
args: []string{"--foo.name=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]string
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo map[string]string
|
||||
}{
|
||||
Foo: map[string]string{
|
||||
"name": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct",
|
||||
args: []string{"--foo.name.value=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]struct{ Value string }
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo map[string]struct{ Value string }
|
||||
}{
|
||||
Foo: map[string]struct{ Value string }{
|
||||
"name": {
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct with sub-struct",
|
||||
args: []string{"--foo.name.bar.value=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Bar *struct{ Value string }
|
||||
}
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo map[string]struct {
|
||||
Bar *struct{ Value string }
|
||||
}
|
||||
}{
|
||||
Foo: map[string]struct {
|
||||
Bar *struct{ Value string }
|
||||
}{
|
||||
"name": {
|
||||
Bar: &struct {
|
||||
Value string
|
||||
}{
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct with sub-map",
|
||||
args: []string{"--foo.name1.bar.name2.value=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Bar map[string]struct{ Value string }
|
||||
}
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo map[string]struct {
|
||||
Bar map[string]struct{ Value string }
|
||||
}
|
||||
}{
|
||||
Foo: map[string]struct {
|
||||
Bar map[string]struct{ Value string }
|
||||
}{
|
||||
"name1": {
|
||||
Bar: map[string]struct{ Value string }{
|
||||
"name2": {
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags 2",
|
||||
args: []string{"--foo", "bar", "--foo", "baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo []string
|
||||
}{
|
||||
Foo: []string{"bar", "baz"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags 3",
|
||||
args: []string{"--foo", "bar", "--foo=", "--baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
Baz bool
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo []string
|
||||
Baz bool
|
||||
}{
|
||||
Foo: []string{"bar", ""},
|
||||
Baz: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags 4",
|
||||
args: []string{"--foo", "bar", "--foo", "--baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
Baz bool
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo []string
|
||||
Baz bool
|
||||
}{
|
||||
Foo: []string{"bar", "--baz"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of struct",
|
||||
args: []string{
|
||||
"--foo[0].Field1", "bar", "--foo[0].Field2", "6",
|
||||
"--foo[1].Field1", "bur", "--foo[1].Field2", "2",
|
||||
},
|
||||
element: &struct {
|
||||
Foo []struct {
|
||||
Field1 string
|
||||
Field2 int
|
||||
}
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo []struct {
|
||||
Field1 string
|
||||
Field2 int
|
||||
}
|
||||
}{
|
||||
Foo: []struct {
|
||||
Field1 string
|
||||
Field2 int
|
||||
}{
|
||||
{
|
||||
Field1: "bar",
|
||||
Field2: 6,
|
||||
},
|
||||
{
|
||||
Field1: "bur",
|
||||
Field2: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice of pointer of struct",
|
||||
args: []string{
|
||||
"--foo[0].Field1", "bar", "--foo[0].Field2", "6",
|
||||
"--foo[1].Field1", "bur", "--foo[1].Field2", "2",
|
||||
},
|
||||
element: &struct {
|
||||
Foo []*struct {
|
||||
Field1 string
|
||||
Field2 int
|
||||
}
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo []*struct {
|
||||
Field1 string
|
||||
Field2 int
|
||||
}
|
||||
}{
|
||||
Foo: []*struct {
|
||||
Field1 string
|
||||
Field2 int
|
||||
}{
|
||||
{
|
||||
Field1: "bar",
|
||||
Field2: 6,
|
||||
},
|
||||
{
|
||||
Field1: "bur",
|
||||
Field2: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple string flag",
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
args: []string{"--foo=bar", "--foo=baz"},
|
||||
expected: &struct {
|
||||
Foo string
|
||||
}{
|
||||
Foo: "baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple string flag 2",
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
args: []string{"--foo", "bar", "--foo", "baz"},
|
||||
expected: &struct {
|
||||
Foo string
|
||||
}{
|
||||
Foo: "baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "string without value",
|
||||
element: &struct {
|
||||
Foo string
|
||||
Bar bool
|
||||
}{},
|
||||
args: []string{"--foo", "--bar"},
|
||||
expected: &struct {
|
||||
Foo string
|
||||
Bar bool
|
||||
}{
|
||||
Foo: "--bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer value",
|
||||
args: []string{"--foo"},
|
||||
element: &struct {
|
||||
Foo *struct{ Field string } `label:"allowEmpty"`
|
||||
}{},
|
||||
expected: &struct {
|
||||
Foo *struct{ Field string } `label:"allowEmpty"`
|
||||
}{
|
||||
Foo: &struct{ Field string }{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := Decode(test.args, test.element)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, test.element)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
element interface{}
|
||||
expected []parser.Flat
|
||||
}{
|
||||
{
|
||||
desc: "string field",
|
||||
element: &struct {
|
||||
Field string `description:"field description"`
|
||||
}{
|
||||
Field: "test",
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "test",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "int field",
|
||||
element: &struct {
|
||||
Field int `description:"field description"`
|
||||
}{
|
||||
Field: 6,
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "6",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "bool field",
|
||||
element: &struct {
|
||||
Field bool `description:"field description"`
|
||||
}{
|
||||
Field: true,
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "true",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "string pointer field",
|
||||
element: &struct {
|
||||
Field *string `description:"field description"`
|
||||
}{
|
||||
Field: func(v string) *string { return &v }("test"),
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "test",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "int pointer field",
|
||||
element: &struct {
|
||||
Field *int `description:"field description"`
|
||||
}{
|
||||
Field: func(v int) *int { return &v }(6),
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "6",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "bool pointer field",
|
||||
element: &struct {
|
||||
Field *bool `description:"field description"`
|
||||
}{
|
||||
Field: func(v bool) *bool { return &v }(true),
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "true",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice of string field, no initial value",
|
||||
element: &struct {
|
||||
Field []string `description:"field description"`
|
||||
}{},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice of string field, with initial value",
|
||||
element: &struct {
|
||||
Field []string `description:"field description"`
|
||||
}{
|
||||
Field: []string{"foo", "bar"},
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "foo, bar",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice of int field, no initial value",
|
||||
element: &struct {
|
||||
Field []int `description:"field description"`
|
||||
}{},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice of int field, with initial value",
|
||||
element: &struct {
|
||||
Field []int `description:"field description"`
|
||||
}{
|
||||
Field: []int{6, 3},
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "6, 3",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "map string field",
|
||||
element: &struct {
|
||||
Field map[string]string `description:"field description"`
|
||||
}{
|
||||
Field: map[string]string{
|
||||
parser.MapNamePlaceholder: "",
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field.<name>",
|
||||
Description: "field description",
|
||||
Default: "",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer field",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Field string `description:"field description"`
|
||||
} `description:"foo description"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Field string `description:"field description"`
|
||||
}{
|
||||
Field: "test",
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.field",
|
||||
Description: "field description",
|
||||
Default: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer field, allow empty",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Field string `description:"field description"`
|
||||
} `description:"foo description" label:"allowEmpty"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Field string `description:"field description"`
|
||||
}{
|
||||
Field: "test",
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo",
|
||||
Description: "foo description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.field",
|
||||
Description: "field description",
|
||||
Default: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer field level 2",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii *struct {
|
||||
Field string `description:"field description"`
|
||||
} `description:"fii description"`
|
||||
} `description:"foo description"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii *struct {
|
||||
Field string `description:"field description"`
|
||||
} `description:"fii description"`
|
||||
}{
|
||||
Fii: &struct {
|
||||
Field string `description:"field description"`
|
||||
}{
|
||||
Field: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.fii.field",
|
||||
Description: "field description",
|
||||
Default: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer field level 2, allow empty",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii *struct {
|
||||
Field string `description:"field description"`
|
||||
} `description:"fii description" label:"allowEmpty"`
|
||||
} `description:"foo description" label:"allowEmpty"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii *struct {
|
||||
Field string `description:"field description"`
|
||||
} `description:"fii description" label:"allowEmpty"`
|
||||
}{
|
||||
Fii: &struct {
|
||||
Field string `description:"field description"`
|
||||
}{
|
||||
Field: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo",
|
||||
Description: "foo description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.fii",
|
||||
Description: "fii description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.fii.field",
|
||||
Description: "field description",
|
||||
Default: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map string field level 2",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii map[string]string `description:"fii description"`
|
||||
} `description:"foo description"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii map[string]string `description:"fii description"`
|
||||
}{
|
||||
Fii: map[string]string{
|
||||
parser.MapNamePlaceholder: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.fii.<name>",
|
||||
Description: "fii description",
|
||||
Default: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map string pointer field level 2",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii map[string]*string `description:"fii description"`
|
||||
} `description:"foo description"`
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii map[string]*string `description:"fii description"`
|
||||
}{
|
||||
Fii: map[string]*string{
|
||||
parser.MapNamePlaceholder: func(v string) *string { return &v }(""),
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.fii.<name>",
|
||||
Description: "fii description",
|
||||
Default: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct level 1",
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Field string `description:"field description"`
|
||||
Yo int `description:"yo description"`
|
||||
} `description:"foo description"`
|
||||
}{},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.<name>",
|
||||
Description: "foo description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.<name>.field",
|
||||
Description: "field description",
|
||||
Default: "",
|
||||
},
|
||||
{
|
||||
Name: "foo.<name>.yo",
|
||||
Description: "yo description",
|
||||
Default: "0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct pointer level 1",
|
||||
element: &struct {
|
||||
Foo map[string]*struct {
|
||||
Field string `description:"field description"`
|
||||
Yo string `description:"yo description"`
|
||||
} `description:"foo description"`
|
||||
}{},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.<name>",
|
||||
Description: "foo description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.<name>.field",
|
||||
Description: "field description",
|
||||
Default: "",
|
||||
},
|
||||
{
|
||||
Name: "foo.<name>.yo",
|
||||
Description: "yo description",
|
||||
Default: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "time duration field",
|
||||
element: &struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}{
|
||||
Field: 1 * time.Second,
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "1s",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "time duration field map",
|
||||
element: &struct {
|
||||
Foo map[string]*struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
} `description:"foo description"`
|
||||
}{
|
||||
Foo: map[string]*struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}{},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.<name>",
|
||||
Description: "foo description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.<name>.field",
|
||||
Description: "field description",
|
||||
Default: "0s",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "time duration field map 2",
|
||||
element: &struct {
|
||||
Foo map[string]*struct {
|
||||
Fii *struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}
|
||||
} `description:"foo description"`
|
||||
}{
|
||||
Foo: map[string]*struct {
|
||||
Fii *struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}
|
||||
}{},
|
||||
},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.<name>",
|
||||
Description: "foo description",
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Name: "foo.<name>.fii.field",
|
||||
Description: "field description",
|
||||
Default: "0s",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "time duration field 2",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}{
|
||||
Field: 1 * time.Second,
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "foo.field",
|
||||
Description: "field description",
|
||||
Default: "1s",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "time duration field 3",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii *struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}
|
||||
}
|
||||
}{
|
||||
Foo: &struct {
|
||||
Fii *struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}
|
||||
}{
|
||||
Fii: &struct {
|
||||
Field time.Duration `description:"field description"`
|
||||
}{
|
||||
Field: 1 * time.Second,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "foo.fii.field",
|
||||
Description: "field description",
|
||||
Default: "1s",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "time duration field",
|
||||
element: &struct {
|
||||
Field types.Duration `description:"field description"`
|
||||
}{
|
||||
Field: types.Duration(180 * time.Second),
|
||||
},
|
||||
expected: []parser.Flat{{
|
||||
Name: "field",
|
||||
Description: "field description",
|
||||
Default: "180",
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice of struct",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii []struct {
|
||||
Field1 string `description:"field1 description"`
|
||||
Field2 int `description:"field2 description"`
|
||||
} `description:"fii description"`
|
||||
} `description:"foo description"`
|
||||
}{},
|
||||
expected: []parser.Flat{
|
||||
{
|
||||
Name: "foo.fii",
|
||||
Description: "fii description",
|
||||
Default: "",
|
||||
},
|
||||
{
|
||||
Name: "foo.fii[0].field1",
|
||||
Description: "field1 description",
|
||||
Default: "",
|
||||
},
|
||||
{
|
||||
Name: "foo.fii[0].field2",
|
||||
Description: "field2 description",
|
||||
Default: "0",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Skipped: because realistically not needed in Traefik for now.
|
||||
// {
|
||||
// desc: "map of map field level 2",
|
||||
// element: &struct {
|
||||
// Foo *struct {
|
||||
// Fii map[string]map[string]string `description:"fii description"`
|
||||
// } `description:"foo description"`
|
||||
// }{
|
||||
// Foo: &struct {
|
||||
// Fii map[string]map[string]string `description:"fii description"`
|
||||
// }{
|
||||
// Fii: map[string]map[string]string{
|
||||
// parser.MapNamePlaceholder: {
|
||||
// parser.MapNamePlaceholder: "test",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// expected: `XXX`,
|
||||
// },
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
generator.Generate(test.element)
|
||||
|
||||
entries, err := Encode(test.element)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expected, entries)
|
||||
})
|
||||
}
|
||||
}
|
108
pkg/config/flag/flagparser.go
Normal file
108
pkg/config/flag/flagparser.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Parse parses the command-line flag arguments into a map,
|
||||
// using the type information in element to discriminate whether a flag is supposed to be a bool,
|
||||
// and other such ambiguities.
|
||||
func Parse(args []string, element interface{}) (map[string]string, error) {
|
||||
f := flagSet{
|
||||
flagTypes: getFlagTypes(element),
|
||||
args: args,
|
||||
values: make(map[string]string),
|
||||
}
|
||||
|
||||
for {
|
||||
seen, err := f.parseOne()
|
||||
if seen {
|
||||
continue
|
||||
}
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return f.values, nil
|
||||
}
|
||||
|
||||
type flagSet struct {
|
||||
flagTypes map[string]reflect.Kind
|
||||
args []string
|
||||
values map[string]string
|
||||
}
|
||||
|
||||
func (f *flagSet) parseOne() (bool, error) {
|
||||
if len(f.args) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
s := f.args[0]
|
||||
if len(s) < 2 || s[0] != '-' {
|
||||
return false, nil
|
||||
}
|
||||
numMinuses := 1
|
||||
if s[1] == '-' {
|
||||
numMinuses++
|
||||
if len(s) == 2 { // "--" terminates the flags
|
||||
f.args = f.args[1:]
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
name := s[numMinuses:]
|
||||
if len(name) == 0 || name[0] == '-' || name[0] == '=' {
|
||||
return false, fmt.Errorf("bad flag syntax: %s", s)
|
||||
}
|
||||
|
||||
// it's a flag. does it have an argument?
|
||||
f.args = f.args[1:]
|
||||
hasValue := false
|
||||
value := ""
|
||||
for i := 1; i < len(name); i++ { // equals cannot be first
|
||||
if name[i] == '=' {
|
||||
value = name[i+1:]
|
||||
hasValue = true
|
||||
name = name[0:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hasValue {
|
||||
f.setValue(name, value)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if f.flagTypes[name] == reflect.Bool || f.flagTypes[name] == reflect.Ptr {
|
||||
f.setValue(name, "true")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if len(f.args) > 0 {
|
||||
// value is the next arg
|
||||
hasValue = true
|
||||
value, f.args = f.args[0], f.args[1:]
|
||||
}
|
||||
|
||||
if !hasValue {
|
||||
return false, fmt.Errorf("flag needs an argument: -%s", name)
|
||||
}
|
||||
|
||||
f.setValue(name, value)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (f *flagSet) setValue(name string, value string) {
|
||||
n := strings.ToLower("traefik." + name)
|
||||
v, ok := f.values[n]
|
||||
|
||||
if ok && f.flagTypes[name] == reflect.Slice {
|
||||
f.values[n] = v + "," + value
|
||||
return
|
||||
}
|
||||
|
||||
f.values[n] = value
|
||||
}
|
255
pkg/config/flag/flagparser_test.go
Normal file
255
pkg/config/flag/flagparser_test.go
Normal file
|
@ -0,0 +1,255 @@
|
|||
package flag
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
element interface{}
|
||||
expected map[string]string
|
||||
}{
|
||||
{
|
||||
desc: "no args",
|
||||
args: nil,
|
||||
expected: map[string]string{},
|
||||
},
|
||||
{
|
||||
desc: "bool value",
|
||||
args: []string{"--foo"},
|
||||
element: &struct {
|
||||
Foo bool
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "equal",
|
||||
args: []string{"--foo=bar"},
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "space separated",
|
||||
args: []string{"--foo", "bar"},
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "space separated with end of parameter",
|
||||
args: []string{"--foo=bir", "--", "--bar"},
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bir",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple bool flags without value",
|
||||
args: []string{"--foo", "--bar"},
|
||||
element: &struct {
|
||||
Foo bool
|
||||
Bar bool
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "true",
|
||||
"traefik.bar": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags",
|
||||
args: []string{"--foo=bar", "--foo=baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bar,baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map string",
|
||||
args: []string{"--foo.name=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]string
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.name": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct",
|
||||
args: []string{"--foo.name.value=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]struct{ Value string }
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.name.value": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct with sub-struct",
|
||||
args: []string{"--foo.name.bar.value=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Bar *struct{ Value string }
|
||||
}
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.name.bar.value": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct with sub-map",
|
||||
args: []string{"--foo.name1.bar.name2.value=bar"},
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Bar map[string]struct{ Value string }
|
||||
}
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo.name1.bar.name2.value": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags 2",
|
||||
args: []string{"--foo", "bar", "--foo", "baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bar,baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags 3",
|
||||
args: []string{"--foo", "bar", "--foo=", "--baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
Baz bool
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bar,",
|
||||
"traefik.baz": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice with several flags 4",
|
||||
args: []string{"--foo", "bar", "--foo", "--baz"},
|
||||
element: &struct {
|
||||
Foo []string
|
||||
Baz bool
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bar,--baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple string flag",
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
args: []string{"--foo=bar", "--foo=baz"},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "multiple string flag 2",
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
args: []string{"--foo", "bar", "--foo", "baz"},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "baz",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "string without value",
|
||||
element: &struct {
|
||||
Foo string
|
||||
Bar bool
|
||||
}{},
|
||||
args: []string{"--foo", "--bar"},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "--bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "struct pointer value",
|
||||
args: []string{"--foo"},
|
||||
element: &struct {
|
||||
Foo *struct{ Field string }
|
||||
}{},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fl, err := Parse(test.args, test.element)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected, fl)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse_Errors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
element interface{}
|
||||
}{
|
||||
{
|
||||
desc: "triple hyphen",
|
||||
args: []string{"---foo"},
|
||||
element: &struct {
|
||||
Foo bool
|
||||
}{},
|
||||
},
|
||||
{
|
||||
desc: "equal",
|
||||
args: []string{"--=foo"},
|
||||
element: &struct {
|
||||
Foo bool
|
||||
}{},
|
||||
},
|
||||
{
|
||||
desc: "string without value",
|
||||
element: &struct {
|
||||
Foo string
|
||||
Bar bool
|
||||
}{},
|
||||
args: []string{"--foo"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := Parse(test.args, test.element)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
60
pkg/config/flag/flagtype.go
Normal file
60
pkg/config/flag/flagtype.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package flag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
func getFlagTypes(element interface{}) map[string]reflect.Kind {
|
||||
ref := map[string]reflect.Kind{}
|
||||
|
||||
if element == nil {
|
||||
return ref
|
||||
}
|
||||
|
||||
tp := reflect.TypeOf(element).Elem()
|
||||
|
||||
addFlagType(ref, "", tp)
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
func addFlagType(ref map[string]reflect.Kind, name string, typ reflect.Type) {
|
||||
switch typ.Kind() {
|
||||
case reflect.Bool, reflect.Slice:
|
||||
ref[name] = typ.Kind()
|
||||
|
||||
case reflect.Map:
|
||||
addFlagType(ref, getName(name, parser.MapNamePlaceholder), typ.Elem())
|
||||
|
||||
case reflect.Ptr:
|
||||
if typ.Elem().Kind() == reflect.Struct {
|
||||
ref[name] = typ.Kind()
|
||||
}
|
||||
addFlagType(ref, name, typ.Elem())
|
||||
|
||||
case reflect.Struct:
|
||||
for j := 0; j < typ.NumField(); j++ {
|
||||
subField := typ.Field(j)
|
||||
|
||||
if !parser.IsExported(subField) {
|
||||
continue
|
||||
}
|
||||
|
||||
if subField.Anonymous {
|
||||
addFlagType(ref, getName(name), subField.Type)
|
||||
} else {
|
||||
addFlagType(ref, getName(name, subField.Name), subField.Type)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
func getName(names ...string) string {
|
||||
return strings.TrimPrefix(strings.ToLower(strings.Join(names, ".")), ".")
|
||||
}
|
226
pkg/config/flag/flagtype_test.go
Normal file
226
pkg/config/flag/flagtype_test.go
Normal file
|
@ -0,0 +1,226 @@
|
|||
package flag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_getFlagTypes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
element interface{}
|
||||
expected map[string]reflect.Kind
|
||||
}{
|
||||
{
|
||||
desc: "nil",
|
||||
element: nil,
|
||||
expected: map[string]reflect.Kind{},
|
||||
},
|
||||
{
|
||||
desc: "no fields",
|
||||
element: &struct {
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{},
|
||||
},
|
||||
{
|
||||
desc: "string field",
|
||||
element: &struct {
|
||||
Foo string
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{},
|
||||
},
|
||||
{
|
||||
desc: "bool field level 0",
|
||||
element: &struct {
|
||||
Foo bool
|
||||
fii bool
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "bool field level 1",
|
||||
element: &struct {
|
||||
Foo struct {
|
||||
Field bool
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo.field": reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "bool field level 2",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii *struct {
|
||||
Field bool
|
||||
}
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Ptr,
|
||||
"foo.fii": reflect.Ptr,
|
||||
"foo.fii.field": reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "pointer field",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Field string
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Ptr,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "bool field level 3",
|
||||
element: &struct {
|
||||
Foo *struct {
|
||||
Fii *struct {
|
||||
Fuu *struct {
|
||||
Field bool
|
||||
}
|
||||
}
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Ptr,
|
||||
"foo.fii": reflect.Ptr,
|
||||
"foo.fii.fuu": reflect.Ptr,
|
||||
"foo.fii.fuu.field": reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map string",
|
||||
element: &struct {
|
||||
Foo map[string]string
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{},
|
||||
},
|
||||
{
|
||||
desc: "map bool",
|
||||
element: &struct {
|
||||
Foo map[string]bool
|
||||
Fii struct{}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo." + parser.MapNamePlaceholder: reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct",
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Field bool
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo." + parser.MapNamePlaceholder + ".field": reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map map bool",
|
||||
element: &struct {
|
||||
Foo map[string]map[string]bool
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo." + parser.MapNamePlaceholder + "." + parser.MapNamePlaceholder: reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map struct map",
|
||||
element: &struct {
|
||||
Foo map[string]struct {
|
||||
Fii map[string]bool
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo." + parser.MapNamePlaceholder + ".fii." + parser.MapNamePlaceholder: reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "pointer bool field level 0",
|
||||
element: &struct {
|
||||
Foo *bool
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Bool,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "pointer int field level 0",
|
||||
element: &struct {
|
||||
Foo *int
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{},
|
||||
},
|
||||
{
|
||||
desc: "bool slice field level 0",
|
||||
element: &struct {
|
||||
Foo []bool
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Slice,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "string slice field level 0",
|
||||
element: &struct {
|
||||
Foo []string
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Slice,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "slice field level 1",
|
||||
element: &struct {
|
||||
Foo struct {
|
||||
Field []string
|
||||
}
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo.field": reflect.Slice,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "map slice string",
|
||||
element: &struct {
|
||||
Foo map[string][]string
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo." + parser.MapNamePlaceholder: reflect.Slice,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "embedded struct",
|
||||
element: &struct {
|
||||
Yo
|
||||
}{},
|
||||
expected: map[string]reflect.Kind{
|
||||
"foo": reflect.Bool,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := getFlagTypes(test.element)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type Yo struct {
|
||||
Foo bool
|
||||
}
|
97
pkg/config/generator/generator.go
Normal file
97
pkg/config/generator/generator.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
// Package generator implements the custom initialization of all the fields of an empty interface.
|
||||
package generator
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/containous/traefik/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)
|
||||
}
|
439
pkg/config/generator/generator_test.go
Normal file
439
pkg/config/generator/generator_test.go
Normal file
|
@ -0,0 +1,439 @@
|
|||
package generator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/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
|
||||
}
|
33
pkg/config/label/label.go
Normal file
33
pkg/config/label/label.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Package label implements the decoding and encoding between flat labels and a typed Configuration.
|
||||
package label
|
||||
|
||||
import (
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/config/parser"
|
||||
)
|
||||
|
||||
// DecodeConfiguration converts the labels to a configuration.
|
||||
func DecodeConfiguration(labels map[string]string) (*config.Configuration, error) {
|
||||
conf := &config.Configuration{
|
||||
HTTP: &config.HTTPConfiguration{},
|
||||
TCP: &config.TCPConfiguration{},
|
||||
}
|
||||
|
||||
err := parser.Decode(labels, conf, "traefik.http", "traefik.tcp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// EncodeConfiguration converts a configuration to labels.
|
||||
func EncodeConfiguration(conf *config.Configuration) (map[string]string, error) {
|
||||
return parser.Encode(conf)
|
||||
}
|
||||
|
||||
// Decode converts the labels to an element.
|
||||
// labels -> [ node -> node + metadata (type) ] -> element (node)
|
||||
func Decode(labels map[string]string, element interface{}, filters ...string) error {
|
||||
return parser.Decode(labels, element, filters...)
|
||||
}
|
|
@ -5,8 +5,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -306,12 +306,12 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
RateLimit: &config.RateLimit{
|
||||
RateSet: map[string]*config.Rate{
|
||||
"Rate0": {
|
||||
Period: parse.Duration(42 * time.Second),
|
||||
Period: types.Duration(42 * time.Second),
|
||||
Average: 42,
|
||||
Burst: 42,
|
||||
},
|
||||
"Rate1": {
|
||||
Period: parse.Duration(42 * time.Second),
|
||||
Period: types.Duration(42 * time.Second),
|
||||
Average: 42,
|
||||
Burst: 42,
|
||||
},
|
||||
|
@ -700,12 +700,12 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
RateLimit: &config.RateLimit{
|
||||
RateSet: map[string]*config.Rate{
|
||||
"Rate0": {
|
||||
Period: parse.Duration(42 * time.Nanosecond),
|
||||
Period: types.Duration(42 * time.Nanosecond),
|
||||
Average: 42,
|
||||
Burst: 42,
|
||||
},
|
||||
"Rate1": {
|
||||
Period: parse.Duration(42 * time.Nanosecond),
|
||||
Period: types.Duration(42 * time.Nanosecond),
|
||||
Average: 42,
|
||||
Burst: 42,
|
||||
},
|
|
@ -1,8 +1,8 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/ip"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
@ -52,7 +52,7 @@ type Auth struct {
|
|||
|
||||
// BasicAuth holds the HTTP basic authentication configuration.
|
||||
type BasicAuth struct {
|
||||
Users `json:"users,omitempty" mapstructure:","`
|
||||
Users Users `json:"users,omitempty"`
|
||||
UsersFile string `json:"usersFile,omitempty"`
|
||||
Realm string `json:"realm,omitempty"`
|
||||
RemoveHeader bool `json:"removeHeader,omitempty"`
|
||||
|
@ -93,7 +93,7 @@ type Compress struct{}
|
|||
|
||||
// DigestAuth holds the Digest HTTP authentication configuration.
|
||||
type DigestAuth struct {
|
||||
Users `json:"users,omitempty" mapstructure:","`
|
||||
Users Users `json:"users,omitempty"`
|
||||
UsersFile string `json:"usersFile,omitempty"`
|
||||
RemoveHeader bool `json:"removeHeader,omitempty"`
|
||||
Realm string `json:"realm,omitempty" mapstructure:","`
|
||||
|
@ -273,7 +273,7 @@ type PassTLSClientCert struct {
|
|||
|
||||
// Rate holds the rate limiting configuration for a specific time period.
|
||||
type Rate struct {
|
||||
Period parse.Duration `json:"period,omitempty"`
|
||||
Period types.Duration `json:"period,omitempty"`
|
||||
Average int64 `json:"average,omitempty"`
|
||||
Burst int64 `json:"burst,omitempty"`
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package internal
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -7,15 +7,14 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
)
|
||||
|
||||
type initializer interface {
|
||||
SetDefaults()
|
||||
}
|
||||
|
||||
// Fill the fields of the element.
|
||||
// nodes -> element
|
||||
// Fill populates the fields of the element using the information in node.
|
||||
func Fill(element interface{}, node *Node) error {
|
||||
if element == nil || node == nil {
|
||||
return nil
|
||||
|
@ -25,12 +24,12 @@ func Fill(element interface{}, node *Node) error {
|
|||
return fmt.Errorf("missing node type: %s", node.Name)
|
||||
}
|
||||
|
||||
elem := reflect.ValueOf(element)
|
||||
if elem.Kind() == reflect.Struct {
|
||||
root := reflect.ValueOf(element)
|
||||
if root.Kind() == reflect.Struct {
|
||||
return fmt.Errorf("struct are not supported, use pointer instead")
|
||||
}
|
||||
|
||||
return fill(elem.Elem(), node)
|
||||
return fill(root.Elem(), node)
|
||||
}
|
||||
|
||||
func fill(field reflect.Value, node *Node) error {
|
||||
|
@ -117,8 +116,9 @@ func setStruct(field reflect.Value, node *Node) error {
|
|||
}
|
||||
|
||||
func setSlice(field reflect.Value, node *Node) error {
|
||||
if field.Type().Elem().Kind() == reflect.Struct {
|
||||
return setSliceAsStruct(field, node)
|
||||
if field.Type().Elem().Kind() == reflect.Struct ||
|
||||
field.Type().Elem().Kind() == reflect.Ptr && field.Type().Elem().Elem().Kind() == reflect.Struct {
|
||||
return setSliceStruct(field, node)
|
||||
}
|
||||
|
||||
if len(node.Value) == 0 {
|
||||
|
@ -135,7 +135,7 @@ func setSlice(field reflect.Value, node *Node) error {
|
|||
|
||||
switch field.Type().Elem().Kind() {
|
||||
case reflect.String:
|
||||
field.Index(i).Set(reflect.ValueOf(value))
|
||||
field.Index(i).SetString(value)
|
||||
case reflect.Int:
|
||||
val, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
|
@ -211,6 +211,27 @@ func setSlice(field reflect.Value, node *Node) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func setSliceStruct(field reflect.Value, node *Node) error {
|
||||
if node.Tag.Get(TagLabelSliceAsStruct) != "" {
|
||||
return 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 := setPtr(value, child)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field.Index(i).Set(value.Elem().Elem())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSliceAsStruct(field reflect.Value, node *Node) error {
|
||||
if len(node.Children) == 0 {
|
||||
return fmt.Errorf("invalid slice: node %s", node.Name)
|
||||
|
@ -254,7 +275,7 @@ func setMap(field reflect.Value, node *Node) error {
|
|||
|
||||
func setInt(field reflect.Value, value string, bitSize int) error {
|
||||
switch field.Type() {
|
||||
case reflect.TypeOf(parse.Duration(0)):
|
||||
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)
|
|
@ -1,11 +1,11 @@
|
|||
package internal
|
||||
package parser
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -390,7 +390,7 @@ func TestFill(t *testing.T) {
|
|||
expected: expected{element: &struct{ Foo time.Duration }{Foo: 4 * time.Nanosecond}},
|
||||
},
|
||||
{
|
||||
desc: "parse.Duration with unit",
|
||||
desc: "types.Duration with unit",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
|
@ -398,11 +398,11 @@ func TestFill(t *testing.T) {
|
|||
{Name: "Foo", FieldName: "Foo", Value: "4s", Kind: reflect.Int64},
|
||||
},
|
||||
},
|
||||
element: &struct{ Foo parse.Duration }{},
|
||||
expected: expected{element: &struct{ Foo parse.Duration }{Foo: parse.Duration(4 * time.Second)}},
|
||||
element: &struct{ Foo types.Duration }{},
|
||||
expected: expected{element: &struct{ Foo types.Duration }{Foo: types.Duration(4 * time.Second)}},
|
||||
},
|
||||
{
|
||||
desc: "parse.Duration without unit",
|
||||
desc: "types.Duration without unit",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
|
@ -410,8 +410,8 @@ func TestFill(t *testing.T) {
|
|||
{Name: "Foo", FieldName: "Foo", Value: "4", Kind: reflect.Int64},
|
||||
},
|
||||
},
|
||||
element: &struct{ Foo parse.Duration }{},
|
||||
expected: expected{element: &struct{ Foo parse.Duration }{Foo: parse.Duration(4 * time.Second)}},
|
||||
element: &struct{ Foo types.Duration }{},
|
||||
expected: expected{element: &struct{ Foo types.Duration }{Foo: types.Duration(4 * time.Second)}},
|
||||
},
|
||||
{
|
||||
desc: "bool",
|
||||
|
@ -722,6 +722,30 @@ func TestFill(t *testing.T) {
|
|||
element: &struct{ Foo []string }{},
|
||||
expected: expected{element: &struct{ Foo []string }{Foo: []string{"huu", "hii", "hoo"}}},
|
||||
},
|
||||
{
|
||||
desc: "slice named type",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "huu,hii,hoo", Kind: reflect.Slice},
|
||||
},
|
||||
},
|
||||
element: &struct{ Foo []NamedType }{},
|
||||
expected: expected{element: &struct{ Foo []NamedType }{Foo: []NamedType{"huu", "hii", "hoo"}}},
|
||||
},
|
||||
{
|
||||
desc: "slice named type int",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "1,2,3", Kind: reflect.Slice},
|
||||
},
|
||||
},
|
||||
element: &struct{ Foo []NamedTypeInt }{},
|
||||
expected: expected{element: &struct{ Foo []NamedTypeInt }{Foo: []NamedTypeInt{1, 2, 3}}},
|
||||
},
|
||||
{
|
||||
desc: "empty slice",
|
||||
node: &Node{
|
||||
|
@ -1046,18 +1070,6 @@ func TestFill(t *testing.T) {
|
|||
element: &struct{ Foo []bool }{},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "slice struct",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "huu", Kind: reflect.Slice},
|
||||
},
|
||||
},
|
||||
element: &struct{ Foo []struct{ Fii string } }{},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "slice slice-as-struct",
|
||||
node: &Node{
|
||||
|
@ -1068,6 +1080,7 @@ func TestFill(t *testing.T) {
|
|||
Name: "Fii",
|
||||
FieldName: "Foo",
|
||||
Kind: reflect.Slice,
|
||||
Tag: `label-slice-as-struct:"Fii"`,
|
||||
Children: []*Node{
|
||||
{Name: "bar", FieldName: "Bar", Kind: reflect.String, Value: "haa"},
|
||||
{Name: "bir", FieldName: "Bir", Kind: reflect.String, Value: "hii"},
|
||||
|
@ -1098,6 +1111,47 @@ func TestFill(t *testing.T) {
|
|||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice slice-as-struct pointer",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{
|
||||
Name: "Fii",
|
||||
FieldName: "Foo",
|
||||
Kind: reflect.Slice,
|
||||
Tag: `label-slice-as-struct:"Fii"`,
|
||||
Children: []*Node{
|
||||
{Name: "bar", FieldName: "Bar", Kind: reflect.String, Value: "haa"},
|
||||
{Name: "bir", FieldName: "Bir", Kind: reflect.String, Value: "hii"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
element: &struct {
|
||||
Foo []*struct {
|
||||
Bar string
|
||||
Bir string
|
||||
} `label-slice-as-struct:"Fii"`
|
||||
}{},
|
||||
expected: expected{element: &struct {
|
||||
Foo []*struct {
|
||||
Bar string
|
||||
Bir string
|
||||
} `label-slice-as-struct:"Fii"`
|
||||
}{
|
||||
Foo: []*struct {
|
||||
Bar string
|
||||
Bir string
|
||||
}{
|
||||
{
|
||||
Bar: "haa",
|
||||
Bir: "hii",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice slice-as-struct without children",
|
||||
node: &Node{
|
||||
|
@ -1107,6 +1161,7 @@ func TestFill(t *testing.T) {
|
|||
{
|
||||
Name: "Fii",
|
||||
FieldName: "Foo",
|
||||
Tag: `label-slice-as-struct:"Fii"`,
|
||||
Kind: reflect.Slice,
|
||||
},
|
||||
},
|
||||
|
@ -1134,12 +1189,12 @@ func TestFill(t *testing.T) {
|
|||
}},
|
||||
}},
|
||||
element: &struct {
|
||||
Foo *initialledFoo
|
||||
Foo *InitializedFoo
|
||||
}{},
|
||||
expected: expected{element: &struct {
|
||||
Foo *initialledFoo
|
||||
Foo *InitializedFoo
|
||||
}{
|
||||
Foo: &initialledFoo{
|
||||
Foo: &InitializedFoo{
|
||||
Fii: "default",
|
||||
Fuu: "huu",
|
||||
},
|
||||
|
@ -1170,6 +1225,164 @@ func TestFill(t *testing.T) {
|
|||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "int pointer",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "4", Kind: reflect.Ptr},
|
||||
},
|
||||
},
|
||||
element: &struct{ Foo *int }{},
|
||||
expected: expected{element: &struct{ Foo *int }{Foo: func(v int) *int { return &v }(4)}},
|
||||
},
|
||||
{
|
||||
desc: "bool pointer",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "true", Kind: reflect.Ptr},
|
||||
},
|
||||
},
|
||||
element: &struct{ Foo *bool }{},
|
||||
expected: expected{element: &struct{ Foo *bool }{Foo: func(v bool) *bool { return &v }(true)}},
|
||||
},
|
||||
{
|
||||
desc: "string pointer",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "bar", Kind: reflect.Ptr},
|
||||
},
|
||||
},
|
||||
element: &struct{ Foo *string }{},
|
||||
expected: expected{element: &struct{ Foo *string }{Foo: func(v string) *string { return &v }("bar")}},
|
||||
},
|
||||
{
|
||||
desc: "embedded",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{
|
||||
Name: "Foo",
|
||||
FieldName: "Foo",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Fuu", FieldName: "Fuu", Value: "huu", Kind: reflect.String},
|
||||
}},
|
||||
}},
|
||||
element: &struct {
|
||||
Foo struct {
|
||||
FiiFoo
|
||||
}
|
||||
}{},
|
||||
expected: expected{element: &struct {
|
||||
Foo struct {
|
||||
FiiFoo
|
||||
}
|
||||
}{
|
||||
Foo: struct {
|
||||
FiiFoo
|
||||
}{
|
||||
FiiFoo: FiiFoo{
|
||||
Fii: "",
|
||||
Fuu: "huu",
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice struct",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Kind: reflect.Slice, Children: []*Node{
|
||||
{Name: "[0]", Kind: reflect.Struct, Children: []*Node{
|
||||
{Name: "Field1", FieldName: "Field1", Value: "A", Kind: reflect.String},
|
||||
{Name: "Field2", FieldName: "Field2", Value: "A", Kind: reflect.String},
|
||||
}},
|
||||
{Name: "[1]", Kind: reflect.Struct, Children: []*Node{
|
||||
{Name: "Field1", FieldName: "Field1", Value: "B", Kind: reflect.String},
|
||||
{Name: "Field2", FieldName: "Field2", Value: "B", Kind: reflect.String},
|
||||
}},
|
||||
{Name: "[2]", Kind: reflect.Struct, Children: []*Node{
|
||||
{Name: "Field1", FieldName: "Field1", Value: "C", Kind: reflect.String},
|
||||
{Name: "Field2", FieldName: "Field2", Value: "C", Kind: reflect.String},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
element: &struct {
|
||||
Foo []struct {
|
||||
Field1 string
|
||||
Field2 string
|
||||
}
|
||||
}{},
|
||||
expected: expected{element: &struct {
|
||||
Foo []struct {
|
||||
Field1 string
|
||||
Field2 string
|
||||
}
|
||||
}{
|
||||
Foo: []struct {
|
||||
Field1 string
|
||||
Field2 string
|
||||
}{
|
||||
{Field1: "A", Field2: "A"},
|
||||
{Field1: "B", Field2: "B"},
|
||||
{Field1: "C", Field2: "C"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "slice pointer struct",
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Kind: reflect.Slice, Children: []*Node{
|
||||
{Name: "[0]", Kind: reflect.Ptr, Children: []*Node{
|
||||
{Name: "Field1", FieldName: "Field1", Value: "A", Kind: reflect.String},
|
||||
{Name: "Field2", FieldName: "Field2", Value: "A", Kind: reflect.String},
|
||||
}},
|
||||
{Name: "[1]", Kind: reflect.Ptr, Children: []*Node{
|
||||
{Name: "Field1", FieldName: "Field1", Value: "B", Kind: reflect.String},
|
||||
{Name: "Field2", FieldName: "Field2", Value: "B", Kind: reflect.String},
|
||||
}},
|
||||
{Name: "[2]", Kind: reflect.Ptr, Children: []*Node{
|
||||
{Name: "Field1", FieldName: "Field1", Value: "C", Kind: reflect.String},
|
||||
{Name: "Field2", FieldName: "Field2", Value: "C", Kind: reflect.String},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
element: &struct {
|
||||
Foo []*struct {
|
||||
Field1 string
|
||||
Field2 string
|
||||
}
|
||||
}{},
|
||||
expected: expected{element: &struct {
|
||||
Foo []*struct {
|
||||
Field1 string
|
||||
Field2 string
|
||||
}
|
||||
}{
|
||||
Foo: []*struct {
|
||||
Field1 string
|
||||
Field2 string
|
||||
}{
|
||||
{Field1: "A", Field2: "A"},
|
||||
{Field1: "B", Field2: "B"},
|
||||
{Field1: "C", Field2: "C"},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
@ -1188,12 +1401,15 @@ func TestFill(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type initialledFoo struct {
|
||||
type NamedType string
|
||||
type NamedTypeInt int
|
||||
|
||||
type InitializedFoo struct {
|
||||
Fii string
|
||||
Fuu string
|
||||
}
|
||||
|
||||
func (t *initialledFoo) SetDefaults() {
|
||||
func (t *InitializedFoo) SetDefaults() {
|
||||
t.Fii = "default"
|
||||
}
|
||||
|
||||
|
@ -1206,3 +1422,10 @@ func (t *wrongInitialledFoo) SetDefaults() error {
|
|||
t.Fii = "default"
|
||||
return nil
|
||||
}
|
||||
|
||||
type Bouya string
|
||||
|
||||
type FiiFoo struct {
|
||||
Fii string
|
||||
Fuu Bouya
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package internal
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -7,13 +7,15 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// EncodeToNode Converts an element to a node.
|
||||
// EncodeToNode converts an element to a node.
|
||||
// element -> nodes
|
||||
func EncodeToNode(element interface{}) (*Node, error) {
|
||||
func EncodeToNode(element interface{}, omitEmpty bool) (*Node, error) {
|
||||
rValue := reflect.ValueOf(element)
|
||||
node := &Node{Name: "traefik"}
|
||||
|
||||
err := setNodeValue(node, rValue)
|
||||
encoder := encoderToNode{omitEmpty: omitEmpty}
|
||||
|
||||
err := encoder.setNodeValue(node, rValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -21,7 +23,11 @@ func EncodeToNode(element interface{}) (*Node, error) {
|
|||
return node, nil
|
||||
}
|
||||
|
||||
func setNodeValue(node *Node, rValue reflect.Value) error {
|
||||
type encoderToNode struct {
|
||||
omitEmpty bool
|
||||
}
|
||||
|
||||
func (e encoderToNode) setNodeValue(node *Node, rValue reflect.Value) error {
|
||||
switch rValue.Kind() {
|
||||
case reflect.String:
|
||||
node.Value = rValue.String()
|
||||
|
@ -34,13 +40,13 @@ func setNodeValue(node *Node, rValue reflect.Value) error {
|
|||
case reflect.Bool:
|
||||
node.Value = strconv.FormatBool(rValue.Bool())
|
||||
case reflect.Struct:
|
||||
return setStructValue(node, rValue)
|
||||
return e.setStructValue(node, rValue)
|
||||
case reflect.Ptr:
|
||||
return setNodeValue(node, rValue.Elem())
|
||||
return e.setNodeValue(node, rValue.Elem())
|
||||
case reflect.Map:
|
||||
return setMapValue(node, rValue)
|
||||
return e.setMapValue(node, rValue)
|
||||
case reflect.Slice:
|
||||
return setSliceValue(node, rValue)
|
||||
return e.setSliceValue(node, rValue)
|
||||
default:
|
||||
// noop
|
||||
}
|
||||
|
@ -48,14 +54,14 @@ func setNodeValue(node *Node, rValue reflect.Value) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func setStructValue(node *Node, rValue reflect.Value) error {
|
||||
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) {
|
||||
if !IsExported(field) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -67,7 +73,7 @@ func setStructValue(node *Node, rValue reflect.Value) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if isSkippedField(field, fieldValue) {
|
||||
if e.isSkippedField(field, fieldValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -76,18 +82,31 @@ func setStructValue(node *Node, rValue reflect.Value) error {
|
|||
nodeName = field.Tag.Get(TagLabelSliceAsStruct)
|
||||
}
|
||||
|
||||
child := &Node{Name: nodeName, FieldName: field.Name}
|
||||
if field.Anonymous {
|
||||
if err := e.setNodeValue(node, fieldValue); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := setNodeValue(child, fieldValue); err != nil {
|
||||
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 && len(child.Children) == 0 {
|
||||
if field.Tag.Get(TagLabel) != "allowEmpty" {
|
||||
if field.Type.Kind() == reflect.Ptr {
|
||||
if field.Type.Elem().Kind() != reflect.Struct && fieldValue.IsNil() {
|
||||
continue
|
||||
}
|
||||
|
||||
child.Value = "true"
|
||||
if field.Type.Elem().Kind() == reflect.Struct && len(child.Children) == 0 {
|
||||
if field.Tag.Get(TagLabel) != TagLabelAllowEmpty {
|
||||
continue
|
||||
}
|
||||
|
||||
child.Value = "true"
|
||||
}
|
||||
}
|
||||
|
||||
node.Children = append(node.Children, child)
|
||||
|
@ -96,28 +115,44 @@ func setStructValue(node *Node, rValue reflect.Value) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func setMapValue(node *Node, rValue reflect.Value) error {
|
||||
func (e encoderToNode) setMapValue(node *Node, rValue reflect.Value) error {
|
||||
for _, key := range rValue.MapKeys() {
|
||||
child := &Node{Name: key.String(), FieldName: key.String()}
|
||||
node.Children = append(node.Children, child)
|
||||
|
||||
if err := setNodeValue(child, rValue.MapIndex(key)); err != nil {
|
||||
if err := e.setNodeValue(child, rValue.MapIndex(key)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSliceValue(node *Node, rValue reflect.Value) error {
|
||||
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())
|
||||
}
|
||||
|
||||
if err := setNodeValue(node, rValue.Index(0)); err != nil {
|
||||
return err
|
||||
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
|
||||
|
@ -145,8 +180,8 @@ func setSliceValue(node *Node, rValue reflect.Value) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func isSkippedField(field reflect.StructField, fieldValue reflect.Value) bool {
|
||||
if field.Type.Kind() == reflect.String && fieldValue.Len() == 0 {
|
||||
func (e encoderToNode) isSkippedField(field reflect.StructField, fieldValue reflect.Value) bool {
|
||||
if e.omitEmpty && field.Type.Kind() == reflect.String && fieldValue.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -154,7 +189,12 @@ func isSkippedField(field reflect.StructField, fieldValue reflect.Value) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
if (field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Map) &&
|
||||
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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package internal
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
@ -18,6 +18,16 @@ func TestEncodeToNode(t *testing.T) {
|
|||
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 {
|
||||
|
@ -257,10 +267,16 @@ func TestEncodeToNode(t *testing.T) {
|
|||
Fuu: "huu",
|
||||
},
|
||||
},
|
||||
expected: expected{error: true},
|
||||
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 nil pointer",
|
||||
desc: "string nil pointer",
|
||||
element: struct {
|
||||
Foo *struct {
|
||||
Fii *string
|
||||
|
@ -271,10 +287,64 @@ func TestEncodeToNode(t *testing.T) {
|
|||
Fii *string
|
||||
Fuu string
|
||||
}{
|
||||
Fii: nil,
|
||||
Fuu: "huu",
|
||||
},
|
||||
},
|
||||
expected: expected{error: true},
|
||||
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",
|
||||
|
@ -545,6 +615,60 @@ func TestEncodeToNode(t *testing.T) {
|
|||
},
|
||||
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 {
|
||||
|
@ -572,6 +696,26 @@ func TestEncodeToNode(t *testing.T) {
|
|||
},
|
||||
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"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
@ -579,7 +723,7 @@ func TestEncodeToNode(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, err := EncodeToNode(test.element)
|
||||
node, err := EncodeToNode(test.element, true)
|
||||
|
||||
if test.expected.error {
|
||||
require.Error(t, err)
|
166
pkg/config/parser/flat_encode.go
Normal file
166
pkg/config/parser/flat_encode.go
Normal file
|
@ -0,0 +1,166 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/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
|
||||
}
|
||||
|
||||
// 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("label") == 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)
|
||||
}
|
1250
pkg/config/parser/flat_encode_test.go
Normal file
1250
pkg/config/parser/flat_encode_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
|||
package internal
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -6,42 +6,39 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// DecodeToNode Converts the labels to a node.
|
||||
// labels -> nodes
|
||||
const labelRoot = "traefik"
|
||||
|
||||
// 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, filters ...string) (*Node, error) {
|
||||
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)
|
||||
|
||||
labelRoot := "traefik"
|
||||
sortedKeys := sortKeys(labels, filters)
|
||||
|
||||
var node *Node
|
||||
for i, key := range sortedKeys {
|
||||
split := strings.Split(key, ".")
|
||||
|
||||
if split[0] != labelRoot {
|
||||
// TODO (@ldez): error or continue
|
||||
return nil, fmt.Errorf("invalid label root %s", split[0])
|
||||
}
|
||||
|
||||
labelRoot = split[0]
|
||||
var parts []string
|
||||
for _, v := range split {
|
||||
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, split, labels[key])
|
||||
decodeToNode(node, parts, labels[key])
|
||||
}
|
||||
|
||||
return node, nil
|
||||
|
@ -76,3 +73,23 @@ func containsNode(nodes []*Node, name string) *Node {
|
|||
}
|
||||
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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package internal
|
||||
package parser
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -177,6 +177,40 @@ func TestDecodeToNode(t *testing.T) {
|
|||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
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 {
|
|
@ -1,4 +1,4 @@
|
|||
package internal
|
||||
package parser
|
||||
|
||||
// EncodeNode Converts a node to labels.
|
||||
// nodes -> labels
|
||||
|
@ -14,7 +14,13 @@ func encodeNode(labels map[string]string, root string, node *Node) {
|
|||
continue
|
||||
}
|
||||
|
||||
childName := root + "." + child.Name
|
||||
var sep string
|
||||
if child.Name[0] != '[' {
|
||||
sep = "."
|
||||
}
|
||||
|
||||
childName := root + sep + child.Name
|
||||
|
||||
if len(child.Children) > 0 {
|
||||
encodeNode(labels, childName, child)
|
||||
} else if len(child.Name) > 0 {
|
|
@ -1,4 +1,4 @@
|
|||
package internal
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
@ -141,6 +141,30 @@ func TestEncodeNode(t *testing.T) {
|
|||
"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",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
18
pkg/config/parser/node.go
Normal file
18
pkg/config/parser/node.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package parser
|
||||
|
||||
import "reflect"
|
||||
|
||||
// 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"`
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
Kind reflect.Kind `json:"kind,omitempty"`
|
||||
Tag reflect.StructTag `json:"tag,omitempty"`
|
||||
Children []*Node `json:"children,omitempty"`
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package internal
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -7,9 +7,8 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// AddMetadata Adds metadata to a node.
|
||||
// nodes + element -> nodes
|
||||
func AddMetadata(structure interface{}, node *Node) error {
|
||||
// AddMetadata adds metadata such as type, inferred from element, to a node.
|
||||
func AddMetadata(element interface{}, node *Node) error {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -18,16 +17,25 @@ func AddMetadata(structure interface{}, node *Node) error {
|
|||
return fmt.Errorf("invalid node %s: no child", node.Name)
|
||||
}
|
||||
|
||||
if structure == nil {
|
||||
if element == nil {
|
||||
return errors.New("nil structure")
|
||||
}
|
||||
|
||||
rootType := reflect.TypeOf(structure)
|
||||
rootType := reflect.TypeOf(element)
|
||||
node.Kind = rootType.Kind()
|
||||
|
||||
return browseChildren(rootType, node)
|
||||
}
|
||||
|
||||
func browseChildren(fType reflect.Type, node *Node) error {
|
||||
for _, child := range node.Children {
|
||||
if err := addMetadata(fType, child); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addMetadata(rootType reflect.Type, node *Node) error {
|
||||
rType := rootType
|
||||
if rootType.Kind() == reflect.Ptr {
|
||||
|
@ -45,14 +53,15 @@ func addMetadata(rootType reflect.Type, node *Node) error {
|
|||
|
||||
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(TagLabel) != "allowEmpty" {
|
||||
return fmt.Errorf("node %s (type %s) must have children", node.Name, fType)
|
||||
if len(node.Children) == 0 && field.Tag.Get(TagLabel) != 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(TagLabel) == "allowEmpty"
|
||||
node.Disabled = len(node.Value) > 0 && !strings.EqualFold(node.Value, "true") && field.Tag.Get(TagLabel) == TagLabelAllowEmpty
|
||||
}
|
||||
|
||||
if len(node.Children) == 0 {
|
||||
|
@ -79,9 +88,18 @@ func addMetadata(rootType reflect.Type, node *Node) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// only for struct/Ptr with label-slice-as-struct tag
|
||||
if fType.Kind() == reflect.Slice {
|
||||
return browseChildren(fType.Elem(), node)
|
||||
if field.Tag.Get(TagLabelSliceAsStruct) != "" {
|
||||
return browseChildren(fType.Elem(), node)
|
||||
}
|
||||
|
||||
for _, ch := range node.Children {
|
||||
ch.Kind = fType.Elem().Kind()
|
||||
if err = browseChildren(fType.Elem(), ch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("invalid node %s: %v", node.Name, fType.Kind())
|
||||
|
@ -96,31 +114,32 @@ func findTypedField(rType reflect.Type, node *Node) (reflect.StructField, error)
|
|||
fieldName = cField.Name
|
||||
}
|
||||
|
||||
if isExported(cField) && strings.EqualFold(fieldName, node.Name) {
|
||||
node.FieldName = cField.Name
|
||||
return cField, nil
|
||||
if IsExported(cField) {
|
||||
if cField.Anonymous {
|
||||
if cField.Type.Kind() == reflect.Struct {
|
||||
structField, err := 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)
|
||||
}
|
||||
|
||||
func browseChildren(fType reflect.Type, node *Node) error {
|
||||
for _, child := range node.Children {
|
||||
if err := addMetadata(fType, child); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isExported return true is a struct field is exported, else false
|
||||
// IsExported reports whether f is exported.
|
||||
// https://golang.org/pkg/reflect/#StructField
|
||||
func isExported(f reflect.StructField) bool {
|
||||
if f.PkgPath != "" && !f.Anonymous {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
func IsExported(f reflect.StructField) bool {
|
||||
return f.PkgPath == ""
|
||||
}
|
||||
|
||||
func isSupportedType(field reflect.StructField) error {
|
||||
|
@ -142,20 +161,15 @@ func isSupportedType(field reflect.StructField) error {
|
|||
reflect.Uint64,
|
||||
reflect.Uintptr,
|
||||
reflect.Float32,
|
||||
reflect.Float64:
|
||||
reflect.Float64,
|
||||
reflect.Struct,
|
||||
reflect.Ptr:
|
||||
return nil
|
||||
default:
|
||||
if len(field.Tag.Get(TagLabelSliceAsStruct)) > 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unsupported slice type: %v", fType)
|
||||
}
|
||||
}
|
||||
|
||||
if fType.Kind() == reflect.Ptr && fType.Elem().Kind() != reflect.Struct {
|
||||
return fmt.Errorf("unsupported pointer type: %v", fType.Elem())
|
||||
}
|
||||
|
||||
if fType.Kind() == reflect.Map && fType.Key().Kind() != reflect.String {
|
||||
return fmt.Errorf("unsupported map key type: %v", fType.Key())
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package internal
|
||||
package parser
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -122,19 +122,6 @@ func TestAddMetadata(t *testing.T) {
|
|||
structure: struct{ Foo interf }{},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "level 1, slice struct",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "1,2"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo []struct{ Foo string }
|
||||
}{},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
{
|
||||
desc: "level 1, map string",
|
||||
tree: &Node{
|
||||
|
@ -217,7 +204,57 @@ func TestAddMetadata(t *testing.T) {
|
|||
structure: struct {
|
||||
Foo *int
|
||||
}{},
|
||||
expected: expected{error: true},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "0", Kind: reflect.Ptr},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 1, bool pointer",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "0"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo *bool
|
||||
}{},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "0", Kind: reflect.Ptr},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 1, string pointer",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Value: "0"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo *string
|
||||
}{},
|
||||
expected: expected{
|
||||
node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "0", Kind: reflect.Ptr},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "level 1, 2 children with different types",
|
||||
|
@ -385,6 +422,7 @@ func TestAddMetadata(t *testing.T) {
|
|||
Name: "Fii",
|
||||
FieldName: "Foo",
|
||||
Kind: reflect.Slice,
|
||||
Tag: reflect.StructTag(`label-slice-as-struct:"Fii"`),
|
||||
Children: []*Node{
|
||||
{Name: "bar", FieldName: "Bar", Kind: reflect.String, Value: "haa"},
|
||||
{Name: "bir", FieldName: "Bir", Kind: reflect.String, Value: "hii"},
|
||||
|
@ -420,6 +458,7 @@ func TestAddMetadata(t *testing.T) {
|
|||
Name: "Fii",
|
||||
FieldName: "Foo",
|
||||
Kind: reflect.Slice,
|
||||
Tag: reflect.StructTag(`label-slice-as-struct:"Fii"`),
|
||||
},
|
||||
},
|
||||
}},
|
||||
|
@ -446,7 +485,7 @@ func TestAddMetadata(t *testing.T) {
|
|||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "true", Kind: reflect.Struct},
|
||||
{Name: "Foo", FieldName: "Foo", Value: "true", Kind: reflect.Struct, Tag: reflect.StructTag(`label:"allowEmpty"`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -473,7 +512,7 @@ func TestAddMetadata(t *testing.T) {
|
|||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "TruE", Kind: reflect.Struct},
|
||||
{Name: "Foo", FieldName: "Foo", Value: "TruE", Kind: reflect.Struct, Tag: reflect.StructTag(`label:"allowEmpty"`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -500,7 +539,7 @@ func TestAddMetadata(t *testing.T) {
|
|||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Value: "false", Disabled: true, Kind: reflect.Struct},
|
||||
{Name: "Foo", FieldName: "Foo", Value: "false", Disabled: true, Kind: reflect.Struct, Tag: reflect.StructTag(`label:"allowEmpty"`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -535,6 +574,7 @@ func TestAddMetadata(t *testing.T) {
|
|||
Value: "false",
|
||||
Disabled: true,
|
||||
Kind: reflect.Struct,
|
||||
Tag: reflect.StructTag(`label:"allowEmpty"`),
|
||||
Children: []*Node{
|
||||
{Name: "Bar", FieldName: "Bar", Value: "hii", Kind: reflect.String},
|
||||
},
|
||||
|
@ -777,6 +817,173 @@ func TestAddMetadata(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Slice struct",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Children: []*Node{
|
||||
{Name: "[0]", Children: []*Node{
|
||||
{Name: "Field1", Value: "A"},
|
||||
{Name: "Field2", Value: "A"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*Node{
|
||||
{Name: "Field1", Value: "B"},
|
||||
{Name: "Field2", Value: "B"},
|
||||
}},
|
||||
{Name: "[2]", Children: []*Node{
|
||||
{Name: "Field1", Value: "C"},
|
||||
{Name: "Field2", Value: "C"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo []struct {
|
||||
Field1 string
|
||||
Field2 string
|
||||
}
|
||||
}{},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Kind: reflect.Slice, Children: []*Node{
|
||||
{Name: "[0]", Kind: reflect.Struct, Children: []*Node{
|
||||
{Name: "Field1", FieldName: "Field1", Value: "A", Kind: reflect.String},
|
||||
{Name: "Field2", FieldName: "Field2", Value: "A", Kind: reflect.String},
|
||||
}},
|
||||
{Name: "[1]", Kind: reflect.Struct, Children: []*Node{
|
||||
{Name: "Field1", FieldName: "Field1", Value: "B", Kind: reflect.String},
|
||||
{Name: "Field2", FieldName: "Field2", Value: "B", Kind: reflect.String},
|
||||
}},
|
||||
{Name: "[2]", Kind: reflect.Struct, Children: []*Node{
|
||||
{Name: "Field1", FieldName: "Field1", Value: "C", Kind: reflect.String},
|
||||
{Name: "Field2", FieldName: "Field2", Value: "C", Kind: reflect.String},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "Slice pointer struct",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Children: []*Node{
|
||||
{Name: "[0]", Children: []*Node{
|
||||
{Name: "Field1", Value: "A"},
|
||||
{Name: "Field2", Value: "A"},
|
||||
}},
|
||||
{Name: "[1]", Children: []*Node{
|
||||
{Name: "Field1", Value: "B"},
|
||||
{Name: "Field2", Value: "B"},
|
||||
}},
|
||||
{Name: "[2]", Children: []*Node{
|
||||
{Name: "Field1", Value: "C"},
|
||||
{Name: "Field2", Value: "C"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo []*struct {
|
||||
Field1 string
|
||||
Field2 string
|
||||
}
|
||||
}{},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Kind: reflect.Slice, Children: []*Node{
|
||||
{Name: "[0]", Kind: reflect.Ptr, Children: []*Node{
|
||||
{Name: "Field1", FieldName: "Field1", Value: "A", Kind: reflect.String},
|
||||
{Name: "Field2", FieldName: "Field2", Value: "A", Kind: reflect.String},
|
||||
}},
|
||||
{Name: "[1]", Kind: reflect.Ptr, Children: []*Node{
|
||||
{Name: "Field1", FieldName: "Field1", Value: "B", Kind: reflect.String},
|
||||
{Name: "Field2", FieldName: "Field2", Value: "B", Kind: reflect.String},
|
||||
}},
|
||||
{Name: "[2]", Kind: reflect.Ptr, Children: []*Node{
|
||||
{Name: "Field1", FieldName: "Field1", Value: "C", Kind: reflect.String},
|
||||
{Name: "Field2", FieldName: "Field2", Value: "C", Kind: reflect.String},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "embedded",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Children: []*Node{
|
||||
{Name: "Fii", Value: "bir"},
|
||||
{Name: "Fuu", Value: "bur"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo struct {
|
||||
FiiFoo
|
||||
}
|
||||
}{},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Kind: reflect.Struct, Children: []*Node{
|
||||
{Name: "Fii", FieldName: "Fii", Value: "bir", Kind: reflect.String},
|
||||
{Name: "Fuu", FieldName: "Fuu", Value: "bur", Kind: reflect.String},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "embedded slice",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "MySliceType", Value: "foo,fii"},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
MySliceType
|
||||
}{},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "MySliceType", FieldName: "MySliceType", Value: "foo,fii", Kind: reflect.Slice},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "embedded slice 2",
|
||||
tree: &Node{
|
||||
Name: "traefik",
|
||||
Children: []*Node{
|
||||
{Name: "Foo", Children: []*Node{
|
||||
{Name: "MySliceType", Value: "foo,fii"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
structure: struct {
|
||||
Foo struct {
|
||||
MySliceType
|
||||
}
|
||||
}{},
|
||||
expected: expected{node: &Node{
|
||||
Name: "traefik",
|
||||
Kind: reflect.Struct,
|
||||
Children: []*Node{
|
||||
{Name: "Foo", FieldName: "Foo", Kind: reflect.Struct, Children: []*Node{
|
||||
{Name: "MySliceType", FieldName: "MySliceType", Value: "foo,fii", Kind: reflect.Slice},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
@ -800,3 +1007,5 @@ func TestAddMetadata(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
type MySliceType []string
|
38
pkg/config/parser/parser.go
Normal file
38
pkg/config/parser/parser.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
// 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{}, filters ...string) error {
|
||||
node, err := DecodeToNode(labels, filters...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = AddMetadata(element, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = Fill(element, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode converts an element to labels.
|
||||
// element -> node (value) -> label (node)
|
||||
func Encode(element interface{}) (map[string]string, error) {
|
||||
node, err := EncodeToNode(element, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return EncodeNode(node), nil
|
||||
}
|
18
pkg/config/parser/tags.go
Normal file
18
pkg/config/parser/tags.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package parser
|
||||
|
||||
const (
|
||||
// TagLabel allows to apply a custom behavior.
|
||||
// - "allowEmpty": allows to create an empty struct.
|
||||
// - "-": ignore the field.
|
||||
TagLabel = "label"
|
||||
|
||||
// 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.
|
||||
TagDescription = "description"
|
||||
|
||||
// TagLabelAllowEmpty is related to TagLabel.
|
||||
TagLabelAllowEmpty = "allowEmpty"
|
||||
)
|
|
@ -1,30 +1,30 @@
|
|||
package static
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
)
|
||||
|
||||
// EntryPoint holds the entry point configuration.
|
||||
type EntryPoint struct {
|
||||
Address string
|
||||
Transport *EntryPointsTransport
|
||||
ProxyProtocol *ProxyProtocol
|
||||
ForwardedHeaders *ForwardedHeaders
|
||||
Address string `description:"Entry point address."`
|
||||
Transport *EntryPointsTransport `description:"Configures communication between clients and Traefik."`
|
||||
ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." label:"allowEmpty"`
|
||||
ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers."`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (e *EntryPoint) SetDefaults() {
|
||||
e.Transport = &EntryPointsTransport{}
|
||||
e.Transport.SetDefaults()
|
||||
e.ForwardedHeaders = &ForwardedHeaders{}
|
||||
}
|
||||
|
||||
// ForwardedHeaders Trust client forwarding headers.
|
||||
type ForwardedHeaders struct {
|
||||
Insecure bool
|
||||
TrustedIPs []string
|
||||
Insecure bool `description:"Trust all forwarded headers." export:"true"`
|
||||
TrustedIPs []string `description:"Trust only forwarded headers from selected IPs."`
|
||||
}
|
||||
|
||||
// ProxyProtocol contains Proxy-Protocol configuration.
|
||||
type ProxyProtocol struct {
|
||||
Insecure bool `export:"true"`
|
||||
TrustedIPs []string
|
||||
Insecure bool `description:"Trust all." export:"true"`
|
||||
TrustedIPs []string `description:"Trust only selected IPs."`
|
||||
}
|
||||
|
||||
// EntryPoints holds the HTTP entry point list.
|
||||
|
@ -32,103 +32,14 @@ type EntryPoints map[string]*EntryPoint
|
|||
|
||||
// EntryPointsTransport configures communication between clients and Traefik.
|
||||
type EntryPointsTransport struct {
|
||||
LifeCycle *LifeCycle `description:"Timeouts influencing the server life cycle" export:"true"`
|
||||
RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance" export:"true"`
|
||||
LifeCycle *LifeCycle `description:"Timeouts influencing the server life cycle." export:"true"`
|
||||
RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance." export:"true"`
|
||||
}
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (ep EntryPoints) String() string {
|
||||
return fmt.Sprintf("%+v", map[string]*EntryPoint(ep))
|
||||
}
|
||||
|
||||
// Get return the EntryPoints map.
|
||||
func (ep *EntryPoints) Get() interface{} {
|
||||
return *ep
|
||||
}
|
||||
|
||||
// SetValue sets the EntryPoints map with val.
|
||||
func (ep *EntryPoints) SetValue(val interface{}) {
|
||||
*ep = val.(EntryPoints)
|
||||
}
|
||||
|
||||
// Type is type of the struct.
|
||||
func (ep *EntryPoints) Type() string {
|
||||
return "entrypoints"
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (ep *EntryPoints) Set(value string) error {
|
||||
result := parseEntryPointsConfiguration(value)
|
||||
|
||||
(*ep)[result["name"]] = &EntryPoint{
|
||||
Address: result["address"],
|
||||
ProxyProtocol: makeEntryPointProxyProtocol(result),
|
||||
ForwardedHeaders: makeEntryPointForwardedHeaders(result),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeEntryPointProxyProtocol(result map[string]string) *ProxyProtocol {
|
||||
var proxyProtocol *ProxyProtocol
|
||||
|
||||
ppTrustedIPs := result["proxyprotocol_trustedips"]
|
||||
if len(result["proxyprotocol_insecure"]) > 0 || len(ppTrustedIPs) > 0 {
|
||||
proxyProtocol = &ProxyProtocol{
|
||||
Insecure: toBool(result, "proxyprotocol_insecure"),
|
||||
}
|
||||
if len(ppTrustedIPs) > 0 {
|
||||
proxyProtocol.TrustedIPs = strings.Split(ppTrustedIPs, ",")
|
||||
}
|
||||
}
|
||||
|
||||
if proxyProtocol != nil && proxyProtocol.Insecure {
|
||||
log.Warn("ProxyProtocol.insecure:true is dangerous. Please use 'ProxyProtocol.TrustedIPs:IPs' and remove 'ProxyProtocol.insecure:true'")
|
||||
}
|
||||
|
||||
return proxyProtocol
|
||||
}
|
||||
|
||||
func parseEntryPointsConfiguration(raw string) map[string]string {
|
||||
sections := strings.Fields(raw)
|
||||
|
||||
config := make(map[string]string)
|
||||
for _, part := range sections {
|
||||
field := strings.SplitN(part, ":", 2)
|
||||
name := strings.ToLower(strings.Replace(field[0], ".", "_", -1))
|
||||
if len(field) > 1 {
|
||||
config[name] = field[1]
|
||||
} else {
|
||||
if strings.EqualFold(name, "TLS") {
|
||||
config["tls_acme"] = "TLS"
|
||||
} else {
|
||||
config[name] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func toBool(conf map[string]string, key string) bool {
|
||||
if val, ok := conf[key]; ok {
|
||||
return strings.EqualFold(val, "true") ||
|
||||
strings.EqualFold(val, "enable") ||
|
||||
strings.EqualFold(val, "on")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func makeEntryPointForwardedHeaders(result map[string]string) *ForwardedHeaders {
|
||||
forwardedHeaders := &ForwardedHeaders{}
|
||||
forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure")
|
||||
|
||||
fhTrustedIPs := result["forwardedheaders_trustedips"]
|
||||
if len(fhTrustedIPs) > 0 {
|
||||
forwardedHeaders.TrustedIPs = strings.Split(fhTrustedIPs, ",")
|
||||
}
|
||||
|
||||
return forwardedHeaders
|
||||
// SetDefaults sets the default values.
|
||||
func (t *EntryPointsTransport) SetDefaults() {
|
||||
t.LifeCycle = &LifeCycle{}
|
||||
t.LifeCycle.SetDefaults()
|
||||
t.RespondingTimeouts = &RespondingTimeouts{}
|
||||
t.RespondingTimeouts.SetDefaults()
|
||||
}
|
||||
|
|
|
@ -1,257 +0,0 @@
|
|||
package static
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_parseEntryPointsConfiguration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value string
|
||||
expectedResult map[string]string
|
||||
}{
|
||||
{
|
||||
name: "all parameters",
|
||||
value: "Name:foo " +
|
||||
"Address::8000 " +
|
||||
"CA:car " +
|
||||
"CA.Optional:true " +
|
||||
"Redirect.EntryPoint:https " +
|
||||
"Redirect.Regex:http://localhost/(.*) " +
|
||||
"Redirect.Replacement:http://mydomain/$1 " +
|
||||
"Redirect.Permanent:true " +
|
||||
"Compress:true " +
|
||||
"ProxyProtocol.TrustedIPs:192.168.0.1 " +
|
||||
"ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24 " +
|
||||
"Auth.Basic.Realm:myRealm " +
|
||||
"Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0 " +
|
||||
"Auth.Basic.RemoveHeader:true " +
|
||||
"Auth.Digest.Users:test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e " +
|
||||
"Auth.Digest.RemoveHeader:true " +
|
||||
"Auth.HeaderField:X-WebAuth-User " +
|
||||
"Auth.Forward.Address:https://authserver.com/auth " +
|
||||
"Auth.Forward.AuthResponseHeaders:X-Auth,X-Test,X-Secret " +
|
||||
"Auth.Forward.TrustForwardHeader:true " +
|
||||
"Auth.Forward.TLS.CA:path/to/local.crt " +
|
||||
"Auth.Forward.TLS.CAOptional:true " +
|
||||
"Auth.Forward.TLS.Cert:path/to/foo.cert " +
|
||||
"Auth.Forward.TLS.Key:path/to/foo.key " +
|
||||
"Auth.Forward.TLS.InsecureSkipVerify:true " +
|
||||
"WhiteList.SourceRange:10.42.0.0/16,152.89.1.33/32,afed:be44::/16 " +
|
||||
"WhiteList.IPStrategy.depth:3 " +
|
||||
"WhiteList.IPStrategy.ExcludedIPs:10.0.0.3/24,20.0.0.3/24 " +
|
||||
"ClientIPStrategy.depth:3 " +
|
||||
"ClientIPStrategy.ExcludedIPs:10.0.0.3/24,20.0.0.3/24 ",
|
||||
expectedResult: map[string]string{
|
||||
"address": ":8000",
|
||||
"auth_basic_realm": "myRealm",
|
||||
"auth_basic_users": "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||
"auth_basic_removeheader": "true",
|
||||
"auth_digest_users": "test:traefik:a2688e031edb4be6a3797f3882655c05,test2:traefik:518845800f9e2bfb1f1f740ec24f074e",
|
||||
"auth_digest_removeheader": "true",
|
||||
"auth_forward_address": "https://authserver.com/auth",
|
||||
"auth_forward_authresponseheaders": "X-Auth,X-Test,X-Secret",
|
||||
"auth_forward_tls_ca": "path/to/local.crt",
|
||||
"auth_forward_tls_caoptional": "true",
|
||||
"auth_forward_tls_cert": "path/to/foo.cert",
|
||||
"auth_forward_tls_insecureskipverify": "true",
|
||||
"auth_forward_tls_key": "path/to/foo.key",
|
||||
"auth_forward_trustforwardheader": "true",
|
||||
"auth_headerfield": "X-WebAuth-User",
|
||||
"ca": "car",
|
||||
"ca_optional": "true",
|
||||
"compress": "true",
|
||||
"forwardedheaders_trustedips": "10.0.0.3/24,20.0.0.3/24",
|
||||
"name": "foo",
|
||||
"proxyprotocol_trustedips": "192.168.0.1",
|
||||
"redirect_entrypoint": "https",
|
||||
"redirect_permanent": "true",
|
||||
"redirect_regex": "http://localhost/(.*)",
|
||||
"redirect_replacement": "http://mydomain/$1",
|
||||
"whitelist_sourcerange": "10.42.0.0/16,152.89.1.33/32,afed:be44::/16",
|
||||
"whitelist_ipstrategy_depth": "3",
|
||||
"whitelist_ipstrategy_excludedips": "10.0.0.3/24,20.0.0.3/24",
|
||||
"clientipstrategy_depth": "3",
|
||||
"clientipstrategy_excludedips": "10.0.0.3/24,20.0.0.3/24",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "compress on",
|
||||
value: "name:foo Compress:on",
|
||||
expectedResult: map[string]string{
|
||||
"name": "foo",
|
||||
"compress": "on",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conf := parseEntryPointsConfiguration(test.value)
|
||||
|
||||
assert.Len(t, conf, len(test.expectedResult))
|
||||
assert.Equal(t, test.expectedResult, conf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toBool(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value string
|
||||
key string
|
||||
expectedBool bool
|
||||
}{
|
||||
{
|
||||
name: "on",
|
||||
value: "on",
|
||||
key: "foo",
|
||||
expectedBool: true,
|
||||
},
|
||||
{
|
||||
name: "true",
|
||||
value: "true",
|
||||
key: "foo",
|
||||
expectedBool: true,
|
||||
},
|
||||
{
|
||||
name: "enable",
|
||||
value: "enable",
|
||||
key: "foo",
|
||||
expectedBool: true,
|
||||
},
|
||||
{
|
||||
name: "arbitrary string",
|
||||
value: "bar",
|
||||
key: "foo",
|
||||
expectedBool: false,
|
||||
},
|
||||
{
|
||||
name: "no existing entry",
|
||||
value: "bar",
|
||||
key: "fii",
|
||||
expectedBool: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conf := map[string]string{
|
||||
"foo": test.value,
|
||||
}
|
||||
|
||||
result := toBool(conf, test.key)
|
||||
|
||||
assert.Equal(t, test.expectedBool, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntryPoints_Set(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
expression string
|
||||
expectedEntryPointName string
|
||||
expectedEntryPoint *EntryPoint
|
||||
}{
|
||||
{
|
||||
name: "all parameters camelcase",
|
||||
expression: "Name:foo " +
|
||||
"Address::8000 " +
|
||||
"CA:car " +
|
||||
"CA.Optional:true " +
|
||||
"ProxyProtocol.TrustedIPs:192.168.0.1 ",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Address: ":8000",
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
Insecure: false,
|
||||
TrustedIPs: []string{"192.168.0.1"},
|
||||
},
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
// FIXME Test ServersTransport
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all parameters lowercase",
|
||||
expression: "Name:foo " +
|
||||
"address::8000 " +
|
||||
"tls " +
|
||||
"tls.minversion:VersionTLS11 " +
|
||||
"tls.ciphersuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA " +
|
||||
"ca:car " +
|
||||
"ca.Optional:true " +
|
||||
"proxyProtocol.TrustedIPs:192.168.0.1 ",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
Address: ":8000",
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
Insecure: false,
|
||||
TrustedIPs: []string{"192.168.0.1"},
|
||||
},
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
// FIXME Test ServersTransport
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default",
|
||||
expression: "Name:foo",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ProxyProtocol insecure true",
|
||||
expression: "Name:foo ProxyProtocol.insecure:true",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ProxyProtocol: &ProxyProtocol{Insecure: true},
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ProxyProtocol insecure false",
|
||||
expression: "Name:foo ProxyProtocol.insecure:false",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ProxyProtocol: &ProxyProtocol{},
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ProxyProtocol TrustedIPs",
|
||||
expression: "Name:foo ProxyProtocol.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||
expectedEntryPointName: "foo",
|
||||
expectedEntryPoint: &EntryPoint{
|
||||
ProxyProtocol: &ProxyProtocol{
|
||||
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||
},
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
eps := EntryPoints{}
|
||||
err := eps.Set(test.expression)
|
||||
require.NoError(t, err)
|
||||
|
||||
ep := eps[test.expectedEntryPointName]
|
||||
assert.EqualValues(t, test.expectedEntryPoint, ep)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/ping"
|
||||
acmeprovider "github.com/containous/traefik/pkg/provider/acme"
|
||||
|
@ -47,99 +46,127 @@ const (
|
|||
type Configuration struct {
|
||||
Global *Global `description:"Global configuration options" export:"true"`
|
||||
|
||||
ServersTransport *ServersTransport `description:"Servers default transport" export:"true"`
|
||||
EntryPoints EntryPoints `description:"Entry points definition using format: --entryPoints='Name:http Address::8000' --entryPoints='Name:https Address::4442'" export:"true"`
|
||||
Providers *Providers `description:"Providers configuration" export:"true"`
|
||||
ServersTransport *ServersTransport `description:"Servers default transport." export:"true"`
|
||||
EntryPoints EntryPoints `description:"Entry points definition." export:"true"`
|
||||
Providers *Providers `description:"Providers configuration." export:"true"`
|
||||
|
||||
API *API `description:"Enable api/dashboard" export:"true"`
|
||||
Metrics *types.Metrics `description:"Enable a metrics exporter" export:"true"`
|
||||
Ping *ping.Handler `description:"Enable ping" export:"true"`
|
||||
API *API `description:"Enable api/dashboard." export:"true" label:"allowEmpty"`
|
||||
Metrics *types.Metrics `description:"Enable a metrics exporter." export:"true"`
|
||||
Ping *ping.Handler `description:"Enable ping." export:"true" label:"allowEmpty"`
|
||||
// Rest *rest.Provider `description:"Enable Rest backend with default settings" export:"true"`
|
||||
|
||||
Log *types.TraefikLog `description:"Traefik log settings" export:"true"`
|
||||
AccessLog *types.AccessLog `description:"Access log settings" export:"true"`
|
||||
Tracing *Tracing `description:"OpenTracing configuration" export:"true"`
|
||||
Log *types.TraefikLog `description:"Traefik log settings." export:"true"`
|
||||
AccessLog *types.AccessLog `description:"Access log settings." export:"true" label:"allowEmpty"`
|
||||
Tracing *Tracing `description:"OpenTracing configuration." export:"true" label:"allowEmpty"`
|
||||
|
||||
HostResolver *types.HostResolverConfig `description:"Enable CNAME Flattening" export:"true"`
|
||||
HostResolver *types.HostResolverConfig `description:"Enable CNAME Flattening." export:"true" label:"allowEmpty"`
|
||||
|
||||
ACME *acmeprovider.Configuration `description:"Enable ACME (Let's Encrypt): automatic SSL" export:"true"`
|
||||
ACME *acmeprovider.Configuration `description:"Enable ACME (Let's Encrypt): automatic SSL." export:"true"`
|
||||
}
|
||||
|
||||
// Global holds the global configuration.
|
||||
type Global struct {
|
||||
Debug bool `short:"d" description:"Enable debug mode" export:"true"`
|
||||
CheckNewVersion bool `description:"Periodically check if a new version has been released" export:"true"`
|
||||
SendAnonymousUsage *bool `description:"send periodically anonymous usage statistics" export:"true"`
|
||||
Debug bool `description:"Enable debug mode." export:"true"`
|
||||
CheckNewVersion bool `description:"Periodically check if a new version has been released." export:"true"`
|
||||
SendAnonymousUsage *bool `description:"Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default." export:"true"`
|
||||
}
|
||||
|
||||
// ServersTransport options to configure communication between Traefik and the servers
|
||||
type ServersTransport struct {
|
||||
InsecureSkipVerify bool `description:"Disable SSL certificate verification" export:"true"`
|
||||
RootCAs tls.FilesOrContents `description:"Add cert file for self-signed certificate"`
|
||||
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" export:"true"`
|
||||
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers" export:"true"`
|
||||
InsecureSkipVerify bool `description:"Disable SSL certificate verification." export:"true"`
|
||||
RootCAs []tls.FileOrContent `description:"Add cert file for self-signed certificate."`
|
||||
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" export:"true"`
|
||||
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers." export:"true"`
|
||||
}
|
||||
|
||||
// API holds the API configuration
|
||||
type API struct {
|
||||
EntryPoint string `description:"EntryPoint" export:"true"`
|
||||
Dashboard bool `description:"Activate dashboard" export:"true"`
|
||||
Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"`
|
||||
Middlewares []string `description:"Middleware list" export:"true"`
|
||||
DashboardAssets *assetfs.AssetFS `json:"-"`
|
||||
EntryPoint string `description:"EntryPoint." export:"true"`
|
||||
Dashboard bool `description:"Activate dashboard." export:"true"`
|
||||
Statistics *types.Statistics `description:"Enable more detailed statistics." export:"true" label:"allowEmpty"`
|
||||
Middlewares []string `description:"Middleware list." export:"true"`
|
||||
DashboardAssets *assetfs.AssetFS `json:"-" label:"-"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (a *API) SetDefaults() {
|
||||
a.EntryPoint = "traefik"
|
||||
a.Dashboard = true
|
||||
}
|
||||
|
||||
// RespondingTimeouts contains timeout configurations for incoming requests to the Traefik instance.
|
||||
type RespondingTimeouts struct {
|
||||
ReadTimeout parse.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set" export:"true"`
|
||||
WriteTimeout parse.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set" export:"true"`
|
||||
IdleTimeout parse.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. Defaults to 180 seconds. If zero, no timeout is set" export:"true"`
|
||||
ReadTimeout types.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." export:"true"`
|
||||
WriteTimeout types.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." 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." export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (a *RespondingTimeouts) SetDefaults() {
|
||||
a.IdleTimeout = types.Duration(DefaultIdleTimeout)
|
||||
}
|
||||
|
||||
// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers.
|
||||
type ForwardingTimeouts struct {
|
||||
DialTimeout parse.Duration `description:"The amount of time to wait until a connection to a backend server can be established. Defaults to 30 seconds. If zero, no timeout exists" export:"true"`
|
||||
ResponseHeaderTimeout parse.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" export:"true"`
|
||||
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." 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." export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (f *ForwardingTimeouts) SetDefaults() {
|
||||
f.DialTimeout = types.Duration(30 * time.Second)
|
||||
}
|
||||
|
||||
// LifeCycle contains configurations relevant to the lifecycle (such as the shutdown phase) of Traefik.
|
||||
type LifeCycle struct {
|
||||
RequestAcceptGraceTimeout parse.Duration `description:"Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure"`
|
||||
GraceTimeOut parse.Duration `description:"Duration to give active requests a chance to finish before Traefik stops"`
|
||||
RequestAcceptGraceTimeout types.Duration `description:"Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure."`
|
||||
GraceTimeOut types.Duration `description:"Duration to give active requests a chance to finish before Traefik stops."`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (a *LifeCycle) SetDefaults() {
|
||||
a.GraceTimeOut = types.Duration(DefaultGraceTimeout)
|
||||
}
|
||||
|
||||
// Tracing holds the tracing configuration.
|
||||
type Tracing struct {
|
||||
Backend string `description:"Selects the tracking backend ('jaeger','zipkin','datadog','instana')." export:"true"`
|
||||
ServiceName string `description:"Set the name for this service" export:"true"`
|
||||
SpanNameLimit int `description:"Set the maximum character limit for Span names (default 0 = no limit)" export:"true"`
|
||||
Jaeger *jaeger.Config `description:"Settings for jaeger"`
|
||||
Zipkin *zipkin.Config `description:"Settings for zipkin"`
|
||||
DataDog *datadog.Config `description:"Settings for DataDog"`
|
||||
Instana *instana.Config `description:"Settings for Instana"`
|
||||
Haystack *haystack.Config `description:"Settings for Haystack"`
|
||||
ServiceName string `description:"Set the name for this service." export:"true"`
|
||||
SpanNameLimit int `description:"Set the maximum character limit for Span names (default 0 = no limit)." export:"true"`
|
||||
Jaeger *jaeger.Config `description:"Settings for jaeger." label:"allowEmpty"`
|
||||
Zipkin *zipkin.Config `description:"Settings for zipkin." label:"allowEmpty"`
|
||||
DataDog *datadog.Config `description:"Settings for DataDog." label:"allowEmpty"`
|
||||
Instana *instana.Config `description:"Settings for Instana." label:"allowEmpty"`
|
||||
Haystack *haystack.Config `description:"Settings for Haystack." label:"allowEmpty"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (t *Tracing) SetDefaults() {
|
||||
t.Backend = "jaeger"
|
||||
t.ServiceName = "traefik"
|
||||
t.SpanNameLimit = 0
|
||||
}
|
||||
|
||||
// Providers contains providers configuration
|
||||
type Providers struct {
|
||||
ProvidersThrottleDuration parse.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." export:"true"`
|
||||
Docker *docker.Provider `description:"Enable Docker backend with default settings" export:"true"`
|
||||
File *file.Provider `description:"Enable File backend with default settings" export:"true"`
|
||||
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings" export:"true"`
|
||||
Kubernetes *ingress.Provider `description:"Enable Kubernetes backend with default settings" export:"true"`
|
||||
KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings" export:"true"`
|
||||
Rest *rest.Provider `description:"Enable Rest backend with default settings" export:"true"`
|
||||
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings" export:"true"`
|
||||
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." export:"true"`
|
||||
Docker *docker.Provider `description:"Enable Docker backend with default settings." export:"true" label:"allowEmpty"`
|
||||
File *file.Provider `description:"Enable File backend with default settings." export:"true" label:"allowEmpty"`
|
||||
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings." export:"true" label:"allowEmpty"`
|
||||
Kubernetes *ingress.Provider `description:"Enable Kubernetes backend with default settings." export:"true" label:"allowEmpty"`
|
||||
KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." export:"true" label:"allowEmpty"`
|
||||
Rest *rest.Provider `description:"Enable Rest backend with default settings." export:"true" label:"allowEmpty"`
|
||||
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings." export:"true" label:"allowEmpty"`
|
||||
}
|
||||
|
||||
// SetEffectiveConfiguration adds missing configuration parameters derived from existing ones.
|
||||
// It also takes care of maintaining backwards compatibility.
|
||||
func (c *Configuration) SetEffectiveConfiguration(configFile string) {
|
||||
if len(c.EntryPoints) == 0 {
|
||||
ep := &EntryPoint{Address: ":80"}
|
||||
ep.SetDefaults()
|
||||
c.EntryPoints = EntryPoints{
|
||||
"http": &EntryPoint{
|
||||
Address: ":80",
|
||||
},
|
||||
"http": ep,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,33 +175,15 @@ func (c *Configuration) SetEffectiveConfiguration(configFile string) {
|
|||
(c.Metrics != nil && c.Metrics.Prometheus != nil && c.Metrics.Prometheus.EntryPoint == DefaultInternalEntryPointName) ||
|
||||
(c.Providers.Rest != nil && c.Providers.Rest.EntryPoint == DefaultInternalEntryPointName) {
|
||||
if _, ok := c.EntryPoints[DefaultInternalEntryPointName]; !ok {
|
||||
c.EntryPoints[DefaultInternalEntryPointName] = &EntryPoint{Address: ":8080"}
|
||||
}
|
||||
}
|
||||
|
||||
for _, entryPoint := range c.EntryPoints {
|
||||
if entryPoint.Transport == nil {
|
||||
entryPoint.Transport = &EntryPointsTransport{}
|
||||
}
|
||||
|
||||
// Make sure LifeCycle isn't nil to spare nil checks elsewhere.
|
||||
if entryPoint.Transport.LifeCycle == nil {
|
||||
entryPoint.Transport.LifeCycle = &LifeCycle{
|
||||
GraceTimeOut: parse.Duration(DefaultGraceTimeout),
|
||||
}
|
||||
entryPoint.Transport.RespondingTimeouts = &RespondingTimeouts{
|
||||
IdleTimeout: parse.Duration(DefaultIdleTimeout),
|
||||
}
|
||||
}
|
||||
|
||||
if entryPoint.ForwardedHeaders == nil {
|
||||
entryPoint.ForwardedHeaders = &ForwardedHeaders{}
|
||||
ep := &EntryPoint{Address: ":8080"}
|
||||
ep.SetDefaults()
|
||||
c.EntryPoints[DefaultInternalEntryPointName] = ep
|
||||
}
|
||||
}
|
||||
|
||||
if c.Providers.Docker != nil {
|
||||
if c.Providers.Docker.SwarmModeRefreshSeconds <= 0 {
|
||||
c.Providers.Docker.SwarmModeRefreshSeconds = 15
|
||||
c.Providers.Docker.SwarmModeRefreshSeconds = types.Duration(15 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -64,13 +64,8 @@ func initDatadogClient(ctx context.Context, config *types.Datadog) *time.Ticker
|
|||
if len(address) == 0 {
|
||||
address = "localhost:8125"
|
||||
}
|
||||
pushInterval, err := time.ParseDuration(config.PushInterval)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Warnf("Unable to parse %s from config.PushInterval: using 10s as the default value", config.PushInterval)
|
||||
pushInterval = 10 * time.Second
|
||||
}
|
||||
|
||||
report := time.NewTicker(pushInterval)
|
||||
report := time.NewTicker(time.Duration(config.PushInterval))
|
||||
|
||||
safe.Go(func() {
|
||||
datadogClient.SendLoop(report.C, "udp", address)
|
||||
|
|
|
@ -16,7 +16,7 @@ func TestDatadog(t *testing.T) {
|
|||
// This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond
|
||||
udp.Timeout = 5 * time.Second
|
||||
|
||||
datadogRegistry := RegisterDatadog(context.Background(), &types.Datadog{Address: ":18125", PushInterval: "1s"})
|
||||
datadogRegistry := RegisterDatadog(context.Background(), &types.Datadog{Address: ":18125", PushInterval: types.Duration(time.Second)})
|
||||
defer StopDatadog()
|
||||
|
||||
if !datadogRegistry.IsEnabled() {
|
||||
|
|
|
@ -51,7 +51,7 @@ func RegisterInfluxDB(ctx context.Context, config *types.InfluxDB) Registry {
|
|||
influxDBClient = initInfluxDBClient(ctx, config)
|
||||
}
|
||||
if influxDBTicker == nil {
|
||||
influxDBTicker = initInfluxDBTicker(ctx, config)
|
||||
influxDBTicker = initInfluxDBTicker(config)
|
||||
}
|
||||
|
||||
return &standardRegistry{
|
||||
|
@ -115,14 +115,8 @@ func initInfluxDBClient(ctx context.Context, config *types.InfluxDB) *influx.Inf
|
|||
}
|
||||
|
||||
// initInfluxDBTicker initializes metrics pusher
|
||||
func initInfluxDBTicker(ctx context.Context, config *types.InfluxDB) *time.Ticker {
|
||||
pushInterval, err := time.ParseDuration(config.PushInterval)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Warnf("Unable to parse %s from config.PushInterval: using 10s as the default value", config.PushInterval)
|
||||
pushInterval = 10 * time.Second
|
||||
}
|
||||
|
||||
report := time.NewTicker(pushInterval)
|
||||
func initInfluxDBTicker(config *types.InfluxDB) *time.Ticker {
|
||||
report := time.NewTicker(time.Duration(config.PushInterval))
|
||||
|
||||
safe.Go(func() {
|
||||
var buf bytes.Buffer
|
||||
|
|
|
@ -20,7 +20,7 @@ func TestInfluxDB(t *testing.T) {
|
|||
// This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond
|
||||
udp.Timeout = 5 * time.Second
|
||||
|
||||
influxDBRegistry := RegisterInfluxDB(context.Background(), &types.InfluxDB{Address: ":8089", PushInterval: "1s"})
|
||||
influxDBRegistry := RegisterInfluxDB(context.Background(), &types.InfluxDB{Address: ":8089", PushInterval: types.Duration(time.Second)})
|
||||
defer StopInfluxDB()
|
||||
|
||||
if !influxDBRegistry.IsEnabled() {
|
||||
|
@ -80,7 +80,7 @@ func TestInfluxDBHTTP(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
influxDBRegistry := RegisterInfluxDB(context.Background(), &types.InfluxDB{Address: ts.URL, Protocol: "http", PushInterval: "1s", Database: "test", RetentionPolicy: "autogen"})
|
||||
influxDBRegistry := RegisterInfluxDB(context.Background(), &types.InfluxDB{Address: ts.URL, Protocol: "http", PushInterval: types.Duration(time.Second), Database: "test", RetentionPolicy: "autogen"})
|
||||
defer StopInfluxDB()
|
||||
|
||||
if !influxDBRegistry.IsEnabled() {
|
||||
|
|
|
@ -62,13 +62,8 @@ func initStatsdTicker(ctx context.Context, config *types.Statsd) *time.Ticker {
|
|||
if len(address) == 0 {
|
||||
address = "localhost:8125"
|
||||
}
|
||||
pushInterval, err := time.ParseDuration(config.PushInterval)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Warnf("Unable to parse %s from config.PushInterval: using 10s as the default value", config.PushInterval)
|
||||
pushInterval = 10 * time.Second
|
||||
}
|
||||
|
||||
report := time.NewTicker(pushInterval)
|
||||
report := time.NewTicker(time.Duration(config.PushInterval))
|
||||
|
||||
safe.Go(func() {
|
||||
statsdClient.SendLoop(report.C, "udp", address)
|
||||
|
|
|
@ -15,7 +15,7 @@ func TestStatsD(t *testing.T) {
|
|||
// This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond
|
||||
udp.Timeout = 5 * time.Second
|
||||
|
||||
statsdRegistry := RegisterStatsd(context.Background(), &types.Statsd{Address: ":18125", PushInterval: "1s"})
|
||||
statsdRegistry := RegisterStatsd(context.Background(), &types.Statsd{Address: ":18125", PushInterval: types.Duration(time.Second)})
|
||||
defer StopStatsd()
|
||||
|
||||
if !statsdRegistry.IsEnabled() {
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -330,7 +329,7 @@ func (h *Handler) keepAccessLog(statusCode, retryAttempts int, duration time.Dur
|
|||
return true
|
||||
}
|
||||
|
||||
if h.config.Filters.MinDuration > 0 && (parse.Duration(duration) > h.config.Filters.MinDuration) {
|
||||
if h.config.Filters.MinDuration > 0 && (types.Duration(duration) > h.config.Filters.MinDuration) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -287,12 +286,12 @@ func TestLoggerJSON(t *testing.T) {
|
|||
Format: JSONFormat,
|
||||
Fields: &types.AccessLogFields{
|
||||
DefaultMode: "drop",
|
||||
Names: types.FieldNames{
|
||||
Names: map[string]string{
|
||||
RequestHost: "keep",
|
||||
},
|
||||
Headers: &types.FieldHeaders{
|
||||
DefaultMode: "drop",
|
||||
Names: types.FieldHeaderNames{
|
||||
Names: map[string]string{
|
||||
"Referer": "keep",
|
||||
},
|
||||
},
|
||||
|
@ -388,7 +387,7 @@ func TestNewLogHandlerOutputStdout(t *testing.T) {
|
|||
FilePath: "",
|
||||
Format: CommonFormat,
|
||||
Filters: &types.AccessLogFilters{
|
||||
MinDuration: parse.Duration(1 * time.Hour),
|
||||
MinDuration: types.Duration(1 * time.Hour),
|
||||
},
|
||||
},
|
||||
expectedLog: ``,
|
||||
|
@ -399,7 +398,7 @@ func TestNewLogHandlerOutputStdout(t *testing.T) {
|
|||
FilePath: "",
|
||||
Format: CommonFormat,
|
||||
Filters: &types.AccessLogFilters{
|
||||
MinDuration: parse.Duration(1 * time.Millisecond),
|
||||
MinDuration: types.Duration(1 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
expectedLog: `TestHost - TestUser [13/Apr/2016:07:14:19 -0700] "POST testpath HTTP/0.0" 123 12 "testReferer" "testUserAgent" 23 "testRouter" "http://127.0.0.1/testService" 1ms`,
|
||||
|
@ -433,7 +432,7 @@ func TestNewLogHandlerOutputStdout(t *testing.T) {
|
|||
Format: CommonFormat,
|
||||
Fields: &types.AccessLogFields{
|
||||
DefaultMode: "keep",
|
||||
Names: types.FieldNames{
|
||||
Names: map[string]string{
|
||||
ClientHost: "drop",
|
||||
},
|
||||
},
|
||||
|
@ -458,7 +457,7 @@ func TestNewLogHandlerOutputStdout(t *testing.T) {
|
|||
Format: CommonFormat,
|
||||
Fields: &types.AccessLogFields{
|
||||
DefaultMode: "drop",
|
||||
Names: types.FieldNames{
|
||||
Names: map[string]string{
|
||||
ClientHost: "drop",
|
||||
ClientUsername: "keep",
|
||||
},
|
||||
|
@ -473,7 +472,7 @@ func TestNewLogHandlerOutputStdout(t *testing.T) {
|
|||
Format: CommonFormat,
|
||||
Fields: &types.AccessLogFields{
|
||||
DefaultMode: "drop",
|
||||
Names: types.FieldNames{
|
||||
Names: map[string]string{
|
||||
ClientHost: "drop",
|
||||
ClientUsername: "keep",
|
||||
},
|
||||
|
@ -491,7 +490,7 @@ func TestNewLogHandlerOutputStdout(t *testing.T) {
|
|||
Format: CommonFormat,
|
||||
Fields: &types.AccessLogFields{
|
||||
DefaultMode: "drop",
|
||||
Names: types.FieldNames{
|
||||
Names: map[string]string{
|
||||
ClientHost: "drop",
|
||||
ClientUsername: "keep",
|
||||
},
|
||||
|
@ -509,13 +508,13 @@ func TestNewLogHandlerOutputStdout(t *testing.T) {
|
|||
Format: CommonFormat,
|
||||
Fields: &types.AccessLogFields{
|
||||
DefaultMode: "drop",
|
||||
Names: types.FieldNames{
|
||||
Names: map[string]string{
|
||||
ClientHost: "drop",
|
||||
ClientUsername: "keep",
|
||||
},
|
||||
Headers: &types.FieldHeaders{
|
||||
DefaultMode: "keep",
|
||||
Names: types.FieldHeaderNames{
|
||||
Names: map[string]string{
|
||||
"Referer": "redact",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -10,11 +10,16 @@ import (
|
|||
|
||||
// Handler expose ping routes.
|
||||
type Handler struct {
|
||||
EntryPoint string `description:"Ping entryPoint" export:"true"`
|
||||
Middlewares []string `description:"Middleware list" export:"true"`
|
||||
EntryPoint string `description:"Ping entryPoint." export:"true"`
|
||||
Middlewares []string `description:"Middleware list." export:"true"`
|
||||
terminating bool
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (h *Handler) SetDefaults() {
|
||||
h.EntryPoint = "traefik"
|
||||
}
|
||||
|
||||
// WithContext causes the ping endpoint to serve non 200 responses.
|
||||
func (h *Handler) WithContext(ctx context.Context) {
|
||||
go func() {
|
||||
|
|
|
@ -14,7 +14,6 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/rules"
|
||||
|
@ -39,17 +38,24 @@ var (
|
|||
|
||||
// Configuration holds ACME configuration provided by users
|
||||
type Configuration struct {
|
||||
Email string `description:"Email address used for registration"`
|
||||
Email string `description:"Email address used for registration."`
|
||||
ACMELogging bool `description:"Enable debug logging of ACME actions."`
|
||||
CAServer string `description:"CA server to use."`
|
||||
Storage string `description:"Storage to use."`
|
||||
EntryPoint string `description:"EntryPoint to use."`
|
||||
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. Default to 'RSA4096'"`
|
||||
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
|
||||
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge"`
|
||||
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge"`
|
||||
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge"`
|
||||
Domains []types.Domain `description:"CN and SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='*.main.net'. Wildcard domains only accepted with DNSChallenge"`
|
||||
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'."`
|
||||
OnHostRule bool `description:"Enable certificate generation on router Host rules."`
|
||||
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." label:"allowEmpty"`
|
||||
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." label:"allowEmpty"`
|
||||
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." label:"allowEmpty"`
|
||||
Domains []types.Domain `description:"The list of domains for which certificates are generated on startup. Wildcard domains only accepted with DNSChallenge."`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (a *Configuration) SetDefaults() {
|
||||
a.CAServer = lego.LEDirectoryProduction
|
||||
a.Storage = "acme.json"
|
||||
a.KeyType = "RSA4096"
|
||||
}
|
||||
|
||||
// Certificate is a struct which contains all data needed from an ACME certificate
|
||||
|
@ -61,10 +67,10 @@ type Certificate struct {
|
|||
|
||||
// DNSChallenge contains DNS challenge Configuration
|
||||
type DNSChallenge struct {
|
||||
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."`
|
||||
DelayBeforeCheck parse.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
|
||||
Resolvers types.DNSResolvers `description:"Use following DNS servers to resolve the FQDN authority."`
|
||||
DisablePropagationCheck bool `description:"Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended]"`
|
||||
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."`
|
||||
DelayBeforeCheck types.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
|
||||
Resolvers []string `description:"Use following DNS servers to resolve the FQDN authority."`
|
||||
DisablePropagationCheck bool `description:"Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended]"`
|
||||
}
|
||||
|
||||
// HTTPChallenge contains HTTP challenge Configuration
|
||||
|
@ -239,7 +245,7 @@ func (p *Provider) getClient() (*lego.Client, error) {
|
|||
|
||||
logger.Debug("Building ACME client...")
|
||||
|
||||
caServer := "https://acme-v02.api.letsencrypt.org/directory"
|
||||
caServer := lego.LEDirectoryProduction
|
||||
if len(p.CAServer) > 0 {
|
||||
caServer = p.CAServer
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import "github.com/containous/traefik/pkg/types"
|
|||
|
||||
// Constrainer Filter services by constraint, matching with Traefik tags.
|
||||
type Constrainer struct {
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with Traefik tags." export:"true"`
|
||||
Constraints []*types.Constraint `description:"Filter services by constraint, matching with Traefik tags." export:"true"`
|
||||
}
|
||||
|
||||
// MatchConstraints must match with EVERY single constraint
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/config/label"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/provider"
|
||||
"github.com/containous/traefik/pkg/provider/label"
|
||||
"github.com/docker/go-connections/nat"
|
||||
)
|
||||
|
||||
|
|
|
@ -339,7 +339,7 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
testCases := []struct {
|
||||
desc string
|
||||
containers []dockerData
|
||||
constraints types.Constraints
|
||||
constraints []*types.Constraint
|
||||
expected *config.Configuration
|
||||
}{
|
||||
{
|
||||
|
@ -1924,11 +1924,11 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
constraints: types.Constraints{
|
||||
&types.Constraint{
|
||||
constraints: []*types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "bar",
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
expected: &config.Configuration{
|
||||
|
@ -1965,11 +1965,11 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
constraints: types.Constraints{
|
||||
&types.Constraint{
|
||||
constraints: []*types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "foo",
|
||||
Value: "foo",
|
||||
},
|
||||
},
|
||||
expected: &config.Configuration{
|
||||
|
|
|
@ -45,19 +45,29 @@ var _ provider.Provider = (*Provider)(nil)
|
|||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
provider.Constrainer `mapstructure:",squash" export:"true"`
|
||||
Watch bool `description:"Watch provider" export:"true"`
|
||||
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
|
||||
DefaultRule string `description:"Default rule"`
|
||||
TLS *types.ClientTLS `description:"Enable Docker TLS support" export:"true"`
|
||||
ExposedByDefault bool `description:"Expose containers by default" export:"true"`
|
||||
UseBindPortIP bool `description:"Use the ip address from the bound port, rather than from the inner network" export:"true"`
|
||||
SwarmMode bool `description:"Use Docker on Swarm Mode" export:"true"`
|
||||
Network string `description:"Default Docker network used" export:"true"`
|
||||
SwarmModeRefreshSeconds int `description:"Polling interval for swarm mode (in seconds)" export:"true"`
|
||||
provider.Constrainer `description:"List of constraints used to filter out some containers." export:"true"`
|
||||
Watch bool `description:"Watch provider." export:"true"`
|
||||
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint."`
|
||||
DefaultRule string `description:"Default rule."`
|
||||
TLS *types.ClientTLS `description:"Enable Docker TLS support." export:"true"`
|
||||
ExposedByDefault bool `description:"Expose containers by default." export:"true"`
|
||||
UseBindPortIP bool `description:"Use the ip address from the bound port, rather than from the inner network." export:"true"`
|
||||
SwarmMode bool `description:"Use Docker on Swarm Mode." export:"true"`
|
||||
Network string `description:"Default Docker network used." export:"true"`
|
||||
SwarmModeRefreshSeconds types.Duration `description:"Polling interval for swarm mode." export:"true"`
|
||||
defaultRuleTpl *template.Template
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
p.Watch = true
|
||||
p.ExposedByDefault = true
|
||||
p.Endpoint = "unix:///var/run/docker.sock"
|
||||
p.SwarmMode = false
|
||||
p.SwarmModeRefreshSeconds = types.Duration(15 * time.Second)
|
||||
p.DefaultRule = DefaultTemplateRule
|
||||
}
|
||||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil)
|
||||
|
@ -184,7 +194,7 @@ func (p *Provider) Provide(configurationChan chan<- config.Message, pool *safe.P
|
|||
if p.SwarmMode {
|
||||
errChan := make(chan error)
|
||||
// TODO: This need to be change. Linked to Swarm events docker/docker#23827
|
||||
ticker := time.NewTicker(time.Second * time.Duration(p.SwarmModeRefreshSeconds))
|
||||
ticker := time.NewTicker(time.Duration(p.SwarmModeRefreshSeconds))
|
||||
pool.GoCtx(func(ctx context.Context) {
|
||||
|
||||
ctx = log.With(ctx, log.Str(log.ProviderName, "docker"))
|
||||
|
|
|
@ -3,7 +3,7 @@ package docker
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containous/traefik/pkg/provider/label"
|
||||
"github.com/containous/traefik/pkg/config/label"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -28,11 +28,17 @@ var _ provider.Provider = (*Provider)(nil)
|
|||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
Directory string `description:"Load configuration from one or more .toml files in a directory" export:"true"`
|
||||
Watch bool `description:"Watch provider" export:"true"`
|
||||
Directory string `description:"Load configuration from one or more .toml files in a directory." export:"true"`
|
||||
Watch bool `description:"Watch provider." export:"true"`
|
||||
Filename string `description:"Override default configuration template. For advanced users :)" export:"true"`
|
||||
DebugLogGeneratedTemplate bool `description:"Enable debug logging of generated configuration template." export:"true"`
|
||||
TraefikFile string
|
||||
TraefikFile string `description:"-"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
p.Watch = true
|
||||
p.Filename = ""
|
||||
}
|
||||
|
||||
// Init the provider
|
||||
|
|
|
@ -224,7 +224,7 @@ func createProvider(t *testing.T, test ProvideTestCase, watch bool) (*Provider,
|
|||
tempDir := createTempDir(t, "testdir")
|
||||
|
||||
provider := &Provider{}
|
||||
provider.Watch = watch
|
||||
provider.Watch = true
|
||||
|
||||
if len(test.directoryContent) > 0 {
|
||||
if !watch {
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/clientset/versioned"
|
||||
"github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/informers/externalversions"
|
||||
"github.com/containous/traefik/pkg/provider/kubernetes/crd/traefik/v1alpha1"
|
||||
"github.com/containous/traefik/pkg/provider/kubernetes/k8s"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
kubeerror "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
@ -45,7 +44,7 @@ func (reh *resourceEventHandler) OnDelete(obj interface{}) {
|
|||
// WatchAll starts the watch of the Provider resources and updates the stores.
|
||||
// The stores can then be accessed via the Get* functions.
|
||||
type Client interface {
|
||||
WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error)
|
||||
WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error)
|
||||
|
||||
GetIngressRoutes() []*v1alpha1.IngressRoute
|
||||
GetIngressRouteTCPs() []*v1alpha1.IngressRouteTCP
|
||||
|
@ -69,7 +68,7 @@ type clientWrapper struct {
|
|||
labelSelector labels.Selector
|
||||
|
||||
isNamespaceAll bool
|
||||
watchedNamespaces k8s.Namespaces
|
||||
watchedNamespaces []string
|
||||
}
|
||||
|
||||
func createClientFromConfig(c *rest.Config) (*clientWrapper, error) {
|
||||
|
@ -144,12 +143,12 @@ func newExternalClusterClient(endpoint, token, caFilePath string) (*clientWrappe
|
|||
}
|
||||
|
||||
// WatchAll starts namespace-specific controllers for all relevant kinds.
|
||||
func (c *clientWrapper) WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||
func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||
eventCh := make(chan interface{}, 1)
|
||||
eventHandler := c.newResourceEventHandler(eventCh)
|
||||
|
||||
if len(namespaces) == 0 {
|
||||
namespaces = k8s.Namespaces{metav1.NamespaceAll}
|
||||
namespaces = []string{metav1.NamespaceAll}
|
||||
c.isNamespaceAll = true
|
||||
}
|
||||
c.watchedNamespaces = namespaces
|
||||
|
|
|
@ -132,7 +132,7 @@ func (c clientMock) GetSecret(namespace, name string) (*corev1.Secret, bool, err
|
|||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (c clientMock) WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||
func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||
return c.watchChan, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ import (
|
|||
"github.com/containous/traefik/pkg/job"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/provider/kubernetes/crd/traefik/v1alpha1"
|
||||
"github.com/containous/traefik/pkg/provider/kubernetes/k8s"
|
||||
"github.com/containous/traefik/pkg/safe"
|
||||
"github.com/containous/traefik/pkg/tls"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
@ -32,13 +31,13 @@ const (
|
|||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)"`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)"`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)"`
|
||||
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers" export:"true"`
|
||||
Namespaces k8s.Namespaces `description:"Kubernetes namespaces" export:"true"`
|
||||
LabelSelector string `description:"Kubernetes label selector to use" export:"true"`
|
||||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for" export:"true"`
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)."`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)."`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)."`
|
||||
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers." export:"true"`
|
||||
Namespaces []string `description:"Kubernetes namespaces." export:"true"`
|
||||
LabelSelector string `description:"Kubernetes label selector to use." export:"true"`
|
||||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." export:"true"`
|
||||
lastConfiguration safe.Safe
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/provider/kubernetes/k8s"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
kubeerror "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
@ -42,7 +41,7 @@ func (reh *resourceEventHandler) OnDelete(obj interface{}) {
|
|||
// WatchAll starts the watch of the Provider resources and updates the stores.
|
||||
// The stores can then be accessed via the Get* functions.
|
||||
type Client interface {
|
||||
WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error)
|
||||
WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error)
|
||||
GetIngresses() []*extensionsv1beta1.Ingress
|
||||
GetService(namespace, name string) (*corev1.Service, bool, error)
|
||||
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
|
||||
|
@ -55,7 +54,7 @@ type clientWrapper struct {
|
|||
factories map[string]informers.SharedInformerFactory
|
||||
ingressLabelSelector labels.Selector
|
||||
isNamespaceAll bool
|
||||
watchedNamespaces k8s.Namespaces
|
||||
watchedNamespaces []string
|
||||
}
|
||||
|
||||
// newInClusterClient returns a new Provider client that is expected to run
|
||||
|
@ -122,12 +121,12 @@ func newClientImpl(clientset *kubernetes.Clientset) *clientWrapper {
|
|||
}
|
||||
|
||||
// WatchAll starts namespace-specific controllers for all relevant kinds.
|
||||
func (c *clientWrapper) WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||
func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||
eventCh := make(chan interface{}, 1)
|
||||
eventHandler := c.newResourceEventHandler(eventCh)
|
||||
|
||||
if len(namespaces) == 0 {
|
||||
namespaces = k8s.Namespaces{metav1.NamespaceAll}
|
||||
namespaces = []string{metav1.NamespaceAll}
|
||||
c.isNamespaceAll = true
|
||||
}
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ func (c clientMock) GetSecret(namespace, name string) (*corev1.Secret, bool, err
|
|||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (c clientMock) WatchAll(namespaces k8s.Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||
func (c clientMock) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||
return c.watchChan, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ import (
|
|||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/job"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/provider/kubernetes/k8s"
|
||||
"github.com/containous/traefik/pkg/safe"
|
||||
"github.com/containous/traefik/pkg/tls"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
@ -33,22 +32,22 @@ const (
|
|||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)"`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)"`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)"`
|
||||
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers" export:"true"`
|
||||
Namespaces k8s.Namespaces `description:"Kubernetes namespaces" export:"true"`
|
||||
LabelSelector string `description:"Kubernetes Ingress label selector to use" export:"true"`
|
||||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for" export:"true"`
|
||||
IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint"`
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)."`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)."`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)."`
|
||||
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers." export:"true"`
|
||||
Namespaces []string `description:"Kubernetes namespaces." export:"true"`
|
||||
LabelSelector string `description:"Kubernetes Ingress label selector to use." export:"true"`
|
||||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." export:"true"`
|
||||
IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint."`
|
||||
lastConfiguration safe.Safe
|
||||
}
|
||||
|
||||
// EndpointIngress holds the endpoint information for the Kubernetes provider
|
||||
type EndpointIngress struct {
|
||||
IP string `description:"IP used for Kubernetes Ingress endpoints"`
|
||||
Hostname string `description:"Hostname used for Kubernetes Ingress endpoints"`
|
||||
PublishedService string `description:"Published Kubernetes Service to copy status from"`
|
||||
IP string `description:"IP used for Kubernetes Ingress endpoints."`
|
||||
Hostname string `description:"Hostname used for Kubernetes Ingress endpoints."`
|
||||
PublishedService string `description:"Published Kubernetes Service to copy status from."`
|
||||
}
|
||||
|
||||
func (p *Provider) newK8sClient(ctx context.Context, ingressLabelSelector string) (*clientWrapper, error) {
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Namespaces holds kubernetes namespaces.
|
||||
type Namespaces []string
|
||||
|
||||
// Set adds strings elem into the the parser
|
||||
// it splits str on , and ;.
|
||||
func (ns *Namespaces) Set(str string) error {
|
||||
fargs := func(c rune) bool {
|
||||
return c == ',' || c == ';'
|
||||
}
|
||||
// get function
|
||||
slice := strings.FieldsFunc(str, fargs)
|
||||
*ns = append(*ns, slice...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get []string.
|
||||
func (ns *Namespaces) Get() interface{} { return *ns }
|
||||
|
||||
// String return slice in a string.
|
||||
func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) }
|
||||
|
||||
// SetValue sets []string into the parser.
|
||||
func (ns *Namespaces) SetValue(val interface{}) {
|
||||
*ns = val.(Namespaces)
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package internal
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Node a label node.
|
||||
type Node struct {
|
||||
Name string `json:"name"`
|
||||
FieldName string `json:"fieldName"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
Kind reflect.Kind `json:"kind,omitempty"`
|
||||
Children []*Node `json:"children,omitempty"`
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package internal
|
||||
|
||||
const (
|
||||
// TagLabel allow to apply a custom behavior.
|
||||
// - "allowEmpty": allow to create an empty struct.
|
||||
// - "-": ignore the field.
|
||||
TagLabel = "label"
|
||||
|
||||
// TagLabelSliceAsStruct allow to use a slice of struct by creating one entry into the slice.
|
||||
// The value is the substitution name use in the label to access the slice.
|
||||
TagLabelSliceAsStruct = "label-slice-as-struct"
|
||||
)
|
|
@ -1,58 +0,0 @@
|
|||
package label
|
||||
|
||||
import (
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/provider/label/internal"
|
||||
)
|
||||
|
||||
// DecodeConfiguration Converts the labels to a configuration.
|
||||
func DecodeConfiguration(labels map[string]string) (*config.Configuration, error) {
|
||||
conf := &config.Configuration{
|
||||
HTTP: &config.HTTPConfiguration{},
|
||||
TCP: &config.TCPConfiguration{},
|
||||
}
|
||||
|
||||
err := Decode(labels, conf, "traefik.http", "traefik.tcp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// EncodeConfiguration Converts a configuration to labels.
|
||||
func EncodeConfiguration(conf *config.Configuration) (map[string]string, error) {
|
||||
return Encode(conf)
|
||||
}
|
||||
|
||||
// Decode Converts the labels to an element.
|
||||
// labels -> [ node -> node + metadata (type) ] -> element (node)
|
||||
func Decode(labels map[string]string, element interface{}, filters ...string) error {
|
||||
node, err := internal.DecodeToNode(labels, filters...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = internal.AddMetadata(element, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = internal.Fill(element, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode Converts an element to labels.
|
||||
// element -> node (value) -> label (node)
|
||||
func Encode(element interface{}) (map[string]string, error) {
|
||||
node, err := internal.EncodeToNode(element)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return internal.EncodeNode(node), nil
|
||||
}
|
|
@ -10,9 +10,9 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/config/label"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/provider"
|
||||
"github.com/containous/traefik/pkg/provider/label"
|
||||
"github.com/gambol99/go-marathon"
|
||||
)
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
testCases := []struct {
|
||||
desc string
|
||||
applications *marathon.Applications
|
||||
constraints types.Constraints
|
||||
constraints []*types.Constraint
|
||||
filterMarathonConstraints bool
|
||||
defaultRule string
|
||||
expected *config.Configuration
|
||||
|
@ -1065,11 +1065,11 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
withTasks(localhostTask(taskPorts(80, 81))),
|
||||
withLabel("traefik.tags", "foo"),
|
||||
)),
|
||||
constraints: types.Constraints{
|
||||
&types.Constraint{
|
||||
constraints: []*types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "bar",
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
expected: &config.Configuration{
|
||||
|
@ -1094,11 +1094,11 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
constraint("rack_id:CLUSTER:rack-1"),
|
||||
)),
|
||||
filterMarathonConstraints: true,
|
||||
constraints: types.Constraints{
|
||||
&types.Constraint{
|
||||
constraints: []*types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "rack_id:CLUSTER:rack-2",
|
||||
Value: "rack_id:CLUSTER:rack-2",
|
||||
},
|
||||
},
|
||||
expected: &config.Configuration{
|
||||
|
@ -1123,11 +1123,11 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
constraint("rack_id:CLUSTER:rack-1"),
|
||||
)),
|
||||
filterMarathonConstraints: true,
|
||||
constraints: types.Constraints{
|
||||
&types.Constraint{
|
||||
constraints: []*types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "rack_id:CLUSTER:rack-1",
|
||||
Value: "rack_id:CLUSTER:rack-1",
|
||||
},
|
||||
},
|
||||
expected: &config.Configuration{
|
||||
|
@ -1168,11 +1168,11 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
withLabel("traefik.tags", "bar"),
|
||||
)),
|
||||
|
||||
constraints: types.Constraints{
|
||||
&types.Constraint{
|
||||
constraints: []*types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "bar",
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
expected: &config.Configuration{
|
||||
|
@ -1466,7 +1466,7 @@ func TestApplicationFilterEnabled(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
provider := &Provider{ExposedByDefault: test.exposedByDefault}
|
||||
provider := &Provider{ExposedByDefault: true}
|
||||
|
||||
app := application(withLabel("traefik.enable", test.enabledLabel))
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/provider/label"
|
||||
"github.com/containous/traefik/pkg/config/label"
|
||||
"github.com/gambol99/go-marathon"
|
||||
)
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/job"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
|
@ -46,31 +45,44 @@ var _ provider.Provider = (*Provider)(nil)
|
|||
|
||||
// Provider holds configuration of the provider.
|
||||
type Provider struct {
|
||||
provider.Constrainer `mapstructure:",squash" export:"true"`
|
||||
provider.Constrainer `description:"List of constraints used to filter out some containers." export:"true"`
|
||||
|
||||
Trace bool `description:"Display additional provider logs." export:"true"`
|
||||
Watch bool `description:"Watch provider" export:"true"`
|
||||
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon" export:"true"`
|
||||
DefaultRule string `description:"Default rule"`
|
||||
ExposedByDefault bool `description:"Expose Marathon apps by default" export:"true"`
|
||||
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header" export:"true"`
|
||||
FilterMarathonConstraints bool `description:"Enable use of Marathon constraints in constraint filtering" export:"true"`
|
||||
TLS *types.ClientTLS `description:"Enable TLS support" export:"true"`
|
||||
DialerTimeout parse.Duration `description:"Set a dialer timeout for Marathon" export:"true"`
|
||||
ResponseHeaderTimeout parse.Duration `description:"Set a response header timeout for Marathon" export:"true"`
|
||||
TLSHandshakeTimeout parse.Duration `description:"Set a TLS handhsake timeout for Marathon" export:"true"`
|
||||
KeepAlive parse.Duration `description:"Set a TCP Keep Alive time in seconds" export:"true"`
|
||||
Watch bool `description:"Watch provider." export:"true"`
|
||||
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon." export:"true"`
|
||||
DefaultRule string `description:"Default rule."`
|
||||
ExposedByDefault bool `description:"Expose Marathon apps by default." export:"true"`
|
||||
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header." export:"true"`
|
||||
FilterMarathonConstraints bool `description:"Enable use of Marathon constraints in constraint filtering." export:"true"`
|
||||
TLS *types.ClientTLS `description:"Enable TLS support." export:"true"`
|
||||
DialerTimeout types.Duration `description:"Set a dialer timeout for Marathon." export:"true"`
|
||||
ResponseHeaderTimeout types.Duration `description:"Set a response header timeout for Marathon." export:"true"`
|
||||
TLSHandshakeTimeout types.Duration `description:"Set a TLS handshake timeout for Marathon." export:"true"`
|
||||
KeepAlive types.Duration `description:"Set a TCP Keep Alive time." export:"true"`
|
||||
ForceTaskHostname bool `description:"Force to use the task's hostname." export:"true"`
|
||||
Basic *Basic `description:"Enable basic authentication" export:"true"`
|
||||
RespectReadinessChecks bool `description:"Filter out tasks with non-successful readiness checks during deployments" export:"true"`
|
||||
Basic *Basic `description:"Enable basic authentication." export:"true"`
|
||||
RespectReadinessChecks bool `description:"Filter out tasks with non-successful readiness checks during deployments." export:"true"`
|
||||
readyChecker *readinessChecker
|
||||
marathonClient marathon.Marathon
|
||||
defaultRuleTpl *template.Template
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
p.Watch = true
|
||||
p.Endpoint = "http://127.0.0.1:8080"
|
||||
p.ExposedByDefault = true
|
||||
p.DialerTimeout = types.Duration(5 * time.Second)
|
||||
p.ResponseHeaderTimeout = types.Duration(60 * time.Second)
|
||||
p.TLSHandshakeTimeout = types.Duration(5 * time.Second)
|
||||
p.KeepAlive = types.Duration(10 * time.Second)
|
||||
p.DefaultRule = DefaultTemplateRule
|
||||
}
|
||||
|
||||
// Basic holds basic authentication specific configurations
|
||||
type Basic struct {
|
||||
HTTPBasicAuthUser string `description:"Basic authentication User"`
|
||||
HTTPBasicPassword string `description:"Basic authentication Password"`
|
||||
HTTPBasicAuthUser string `description:"Basic authentication User."`
|
||||
HTTPBasicPassword string `description:"Basic authentication Password."`
|
||||
}
|
||||
|
||||
// Init the provider
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/config/label"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/provider"
|
||||
"github.com/containous/traefik/pkg/provider/label"
|
||||
)
|
||||
|
||||
func (p *Provider) buildConfiguration(ctx context.Context, services []rancherData) *config.Configuration {
|
||||
|
|
|
@ -14,7 +14,7 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
testCases := []struct {
|
||||
desc string
|
||||
containers []rancherData
|
||||
constraints types.Constraints
|
||||
constraints []*types.Constraint
|
||||
expected *config.Configuration
|
||||
}{
|
||||
{
|
||||
|
@ -330,11 +330,11 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
State: "",
|
||||
},
|
||||
},
|
||||
constraints: types.Constraints{
|
||||
&types.Constraint{
|
||||
constraints: []*types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "bar",
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
expected: &config.Configuration{
|
||||
|
@ -363,11 +363,11 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
State: "",
|
||||
},
|
||||
},
|
||||
constraints: types.Constraints{
|
||||
&types.Constraint{
|
||||
constraints: []*types.Constraint{
|
||||
{
|
||||
Key: "tag",
|
||||
MustMatch: true,
|
||||
Regex: "foo",
|
||||
Value: "foo",
|
||||
},
|
||||
},
|
||||
expected: &config.Configuration{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package rancher
|
||||
|
||||
import (
|
||||
"github.com/containous/traefik/pkg/provider/label"
|
||||
"github.com/containous/traefik/pkg/config/label"
|
||||
)
|
||||
|
||||
type configuration struct {
|
||||
|
|
|
@ -40,15 +40,26 @@ var _ provider.Provider = (*Provider)(nil)
|
|||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
provider.Constrainer `mapstructure:",squash" export:"true"`
|
||||
Watch bool `description:"Watch provider" export:"true"`
|
||||
DefaultRule string `description:"Default rule"`
|
||||
ExposedByDefault bool `description:"Expose containers by default" export:"true"`
|
||||
EnableServiceHealthFilter bool
|
||||
RefreshSeconds int
|
||||
provider.Constrainer `description:"List of constraints used to filter out some containers." export:"true"`
|
||||
|
||||
Watch bool `description:"Watch provider." export:"true"`
|
||||
DefaultRule string `description:"Default rule."`
|
||||
ExposedByDefault bool `description:"Expose containers by default." export:"true"`
|
||||
EnableServiceHealthFilter bool `description:"Filter services with unhealthy states and inactive states." export:"true"`
|
||||
RefreshSeconds int `description:"Defines the polling interval in seconds." export:"true"`
|
||||
IntervalPoll bool `description:"Poll the Rancher metadata service every 'rancher.refreshseconds' (less accurate)."`
|
||||
Prefix string `description:"Prefix used for accessing the Rancher metadata service."`
|
||||
defaultRuleTpl *template.Template
|
||||
IntervalPoll bool `description:"Poll the Rancher metadata service every 'rancher.refreshseconds' (less accurate)"`
|
||||
Prefix string `description:"Prefix used for accessing the Rancher metadata service"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
p.Watch = true
|
||||
p.ExposedByDefault = true
|
||||
p.EnableServiceHealthFilter = true
|
||||
p.RefreshSeconds = 15
|
||||
p.DefaultRule = DefaultTemplateRule
|
||||
p.Prefix = "latest"
|
||||
}
|
||||
|
||||
type rancherData struct {
|
||||
|
|
|
@ -19,7 +19,13 @@ var _ provider.Provider = (*Provider)(nil)
|
|||
// Provider is a provider.Provider implementation that provides a Rest API.
|
||||
type Provider struct {
|
||||
configurationChan chan<- config.Message
|
||||
EntryPoint string `description:"EntryPoint" export:"true"`
|
||||
EntryPoint string `description:"EntryPoint." export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
p.EntryPoint = "traefik"
|
||||
// FIXME p.EntryPoint = static.DefaultInternalEntryPointName
|
||||
}
|
||||
|
||||
var templatesRenderer = render.New(render.Options{Directory: "nowhere"})
|
||||
|
|
|
@ -83,7 +83,7 @@ func createHTTPTransport(transportConfiguration *static.ServersTransport) (*http
|
|||
return transport, nil
|
||||
}
|
||||
|
||||
func createRootCACertPool(rootCAs traefiktls.FilesOrContents) *x509.CertPool {
|
||||
func createRootCACertPool(rootCAs []traefiktls.FileOrContent) *x509.CertPool {
|
||||
roots := x509.NewCertPool()
|
||||
|
||||
for _, cert := range rootCAs {
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
"github.com/containous/traefik/pkg/tcp"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -21,7 +21,7 @@ func TestShutdownHTTP(t *testing.T) {
|
|||
Transport: &static.EntryPointsTransport{
|
||||
LifeCycle: &static.LifeCycle{
|
||||
RequestAcceptGraceTimeout: 0,
|
||||
GraceTimeOut: parse.Duration(5 * time.Second),
|
||||
GraceTimeOut: types.Duration(5 * time.Second),
|
||||
},
|
||||
},
|
||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||
|
@ -59,7 +59,7 @@ func TestShutdownHTTPHijacked(t *testing.T) {
|
|||
Transport: &static.EntryPointsTransport{
|
||||
LifeCycle: &static.LifeCycle{
|
||||
RequestAcceptGraceTimeout: 0,
|
||||
GraceTimeOut: parse.Duration(5 * time.Second),
|
||||
GraceTimeOut: types.Duration(5 * time.Second),
|
||||
},
|
||||
},
|
||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||
|
@ -103,7 +103,7 @@ func TestShutdownTCPConn(t *testing.T) {
|
|||
Transport: &static.EntryPointsTransport{
|
||||
LifeCycle: &static.LifeCycle{
|
||||
RequestAcceptGraceTimeout: 0,
|
||||
GraceTimeOut: parse.Duration(5 * time.Second),
|
||||
GraceTimeOut: types.Duration(5 * time.Second),
|
||||
},
|
||||
},
|
||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||
|
|
|
@ -7,10 +7,10 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/config/static"
|
||||
th "github.com/containous/traefik/pkg/testhelpers"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -132,7 +132,7 @@ func setupListenProvider(throttleDuration time.Duration) (server *Server, stop c
|
|||
|
||||
staticConfiguration := static.Configuration{
|
||||
Providers: &static.Providers{
|
||||
ProvidersThrottleDuration: parse.Duration(throttleDuration),
|
||||
ProvidersThrottleDuration: types.Duration(throttleDuration),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ import (
|
|||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/containous/traefik/pkg/types"
|
||||
)
|
||||
|
||||
// StatusClientClosedRequest non-standard HTTP status code for client disconnection
|
||||
|
@ -22,7 +22,7 @@ const StatusClientClosedRequest = 499
|
|||
const StatusClientClosedRequestText = "Client Closed Request"
|
||||
|
||||
func buildProxy(passHostHeader bool, responseForwarding *config.ResponseForwarding, defaultRoundTripper http.RoundTripper, bufferPool httputil.BufferPool, responseModifier func(*http.Response) error) (http.Handler, error) {
|
||||
var flushInterval parse.Duration
|
||||
var flushInterval types.Duration
|
||||
if responseForwarding != nil {
|
||||
err := flushInterval.Set(responseForwarding.FlushInterval)
|
||||
if err != nil {
|
||||
|
@ -30,7 +30,7 @@ func buildProxy(passHostHeader bool, responseForwarding *config.ResponseForwardi
|
|||
}
|
||||
}
|
||||
if flushInterval == 0 {
|
||||
flushInterval = parse.Duration(100 * time.Millisecond)
|
||||
flushInterval = types.Duration(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
proxy := &httputil.ReverseProxy{
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const certificateHeader = "-----BEGIN CERTIFICATE-----\n"
|
||||
|
||||
// ClientCA defines traefik CA files for a entryPoint
|
||||
// and it indicates if they are mandatory or have just to be analyzed if provided
|
||||
// and it indicates if they are mandatory or have just to be analyzed if provided.
|
||||
type ClientCA struct {
|
||||
Files FilesOrContents
|
||||
Files []FileOrContent
|
||||
Optional bool
|
||||
}
|
||||
|
||||
|
@ -27,50 +22,8 @@ type Store struct {
|
|||
DefaultCertificate *Certificate
|
||||
}
|
||||
|
||||
// FilesOrContents hold the CA we want to have in root
|
||||
type FilesOrContents []FileOrContent
|
||||
|
||||
// Configuration allows mapping a TLS certificate to a list of entrypoints
|
||||
// Configuration allows mapping a TLS certificate to a list of entry points.
|
||||
type Configuration struct {
|
||||
Stores []string
|
||||
Certificate *Certificate
|
||||
}
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (r *FilesOrContents) String() string {
|
||||
sliceOfString := make([]string, len([]FileOrContent(*r)))
|
||||
for key, value := range *r {
|
||||
sliceOfString[key] = value.String()
|
||||
}
|
||||
return strings.Join(sliceOfString, ",")
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (r *FilesOrContents) Set(value string) error {
|
||||
filesOrContents := strings.Split(value, ",")
|
||||
if len(filesOrContents) == 0 {
|
||||
return fmt.Errorf("bad FilesOrContents format: %s", value)
|
||||
}
|
||||
for _, fileOrContent := range filesOrContents {
|
||||
*r = append(*r, FileOrContent(fileOrContent))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get return the FilesOrContents list
|
||||
func (r *FilesOrContents) Get() interface{} {
|
||||
return *r
|
||||
}
|
||||
|
||||
// SetValue sets the FilesOrContents with val
|
||||
func (r *FilesOrContents) SetValue(val interface{}) {
|
||||
*r = val.(FilesOrContents)
|
||||
}
|
||||
|
||||
// Type is type of the struct
|
||||
func (r *FilesOrContents) Type() string {
|
||||
return "filesorcontents"
|
||||
}
|
||||
|
|
|
@ -15,14 +15,22 @@ const Name = "datadog"
|
|||
|
||||
// Config provides configuration settings for a datadog tracer
|
||||
type Config struct {
|
||||
LocalAgentHostPort string `description:"Set datadog-agent's host:port that the reporter will used. Defaults to localhost:8126" export:"false"`
|
||||
LocalAgentHostPort string `description:"Set datadog-agent's host:port that the reporter will used." export:"false"`
|
||||
GlobalTag string `description:"Key:Value tag to be set on all the spans." export:"true"`
|
||||
Debug bool `description:"Enable DataDog debug." export:"true"`
|
||||
PrioritySampling bool `description:"Enable priority sampling. When using distributed tracing, this option must be enabled in order to get all the parts of a distributed trace sampled."`
|
||||
TraceIDHeaderName string `description:"Specifies the header name that will be used to store the trace ID." export:"true"`
|
||||
ParentIDHeaderName string `description:"Specifies the header name that will be used to store the parent ID." export:"true"`
|
||||
SamplingPriorityHeaderName string `description:"Specifies the header name that will be used to store the sampling priority." export:"true"`
|
||||
BagagePrefixHeaderName string `description:"specifies the header name prefix that will be used to store baggage items in a map." export:"true"`
|
||||
BagagePrefixHeaderName string `description:"Specifies the header name prefix that will be used to store baggage items in a map." export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Config) SetDefaults() {
|
||||
c.LocalAgentHostPort = "localhost:8126"
|
||||
c.GlobalTag = ""
|
||||
c.Debug = false
|
||||
c.PrioritySampling = false
|
||||
}
|
||||
|
||||
// Setup sets up the tracer
|
||||
|
|
|
@ -15,15 +15,21 @@ const Name = "haystack"
|
|||
|
||||
// Config provides configuration settings for a haystack tracer
|
||||
type Config struct {
|
||||
LocalAgentHost string `description:"Set haystack-agent's host that the reporter will used. Defaults to localhost" export:"false"`
|
||||
LocalAgentPort int `description:"Set haystack-agent's port that the reporter will used. Defaults to 35000" export:"false"`
|
||||
LocalAgentHost string `description:"Set haystack-agent's host that the reporter will used." export:"false"`
|
||||
LocalAgentPort int `description:"Set haystack-agent's port that the reporter will used." export:"false"`
|
||||
GlobalTag string `description:"Key:Value tag to be set on all the spans." export:"true"`
|
||||
TraceIDHeaderName string `description:"Specifies the header name that will be used to store the trace ID.." export:"true"`
|
||||
TraceIDHeaderName string `description:"Specifies the header name that will be used to store the trace ID." export:"true"`
|
||||
ParentIDHeaderName string `description:"Specifies the header name that will be used to store the parent ID." export:"true"`
|
||||
SpanIDHeaderName string `description:"Specifies the header name that will be used to store the span ID." export:"true"`
|
||||
BaggagePrefixHeaderName string `description:"specifies the header name prefix that will be used to store baggage items in a map." export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Config) SetDefaults() {
|
||||
c.LocalAgentHost = "LocalAgentHost"
|
||||
c.LocalAgentPort = 35000
|
||||
}
|
||||
|
||||
// Setup sets up the tracer
|
||||
func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) {
|
||||
tag := strings.SplitN(c.GlobalTag, ":", 2)
|
||||
|
|
|
@ -18,6 +18,13 @@ type Config struct {
|
|||
LogLevel string `description:"Set instana-agent's log level. ('error','warn','info','debug')" export:"false"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Config) SetDefaults() {
|
||||
c.LocalAgentHost = "localhost"
|
||||
c.LocalAgentPort = 42699
|
||||
c.LogLevel = "info"
|
||||
}
|
||||
|
||||
// Setup sets up the tracer
|
||||
func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) {
|
||||
// set default logLevel
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/containous/traefik/pkg/log"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
jaeger "github.com/uber/jaeger-client-go"
|
||||
jaegercli "github.com/uber/jaeger-client-go"
|
||||
jaegercfg "github.com/uber/jaeger-client-go/config"
|
||||
"github.com/uber/jaeger-client-go/zipkin"
|
||||
jaegermet "github.com/uber/jaeger-lib/metrics"
|
||||
|
@ -17,13 +18,24 @@ const Name = "jaeger"
|
|||
|
||||
// Config provides configuration settings for a jaeger tracer
|
||||
type Config struct {
|
||||
SamplingServerURL string `description:"set the sampling server url." export:"false"`
|
||||
SamplingType string `description:"set the sampling type." export:"true"`
|
||||
SamplingParam float64 `description:"set the sampling parameter." export:"true"`
|
||||
LocalAgentHostPort string `description:"set jaeger-agent's host:port that the reporter will used." export:"false"`
|
||||
Gen128Bit bool `description:"generate 128 bit span IDs." export:"true"`
|
||||
Propagation string `description:"which propgation format to use (jaeger/b3)." export:"true"`
|
||||
TraceContextHeaderName string `description:"set the header to use for the trace-id." export:"true"`
|
||||
SamplingServerURL string `description:"Set the sampling server url." export:"false"`
|
||||
SamplingType string `description:"Set the sampling type." export:"true"`
|
||||
SamplingParam float64 `description:"Set the sampling parameter." export:"true"`
|
||||
LocalAgentHostPort string `description:"Set jaeger-agent's host:port that the reporter will used." export:"false"`
|
||||
Gen128Bit bool `description:"Generate 128 bit span IDs." export:"true"`
|
||||
Propagation string `description:"Which propgation format to use (jaeger/b3)." export:"true"`
|
||||
TraceContextHeaderName string `description:"Set the header to use for the trace-id." export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Config) SetDefaults() {
|
||||
c.SamplingServerURL = "http://localhost:5778/sampling"
|
||||
c.SamplingType = "const"
|
||||
c.SamplingParam = 1.0
|
||||
c.LocalAgentHostPort = "127.0.0.1:6831"
|
||||
c.Propagation = "jaeger"
|
||||
c.Gen128Bit = false
|
||||
c.TraceContextHeaderName = jaegercli.TraceContextHeaderName
|
||||
}
|
||||
|
||||
// Setup sets up the tracer
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue