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, ".yml": LoadConfigFromYamlBytes,
} }
func LoadConfig(file string, v interface{}) error { func LoadConfig(file string, v interface{}, opts ...Option) error {
if content, err := ioutil.ReadFile(file); err != nil { content, err := ioutil.ReadFile(file)
if err != nil {
return err 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) return loader([]byte(os.ExpandEnv(string(content))), v)
} else { } 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) return mapping.UnmarshalYamlBytes(content, v)
} }
func MustLoad(path string, v interface{}) { func MustLoad(path string, v interface{}, opts ...Option) {
if err := LoadConfig(path, v); err != nil { if err := LoadConfig(path, v, opts...); err != nil {
log.Fatalf("error: config file %s, %s", path, err.Error()) log.Fatalf("error: config file %s, %s", path, err.Error())
} }
} }

@ -30,7 +30,8 @@ func TestConfigJson(t *testing.T) {
text := `{ text := `{
"a": "foo", "a": "foo",
"b": 1, "b": 1,
"c": "${FOO}" "c": "${FOO}",
"d": "abcd!@#$112"
}` }`
for _, test := range tests { for _, test := range tests {
test := test test := test
@ -45,11 +46,49 @@ func TestConfigJson(t *testing.T) {
A string `json:"a"` A string `json:"a"`
B int `json:"b"` B int `json:"b"`
C string `json:"c"` C string `json:"c"`
D string `json:"d"`
} }
MustLoad(tmpfile, &val) MustLoad(tmpfile, &val)
assert.Equal(t, "foo", val.A) assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B) 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, "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 ( import (
"fmt" "fmt"
"os"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -32,12 +33,17 @@ type mapBasedProperties struct {
// Loads the properties into a properties configuration instance. // Loads the properties into a properties configuration instance.
// Returns an error that indicates if there was a problem loading the configuration. // 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("#")) lines, err := iox.ReadTextLines(filename, iox.WithoutBlank(), iox.OmitWithPrefix("#"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
var opt options
for _, o := range opts {
o(&opt)
}
raw := make(map[string]string) raw := make(map[string]string)
for i := range lines { for i := range lines {
pair := strings.Split(lines[i], "=") pair := strings.Split(lines[i], "=")
@ -50,7 +56,11 @@ func LoadProperties(filename string) (Properties, error) {
key := strings.TrimSpace(pair[0]) key := strings.TrimSpace(pair[0])
value := strings.TrimSpace(pair[1]) value := strings.TrimSpace(pair[1])
raw[key] = value if opt.env {
raw[key] = os.ExpandEnv(value)
} else {
raw[key] = value
}
} }
return &mapBasedProperties{ return &mapBasedProperties{

@ -31,6 +31,39 @@ func TestProperties(t *testing.T) {
assert.Contains(t, val, "app.threads") 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) { func TestLoadProperties_badContent(t *testing.T) {
filename, err := fs.TempFilenameWithText("hello") filename, err := fs.TempFilenameWithText("hello")
assert.Nil(t, err) assert.Nil(t, err)

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

Loading…
Cancel
Save