Golang configuration made easy with Flæg and Stært.

0-abkj5v2gw7tezuc5
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 !
0-duvqmyavafiyuhxt
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 :

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

%d blogueurs aiment cette page :