Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter Teich committed Dec 2, 2019
1 parent 436c548 commit 86338f1
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 0 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,25 @@
# configstruct
Simple Go module to parse a configuration from environment and cli flags using struct tags.

Usage
```Go
// define a struct with tags for env name, cli flag and usage
type Config struct {
Hostname string `env:"CONFIGSTRUCT_HOSTNAME" cli:"hostname" usage:"hostname value"`
Port int `env:"CONFIGSTRUCT_PORT" cli:"port" usage:"listen port"`
Debug bool `env:"CONFIGSTRUCT_DEBUG" cli:"debug" usage:"debug mode"`
}

// create a variable of the struct type and define defaults if needed
conf := testConfig{
Hostname: "localhost",
Port: 8000,
Debug: true,
}

// now parse values from first cli flags and then env into this var
err := configstruct.Parse(&conf)
if err != nil {...}


```
70 changes: 70 additions & 0 deletions configstruct.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package configstruct

import (
"flag"
"fmt"
"os"
"reflect"
"strconv"
"strings"
)

// Parse uses a given struct c with tags and parses values from env or cli flags
func Parse(c interface{}) error {

// use reflection to deep dive into our struct
valueRef := reflect.ValueOf(c)
confType := valueRef.Elem().Type()

// iterate over struct fields for cli flags
for i := 0; i < confType.NumField(); i++ {
field := confType.Field(i)
value := valueRef.Elem().Field(i)
cli := field.Tag.Get("cli")
usage := field.Tag.Get("usage")

if cli != "" {
switch field.Type.Kind() {
case reflect.String:
flag.StringVar(valueRef.Elem().FieldByName(field.Name).Addr().Interface().(*string), cli, value.String(), usage)
case reflect.Bool:
flag.BoolVar(valueRef.Elem().FieldByName(field.Name).Addr().Interface().(*bool), cli, value.Bool(), usage)
case reflect.Int:
flag.IntVar(valueRef.Elem().FieldByName(field.Name).Addr().Interface().(*int), cli, int(value.Int()), usage)
default:
return fmt.Errorf("config cli type %s not implemented", field.Type.Name())
}
}
}

// parse cli flags
flag.Parse()

// iterate over struct fields for env values
for i := 0; i < confType.NumField(); i++ {
field := confType.Field(i)
env := field.Tag.Get("env")

envValue, found := os.LookupEnv(env)
if found {
switch field.Type.Kind() {
case reflect.String:
valueRef.Elem().FieldByName(field.Name).SetString(envValue)
case reflect.Bool:
valueRef.Elem().FieldByName(field.Name).SetBool(false)
if strings.EqualFold(envValue, "true") {
valueRef.Elem().FieldByName(field.Name).SetBool(true)
}
case reflect.Int:
value, err := strconv.ParseInt(envValue, 0, 64)
if err == nil {
valueRef.Elem().FieldByName(field.Name).SetInt(value)
}
default:
return fmt.Errorf("config env type %s not implemented", field.Type.Name())
}
}
}

return nil
}
85 changes: 85 additions & 0 deletions configstruct_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package configstruct

import (
"flag"
"github.com/stretchr/testify/assert"
"os"
"testing"
)

type testConfig struct {
Hostname string `env:"CONFIGSTRUCT_HOSTNAME" cli:"hostname" usage:"hostname value"`
Port int `env:"CONFIGSTRUCT_PORT" cli:"port" usage:"listen port"`
Debug bool `env:"CONFIGSTRUCT_DEBUG" cli:"debug" usage:"debug mode"`
}

func TestParse(t *testing.T) {

t.Run("valid cli fields", func(t *testing.T) {
os.Args = []string{"command", "-hostname", "localhost", "-port", "8080", "-debug", "true"}
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)

conf := testConfig{}

err := Parse(&conf)
assert.NoError(t, err)

assert.Equal(t, 8080, conf.Port)
assert.Equal(t, "localhost", conf.Hostname)
assert.True(t, conf.Debug)

})

t.Run("set using env fields", func(t *testing.T) {
os.Clearenv()
os.Setenv("CONFIGSTRUCT_HOSTNAME", "myhost")
os.Setenv("CONFIGSTRUCT_PORT", "9000")
os.Setenv("CONFIGSTRUCT_DEBUG", "true")

conf := testConfig{}

err := Parse(&conf)
assert.NoError(t, err)

assert.Equal(t, 9000, conf.Port)
assert.Equal(t, "myhost", conf.Hostname)
assert.True(t, conf.Debug)

})

t.Run("overwrite cli flags with env fields", func(t *testing.T) {
os.Args = []string{"command", "-hostname", "localhost", "-port", "8080", "-debug", "true"}
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)

os.Clearenv()
os.Setenv("CONFIGSTRUCT_HOSTNAME", "myhost")
os.Setenv("CONFIGSTRUCT_PORT", "9000")

conf := testConfig{}

err := Parse(&conf)
assert.NoError(t, err)

assert.Equal(t, 9000, conf.Port)
assert.Equal(t, "myhost", conf.Hostname)
assert.True(t, conf.Debug)
})

t.Run("cli with defaults", func(t *testing.T) {
os.Args = []string{"command", "-hostname", "myhost"}
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)

conf := testConfig{
Hostname: "localhost",
Port: 8000,
Debug: true,
}

err := Parse(&conf)
assert.NoError(t, err)

assert.Equal(t, 8000, conf.Port)
assert.Equal(t, "myhost", conf.Hostname)
assert.True(t, conf.Debug)
})
}
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/pteich/configstruct

go 1.13

require (
github.com/stretchr/objx v0.2.0 // indirect
github.com/stretchr/testify v1.4.0
gopkg.in/yaml.v2 v2.2.7 // indirect
)
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

0 comments on commit 86338f1

Please sign in to comment.