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
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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue