Golang configuration made easy with Flæg and Stært.
There are many ways to provide a configuration to a program. Some use Command Line Interfaces, others use configuration files. Any setting change can require a refactor on many lines of code.
The configuration loading process have to be very robust. If not, programs could not work as expected. Unfortunately golang programs are no exception. For instance, adding a new argument in a CLI requires to create a flag, to parse data, to deal with the help section, to manage default values and to affect the collected value into the configuration structure.
Close your eyes and imagine a library able to automatically manage all this configuration process for you. You can open them because we did it 🙂
Flæg
Flæg is a Go package for building dynamically a powerful modern Command Line Interface and loading a program configuration structure from arguments. Go developers don’t need to worry about keeping flags and commands updated anymore : it works by itself !
Flæg package was born from the need of Træfik, the famous reverse proxy created by Emile Vauge. It was more and more difficult to maintain the list of `flags`: parameters passed in argument which drive the program.
Thanks to Flæg, the maintenance became a thing of the past. The `flags` are auto generated from the field names of the configuration structure. To do that, developers just have to add a StructTag following the field to create a flag on it. The helper (i.e. what is displayed with ‘–help’ argument) is generated from the key `description` in the StructTag
Here an example of a complex configuration with StructTag that Flæg can process:
```go // Configuration is a struct which contains all differents type to field // using parsers on string, time.Duration, pointer, bool, int, int64, time.Time, float64 type Configuration struct { VersionName string //no description struct tag, it will not be flagged LogLevel string `short:"l" description:"Log level"` //string type field, short flag "-l" Timeout time.Duration `description:"Timeout duration"` //time.Duration type field Db *DatabaseInfo `description:"Enable database"` //pointer type field (on DatabaseInfo) Owner *OwnerInfo `description:"Enable Owner description"` //another pointer type field (on OwnerInfo) } // Sub-structures DatabaseInfo & OwnerInfo are defined on // https://github.com/cocap10/flaegdemo/blob/master/flaegdemo.go
Flæg is able to load the argument values given in the configuration structure through reflection. The package supports most of field types, but no collections. Hopefully users can add a custom parser on specific types as collections or even whole structures.
Let’s see how simple it is to use Flæg.
First we will create a trivial command which do nothing but print the configuration:
```go flaegCmd := &flaeg.Command{ Name: "flaegdemo", Description: "flaegdemo is a golang program...(here the program description)", Config: &config, DefaultPointersConfig: &defaultConfigOfPointerFields, Run: func() error { printableConfig, _ := json.Marshal(config) fmt.Printf("%s\n", printableConfig) return nil }, } // config and defaultConfigOfPointerFields has been defined on // https://github.com/cocap10/flaegdemo/blob/master/flaegdemo.go ```
Then, we only have to create a Flæg object and call the function Run:
```go f := flaeg.New(flaegCmd, os.Args[1:]) if err := f.Run(); err != nil { fmt.Println(err.Error()) } ```
Let’s try this program. We will first call the helper:
```shell $ ./flaegdemo --help flaegdemo is a golang program...(here the program description) Usage: flaegdemo [--flag=flag_argument] [-f[flag_argument]] ... set flag_argument to flag(s) or: flaegdemo [--flag[=true|false| ]] [-f[true|false| ]] ... set true/false to boolean flag(s) Available Commands: version Print version Use "flaegdemo [command] --help" for more information about a command. Flags: --db Enable database (default "false") --db.comax Number max of connections on database (default "100") --db.connectionmax64 Number max of connections on database (default "6400000000000000000") --db.ip Server ip address (default "192.168.0.1") --db.watch Watch device (default "true") -l, --loglevel Log level (default "INFO") --owner Enable Owner description (default "true") --owner.dob Owner date of birth (default "2016-08-03 17:03:00.665523393 +0200 CEST") --owner.name Owner name (default "default owner") --owner.rate Owner rate (default "0.5") --timeout Timeout duration (default "1s") -h, --help Print Help (this message) and exit ```
As you can see, the generated flags match the fields of the configuration structure with substructures `DatabaseInfo` and `OwnerInfo`. Default values can be defined directly in the fields of the configuration structure. These fields will be unchanged if their `flags` are not used in argument.
Then, we can try the program:
```shell $ ./flaegdemo {"VersionName":"Rebloch","LogLevel":"INFO","Timeout":1000000000,"Db":null,"Owner":{"Name":"default owner","DateOfBirth":"2016-08-03T17:36:20.85900754+02:00","Rate":0.5}} $ ./flaegdemo --db {"VersionName":"Rebloch","LogLevel":"INFO","Timeout":1000000000,"Db":{"ConnectionMax":100,"ConnectionMax64":6400000000000000000,"Watch":true,"IP":"192.168.0.1"},"Owner":{"Name":"default owner","DateOfBirth":"2016-08-03T17:36:33.683130019+02:00","Rate":0.5}} $ ./flaegdemo --db.ip=127.0.0.1 --owner.dob=1979-05-27T07:32:00Z {"VersionName":"Rebloch","LogLevel":"INFO","Timeout":1000000000,"Db":{"ConnectionMax":100,"ConnectionMax64":6400000000000000000,"Watch":true,"IP":"127.0.0.1"},"Owner":{"Name":"default owner","DateOfBirth":"1979-05-27T07:32:00Z","Rate":0.5}} ```
Flæg also handles sub-command, like the “version” command in the example:
```go f.AddCommand(flaegVersionCmd) ```
```shell $ ./flaegdemo version Version Rebloch ```
To learn much more about Flæg, please visit the github project page https://github.com/containous/flaeg.
Thanks to Flæg, we have a nice solution to load the configuration from the flags. The maintenance is now way simpler than it used to be, which is appreciated on open source projects. But how can we manage merging this CLI configuration with other sources like configuration files ?
Stært
The CLI is not the only way to provide configuration to a program. There are other sources of configuration. For instance, Træfik can take a configuration from flags, from a config file and from a distributed Key-Value Store.
We created Stært to merge those configuration sources. So far, only flags (using Flæg), TOML config file and Key-Value Stored configuration are implemented, but we did the package in such way that it is easy to add other sources.
Stært uses the same Command type as Flæg.
We just have to create some sources:
```go // TOML source toml:=staert.NewTomlSource("example", []string{"./toml/", "/any/other/path"}) // Flaeg source f:=flaeg.New(command, os.Args[1:]) // Key-Value Store source kv, err :=staert.NewKvSource(backend, options, “prefix”) ```
Then, we can create a Stært object and use it:
```go // Create staert object s:=staert.NewStaert(command) // Adding sources s.AddSource(toml) s.AddSource(f) s.AddSource(kv) // Parse and merge sources loadedConfig, err := s.LoadConfig() // Run err := s.Run(); ```
Stært will load the configuration in the row and overwrite it following precedence order:
- Key-Value Store source
- Flæg source
- TOML config file source
Here an example of TOML file:
```toml # This is a TOML document. LogLevel = "DEBUG" Timeout = "2s" [owner] name = "Tom Preston-Werner" DateOfBirth = 1979-05-27T07:32:00-08:00 Rate = 0.75 [db] IP = "192.168.1.1" ConnectionMax = 5000 ConnectionMax64 = 50000000000000000 Watch = true ```
And there is the way to store the Key-Value:
KeyValue
Key | Value |
---|---|
prefix/loglevel | “DEBUG” |
prefix/timeout | “2s” |
prefix/owner/name | “Tom Preston-Werner” |
prefix/owner/dateofbirth | “1979-05-27T07:32:00-08:00” |
prefix/owner/rate | “0.75” |
prefix/db/ip | “192.168.1.1” |
prefix/db/connectionmax | “5000” |
prefix/db/connectionmax64 | “50000000000000000” |
prefix/db/watch | “true” |
This was a simple example of what we can do with Stært.
To conclude, those two packages made us save a lot of time on the Træfik project. We can now load and merge several configurations from different sources. Any changes on the configuration structure are automagically handled.
More features are described on the Github project pages, please visit :