Skip to content

Instantly share code, notes, and snippets.

@abraithwaite
Created March 15, 2017 03:03
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save abraithwaite/ab77f610387ec9643576878c58b51923 to your computer and use it in GitHub Desktop.
Save abraithwaite/ab77f610387ec9643576878c58b51923 to your computer and use it in GitHub Desktop.
Awesome way to do configuration in go. Taken from https://github.com/influxdata/telegraf
package main
import (
"io/ioutil"
"log"
"github.com/naoina/toml"
"github.com/naoina/toml/ast"
)
type global struct {
Planet string
}
type config struct {
World global
Inputs []input
Outputs []output
}
func parseConfig(file string) config {
tbl, err := parseFile(file)
if err != nil {
log.Println(err)
}
c := config{}
for k, v := range tbl.Fields {
st, ok := v.(*ast.Table)
if !ok {
log.Println("not a table")
}
if k == "global" {
if err := toml.UnmarshalTable(st, &c.World); err != nil {
log.Println(err)
}
} else {
// table is a plugin
for plug, val := range st.Fields {
log.Println("plugin:", plug)
plugt, ok := val.([]*ast.Table)
if !ok {
log.Println("improper format")
}
for _, v := range plugt {
switch k {
case "inputs":
init := ireg[plug]()
if err := toml.UnmarshalTable(v, init); err != nil {
log.Println(err)
}
c.Inputs = append(c.Inputs, init)
case "outputs":
init := oreg[plug]()
if err := toml.UnmarshalTable(v, init); err != nil {
log.Println(err)
}
c.Outputs = append(c.Outputs, init)
default:
log.Printf("unknown config: %s, %s", k, v)
}
}
}
}
}
return c
}
// parseFile loads a TOML configuration from a provided path and
// returns the AST produced from the TOML parser. When loading the file, it
// will find environment variables and replace them.
func parseFile(fpath string) (*ast.Table, error) {
contents, err := ioutil.ReadFile(fpath)
if err != nil {
return nil, err
}
return toml.Parse(contents)
}
[global]
planet = "Earth"
# comment
[[inputs.chinese]]
surname = "Braithwaite"
[[inputs.chinese]]
surname = "Brood"
[[inputs.japanese]]
fullname = "草薙 素子"
[[inputs.english]]
name = "Alan"
[[outputs.chinese]]
surname = "Braithwaite"
[[outputs.english]]
Name = "Vasti"
package main
import (
"fmt"
"io/ioutil"
"github.com/naoina/toml"
"github.com/naoina/toml/ast"
)
type input interface {
Hello()
}
type output interface {
Goodbye()
}
func main() {
c := parseConfig("main.conf")
fmt.Printf("Hello, %s\n", c.World.Planet)
for _, v := range c.Inputs {
v.Hello()
}
for _, v := range c.Outputs {
v.Goodbye()
}
}
abraithwaite at interwebs in ~GOPATH/src/sandbox
02:59:15 $ ./sandbox
2017/03/15 02:59:15 plugin: chinese
2017/03/15 02:59:15 plugin: japanese
2017/03/15 02:59:15 plugin: english
2017/03/15 02:59:15 plugin: chinese
2017/03/15 02:59:15 plugin: english
Hello, Earth
你好,Braithwaite!
你好,Brood!
こんにちは,草薙 素子!
Hello, Alan!
再见, Braithwaite!
Goodbye, Vasti!
package main
import "fmt"
type chinese struct {
Surname string
}
func (c chinese) Hello() {
fmt.Printf("你好,%s!\n", c.Surname)
}
func (c chinese) Goodbye() {
fmt.Printf("再见, %s!\n", c.Surname)
}
func newChinese() input { return &chinese{} }
func newChineseo() output { return &chinese{} }
type japanese struct {
FullName string `toml:"fullname"`
}
func (c japanese) Hello() {
fmt.Printf("こんにちは,%s!\n", c.FullName)
}
func newJapanese() input { return &japanese{} }
type english struct {
Name string
}
func (c english) Hello() {
fmt.Printf("Hello, %s!\n", c.Name)
}
func (c english) Goodbye() {
fmt.Printf("Goodbye, %s!\n", c.Name)
}
func newEnglish() input { return &english{} }
func newEnglisho() output { return &english{} }
var ireg = make(map[string]func() input)
var oreg = make(map[string]func() output)
// init registers the plugins. Each new function returns a blank copy to be
// filled in by the config
func init() {
ireg["chinese"] = newChinese
ireg["english"] = newEnglish
ireg["japanese"] = newJapanese
oreg["chinese"] = newChineseo
oreg["english"] = newEnglisho
}
@djui
Copy link

djui commented Mar 23, 2017

I assume this style of configuration file parsing is only preferable in scenarios where secrets are less relevant? Otherwise I wonder how the file gets shipped? On platforms like Heroku, GCP, Digital Ocean, et al. Using environment variables is preferable? #12factors
Or would it be a mix of both technics?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment