config/README.md

215 lines
6.9 KiB
Markdown
Raw Normal View History

2024-08-09 20:05:53 +00:00
# GO Config Module
## Common golang config module for all programs
2024-08-09 04:36:20 +00:00
2024-08-09 20:05:53 +00:00
### 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 "<app.prefix>_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
}
...
}
~~~