diff --git a/config/config.go b/config/config.go index c0afec05..9f41fb79 100644 --- a/config/config.go +++ b/config/config.go @@ -12,11 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package config is used to parse config +// Package config is used to parse config. // Usage: -// import( -// "github.com/astaxie/beego/config" -// ) +// import "github.com/astaxie/beego/config" +//Examples. // // cnf, err := config.NewConfig("ini", "config.conf") // @@ -38,12 +37,12 @@ // cnf.DIY(key string) (interface{}, error) // cnf.GetSection(section string) (map[string]string, error) // cnf.SaveConfigFile(filename string) error -// -// more docs http://beego.me/docs/module/config.md +//More docs http://beego.me/docs/module/config.md package config import ( "fmt" + "os" ) // Configer defines how to get and set value from configuration raw data. @@ -107,6 +106,69 @@ func NewConfigData(adapterName string, data []byte) (Configer, error) { return adapter.ParseData(data) } +// ExpandValueEnvForMap convert all string value with environment variable. +func ExpandValueEnvForMap(m map[string]interface{}) map[string]interface{} { + for k, v := range m { + switch value := v.(type) { + case string: + m[k] = ExpandValueEnv(value) + case map[string]interface{}: + m[k] = ExpandValueEnvForMap(value) + case map[string]string: + for k2, v2 := range value { + value[k2] = ExpandValueEnv(v2) + } + m[k] = value + } + } + return m +} + +// ExpandValueEnv returns value of convert with environment variable. +// +// Return environment variable if value start with "${" and end with "}". +// Return default value if environment variable is empty or not exist. +// +// It accept value formats "${env}" , "${env||}}" , "${env||defaultValue}" , "defaultvalue". +// Examples: +// v1 := config.ExpandValueEnv("${GOPATH}") // return the GOPATH environment variable. +// v2 := config.ExpandValueEnv("${GOAsta||/usr/local/go}") // return the default value "/usr/local/go/". +// v3 := config.ExpandValueEnv("Astaxie") // return the value "Astaxie". +func ExpandValueEnv(value string) (realValue string) { + realValue = value + + vLen := len(value) + // 3 = ${} + if vLen < 3 { + return + } + // Need start with "${" and end with "}", then return. + if value[0] != '$' || value[1] != '{' || value[vLen-1] != '}' { + return + } + + key := "" + defalutV := "" + // value start with "${" + for i := 2; i < vLen; i++ { + if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') { + key = value[2:i] + defalutV = value[i+2 : vLen-1] // other string is default value. + break + } else if value[i] == '}' { + key = value[2:i] + break + } + } + + realValue = os.Getenv(key) + if realValue == "" { + realValue = defalutV + } + + return +} + // ParseBool returns the boolean value represented by the string. // // It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On, diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 00000000..15d6ffa6 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,55 @@ +// Copyright 2016 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "os" + "testing" +) + +func TestExpandValueEnv(t *testing.T) { + + testCases := []struct { + item string + want string + }{ + {"", ""}, + {"$", "$"}, + {"{", "{"}, + {"{}", "{}"}, + {"${}", ""}, + {"${|}", ""}, + {"${}", ""}, + {"${{}}", ""}, + {"${{||}}", "}"}, + {"${pwd||}", ""}, + {"${pwd||}", ""}, + {"${pwd||}", ""}, + {"${pwd||}}", "}"}, + {"${pwd||{{||}}}", "{{||}}"}, + {"${GOPATH}", os.Getenv("GOPATH")}, + {"${GOPATH||}", os.Getenv("GOPATH")}, + {"${GOPATH||root}", os.Getenv("GOPATH")}, + {"${GOPATH_NOT||root}", "root"}, + {"${GOPATH_NOT||||root}", "||root"}, + } + + for _, c := range testCases { + if got := ExpandValueEnv(c.item); got != c.want { + t.Errorf("expand value error, item %q want %q, got %q", c.item, c.want, got) + } + } + +} diff --git a/config/fake.go b/config/fake.go index 7e362608..f5144598 100644 --- a/config/fake.go +++ b/config/fake.go @@ -38,7 +38,7 @@ func (c *fakeConfigContainer) String(key string) string { } func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string { - v := c.getData(key) + v := c.String(key) if v == "" { return defaultval } @@ -46,7 +46,7 @@ func (c *fakeConfigContainer) DefaultString(key string, defaultval string) strin } func (c *fakeConfigContainer) Strings(key string) []string { - v := c.getData(key) + v := c.String(key) if v == "" { return nil } diff --git a/config/ini.go b/config/ini.go index c3934c53..53bd992d 100644 --- a/config/ini.go +++ b/config/ini.go @@ -166,7 +166,7 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) { val = bytes.Trim(val, `"`) } - cfg.data[section][key] = string(val) + cfg.data[section][key] = ExpandValueEnv(string(val)) if comment.Len() > 0 { cfg.keyComment[section+"."+key] = comment.String() comment.Reset() @@ -300,7 +300,9 @@ func (c *IniConfigContainer) GetSection(section string) (map[string]string, erro return nil, errors.New("not exist setction") } -// SaveConfigFile save the config into file +// SaveConfigFile save the config into file. +// +// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Funcation. func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) { // Write configuration file by filename. f, err := os.Create(filename) diff --git a/config/ini_test.go b/config/ini_test.go index 93fce61f..83ff3668 100644 --- a/config/ini_test.go +++ b/config/ini_test.go @@ -42,11 +42,14 @@ needlogin = ON enableSession = Y enableCookie = N flag = 1 +path1 = ${GOPATH} +path2 = ${GOPATH||/home/go} [demo] key1="asta" key2 = "xie" CaseInsensitive = true peers = one;two;three +password = ${GOPATH} ` keyValue = map[string]interface{}{ @@ -64,10 +67,13 @@ peers = one;two;three "enableSession": true, "enableCookie": false, "flag": true, + "path1": os.Getenv("GOPATH"), + "path2": os.Getenv("GOPATH"), "demo::key1": "asta", "demo::key2": "xie", "demo::CaseInsensitive": true, "demo::peers": []string{"one", "two", "three"}, + "demo::password": os.Getenv("GOPATH"), "null": "", "demo2::key1": "", "error": "", diff --git a/config/json.go b/config/json.go index fce517eb..a0d93210 100644 --- a/config/json.go +++ b/config/json.go @@ -57,6 +57,9 @@ func (js *JSONConfig) ParseData(data []byte) (Configer, error) { } x.data["rootArray"] = wrappingArray } + + x.data = ExpandValueEnvForMap(x.data) + return x, nil } diff --git a/config/json_test.go b/config/json_test.go index df663461..24ff9644 100644 --- a/config/json_test.go +++ b/config/json_test.go @@ -86,16 +86,19 @@ func TestJson(t *testing.T) { "enableSession": "Y", "enableCookie": "N", "flag": 1, +"path1": "${GOPATH}", +"path2": "${GOPATH||/home/go}", "database": { "host": "host", "port": "port", "database": "database", "username": "username", - "password": "password", + "password": "${GOPATH}", "conns":{ "maxconnection":12, "autoconnect":true, - "connectioninfo":"info" + "connectioninfo":"info", + "root": "${GOPATH}" } } }` @@ -115,13 +118,16 @@ func TestJson(t *testing.T) { "enableSession": true, "enableCookie": false, "flag": true, + "path1": os.Getenv("GOPATH"), + "path2": os.Getenv("GOPATH"), "database::host": "host", "database::port": "port", "database::database": "database", - "database::password": "password", + "database::password": os.Getenv("GOPATH"), "database::conns::maxconnection": 12, "database::conns::autoconnect": true, "database::conns::connectioninfo": "info", + "database::conns::root": os.Getenv("GOPATH"), "unknown": "", } ) diff --git a/config/xml/xml.go b/config/xml/xml.go index b5291bf4..0c4e4d27 100644 --- a/config/xml/xml.go +++ b/config/xml/xml.go @@ -12,21 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package xml for config provider +// Package xml for config provider. // -// depend on github.com/beego/x2j +// depend on github.com/beego/x2j. // -// go install github.com/beego/x2j +// go install github.com/beego/x2j. // // Usage: -// import( -// _ "github.com/astaxie/beego/config/xml" -// "github.com/astaxie/beego/config" -// ) +// import( +// _ "github.com/astaxie/beego/config/xml" +// "github.com/astaxie/beego/config" +// ) // // cnf, err := config.NewConfig("xml", "config.xml") // -// more docs http://beego.me/docs/module/config.md +//More docs http://beego.me/docs/module/config.md package xml import ( @@ -69,7 +69,7 @@ func (xc *Config) Parse(filename string) (config.Configer, error) { return nil, err } - x.data = d["config"].(map[string]interface{}) + x.data = config.ExpandValueEnvForMap(d["config"].(map[string]interface{})) return x, nil } @@ -92,7 +92,7 @@ type ConfigContainer struct { // Bool returns the boolean value for a given key. func (c *ConfigContainer) Bool(key string) (bool, error) { - if v, ok := c.data[key]; ok { + if v := c.data[key]; v != nil { return config.ParseBool(v) } return false, fmt.Errorf("not exist key: %q", key) diff --git a/config/xml/xml_test.go b/config/xml/xml_test.go index 60dcba54..d8a09a59 100644 --- a/config/xml/xml_test.go +++ b/config/xml/xml_test.go @@ -15,14 +15,18 @@ package xml import ( + "fmt" "os" "testing" "github.com/astaxie/beego/config" ) -//xml parse should incluce in tags -var xmlcontext = ` +func TestXML(t *testing.T) { + + var ( + //xml parse should incluce in tags + xmlcontext = ` beeapi 8080 @@ -31,10 +35,25 @@ var xmlcontext = ` dev false true +${GOPATH} +${GOPATH||/home/go} ` + keyValue = map[string]interface{}{ + "appname": "beeapi", + "httpport": 8080, + "mysqlport": int64(3600), + "PI": 3.1415976, + "runmode": "dev", + "autorender": false, + "copyrequestbody": true, + "path1": os.Getenv("GOPATH"), + "path2": os.Getenv("GOPATH"), + "error": "", + "emptystrings": []string{}, + } + ) -func TestXML(t *testing.T) { f, err := os.Create("testxml.conf") if err != nil { t.Fatal(err) @@ -50,39 +69,42 @@ func TestXML(t *testing.T) { if err != nil { t.Fatal(err) } - if xmlconf.String("appname") != "beeapi" { - t.Fatal("appname not equal to beeapi") - } - if port, err := xmlconf.Int("httpport"); err != nil || port != 8080 { - t.Error(port) - t.Fatal(err) - } - if port, err := xmlconf.Int64("mysqlport"); err != nil || port != 3600 { - t.Error(port) - t.Fatal(err) - } - if pi, err := xmlconf.Float("PI"); err != nil || pi != 3.1415976 { - t.Error(pi) - t.Fatal(err) - } - if xmlconf.String("runmode") != "dev" { - t.Fatal("runmode not equal to dev") - } - if v, err := xmlconf.Bool("autorender"); err != nil || v != false { - t.Error(v) - t.Fatal(err) - } - if v, err := xmlconf.Bool("copyrequestbody"); err != nil || v != true { - t.Error(v) - t.Fatal(err) + + for k, v := range keyValue { + + var ( + value interface{} + err error + ) + + switch v.(type) { + case int: + value, err = xmlconf.Int(k) + case int64: + value, err = xmlconf.Int64(k) + case float64: + value, err = xmlconf.Float(k) + case bool: + value, err = xmlconf.Bool(k) + case []string: + value = xmlconf.Strings(k) + case string: + value = xmlconf.String(k) + default: + value, err = xmlconf.DIY(k) + } + if err != nil { + t.Errorf("get key %q value fatal,%v err %s", k, v, err) + } else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) { + t.Errorf("get key %q value, want %v got %v .", k, v, value) + } + } + if err = xmlconf.Set("name", "astaxie"); err != nil { t.Fatal(err) } if xmlconf.String("name") != "astaxie" { t.Fatal("get name error") } - if xmlconf.Strings("emptystrings") != nil { - t.Fatal("get emtpy strings error") - } } diff --git a/config/yaml/yaml.go b/config/yaml/yaml.go index 7e1d0426..64e25cb3 100644 --- a/config/yaml/yaml.go +++ b/config/yaml/yaml.go @@ -19,14 +19,14 @@ // go install github.com/beego/goyaml2 // // Usage: -// import( +// import( // _ "github.com/astaxie/beego/config/yaml" -// "github.com/astaxie/beego/config" -// ) +// "github.com/astaxie/beego/config" +// ) // // cnf, err := config.NewConfig("yaml", "config.yaml") // -// more docs http://beego.me/docs/module/config.md +//More docs http://beego.me/docs/module/config.md package yaml import ( @@ -110,6 +110,7 @@ func ReadYmlReader(path string) (cnf map[string]interface{}, err error) { log.Println("Not a Map? >> ", string(buf), data) cnf = nil } + cnf = config.ExpandValueEnvForMap(cnf) return } @@ -121,10 +122,11 @@ type ConfigContainer struct { // Bool returns the boolean value for a given key. func (c *ConfigContainer) Bool(key string) (bool, error) { - if v, ok := c.data[key]; ok { - return config.ParseBool(v) + v, err := c.getData(key) + if err != nil { + return false, err } - return false, fmt.Errorf("not exist key: %q", key) + return config.ParseBool(v) } // DefaultBool return the bool value if has no error @@ -139,8 +141,12 @@ func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool { // Int returns the integer value for a given key. func (c *ConfigContainer) Int(key string) (int, error) { - if v, ok := c.data[key].(int64); ok { - return int(v), nil + if v, err := c.getData(key); err != nil { + return 0, err + } else if vv, ok := v.(int); ok { + return vv, nil + } else if vv, ok := v.(int64); ok { + return int(vv), nil } return 0, errors.New("not int value") } @@ -157,8 +163,10 @@ func (c *ConfigContainer) DefaultInt(key string, defaultval int) int { // Int64 returns the int64 value for a given key. func (c *ConfigContainer) Int64(key string) (int64, error) { - if v, ok := c.data[key].(int64); ok { - return v, nil + if v, err := c.getData(key); err != nil { + return 0, err + } else if vv, ok := v.(int64); ok { + return vv, nil } return 0, errors.New("not bool value") } @@ -175,8 +183,14 @@ func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 { // Float returns the float value for a given key. func (c *ConfigContainer) Float(key string) (float64, error) { - if v, ok := c.data[key].(float64); ok { - return v, nil + if v, err := c.getData(key); err != nil { + return 0.0, err + } else if vv, ok := v.(float64); ok { + return vv, nil + } else if vv, ok := v.(int); ok { + return float64(vv), nil + } else if vv, ok := v.(int64); ok { + return float64(vv), nil } return 0.0, errors.New("not float64 value") } @@ -193,8 +207,10 @@ func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 { // String returns the string value for a given key. func (c *ConfigContainer) String(key string) string { - if v, ok := c.data[key].(string); ok { - return v + if v, err := c.getData(key); err == nil { + if vv, ok := v.(string); ok { + return vv + } } return "" } @@ -230,8 +246,8 @@ func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []stri // GetSection returns map for the given section func (c *ConfigContainer) GetSection(section string) (map[string]string, error) { - v, ok := c.data[section] - if ok { + + if v, ok := c.data[section]; ok { return v.(map[string]string), nil } return nil, errors.New("not exist setction") @@ -259,10 +275,19 @@ func (c *ConfigContainer) Set(key, val string) error { // DIY returns the raw value by a given key. func (c *ConfigContainer) DIY(key string) (v interface{}, err error) { + return c.getData(key) +} + +func (c *ConfigContainer) getData(key string) (interface{}, error) { + + if len(key) == 0 { + return nil, errors.New("key is emtpy") + } + if v, ok := c.data[key]; ok { return v, nil } - return nil, errors.New("not exist key") + return nil, fmt.Errorf("not exist key %q", key) } func init() { diff --git a/config/yaml/yaml_test.go b/config/yaml/yaml_test.go index 80cbb8fe..49cc1d1e 100644 --- a/config/yaml/yaml_test.go +++ b/config/yaml/yaml_test.go @@ -15,13 +15,17 @@ package yaml import ( + "fmt" "os" "testing" "github.com/astaxie/beego/config" ) -var yamlcontext = ` +func TestYaml(t *testing.T) { + + var ( + yamlcontext = ` "appname": beeapi "httpport": 8080 "mysqlport": 3600 @@ -29,9 +33,27 @@ var yamlcontext = ` "runmode": dev "autorender": false "copyrequestbody": true +"PATH": GOPATH +"path1": ${GOPATH} +"path2": ${GOPATH||/home/go} +"empty": "" ` -func TestYaml(t *testing.T) { + keyValue = map[string]interface{}{ + "appname": "beeapi", + "httpport": 8080, + "mysqlport": int64(3600), + "PI": 3.1415976, + "runmode": "dev", + "autorender": false, + "copyrequestbody": true, + "PATH": "GOPATH", + "path1": os.Getenv("GOPATH"), + "path2": os.Getenv("GOPATH"), + "error": "", + "emptystrings": []string{}, + } + ) f, err := os.Create("testyaml.conf") if err != nil { t.Fatal(err) @@ -47,32 +69,42 @@ func TestYaml(t *testing.T) { if err != nil { t.Fatal(err) } + if yamlconf.String("appname") != "beeapi" { t.Fatal("appname not equal to beeapi") } - if port, err := yamlconf.Int("httpport"); err != nil || port != 8080 { - t.Error(port) - t.Fatal(err) - } - if port, err := yamlconf.Int64("mysqlport"); err != nil || port != 3600 { - t.Error(port) - t.Fatal(err) - } - if pi, err := yamlconf.Float("PI"); err != nil || pi != 3.1415976 { - t.Error(pi) - t.Fatal(err) - } - if yamlconf.String("runmode") != "dev" { - t.Fatal("runmode not equal to dev") - } - if v, err := yamlconf.Bool("autorender"); err != nil || v != false { - t.Error(v) - t.Fatal(err) - } - if v, err := yamlconf.Bool("copyrequestbody"); err != nil || v != true { - t.Error(v) - t.Fatal(err) + + for k, v := range keyValue { + + var ( + value interface{} + err error + ) + + switch v.(type) { + case int: + value, err = yamlconf.Int(k) + case int64: + value, err = yamlconf.Int64(k) + case float64: + value, err = yamlconf.Float(k) + case bool: + value, err = yamlconf.Bool(k) + case []string: + value = yamlconf.Strings(k) + case string: + value = yamlconf.String(k) + default: + value, err = yamlconf.DIY(k) + } + if err != nil { + t.Errorf("get key %q value fatal,%v err %s", k, v, err) + } else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) { + t.Errorf("get key %q value, want %v got %v .", k, v, value) + } + } + if err = yamlconf.Set("name", "astaxie"); err != nil { t.Fatal(err) } @@ -80,7 +112,4 @@ func TestYaml(t *testing.T) { t.Fatal("get name error") } - if yamlconf.Strings("emptystrings") != nil { - t.Fatal("get emtpy strings error") - } }