Ver código fonte

use env if necessary in loading config (#409)

Kevin Wan 3 anos atrás
pai
commit
ebec5aafab

+ 19 - 6
core/conf/config.go

@@ -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())
 	}
 }

+ 40 - 1
core/conf/config_test.go

@@ -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)
 		})
 	}
 }

+ 15 - 0
core/conf/options.go

@@ -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
+	}
+}

+ 12 - 2
core/conf/properties.go

@@ -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{

+ 33 - 0
core/conf/properties_test.go

@@ -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)

+ 1 - 1
tools/goctl/goctl.go

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