use env if necessary in loading config (#409)

master
Kevin Wan 4 years ago committed by GitHub
parent 572b32729f
commit ebec5aafab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -16,13 +16,26 @@ var loaders = map[string]func([]byte, interface{}) error{
".yml": LoadConfigFromYamlBytes,
}
func LoadConfig(file string, v interface{}) error {
if content, err := ioutil.ReadFile(file); err != nil {
func LoadConfig(file string, v interface{}, opts ...Option) error {
content, err := ioutil.ReadFile(file)
if err != nil {
return err
} else if loader, ok := loaders[path.Ext(file)]; ok {
}
loader, ok := loaders[path.Ext(file)]
if !ok {
return fmt.Errorf("unrecoginized file type: %s", file)
}
var opt options
for _, o := range opts {
o(&opt)
}
if opt.env {
return loader([]byte(os.ExpandEnv(string(content))), v)
} else {
return fmt.Errorf("unrecoginized file type: %s", file)
return loader(content, v)
}
}
@ -34,8 +47,8 @@ func LoadConfigFromYamlBytes(content []byte, v interface{}) error {
return mapping.UnmarshalYamlBytes(content, v)
}
func MustLoad(path string, v interface{}) {
if err := LoadConfig(path, v); err != nil {
func MustLoad(path string, v interface{}, opts ...Option) {
if err := LoadConfig(path, v, opts...); err != nil {
log.Fatalf("error: config file %s, %s", path, err.Error())
}
}

@ -30,7 +30,8 @@ func TestConfigJson(t *testing.T) {
text := `{
"a": "foo",
"b": 1,
"c": "${FOO}"
"c": "${FOO}",
"d": "abcd!@#$112"
}`
for _, test := range tests {
test := test
@ -45,11 +46,49 @@ func TestConfigJson(t *testing.T) {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
MustLoad(tmpfile, &val)
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "${FOO}", val.C)
assert.Equal(t, "abcd!@#$112", val.D)
})
}
}
func TestConfigJsonEnv(t *testing.T) {
tests := []string{
".json",
".yaml",
".yml",
}
text := `{
"a": "foo",
"b": 1,
"c": "${FOO}",
"d": "abcd!@#$a12 3"
}`
for _, test := range tests {
test := test
t.Run(test, func(t *testing.T) {
os.Setenv("FOO", "2")
defer os.Unsetenv("FOO")
tmpfile, err := createTempFile(test, text)
assert.Nil(t, err)
defer os.Remove(tmpfile)
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
MustLoad(tmpfile, &val, UseEnv())
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "2", val.C)
assert.Equal(t, "abcd!@# 3", val.D)
})
}
}

@ -0,0 +1,15 @@
package conf
type (
Option func(opt *options)
options struct {
env bool
}
)
func UseEnv() Option {
return func(opt *options) {
opt.env = true
}
}

@ -2,6 +2,7 @@ package conf
import (
"fmt"
"os"
"strconv"
"strings"
"sync"
@ -32,12 +33,17 @@ type mapBasedProperties struct {
// Loads the properties into a properties configuration instance.
// Returns an error that indicates if there was a problem loading the configuration.
func LoadProperties(filename string) (Properties, error) {
func LoadProperties(filename string, opts ...Option) (Properties, error) {
lines, err := iox.ReadTextLines(filename, iox.WithoutBlank(), iox.OmitWithPrefix("#"))
if err != nil {
return nil, err
}
var opt options
for _, o := range opts {
o(&opt)
}
raw := make(map[string]string)
for i := range lines {
pair := strings.Split(lines[i], "=")
@ -50,7 +56,11 @@ func LoadProperties(filename string) (Properties, error) {
key := strings.TrimSpace(pair[0])
value := strings.TrimSpace(pair[1])
raw[key] = value
if opt.env {
raw[key] = os.ExpandEnv(value)
} else {
raw[key] = value
}
}
return &mapBasedProperties{

@ -31,6 +31,39 @@ func TestProperties(t *testing.T) {
assert.Contains(t, val, "app.threads")
}
func TestPropertiesEnv(t *testing.T) {
text := `app.name = test
app.program=app
app.env1 = ${FOO}
app.env2 = $none
# this is comment
app.threads = 5`
tmpfile, err := fs.TempFilenameWithText(text)
assert.Nil(t, err)
defer os.Remove(tmpfile)
os.Setenv("FOO", "2")
defer os.Unsetenv("FOO")
props, err := LoadProperties(tmpfile, UseEnv())
assert.Nil(t, err)
assert.Equal(t, "test", props.GetString("app.name"))
assert.Equal(t, "app", props.GetString("app.program"))
assert.Equal(t, 5, props.GetInt("app.threads"))
assert.Equal(t, "2", props.GetString("app.env1"))
assert.Equal(t, "", props.GetString("app.env2"))
val := props.ToString()
assert.Contains(t, val, "app.name")
assert.Contains(t, val, "app.program")
assert.Contains(t, val, "app.threads")
assert.Contains(t, val, "app.env1")
assert.Contains(t, val, "app.env2")
}
func TestLoadProperties_badContent(t *testing.T) {
filename, err := fs.TempFilenameWithText("hello")
assert.Nil(t, err)

@ -28,7 +28,7 @@ import (
)
var (
BuildVersion = "1.1.3"
BuildVersion = "1.1.5"
commands = []cli.Command{
{
Name: "upgrade",

Loading…
Cancel
Save