

This repo is licensed under the MIT license. Please read the full license here.
Config
This library allows you to read configuration data from a variety of
sources. Information is automatically merged according to the priority of the
source. Sources are (in priority order):
- Commandline arguments
- Environment variables
- Yaml or Json configuration files
You can access configuration data by populating a struct or by direct access
via function calls.
Configuration Files
You can specify a file to read from, but the following files will be examined
by default. If you have more than one of the files below, they will be merged
and can override each other according to priority. The default files are
(in priority order):
Populating A Struct
You can read configuration data by populating a struct. You can make use of the following tags in your structs:
default
- set a default value if no source data is found
required
(true
) - returns an error if no data is found for this variable
src
- override the name of the data source - if you add src="myVar"
to any variable, it will populate from the
environment variable or yaml or json variable myVar
base64
- this will decode your sourced data from base64 encoding. Set this tag to
optional
- in this case the data will be decoded from base64. If decoding fails, the
original value will be used. This is useful for times when the source of the data changes
and sometimes you need to base64 encode, and sometimes not
true
- in this case the data will be decoded; if decoding fails the data will
be set to the empty string
Commandline arguments, environment variables and variables in
yaml or json files can be either a direct case match, or all in capitals eg.
if your struct contains the variable Name
, the environment variables Name
and
NAME
will be a match. If you require an exact match, use the struct
tag literal
(set to true) to enforce an exact match.
Code Examples
Populating A Struct
package main
import (
"encoding/json"
"fmt"
"github.com/driscollos/config"
"time"
)
type Teacher struct {
Name string `required:"true"`
Age int
Classes map[string]struct {
Pupils []struct {
Name string
Attendance float64
Enrolled bool `default:"true"`
}
ClassLength time.Duration
Location string `default:"Spare Classroom"`
}
LuckyNumbers []float64
LotteryPicks []float64 `default:"10,31,55"`
}
func main() {
t := Teacher{}
c := config.New()
c.Populate(&t)
bytes, _ := json.Marshal(t)
fmt.Println(string(bytes))
}
And the associated yaml file (in this case env.yml
)
Name: John
Age: 41
Classes:
Computer Science:
ClassLength: 2 hours
Pupils:
- Name: Bob
Attendance: 78.4
Enrolled: yes
- Name: Theresa
Attendance: 81.6
Enrolled: y
- Name: Jim
Attendance: 80.5
Enrolled: true
- Name: Tom
Attendance: 30.2
Enrolled: n
- Name: Henry
Attendance: 45.82
Enrolled: false
- Name: Laura
Attendance: 88.1
History:
ClassLength: 3 hours
Pupils:
- Name: Pete
Attendance: 81.4
Enrolled: 1
Location: Room C4
LuckyNumbers:
- 10
- 21
- 56
The output of this code will be:
{"Name":"John","Age":41,"Classes":{"Computer Science":{"Pupils":[{"Name":"Bob","Attendance":78.4,"Enrolled":true},{"Name":"Theresa","Attendance":81.6,"Enrolled":true},{"Name":"Jim","Attendance":80.5,"Enrolled":true},{"Name":"Tom","Attendance":30.2,"Enrolled":false},{"Name":"Henry","Attendance":45.82,"Enrolled":false},{"Name":"Laura","Attendance":88.1,"Enrolled":true}],"ClassLength":7200000000000,"Location":"Spare Classroom"},"History":{"Pupils":[{"Name":"Pete","Attendance":81.4,"Enrolled":true}],"ClassLength":10800000000000,"Location":"Room C4"}},"LuckyNumbers":[10,21,56],"LotteryPicks":[10,31,55]}
You can override any of the data in the yaml file by setting an environment variable (as these have higher priorty than yaml files).
For example running this:
export Classes_Computer_Science_Pupils_0_Name="Steve"
will change the output of the code to this:
{"Name":"John","Age":41,"Classes":{"Computer Science":{"Pupils":[{"Name":"Steve","Attendance":78.4,"Enrolled":true},{"Name":"Theresa","Attendance":81.6,"Enrolled":true},{"Name":"Jim","Attendance":80.5,"Enrolled":true},{"Name":"Tom","Attendance":30.2,"Enrolled":false},{"Name":"Henry","Attendance":45.82,"Enrolled":false},{"Name":"Laura","Attendance":88.1,"Enrolled":true}],"ClassLength":7200000000000,"Location":"Spare Classroom"},"History":{"Pupils":[{"Name":"Pete","Attendance":81.4,"Enrolled":true}],"ClassLength":10800000000000,"Location":"Room C4"}},"LuckyNumbers":[10,21,56],"LotteryPicks":[10,31,55]}
Note that:
- You can populate elements of a slice by adding the integer index to your env variable
- Spaces in the name of variables eg. the class
Computer Science
should be converted to underscores eg Computer_Science
Accessing Variables Directly
You can access parameters with the following type functions. Give the name of the variable you want to access; separate levels of nested fields
with an underscore eg. Classes_Computer_Science_Pupils_0_Name
.
These functions will take environment variables and provide them in various formats.
Bool(param string) bool
Date(param string) time.Time
Exists(name string) bool
Float(param string) float64
Int(param string) int
IntWithDefault(param string, defaultVal int) int
String(param string) string
StringWithDefault(param, defaultVal string) string
Parsing of time.Duration
default values in struct tags supports a variety of conventions. All of the following are supported defaults:
1s1m1h1d
1s, 1m, 1h, 1d
1 second, 1 minute, 1 hour, 1 day
1 sec, 1 minute, 1 hr, 1d
1 second, 1 min, 1hr, 1 day
Specifying a file to source data from
You can specify the exact file which should be used to populate your config. If you specify a source file,
all other potential file sources will be ignored. Data from the terminal and the environment
will still take precedence, in that order. Here is an example:
c := config.New()
c.Source("./env.yml")
Hot Reload
You can reload your configuration on the fly without stopping your service.
In order to do this, you must make a change to any of the files which contained
data when your service started up eg env.local.yml
. If one of the options
for a local data file did not exist when the service started, it will not be
checked for changes.
Using Hot Reload
To activate hot reload, call the HotReload(ctx context.Context, actions ...interface{})
function. The first parameter is a context; if the context is ever done
then
the hot reload functionality will cease. This is very useful for handling
os signals.
The second parameter is a variadic list of actions, which are defined as either
a pointer to a struct
or a function which has no parameters. You can supply
any number of these in any order you like. If you pass a pointer to a struct
it will be rehydrated from the latest information whenever the source data
changes; it is the same as calling the Populate()
function.
If you pass a function with no parameters, it will be called any time the
source data changes; you can use this to update db connections or reload
anything which relies upon the data in your configuration.
In the example below, we rehydrate the struct myInformation
every time
the underlying data changes, and we also call a function which makes use
of a closure to act on the new data.
package main
import (
"context"
"fmt"
"github.com/driscollos/config"
"time"
)
func main() {
c := config.New()
myInformation := myData{}
if err := c.Populate(&myInformation); err != nil {
fmt.Println("error", err.Error())
}
c.HotReload(context.Background(), &myInformation, func() {
func(data myData) {
fmt.Println("new name is:", data.Name)
}(myInformation)
})
for {
fmt.Println("name", myInformation.Name)
time.Sleep(time.Second)
}
}
type myData struct {
Name string
}