mirror of
https://github.com/aykhans/dodo.git
synced 2025-09-05 18:44:21 +00:00
add env parser
This commit is contained in:
@@ -49,7 +49,7 @@ func NewConfig() *Config {
|
||||
return &Config{}
|
||||
}
|
||||
|
||||
func (config *Config) MergeConfig(newConfig *Config) {
|
||||
func (config *Config) Merge(newConfig *Config) {
|
||||
config.Files = append(config.Files, newConfig.Files...)
|
||||
if newConfig.Method != nil {
|
||||
config.Method = newConfig.Method
|
||||
@@ -75,19 +75,19 @@ func (config *Config) MergeConfig(newConfig *Config) {
|
||||
if newConfig.SkipVerify != nil {
|
||||
config.SkipVerify = newConfig.SkipVerify
|
||||
}
|
||||
if len(newConfig.Params) != 0 {
|
||||
if len(newConfig.Params) != 0 { // TODO: append
|
||||
config.Params = newConfig.Params
|
||||
}
|
||||
if len(newConfig.Headers) != 0 {
|
||||
if len(newConfig.Headers) != 0 { // TODO: append
|
||||
config.Headers = newConfig.Headers
|
||||
}
|
||||
if len(newConfig.Cookies) != 0 {
|
||||
if len(newConfig.Cookies) != 0 { // TODO: append
|
||||
config.Cookies = newConfig.Cookies
|
||||
}
|
||||
if len(newConfig.Bodies) != 0 {
|
||||
if len(newConfig.Bodies) != 0 { // TODO: append
|
||||
config.Bodies = newConfig.Bodies
|
||||
}
|
||||
if len(newConfig.Proxies) != 0 {
|
||||
if len(newConfig.Proxies) != 0 { // TODO: append
|
||||
config.Proxies = newConfig.Proxies
|
||||
}
|
||||
}
|
||||
|
@@ -56,7 +56,7 @@ func TestMergeConfig(t *testing.T) {
|
||||
Proxies: types.Proxies{},
|
||||
}
|
||||
|
||||
config.MergeConfig(newConfig)
|
||||
config.Merge(newConfig)
|
||||
|
||||
assert.Equal(t, "POST", *config.Method)
|
||||
assert.Equal(t, newURL, config.URL)
|
||||
@@ -93,7 +93,7 @@ func TestMergeConfig(t *testing.T) {
|
||||
DodosCount: utils.ToPtr(uint(10)),
|
||||
}
|
||||
|
||||
config.MergeConfig(newConfig)
|
||||
config.Merge(newConfig)
|
||||
|
||||
assert.Equal(t, "GET", *config.Method, "Method should remain unchanged")
|
||||
assert.Equal(t, newURL, config.URL, "URL should be updated")
|
||||
@@ -127,7 +127,7 @@ func TestMergeConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
originalConfigCopy := *config
|
||||
config.MergeConfig(newConfig)
|
||||
config.Merge(newConfig)
|
||||
|
||||
assert.Equal(t, originalConfigCopy.Method, config.Method)
|
||||
assert.Equal(t, originalConfigCopy.URL, config.URL)
|
||||
@@ -157,7 +157,7 @@ func TestMergeConfig(t *testing.T) {
|
||||
Proxies: types.Proxies{},
|
||||
}
|
||||
|
||||
config.MergeConfig(newConfig)
|
||||
config.Merge(newConfig)
|
||||
|
||||
assert.Equal(t, []types.ConfigFile{*configFile}, config.Files, "Empty Files should not override")
|
||||
assert.Equal(t, types.Params{{Key: "original", Value: []string{"value"}}}, config.Params, "Empty Params should not override")
|
||||
@@ -182,7 +182,7 @@ func TestMergeConfig(t *testing.T) {
|
||||
Method: utils.ToPtr("POST"),
|
||||
}
|
||||
|
||||
config.MergeConfig(newConfig)
|
||||
config.Merge(newConfig)
|
||||
|
||||
assert.Equal(t, "POST", *config.Method, "Method should be updated")
|
||||
assert.Equal(t, []types.ConfigFile{*configFile1, *configFile2}, config.Files, "Files should be appended")
|
||||
@@ -213,7 +213,7 @@ func TestMergeConfig(t *testing.T) {
|
||||
Proxies: types.Proxies{},
|
||||
}
|
||||
|
||||
config.MergeConfig(newConfig)
|
||||
config.Merge(newConfig)
|
||||
|
||||
assert.Equal(t, "POST", *config.Method)
|
||||
assert.Equal(t, newURL, config.URL)
|
||||
|
7
pkg/config/parser/base.go
Normal file
7
pkg/config/parser/base.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package parser
|
||||
|
||||
import "github.com/aykhans/dodo/pkg/config"
|
||||
|
||||
type IParser interface {
|
||||
Parse() (*config.Config, error)
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package config
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aykhans/dodo/pkg/config"
|
||||
"github.com/aykhans/dodo/pkg/types"
|
||||
"github.com/aykhans/dodo/pkg/utils"
|
||||
)
|
||||
@@ -52,6 +53,8 @@ Flags:
|
||||
-x, -proxy [string] Proxy for the request (e.g. "http://proxy.example.com:8080")
|
||||
-skip-verify bool Skip SSL/TLS certificate verification (default %v)`
|
||||
|
||||
var _ IParser = ConfigCLIParser{}
|
||||
|
||||
type ConfigCLIParser struct {
|
||||
args []string
|
||||
}
|
||||
@@ -79,13 +82,13 @@ func (arg *stringSliceArg) Set(value string) error {
|
||||
// - types.ErrCLINoArgs
|
||||
// - types.CLIUnexpectedArgsError
|
||||
// - types.FieldParseErrors
|
||||
func (parser *ConfigCLIParser) Parse() (*Config, error) {
|
||||
func (parser ConfigCLIParser) Parse() (*config.Config, error) {
|
||||
flagSet := flag.NewFlagSet("dodo", flag.ExitOnError)
|
||||
|
||||
flagSet.Usage = func() { parser.PrintHelp() }
|
||||
|
||||
var (
|
||||
config = &Config{}
|
||||
config = &config.Config{}
|
||||
configFiles = stringSliceArg{}
|
||||
yes bool
|
||||
skipVerify bool
|
||||
@@ -259,14 +262,14 @@ func (parser *ConfigCLIParser) Parse() (*Config, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (parser *ConfigCLIParser) PrintHelp() {
|
||||
func (parser ConfigCLIParser) PrintHelp() {
|
||||
fmt.Printf(
|
||||
cliUsageText+"\n",
|
||||
Defaults.Yes,
|
||||
Defaults.DodosCount,
|
||||
Defaults.RequestTimeout,
|
||||
Defaults.Method,
|
||||
Defaults.SkipVerify,
|
||||
config.Defaults.Yes,
|
||||
config.Defaults.DodosCount,
|
||||
config.Defaults.RequestTimeout,
|
||||
config.Defaults.Method,
|
||||
config.Defaults.SkipVerify,
|
||||
)
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package config
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aykhans/dodo/pkg/config"
|
||||
"github.com/aykhans/dodo/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -485,7 +486,7 @@ func TestConfigCLIParser_PrintHelp(t *testing.T) {
|
||||
assert.Contains(t, output, "-f, -config-file")
|
||||
|
||||
// Verify default values are included
|
||||
assert.Contains(t, output, Defaults.Method)
|
||||
assert.Contains(t, output, config.Defaults.Method)
|
||||
assert.Contains(t, output, "1") // DodosCount default
|
||||
assert.Contains(t, output, "10s") // RequestTimeout default
|
||||
assert.Contains(t, output, "false") // Yes default
|
236
pkg/config/parser/env.go
Normal file
236
pkg/config/parser/env.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aykhans/dodo/pkg/config"
|
||||
"github.com/aykhans/dodo/pkg/types"
|
||||
"github.com/aykhans/dodo/pkg/utils"
|
||||
)
|
||||
|
||||
var _ IParser = ConfigENVParser{}
|
||||
|
||||
type ConfigENVParser struct {
|
||||
envPrefix string
|
||||
}
|
||||
|
||||
func NewConfigENVParser(envPrefix string) *ConfigENVParser {
|
||||
return &ConfigENVParser{envPrefix}
|
||||
}
|
||||
|
||||
// Parse parses env arguments into a Config object.
|
||||
// It can return the following errors:
|
||||
// - types.FieldParseErrors
|
||||
func (parser ConfigENVParser) Parse() (*config.Config, error) {
|
||||
var (
|
||||
config = &config.Config{}
|
||||
fieldParseErrors []types.FieldParseError
|
||||
)
|
||||
|
||||
if configFile := parser.getEnv("CONFIG_FILE"); configFile != "" {
|
||||
configFileParsed, err := types.ParseConfigFile(configFile)
|
||||
|
||||
_ = utils.HandleErrorOrDie(err,
|
||||
utils.OnSentinelError(types.ErrConfigFileExtensionNotFound, func(err error) error {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("CONFIG_FILE"),
|
||||
configFile,
|
||||
errors.New("file extension not found"),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}),
|
||||
utils.OnCustomError(func(err types.RemoteConfigFileParseError) error {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("CONFIG_FILE"),
|
||||
configFile,
|
||||
fmt.Errorf("parse error: %w", err),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}),
|
||||
utils.OnCustomError(func(err types.UnknownConfigFileTypeError) error {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("CONFIG_FILE"),
|
||||
configFile,
|
||||
fmt.Errorf("file type '%s' not supported (supported types: %s)", err.Type, types.ConfigFileTypeYAML),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
config.Files = append(config.Files, *configFileParsed)
|
||||
}
|
||||
}
|
||||
|
||||
if yes := parser.getEnv("YES"); yes != "" {
|
||||
yesParsed, err := utils.ParseString[bool](yes)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("YES"),
|
||||
yes,
|
||||
errors.New("invalid value for boolean, expected 'true' or 'false'"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
config.Yes = &yesParsed
|
||||
}
|
||||
}
|
||||
|
||||
if skipVerify := parser.getEnv("SKIP_VERIFY"); skipVerify != "" {
|
||||
skipVerifyParsed, err := utils.ParseString[bool](skipVerify)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("SKIP_VERIFY"),
|
||||
skipVerify,
|
||||
errors.New("invalid value for boolean, expected 'true' or 'false'"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
config.SkipVerify = &skipVerifyParsed
|
||||
}
|
||||
}
|
||||
|
||||
if method := parser.getEnv("METHOD"); method != "" {
|
||||
config.Method = &method
|
||||
}
|
||||
|
||||
if urlEnv := parser.getEnv("URL"); urlEnv != "" {
|
||||
urlEnvParsed, err := url.Parse(urlEnv)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(parser.getFullEnvName("URL"), urlEnv, err),
|
||||
)
|
||||
} else {
|
||||
config.URL = urlEnvParsed
|
||||
}
|
||||
}
|
||||
|
||||
if dodos := parser.getEnv("DODOS"); dodos != "" {
|
||||
dodosParsed, err := utils.ParseString[uint](dodos)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("DODOS"),
|
||||
dodos,
|
||||
errors.New("invalid value for unsigned integer"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
config.DodosCount = &dodosParsed
|
||||
}
|
||||
}
|
||||
|
||||
if requests := parser.getEnv("REQUESTS"); requests != "" {
|
||||
requestsParsed, err := utils.ParseString[uint](requests)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("REQUESTS"),
|
||||
requests,
|
||||
errors.New("invalid value for unsigned integer"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
config.RequestCount = &requestsParsed
|
||||
}
|
||||
}
|
||||
|
||||
if duration := parser.getEnv("DURATION"); duration != "" {
|
||||
durationParsed, err := utils.ParseString[time.Duration](duration)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("DURATION"),
|
||||
duration,
|
||||
errors.New("invalid value duration, expected a duration string (e.g., '10s', '1h30m')"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
config.Duration = &durationParsed
|
||||
}
|
||||
}
|
||||
|
||||
if timeout := parser.getEnv("TIMEOUT"); timeout != "" {
|
||||
timeoutParsed, err := utils.ParseString[time.Duration](timeout)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("TIMEOUT"),
|
||||
timeout,
|
||||
errors.New("invalid value duration, expected a duration string (e.g., '10s', '1h30m')"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
config.Timeout = &timeoutParsed
|
||||
}
|
||||
}
|
||||
|
||||
if param := parser.getEnv("PARAM"); param != "" {
|
||||
config.Params.Parse(param)
|
||||
}
|
||||
|
||||
if header := parser.getEnv("HEADER"); header != "" {
|
||||
config.Headers.Parse(header)
|
||||
}
|
||||
|
||||
if cookie := parser.getEnv("COOKIE"); cookie != "" {
|
||||
config.Cookies.Parse(cookie)
|
||||
}
|
||||
|
||||
if body := parser.getEnv("BODY"); body != "" {
|
||||
config.Bodies.Parse(body)
|
||||
}
|
||||
|
||||
if proxy := parser.getEnv("PROXY"); proxy != "" {
|
||||
err := config.Proxies.Parse(proxy)
|
||||
if err != nil {
|
||||
fieldParseErrors = append(
|
||||
fieldParseErrors,
|
||||
*types.NewFieldParseError(
|
||||
parser.getFullEnvName("PROXY"),
|
||||
proxy,
|
||||
err,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if len(fieldParseErrors) > 0 {
|
||||
return nil, types.NewFieldParseErrors(fieldParseErrors)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (parser ConfigENVParser) getFullEnvName(envName string) string {
|
||||
if parser.envPrefix == "" {
|
||||
return envName
|
||||
}
|
||||
return parser.envPrefix + "_" + envName
|
||||
}
|
||||
|
||||
func (parser ConfigENVParser) getEnv(envName string) string {
|
||||
return os.Getenv(parser.getFullEnvName(envName))
|
||||
}
|
1166
pkg/config/parser/env_test.go
Normal file
1166
pkg/config/parser/env_test.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user