diff --git a/config/config.go b/config/config.go index ef7738d9..59cc356b 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,8 +37,7 @@ // 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 ( @@ -109,52 +107,61 @@ func NewConfigData(adapterName string, data []byte) (Configer, error) { return adapter.ParseData(data) } -const envKeySign = "$ENV_" - -// Getenv return environment variable if env has prefix "$ENV_". -func Getenv(env interface{}) (string, bool) { - if env == nil { - return "", false - } - - // Onley support string key. - if key, ok := env.(string); ok { - - if envKey := strings.TrimPrefix(key, envKeySign); envKey != key { - return os.Getenv(envKey), true +// ChooseRealValueForMap convert all string value with environment variable. +func ChooseRealValueForMap(m map[string]interface{}) map[string]interface{} { + for k, v := range m { + switch value := v.(type) { + case string: + m[k] = ChooseRealValue(value) + case map[string]interface{}: + m[k] = ChooseRealValueForMap(value) + case map[string]string: + for k2, v2 := range value { + value[k2] = ChooseRealValue(v2) + } + m[k] = value } } - return "", false + return m } -// ConvertToStringMap convert interface to string config value only for map[string]interface{} config info. -func ConvertToStringMap(m map[string]interface{}) map[string]string { - items := make(map[string]string, len(m)) - if m == nil || len(m) == 0 { - return items +// ChooseRealValue returns value of convert with environment variable. +// +// Return environment variable if value start with "$$". +// Return default value if environment variable is empty or not exist. +// +// It accept value formats "$$env" , "$$env||" , "$$env||defaultValue" , "defaultvalue". +// Examples: +// v1 := config.ChooseRealValue("$$GOROOT") // return the GOROOT environment variable. +// v2 := config.ChooseRealValue("$$GOAsta||/usr/local/go/") // return the default value "/usr/local/go/". +// v3 := config.ChooseRealValue("Astaxie") // return the value "Astaxie". +func ChooseRealValue(value string) (realValue string) { + realValue = value + + if value == "" { + return } - var s string - for k, v := range m { - s = "" - if v == nil { - s = "" - } else if str, ok := v.(string); ok { - s = str - } else if m, ok := v.(map[string]interface{}); ok { - s = fmt.Sprintf("%+v", ConvertToStringMap(m)) - } else { - s = fmt.Sprintf("%+v", v) - } + sign := "$$" // Environment variable identifier. + sep := "||" // Environment variable and default value separator. - if len(s) > 0 { - if env, ok := Getenv(s); ok { - s = env - } - } - items[k] = s + // Not use environment variable. + if strings.HasPrefix(value, sign) == false { + return } - return items + + sepIndex := strings.Index(value, sep) + if sepIndex == -1 { + realValue = os.Getenv(string(value[len(sign):])) + } else { + realValue = os.Getenv(string(value[len(sign):sepIndex])) + // Find defalut value. + if realValue == "" { + realValue = string(value[sepIndex+len(sep):]) + } + } + + return } // ParseBool returns the boolean value represented by the string. diff --git a/config/fake.go b/config/fake.go index 347f1cfe..f5144598 100644 --- a/config/fake.go +++ b/config/fake.go @@ -25,15 +25,7 @@ type fakeConfigContainer struct { } func (c *fakeConfigContainer) getData(key string) string { - if len(key) == 0 { - return "" - } - - v := c.data[strings.ToLower(key)] - if env, ok := Getenv(v); ok { - return env - } - return v + return c.data[strings.ToLower(key)] } func (c *fakeConfigContainer) Set(key, val string) error { diff --git a/config/ini.go b/config/ini.go index 0d8571e4..829901d8 100644 --- a/config/ini.go +++ b/config/ini.go @@ -162,7 +162,7 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) { val = bytes.Trim(val, `"`) } - cfg.data[section][key] = string(val) + cfg.data[section][key] = ChooseRealValue(string(val)) if comment.Len() > 0 { cfg.keyComment[section+"."+key] = comment.String() comment.Reset() @@ -291,17 +291,14 @@ func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []s // GetSection returns map for the given section func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) { if v, ok := c.data[section]; ok { - for k, vv := range v { - if env, ok := Getenv(vv); ok { - v[k] = env - } - } return v, nil } 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) @@ -458,9 +455,6 @@ func (c *IniConfigContainer) getdata(key string) string { } if v, ok := c.data[section]; ok { if vv, ok := v[k]; ok { - if env, ok := Getenv(vv); ok { - return env - } return vv } } diff --git a/config/ini_test.go b/config/ini_test.go index 9396db5b..b3f57fbe 100644 --- a/config/ini_test.go +++ b/config/ini_test.go @@ -42,13 +42,20 @@ needlogin = ON enableSession = Y enableCookie = N flag = 1 -path = $ENV_GOROOT +path1 = $$GOROOT +path2 = $$GOROOT||/home/go +path3 = $$GOROOT$$GOPATH2||/home/go +token1 = $$TOKEN +token2 = $$TOKEN|| +token3 = $$TOKEN||astaxie +token4 = token$$TOKEN +token5 = $$TOKEN$$TOKEN||TOKEN [demo] key1="asta" key2 = "xie" CaseInsensitive = true peers = one;two;three -password = $ENV_GOROOT +password = $$GOROOT ` keyValue = map[string]interface{}{ @@ -66,7 +73,14 @@ password = $ENV_GOROOT "enableSession": true, "enableCookie": false, "flag": true, - "path": os.Getenv("GOROOT"), + "path1": os.Getenv("GOROOT"), + "path2": os.Getenv("GOROOT"), + "path3": "/home/go", + "token1": "", + "token2": "", + "token3": "astaxie", + "token4": "token$$TOKEN", + "token5": "TOKEN", "demo::key1": "asta", "demo::key2": "xie", "demo::CaseInsensitive": true, @@ -145,7 +159,6 @@ httpport = 8080 # db type name # suport mysql,sqlserver name = mysql -path = $ENV_GOROOT ` saveResult = ` @@ -162,7 +175,6 @@ httpport=8080 # db type name # suport mysql,sqlserver name=mysql -path=$ENV_GOROOT ` ) cfg, err := NewConfigData("ini", []byte(inicontext)) diff --git a/config/json.go b/config/json.go index ecef9439..3b4569b8 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 = ChooseRealValueForMap(x.data) + return x, nil } @@ -250,18 +253,11 @@ func (c *JSONConfigContainer) getData(key string) interface{} { } } } - if env, ok := Getenv(curValue); ok { - return env - } return curValue } if v, ok := c.data[key]; ok { - if env, ok := Getenv(v); ok { - return env - } return v } - return nil } diff --git a/config/json_test.go b/config/json_test.go index 4ac2cdfc..6ff26dba 100644 --- a/config/json_test.go +++ b/config/json_test.go @@ -86,18 +86,25 @@ func TestJson(t *testing.T) { "enableSession": "Y", "enableCookie": "N", "flag": 1, -"path": "$ENV_GOROOT", +"path1": "$$GOROOT", +"path2": "$$GOROOT||/home/go", +"path3": "$$GOROOT$$GOPATH2||/home/go", +"token1": "$$TOKEN", +"token2": "$$TOKEN||", +"token3": "$$TOKEN||astaxie", +"token4": "token$$TOKEN", +"token5": "$$TOKEN$$TOKEN||TOKEN", "database": { "host": "host", "port": "port", "database": "database", "username": "username", - "password": "$ENV_GOROOT", + "password": "$$GOROOT", "conns":{ "maxconnection":12, "autoconnect":true, "connectioninfo":"info", - "root": "$ENV_GOROOT" + "root": "$$GOROOT" } } }` @@ -117,7 +124,14 @@ func TestJson(t *testing.T) { "enableSession": true, "enableCookie": false, "flag": true, - "path": os.Getenv("GOROOT"), + "path1": os.Getenv("GOROOT"), + "path2": os.Getenv("GOROOT"), + "path3": "/home/go", + "token1": "", + "token2": "", + "token3": "astaxie", + "token4": "token$$TOKEN", + "token5": "TOKEN", "database::host": "host", "database::port": "port", "database::database": "database", diff --git a/config/xml/xml.go b/config/xml/xml.go index a9218b03..63f3cb23 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.ChooseRealValueForMap(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 := c.getData(key); v != nil { + if v := c.data[key]; v != nil { return config.ParseBool(v) } return false, fmt.Errorf("not exist key: %q", key) @@ -110,7 +110,7 @@ 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) { - return strconv.Atoi(c.getData(key).(string)) + return strconv.Atoi(c.data[key].(string)) } // DefaultInt returns the integer value for a given key. @@ -125,7 +125,7 @@ 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) { - return strconv.ParseInt(c.getData(key).(string), 10, 64) + return strconv.ParseInt(c.data[key].(string), 10, 64) } // DefaultInt64 returns the int64 value for a given key. @@ -141,7 +141,7 @@ 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) { - return strconv.ParseFloat(c.getData(key).(string), 64) + return strconv.ParseFloat(c.data[key].(string), 64) } // DefaultFloat returns the float64 value for a given key. @@ -156,7 +156,7 @@ 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.getData(key).(string); ok { + if v, ok := c.data[key].(string); ok { return v } return "" @@ -194,7 +194,7 @@ 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) { if v, ok := c.data[section]; ok { - return config.ConvertToStringMap(v.(map[string]interface{})), nil + return v.(map[string]string), nil } return nil, errors.New("not exist setction") } @@ -231,18 +231,6 @@ func (c *ConfigContainer) DIY(key string) (v interface{}, err error) { return nil, errors.New("not exist key") } -// Get Data -func (c *ConfigContainer) getData(key string) interface{} { - if v, ok := c.data[key]; ok { - if env, ok := config.Getenv(v); ok { - return env - } - return v - - } - return nil -} - func init() { config.Register("xml", &Config{}) } diff --git a/config/xml/xml_test.go b/config/xml/xml_test.go index 5ef43c9b..85c92e8b 100644 --- a/config/xml/xml_test.go +++ b/config/xml/xml_test.go @@ -15,8 +15,8 @@ package xml import ( + "fmt" "os" - "strings" "testing" "github.com/astaxie/beego/config" @@ -24,8 +24,9 @@ import ( func TestXML(t *testing.T) { - //xml parse should incluce in tags - var xmlcontext = ` + var ( + //xml parse should incluce in tags + xmlcontext = ` beeapi 8080 @@ -34,23 +35,36 @@ func TestXML(t *testing.T) { dev false true -$ENV_GOROOT - - beego - $ENV_GOROOT - localhost - - value1 - $ENV_GOROOT - - - - 001 - gp2 - - +$$GOROOT +$$GOROOT||/home/go +$$GOROOT$$GOPATH2||/home/go +$$TOKEN +$$TOKEN|| +$$TOKEN||astaxie +token$$TOKEN +$$TOKEN$$TOKEN||TOKEN ` + keyValue = map[string]interface{}{ + "appname": "beeapi", + "httpport": 8080, + "mysqlport": int64(3600), + "PI": 3.1415976, + "runmode": "dev", + "autorender": false, + "copyrequestbody": true, + "path1": os.Getenv("GOROOT"), + "path2": os.Getenv("GOROOT"), + "path3": "/home/go", + "token1": "", + "token2": "", + "token3": "astaxie", + "token4": "token$$TOKEN", + "token5": "TOKEN", + "error": "", + "emptystrings": []string{}, + } + ) f, err := os.Create("testxml.conf") if err != nil { @@ -67,50 +81,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.String("path") != os.Getenv("GOROOT") { - t.Fatal("get path error") - } - - if dbinfo, err := xmlconf.GetSection("dbinfo"); err != nil { - t.Fatal(err) - } else if dbinfo["pwd"] != os.Getenv("GOROOT") { - t.Fatal("get pwd error") - } else if strings.Contains(dbinfo["detail"], os.Getenv("GOROOT")) == false { - t.Fatal("get goroot path 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 e1dd7012..b94adbc1 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.ChooseRealValueForMap(cnf) return } @@ -248,7 +249,7 @@ func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []stri func (c *ConfigContainer) GetSection(section string) (map[string]string, error) { if v, ok := c.data[section]; ok { - return config.ConvertToStringMap(v.(map[string]interface{})), nil + return v.(map[string]string), nil } return nil, errors.New("not exist setction") } @@ -285,11 +286,7 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) { } if v, ok := c.data[key]; ok { - if env, ok := config.Getenv(v); ok { - return env, nil - } else { - return v, nil - } + return v, nil } return nil, fmt.Errorf("not exist key %q", key) } diff --git a/config/yaml/yaml_test.go b/config/yaml/yaml_test.go index 4eb36619..30c79d0b 100644 --- a/config/yaml/yaml_test.go +++ b/config/yaml/yaml_test.go @@ -15,8 +15,8 @@ package yaml import ( + "fmt" "os" - "strings" "testing" "github.com/astaxie/beego/config" @@ -33,21 +33,38 @@ func TestYaml(t *testing.T) { "runmode": dev "autorender": false "copyrequestbody": true -"path": $ENV_GOROOT "PATH": GOROOT -"dbinfo": - "db": beego - "pwd": $ENV_GOROOT - "url": localhost - "detail": - "d1": value1 - "d2": $ENV_GOROOT - "d3": "" - "group": - "id": 001 - "name": gp2 +"path1": $$GOROOT +"path2": $$GOROOT||/home/go +"path3": $$GOROOT$$GOPATH2||/home/go +"token1": $$TOKEN +"token2": $$TOKEN|| +"token3": $$TOKEN||astaxie +"token4": token$$TOKEN +"token5": $$TOKEN$$TOKEN||TOKEN "empty": "" ` + + keyValue = map[string]interface{}{ + "appname": "beeapi", + "httpport": 8080, + "mysqlport": int64(3600), + "PI": 3.1415976, + "runmode": "dev", + "autorender": false, + "copyrequestbody": true, + "PATH": "GOROOT", + "path1": os.Getenv("GOROOT"), + "path2": os.Getenv("GOROOT"), + "path3": "/home/go", + "token1": "", + "token2": "", + "token3": "astaxie", + "token4": "token$$TOKEN", + "token5": "TOKEN", + "error": "", + "emptystrings": []string{}, + } ) f, err := os.Create("testyaml.conf") if err != nil { @@ -68,29 +85,38 @@ func TestYaml(t *testing.T) { 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) } @@ -98,15 +124,4 @@ func TestYaml(t *testing.T) { t.Fatal("get name error") } - if dbinfo, err := yamlconf.GetSection("dbinfo"); err != nil { - t.Fatal(err) - } else if dbinfo["pwd"] != os.Getenv("GOROOT") { - t.Fatal("get pwd error") - } else if strings.Contains(dbinfo["detail"], os.Getenv("GOROOT")) == false { - t.Fatal("get GOROOT path error") - } - - if yamlconf.Strings("emptystrings") != nil { - t.Fatal("get emtpy strings error") - } }