From 6e5331dfe99fe1a98b30d0c7517a3fc0060b985a Mon Sep 17 00:00:00 2001 From: Kevin Fries Date: Fri, 9 Aug 2024 14:05:53 -0600 Subject: [PATCH] Initial Version --- .tool-versions | 1 + README.md | 214 ++++++++++++++++++++++++++++- config.go | 126 ++++++++++++++++++ config_test.go | 281 +++++++++++++++++++++++++++++++++++++++ features/app.feature | 20 +++ features/bool.feature | 29 ++++ features/float.feature | 29 ++++ features/integer.feature | 29 ++++ features/string.feature | 29 ++++ go.mod | 40 ++++++ go.sum | 103 ++++++++++++++ 11 files changed, 900 insertions(+), 1 deletion(-) create mode 100644 .tool-versions create mode 100644 config.go create mode 100644 config_test.go create mode 100644 features/app.feature create mode 100644 features/bool.feature create mode 100644 features/float.feature create mode 100644 features/integer.feature create mode 100644 features/string.feature create mode 100644 go.mod create mode 100644 go.sum diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..2f1d353 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +golang 1.22.6 diff --git a/README.md b/README.md index 5de22be..4c29971 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,214 @@ -# config +# GO Config Module +## Common golang config module for all programs +### Purpose + +There are several config management libraries for every programming language. Keeping them straight and consistent can be a challenge. This library uses a very common library for the GO programming language, and wraps it up with some standard practices. The goal is to ensure that all programs written in GO will behave similarly from the command line. + +### Underlying libraries + +This package is a wrapper around 3 libraries: + +* [Viper](https://pkg.go.dev/github.com/spf13/viper) - For storing and retrieving configuration items +* [PFlag](https://pkg.go.dev/github.com/spf13/pflag) - For enhanced command line configuration +* [SemVer](https://pkg.go.dev/github.com/Masterminds/semver) - For Semantic Versioning + +The config item in this package is a Viper object, so anything my convenience methods do not support, you still can operate directly on Viper and PFlag objects. You can look those APIs up on your own. + +The remainder of this README file will discuss the value add from the plain vanilla libraries + +### Multiple levels of Config + +This package is built to allow multiple methods to config your application depending on your use case. But do so in a way that makes it consistent across multiple applications. A config item can be defined in any one of the following locations: + +* A Default Value +* Inside a config file (YAML format by default, but TOML, and JSON also supported) +* An Environment Variable +* A command line flag +* Code level override + +### Workflow + +Configuration items are designed so that there is a hierarchy to them. If you look at the list above, they are from most static source, to most dynamic source. IF an item is defined in multiple locations, the more dynamic source will override any more static settings for the item. + +* A config item is created by defining it a default value. + * If a value exists in a config file, the config item will then take that value + * If an environment variable exists, it will now set the config item + * If a command line flag is defined, it will override all others + +The final level is setting the value in code. Think of this as a "running config" + +### Set application level parameters + +This library at this point only contains 3 application level parameters: + +* Application Name: a name for your application, shows up in help +* Application Prefix: a short one word name used to path environment variables and config file +* Application Version: The semantic version value for this application + +To set the application name and prefix: + +~~~ + SetAppName("My Awesome App", "myapp") +~~~ + +To set the application version: + +~~~ + SetAppVersion("1.2.3") +~~~ + +This will define the following config items: + +* app.name +* app.prefix +* app.version + +These are all queryable from within your program + +It will also set the default locations to look for the config file to: + +* /etc/{app.prefix}/config +* $HOME/.{app.prefix}/config +* $PWD/etc/config + +### Overriding the config filename or format + +The library contains a convenience method to set the file name and format: + +~~~ + SetConfigFile("config", "yaml") +~~~ + +If you do nothing, this is the default, otherwise set it as your use case dictates + +### Automatic Command Line Flags + +By default, this package will automatically define two command line flags for you. + +* -h/--help to get help from the command line +* -v/--version to get the application version from the command line + +To stop this automatic behavior, simply set the variable autoHelpAndVersion to false + +### Defining Configuration Items + +To define configuration items, there is a simple convenience method to do so: + +~~~ + DefineConfigItem("myString", "s", "cool saying", "A string to show how cool I am") + DefineConfigItem("myInt", "i", 1500, "An int just because") + DefineConfigItem("myFloat", "f", 35.99, "Great for holding a price") + DefineConfigItem("myBool", "b", true, "Yep, supports bools too") +~~~ + +The first line of this code defines: + +* A config item "myString" +* a command line flag "--myString" +* a short flag "-s" +* a help message "A string to show how cool I am" +* sets the value to "cool saying" +* will look in the config file for a setting named myString +* Will look for an environment variable named "_MYSTRING" + +lots of stuff for a single line of code. The other three items will do similar things with their appropriate data types. + +***Note:** if you set the short flag parameter to nil, it will not create a short flag, only a long one + +***Note2:** Environment variable will be uppercased as is convention, and prefixed with the application prefix to keep this programs env vars separate from other applications running on the same box.* + +### Hierarchical config items + +Sometimes you desire to group certain config items together when it makes sense to do so. For example, if your application were to connect to a remote database. It would make sense that the database config items should be grouped together. Items such as: + +* URL to reach the server +* Port the server is listening to +* Username to access the database +* Password to access the database +* Database name +* and Table name + +to do this, you can separate the parts of the name with periods. So, I could name each of the items above as follows: + +* db.url +* db.port +* db.username +* db.password +* db.database +* db.table + +If I use the yaml config file to set these values, the yaml will also reflect the hierarchical nature of this data. Add these values to the config file like so: + +~~~yaml +--- +db: + url: localhost + port: 5432 + username: dbGuru + password: YouWillNeverGuessThis + database: employees + table: users +~~~ + +Environment variables will replace the period with underscores, so I can set the password in an environment variable for an application with a prefix of "myapp" as: + + MYAPP_DB_PASSWORD=OkYouGuessedTheLastOneButYouWillNeverGuessThisOne + +### Main Method Call + +Once your application and config items are defined, you can have the library retrieve all your configs with a single call from your main(): + + GetConfigs() + +## Example + +**db.go** +~~~ +... + +func init() { + DefineConfigItem("db.url", nil, "localhost", "Database URL") + DefineConfigItem("db.port", nil, 5432, "Database port") + DefineConfigItem("db.username", nil, "dbGuru") + DefineConfigItem("db.password", nil, "StopGuessingMyPasswords") + DefineConfigItem("db.database", nil, "Inventory") + } + + // My database manipulation code goes here + // dbConnect(config.getString("db.url"), config.getString("db.username"), config.getString("db.password") +~~~ + +**web.go** +~~~ +... + +func init() { + DefineConfigItem("web.bindaddr", "a", "localhost", "Web Bind Address") + DefineConfigItem("web.port", "p", 443, "Web Bind Port") + } + + // My web server definition code goes here +~~~ + +**main.go** +~~~ +... + +func init() { + SetAppName("My App", "myapp") + SetAppVersion("1.0.5") + + DefineConfigItem("debug", "D", false, "debug node") +} + +func main() { + GetCongig() + + if config.GetBool("debug") { + // debug mode is on + } + + ... +} +~~~ diff --git a/config.go b/config.go new file mode 100644 index 0000000..586d8f1 --- /dev/null +++ b/config.go @@ -0,0 +1,126 @@ +package config + +import ( + "errors" + "fmt" + "log" + "os" + "strings" + + "github.com/Masterminds/semver" + + "github.com/spf13/afero" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +var ( + Config = viper.GetViper() + autoHelpAndVersion = true + configFS = afero.NewOsFs() +) + +func init() { + SetConfigFile("config", "yaml") +} + +func SetConfigFile(name string, format string) { + viper.SetDefault("Config.filename", name) + viper.SetDefault("Config.format", format) +} + +func AddHelpAndVersion(helpRequested *bool, versionRequested *bool) { + if autoHelpAndVersion { + if !viper.IsSet("help") { + pflag.BoolVarP(helpRequested, "help", "h", false, "This message") + } + + if !viper.IsSet("version") { + pflag.BoolVarP(versionRequested, "version", "v", false, "Get Program Version") + } + } +} + +func DefineConfigItem(key string, short string, def interface{}, helpString string) { + switch t := def.(type) { + case bool: + viper.SetDefault(key, t) + pflag.BoolP(key, short, viper.GetBool(key), helpString) + case float64: + viper.SetDefault(key, t) + pflag.Float64P(key, short, viper.GetFloat64(key), helpString) + case int: + viper.SetDefault(key, t) + pflag.IntP(key, short, viper.GetInt(key), helpString) + case string: + viper.SetDefault(key, t) + pflag.StringP(key, short, viper.GetString(key), helpString) + default: + log.Fatalln("Config Type Unsupported") + } +} + +func SetAppName(appName, appPrefix string) { + viper.SetDefault("app.name", appName) + viper.SetDefault("app.prefix", appPrefix) +} + +func SetAppVersion(verStr string) { + var appVersion *semver.Version + var err error + + appVersion, err = semver.NewVersion(verStr) + if err != nil { + log.Fatalln("Error setting app version") + } + + viper.SetDefault("app.version", appVersion) +} + +func GetConfigs() { + var helpRequested bool + var versionRequested bool + + viper.SetFs(configFS) + + viper.SetConfigName(viper.GetString("Config.filename")) + viper.SetConfigType(viper.GetString("Config.format")) + + viper.AddConfigPath(fmt.Sprintf("/etc/%s/", viper.GetString("app.prefix"))) + viper.AddConfigPath(fmt.Sprintf("$HOME/.%s/", viper.GetString("app.prefix"))) + viper.AddConfigPath("./etc/") + viper.WatchConfig() + + viper.SetEnvPrefix(viper.GetString("app.prefix")) + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() + + if autoHelpAndVersion { + AddHelpAndVersion(&helpRequested, &versionRequested) + autoHelpAndVersion = false // insure you do not add these twice, or you will experience a failure + } + + if err := viper.ReadInConfig(); err != nil { + var configFileNotFoundError viper.ConfigFileNotFoundError + + if errors.As(err, &configFileNotFoundError) { + } else { + log.Fatal("invalid Config file format", err) + } + } + + pflag.Parse() + _ = viper.BindPFlags(pflag.CommandLine) + + if helpRequested { + fmt.Printf("Usage: %s:\n", os.Args[0]) + pflag.PrintDefaults() + + os.Exit(0) + } + if versionRequested { + fmt.Printf("%s: %s\n", os.Args[0], viper.Get("app.version")) + + os.Exit(0) + } +} diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..0896eef --- /dev/null +++ b/config_test.go @@ -0,0 +1,281 @@ +package config + +import ( + "context" + "fmt" + "github.com/Masterminds/semver" + "github.com/spf13/afero" + "os" + filepath2 "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/cucumber/godog" + "github.com/cucumber/godog/colors" + + "github.com/spf13/pflag" + + a "github.com/stretchr/testify/assert" +) + +var ( + SupportedDataTypes = [4]string{"string", "integer", "float", "boolean"} + assertTest *a.Assertions + assertError = fmt.Errorf("assert error") + opts = godog.Options{ + Output: colors.Colored(os.Stdout), + Format: "pretty", + Paths: []string{"features"}, + } + originalCommandLine = os.Args +) + +const ( + StringDefaultValue = "default value" + StringConfigValue = "config file value" + StringEnvironmentValue = "environment variable value" + StringFlagValue = "command line value" + + IntegerDefaultValue = 123 + IntegerConfigValue = 234 + IntegerEnvironmentValue = "345" + IntegerFlagValue = 456 + + FloatDefaultValue = 1.99 + FloatConfigValue = 2.99 + FloatEnvironmentValue = "3.99" + FloatFlagValue = 4.99 + + BoolDefaultValue = false + BoolConfigValue = true + BoolEnvironmentValue = "false" + BoolFlagValue = true +) + +func init() { + godog.BindCommandLineFlags("godog.", &opts) + configFS = afero.NewMemMapFs() +} + +func TestCucumber(t *testing.T) { + pflag.Parse() + assertTest = a.New(t) + + opts.Paths = pflag.Args() + opts.TestingT = t + + suite := godog.TestSuite{ + Name: "pretty", + ScenarioInitializer: InitializeScenario, + TestSuiteInitializer: InitializeTestSuite, + Options: &opts, + } + + if suite.Run() != 0 { + t.Fatal("cucumber tests failed") + } +} + +func InitializeTestSuite(ctx *godog.TestSuiteContext) { + ctx.BeforeSuite(func() { + os.Args = []string{originalCommandLine[0]} + }) + + ctx.AfterSuite(func() { + os.Args = originalCommandLine + }) +} + +func InitializeScenario(ctx *godog.ScenarioContext) { + ctx.Given(`^an application with a name of "([^"]*)" and a prefix of "([^"]*)"$`, setAppNameAndPrefix) + ctx.Given(`^a version of "([^"]*)"$`, setAppVersion) + ctx.Given(`^a "([^"]*)" configuration item defines as$`, setupConfigItem) + + ctx.When(`^I call GetConfig$`, callGetConfig) + + ctx.Then(`^the "([^"]*)" result should return "([^"]*)"$`, checkConfigValue) + ctx.Then(`^the app name should return "([^"]*)"$`, theAppNameShouldReturn) + ctx.Then(`^the app prefix should return "([^"]*)"$`, theAppPrefixShouldReturn) + ctx.Then(`^the app version should pass the following constraint: "([^"]*)"$`, theAppVersionShouldPassTheFollowingConstraint) +} + +// Given Callbacks +func setAppNameAndPrefix(ctx context.Context, name, prefix string) context.Context { + SetAppName(name, prefix) + return context.WithValue(ctx, "appPrefix", prefix) +} + +func setAppVersion(_ context.Context, versionString string) { + SetAppVersion(versionString) +} + +func setupConfigItem(ctx context.Context, itemType string, itemDefinition *godog.Table) (context.Context, error) { + if assertTest.Containsf(SupportedDataTypes, itemType, "config items of type '%s' are not yet supported", itemType) { + var ( + data = make(map[string]string) + name string + shortFlag = "" + helpText = "" + + fileContents = "" + + defVal any + configVal any + envVal string + flagVal any + ) + + for idx, key := range itemDefinition.Rows[0].Cells { + data[key.Value] = itemDefinition.Rows[1].Cells[idx].Value + } + + ctx = context.WithValue(ctx, "data", data) + + name = data["name"] + switch itemType { + case "string": + defVal = StringDefaultValue + configVal = StringConfigValue + envVal = StringEnvironmentValue + flagVal = StringFlagValue + helpText = "String Config Item Test" + case "integer": + defVal = IntegerDefaultValue + configVal = IntegerConfigValue + envVal = IntegerEnvironmentValue + flagVal = IntegerFlagValue + helpText = "Integer Config Item Test" + case "float": + defVal = FloatDefaultValue + configVal = FloatConfigValue + envVal = FloatEnvironmentValue + flagVal = FloatFlagValue + helpText = "Float Config Item Test" + case "boolean": + defVal = BoolDefaultValue + configVal = BoolConfigValue + envVal = BoolEnvironmentValue + flagVal = BoolFlagValue + helpText = "Bool Config Item Test" + } + + if data["has_file_entry"] == "true" { + var dirPath = filepath2.Join("/", "etc", Config.GetString("app.prefix")) + var filename = filepath2.Join(dirPath, "config.yaml") + + fileContents = fmt.Sprintf("---\n%s: %v\n", name, configVal) + + err := configFS.MkdirAll(dirPath, 0755) + if !assertTest.NoError(err, "unable to create test config folder") { + if !assertTest.DirExists(dirPath, "unable to create test config folder") { + return ctx, assertError + } + } + + err = afero.WriteFile(configFS, filename, []byte(fileContents), 0755) + if !assertTest.NoError(err, "unable to write file contents") { + return ctx, fmt.Errorf(err.Error()) + } + } + + if data["has_env_var"] == "true" { + if data["has_env_var"] == "true" { + key := strings.ToUpper( + strings.ReplaceAll( + strings.ReplaceAll( + fmt.Sprintf("test_%s", name), + "-", + "_", + ), + ".", + "_", + ), + ) + _ = os.Setenv(key, envVal) + } + } + + if data["commandline_flag"] != "" { + os.Args = append(os.Args, fmt.Sprintf("%s=%v", data["commandline_flag"], flagVal)) + tagName := strings.Trim(data["commandline_flag"], "-") + if len(tagName) == 1 { + shortFlag = tagName + } + } + + DefineConfigItem(name, shortFlag, defVal, helpText) + + return ctx, nil + } else { + return ctx, assertError + } +} + +// When Callbacks +func callGetConfig(_ context.Context) { + GetConfigs() +} + +// Then Callbacks +func checkConfigValue(ctx context.Context, itemType, expectedString string) error { + var ( + // key = ctx.Value("itemName").(string) + data = ctx.Value("data").(map[string]string) + key = data["name"] + err error + ) + + if assertTest.Containsf(SupportedDataTypes, itemType, "config items of type '%s' are not yet supported", itemType) { + var actual, expected interface{} + switch itemType { + case "string": + actual = strings.Trim(Config.GetString(key), "\"") + expected = expectedString + case "integer": + actual = Config.GetInt64(key) + expected, _ = strconv.ParseInt(expectedString, 10, 64) + case "float": + actual = Config.GetFloat64(key) + expected, _ = strconv.ParseFloat(expectedString, 64) + case "boolean": + actual = Config.GetBool(key) + expected, _ = strconv.ParseBool(expectedString) + } + + if !assertTest.Equal(expected, actual, "config item should equal expected", os.Args, data) { + err = assertError + } + } else { + err = assertError + } + + return err +} + +func theAppNameShouldReturn(expected string) error { + if !assertTest.Equal(expected, Config.GetString("app.name")) { + return assertError + } + return nil +} + +func theAppPrefixShouldReturn(expected string) error { + if !assertTest.Equal(expected, Config.GetString("app.prefix")) { + return assertError + } + return nil +} + +func theAppVersionShouldPassTheFollowingConstraint(constraintString string) error { + var ( + constraint, _ = semver.NewConstraint(constraintString) + actual = Config.Get("app.version").(*semver.Version) + ) + + if !assertTest.True(constraint.Check(actual), "version does not pass check") { + return assertError + } + + return nil +} diff --git a/features/app.feature b/features/app.feature new file mode 100644 index 0000000..8dbb184 --- /dev/null +++ b/features/app.feature @@ -0,0 +1,20 @@ +#noinspection CucumberUndefinedStep +Feature: String configuration items + In order to be able to create and retrieve application leven config items + As a developer + I want to develop code that will make this simple, efficient, and consistent + + Scenario: Check app name and prefix + Given an application with a name of "Testing App" and a prefix of "test" + When I call GetConfig + Then the app name should return "Testing App" + And the app prefix should return "test" + + Scenario: Check app version + Given an application with a name of "Testing App" and a prefix of "test" + And a version of "1.2.3" + When I call GetConfig + Then the app version should pass the following constraint: "=1.2.3" + And the app version should pass the following constraint: "~1.2" + And the app version should pass the following constraint: ">1.1" + And the app version should pass the following constraint: "<2.0" diff --git a/features/bool.feature b/features/bool.feature new file mode 100644 index 0000000..5c48dfa --- /dev/null +++ b/features/bool.feature @@ -0,0 +1,29 @@ +#noinspection CucumberUndefinedStep +Feature: String configuration items + In order to be able to create and retrieve boolean configuration items + As a developer + I want to develop code that will make this simple, efficient, and consistent + + Scenario Outline: Integer config items + Given an application with a name of "Testing App" and a prefix of "test" + And a version of "1.2.3" + And a "boolean" configuration item defines as + | name | commandline_flag | has_file_entry | has_env_var | + | | | | | + When I call GetConfig + Then the "boolean" result should return "" + + Examples: + | name | flag | configfile | environment | result | + | boolean_xxxd | | false | false | false | + | boolean_xxcd | | true | false | true | + | boolean_xexd | | false | true | false | + | boolean_xecd | | true | true | false | + | boolean_lxxd | --boolean_lxxd | false | false | true | + | boolean_lxcd | --boolean_lxcd | true | false | true | + | boolean_lexd | --boolean_lexd | false | true | true | + | boolean_lecd | --boolean_lecd | true | true | true | + | boolean_sxxd | -M | false | false | true | + | boolean_sxcd | -N | true | false | true | + | boolean_sexd | -O | false | true | true | + | boolean_secd | -P | true | true | true | diff --git a/features/float.feature b/features/float.feature new file mode 100644 index 0000000..74ef7f3 --- /dev/null +++ b/features/float.feature @@ -0,0 +1,29 @@ +#noinspection CucumberUndefinedStep +Feature: String configuration items + In order to be able to create and retrieve float configuration items + As a developer + I want to develop code that will make this simple, efficient, and consistent + + Scenario Outline: Integer config items + Given an application with a name of "Testing App" and a prefix of "test" + And a version of "1.2.3" + And a "float" configuration item defines as + | name | commandline_flag | has_file_entry | has_env_var | + | | | | | + When I call GetConfig + Then the "float" result should return "" + + Examples: + | name | flag | configfile | environment | result | + | float_xxxd | | false | false | 1.99 | + | float_xxcd | | true | false | 2.99 | + | float_xexd | | false | true | 3.99 | + | float_xecd | | true | true | 3.99 | + | float_lxxd | --float_lxxd | false | false | 4.99 | + | float_lxcd | --float_lxcd | true | false | 4.99 | + | float_lexd | --float_lexd | false | true | 4.99 | + | float_lecd | --float_lecd | true | true | 4.99 | + | float_sxxd | -I | false | false | 4.99 | + | float_sxcd | -J | true | false | 4.99 | + | float_sexd | -K | false | true | 4.99 | + | float_secd | -L | true | true | 4.99 | diff --git a/features/integer.feature b/features/integer.feature new file mode 100644 index 0000000..f64fa3a --- /dev/null +++ b/features/integer.feature @@ -0,0 +1,29 @@ +#noinspection CucumberUndefinedStep +Feature: String configuration items + In order to be able to create and retrieve integer configuration items + As a developer + I want to develop code that will make this simple, efficient, and consistent + + Scenario Outline: Integer config items + Given an application with a name of "Testing App" and a prefix of "test" + And a version of "1.2.3" + And a "integer" configuration item defines as + | name | commandline_flag | has_file_entry | has_env_var | + | | | | | + When I call GetConfig + Then the "integer" result should return "" + + Examples: + | name | flag | configfile | environment | result | + | integer_xxxd | | false | false | 123 | + | integer_xxcd | | true | false | 234 | + | integer_xexd | | false | true | 345 | + | integer_xecd | | true | true | 345 | + | integer_lxxd | --integer_lxxd | false | false | 456 | + | integer_lxcd | --integer_lxcd | true | false | 456 | + | integer_lexd | --integer_lexd | false | true | 456 | + | integer_lecd | --integer_lecd | true | true | 456 | + | integer_sxxd | -E | false | false | 456 | + | integer_sxcd | -F | true | false | 456 | + | integer_sexd | -G | false | true | 456 | + | integer_secd | -H | true | true | 456 | diff --git a/features/string.feature b/features/string.feature new file mode 100644 index 0000000..8d6fa74 --- /dev/null +++ b/features/string.feature @@ -0,0 +1,29 @@ +#noinspection CucumberUndefinedStep +Feature: String configuration items + In order to be able to create and retrieve string configuration items + As a developer + I want to develop code that will make this simple, efficient, and consistent + + Scenario Outline: String config items + Given an application with a name of "Testing App" and a prefix of "test" + And a version of "1.2.3" + And a "string" configuration item defines as + | name | commandline_flag | has_file_entry | has_env_var | + | | | | | + When I call GetConfig + Then the "string" result should return "" + + Examples: + | name | flag | configfile | environment | result | + | string_xxxd | | false | false | default value | + | string_xxcd | | true | false | config file value | + | string_xexd | | false | true | environment variable value | + | string_xecd | | true | true | environment variable value | + | string_lxxd | --string_lxxd | false | false | command line value | + | string_lxcd | --string_lxcd | true | false | command line value | + | string_lexd | --string_lexd | false | true | command line value | + | string_lecd | --string_lecd | true | true | command line value | + | string_sxxd | -A | false | false | command line value | + | string_sxcd | -B | true | false | command line value | + | string_sexd | -C | false | true | command line value | + | string_secd | -D | true | true | command line value | diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a59a54a --- /dev/null +++ b/go.mod @@ -0,0 +1,40 @@ +module config + +go 1.22.6 + +require ( + github.com/Masterminds/semver v1.5.0 + github.com/cucumber/godog v0.14.1 + github.com/spf13/afero v1.11.0 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.9.0 +) + +require ( + github.com/cucumber/gherkin/go/v26 v26.2.0 // indirect + github.com/cucumber/messages/go/v21 v21.0.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gofrs/uuid v4.3.1+incompatible // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-memdb v1.3.4 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b73bff7 --- /dev/null +++ b/go.sum @@ -0,0 +1,103 @@ +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cucumber/gherkin/go/v26 v26.2.0 h1:EgIjePLWiPeslwIWmNQ3XHcypPsWAHoMCz/YEBKP4GI= +github.com/cucumber/gherkin/go/v26 v26.2.0/go.mod h1:t2GAPnB8maCT4lkHL99BDCVNzCh1d7dBhCLt150Nr/0= +github.com/cucumber/godog v0.14.1 h1:HGZhcOyyfaKclHjJ+r/q93iaTJZLKYW6Tv3HkmUE6+M= +github.com/cucumber/godog v0.14.1/go.mod h1:FX3rzIDybWABU4kuIXLZ/qtqEe1Ac5RdXmqvACJOces= +github.com/cucumber/messages/go/v21 v21.0.1 h1:wzA0LxwjlWQYZd32VTlAVDTkW6inOFmSM+RuOwHZiMI= +github.com/cucumber/messages/go/v21 v21.0.1/go.mod h1:zheH/2HS9JLVFukdrsPWoPdmUtmYQAQPLk7w5vWsk5s= +github.com/cucumber/messages/go/v22 v22.0.0/go.mod h1:aZipXTKc0JnjCsXrJnuZpWhtay93k7Rn3Dee7iyPJjs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI= +github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c= +github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=