A GO module to parse config files.
Before creating this module, I tried viper and confer.
Both are powerful with rich features. But lastly I decided to create a different module because neither are easy to implement what I need: a Sub
function, which copies a branch of configuration and makes it a new config object.
For example: there is such a config representing:
app:
cache1:
max-items: 100
item-size: 64
cache2:
max-items: 200
item-size: 80
After executing:
cfg1 := config.Sub("app.cache1")
cfg1
represents:
max-items: 100
item-size: 64
Suppose we have:
func NewCache(cfg *Config) *Cache {...}
which creates a cache based on config information formatted as cfg1
.
Now it's easy to create these 2 caches separately as:
cfg1 := config.Sub("app.cache1")
cache1 := NewCache(cfg1)
cfg2 := config.Sub("app.cache2")
cache2 := NewCache(cfg2)
This module is carefully designed to make Sub()
function easier in these aspects:
viper
has 6 internal data structures, one overlap another, to get the value of a config item lastly. confer
has 3.
But in this module, we merge all config items into one single tree. Thus when you want to get a sub-tree, it's not sufferring to search all these internal data structure and merge them into one.
What makes the thing more complex is that, all these internal data structure may be inhomogenous.
It may be a tree, root level a map[string]interface{}
, other levels map[interface{}]interface{}
.
Or one level key-value store of map[string]interface[]
.
Since GO is not a dynamic programming language, we have to switch for all these cases in the source code and deal with them one by one.
In this module, we use an unified data structure: a tree with each level a map[interface{}][interface{}]
.
Config items in environment are merged into the internal tree when BindEnvs()
is called. If you change the env again after binding, the new value will not be in config.
- Read remote config
- Refresh config periodically or triggered by a signal
- Bind pflag
bash$ go get github.com/flei2000/config
The package itself can be used as a global config:
config.ReadFiles("/etc/app/config.yaml")
More than 1 file can be specified in this function:
config.ReadFiles("/etc/app/config.yaml", "/home/user/.app/config.yaml")
If a key is specified in more than one file, the last value wins.
Or you can create your own instance:
cfg := config.New()
cfg.ReadFiles("/etc/app/config.yaml", "/home/user/.app/config.yaml")
config.Set("app.log.level", "debug")
There a group of ways to get a config vaule:
GetInt(key string) int
GetString(key string) string
GetBool(key string) bool
Get(key string) interface{}
AllSettings() map[string]interface{}
Get()
is common method to get any value: a scalar value, a sub-tree, or even an array.
It always returns interface{}
so you need to convert it to right type.
AllSettings()
returns a map of all keys=>values
.
Sub(key string) *Config
Extract the data of a sub-tree, put it into a new Config object and return the object.
BindEnvs(prefix string)
Read all envs and merge those having prefix
to internal tree. For example:
bash$ export TESTCFG_APP_LOG_LEVEL=warning
config.BindEnvs("TESTCFG")
config.Get("app.log.level")
It should returns warning
.
There is no pre-defined priority of different config sources (file or env). If a key is specified in more than 1 source, the last source wins. Usually env should have higher priority than files, so call this function after all config files are read.