config/README.md
2024-08-09 14:05:53 -06:00

6.9 KiB

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 - For storing and retrieving configuration items
  • PFlag - For enhanced command line configuration
  • 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:

---
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
  }
  
  ...
}