mirror of
https://github.com/aykhans/dodo.git
synced 2025-09-03 18:03:34 +00:00
Add config file support to CLI parser
Add -f/--config-file flag for loading YAML configs from local or remote sources. Fix error handling to return unmatched errors.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/url"
|
||||
@@ -52,14 +53,19 @@ Flags:
|
||||
-skip-verify bool Skip SSL/TLS certificate verification (default %v)`
|
||||
|
||||
type ConfigCLIParser struct {
|
||||
args []string
|
||||
args []string
|
||||
configFile *types.ConfigFile
|
||||
}
|
||||
|
||||
func NewConfigCLIParser(args []string) *ConfigCLIParser {
|
||||
if args == nil {
|
||||
args = []string{}
|
||||
}
|
||||
return &ConfigCLIParser{args}
|
||||
return &ConfigCLIParser{args: args}
|
||||
}
|
||||
|
||||
func (parser ConfigCLIParser) GetConfigFile() *types.ConfigFile {
|
||||
return parser.configFile
|
||||
}
|
||||
|
||||
type stringSliceArg []string
|
||||
@@ -85,6 +91,7 @@ func (parser *ConfigCLIParser) Parse() (*Config, error) {
|
||||
|
||||
var (
|
||||
config = &Config{}
|
||||
configFile string
|
||||
yes bool
|
||||
skipVerify bool
|
||||
method string
|
||||
@@ -101,6 +108,9 @@ func (parser *ConfigCLIParser) Parse() (*Config, error) {
|
||||
)
|
||||
|
||||
{
|
||||
flagSet.StringVar(&configFile, "config-file", "", "Config file")
|
||||
flagSet.StringVar(&configFile, "f", "", "Config file")
|
||||
|
||||
flagSet.BoolVar(&yes, "yes", false, "Answer yes to all questions")
|
||||
flagSet.BoolVar(&yes, "y", false, "Answer yes to all questions")
|
||||
|
||||
@@ -160,6 +170,29 @@ func (parser *ConfigCLIParser) Parse() (*Config, error) {
|
||||
// Iterate over flags that were explicitly set on the command line.
|
||||
flagSet.Visit(func(flagVar *flag.Flag) {
|
||||
switch flagVar.Name {
|
||||
case "config-file", "f":
|
||||
var err error
|
||||
parser.configFile, err = types.ParseConfigFile(configFile)
|
||||
_ = utils.HandleErrorOrDie(err,
|
||||
utils.OnSentinelError(types.ErrConfigFileExtensionNotFound, func(err error) error {
|
||||
fieldParseErrors = append(fieldParseErrors, *types.NewFieldParseError("config-file", errors.New("file extension not found")))
|
||||
return nil
|
||||
}),
|
||||
utils.OnCustomError(func(err types.RemoteConfigFileParseError) error {
|
||||
fieldParseErrors = append(fieldParseErrors, *types.NewFieldParseError("config-file", fmt.Errorf("parse error: %w", err)))
|
||||
return nil
|
||||
}),
|
||||
utils.OnCustomError(func(err types.UnknownConfigFileTypeError) error {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
"config-file",
|
||||
fmt.Errorf("file type '%s' not supported (supported types: %s)", err.Type, types.ConfigFileTypeYAML.String()),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
case "yes", "y":
|
||||
config.Yes = utils.ToPtr(yes)
|
||||
case "skip-verify":
|
||||
|
@@ -20,6 +20,7 @@ func TestNewConfigCLIParser(t *testing.T) {
|
||||
|
||||
require.NotNil(t, parser)
|
||||
assert.Equal(t, args, parser.args)
|
||||
assert.Nil(t, parser.configFile)
|
||||
})
|
||||
|
||||
t.Run("NewConfigCLIParser with nil args", func(t *testing.T) {
|
||||
@@ -27,6 +28,7 @@ func TestNewConfigCLIParser(t *testing.T) {
|
||||
|
||||
require.NotNil(t, parser)
|
||||
assert.Equal(t, []string{}, parser.args)
|
||||
assert.Nil(t, parser.configFile)
|
||||
})
|
||||
|
||||
t.Run("NewConfigCLIParser with empty args", func(t *testing.T) {
|
||||
@@ -35,6 +37,21 @@ func TestNewConfigCLIParser(t *testing.T) {
|
||||
|
||||
require.NotNil(t, parser)
|
||||
assert.Equal(t, args, parser.args)
|
||||
assert.Nil(t, parser.configFile)
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigCLIParser_GetConfigFile(t *testing.T) {
|
||||
t.Run("GetConfigFile returns nil when no config file is set", func(t *testing.T) {
|
||||
parser := NewConfigCLIParser([]string{"dodo"})
|
||||
assert.Nil(t, parser.GetConfigFile())
|
||||
})
|
||||
|
||||
t.Run("GetConfigFile returns config file when set", func(t *testing.T) {
|
||||
parser := NewConfigCLIParser([]string{"dodo"})
|
||||
expectedConfigFile := &types.ConfigFile{}
|
||||
parser.configFile = expectedConfigFile
|
||||
assert.Equal(t, expectedConfigFile, parser.GetConfigFile())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -295,6 +312,58 @@ func TestConfigCLIParser_Parse(t *testing.T) {
|
||||
assert.Equal(t, 10*time.Second, *config.Timeout)
|
||||
})
|
||||
|
||||
t.Run("Parse with config-file flag valid YAML", func(t *testing.T) {
|
||||
parser := NewConfigCLIParser([]string{"dodo", "-f", "/path/to/config.yaml"})
|
||||
config, err := parser.Parse()
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, config)
|
||||
assert.NotNil(t, parser.GetConfigFile())
|
||||
})
|
||||
|
||||
t.Run("Parse with config-file flag using long form", func(t *testing.T) {
|
||||
parser := NewConfigCLIParser([]string{"dodo", "--config-file", "/path/to/config.yml"})
|
||||
config, err := parser.Parse()
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, config)
|
||||
assert.NotNil(t, parser.GetConfigFile())
|
||||
})
|
||||
|
||||
t.Run("Parse with config-file flag invalid extension", func(t *testing.T) {
|
||||
parser := NewConfigCLIParser([]string{"dodo", "-f", "/path/to/config"})
|
||||
config, err := parser.Parse()
|
||||
|
||||
assert.Nil(t, config)
|
||||
var fieldErr types.FieldParseErrors
|
||||
require.ErrorAs(t, err, &fieldErr)
|
||||
assert.Len(t, fieldErr.Errors, 1)
|
||||
assert.Equal(t, "config-file", fieldErr.Errors[0].Field)
|
||||
assert.Contains(t, fieldErr.Errors[0].Err.Error(), "file extension not found")
|
||||
})
|
||||
|
||||
t.Run("Parse with config-file flag unsupported file type", func(t *testing.T) {
|
||||
parser := NewConfigCLIParser([]string{"dodo", "-f", "/path/to/config.json"})
|
||||
config, err := parser.Parse()
|
||||
|
||||
assert.Nil(t, config)
|
||||
var fieldErr types.FieldParseErrors
|
||||
require.ErrorAs(t, err, &fieldErr)
|
||||
assert.Len(t, fieldErr.Errors, 1)
|
||||
assert.Equal(t, "config-file", fieldErr.Errors[0].Field)
|
||||
assert.Contains(t, fieldErr.Errors[0].Err.Error(), "file type")
|
||||
assert.Contains(t, fieldErr.Errors[0].Err.Error(), "not supported")
|
||||
})
|
||||
|
||||
t.Run("Parse with config-file flag remote URL", func(t *testing.T) {
|
||||
parser := NewConfigCLIParser([]string{"dodo", "-f", "https://example.com/config.yaml"})
|
||||
config, err := parser.Parse()
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, config)
|
||||
assert.NotNil(t, parser.GetConfigFile())
|
||||
})
|
||||
|
||||
t.Run("Parse with all flags combined", func(t *testing.T) {
|
||||
parser := NewConfigCLIParser([]string{
|
||||
"dodo",
|
||||
@@ -311,6 +380,7 @@ func TestConfigCLIParser_Parse(t *testing.T) {
|
||||
"-c", "session=token123",
|
||||
"-b", `{"data": "test"}`,
|
||||
"-x", "http://proxy.example.com:3128",
|
||||
"-f", "/path/to/config.yaml",
|
||||
})
|
||||
config, err := parser.Parse()
|
||||
|
||||
@@ -341,6 +411,8 @@ func TestConfigCLIParser_Parse(t *testing.T) {
|
||||
|
||||
assert.Len(t, config.Proxies, 1)
|
||||
assert.Equal(t, "http://proxy.example.com:3128", config.Proxies[0].String())
|
||||
|
||||
assert.NotNil(t, parser.GetConfigFile())
|
||||
})
|
||||
|
||||
t.Run("Parse with multiple field parse errors", func(t *testing.T) {
|
||||
@@ -404,6 +476,7 @@ func TestConfigCLIParser_PrintHelp(t *testing.T) {
|
||||
assert.Contains(t, output, "-x, -proxy")
|
||||
assert.Contains(t, output, "-skip-verify")
|
||||
assert.Contains(t, output, "-y, -yes")
|
||||
assert.Contains(t, output, "-f, -config-file")
|
||||
|
||||
// Verify default values are included
|
||||
assert.Contains(t, output, Defaults.Method)
|
||||
|
Reference in New Issue
Block a user