1
0
mirror of https://github.com/astaxie/beego.git synced 2024-11-25 22:01:29 +00:00

Merge pull request #1636 from ysqi/environmentVar

Support get environment variables in config
This commit is contained in:
astaxie 2016-04-01 13:50:38 +08:00
commit fe4fa6a095
11 changed files with 309 additions and 99 deletions

View File

@ -12,11 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package config is used to parse config // Package config is used to parse config.
// Usage: // Usage:
// import( // import "github.com/astaxie/beego/config"
// "github.com/astaxie/beego/config" //Examples.
// )
// //
// cnf, err := config.NewConfig("ini", "config.conf") // cnf, err := config.NewConfig("ini", "config.conf")
// //
@ -38,12 +37,12 @@
// cnf.DIY(key string) (interface{}, error) // cnf.DIY(key string) (interface{}, error)
// cnf.GetSection(section string) (map[string]string, error) // cnf.GetSection(section string) (map[string]string, error)
// cnf.SaveConfigFile(filename 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 package config
import ( import (
"fmt" "fmt"
"os"
) )
// Configer defines how to get and set value from configuration raw data. // 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) 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. // 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, // It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On,

55
config/config_test.go Normal file
View File

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

View File

@ -38,7 +38,7 @@ func (c *fakeConfigContainer) String(key string) string {
} }
func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string { func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string {
v := c.getData(key) v := c.String(key)
if v == "" { if v == "" {
return defaultval return defaultval
} }
@ -46,7 +46,7 @@ func (c *fakeConfigContainer) DefaultString(key string, defaultval string) strin
} }
func (c *fakeConfigContainer) Strings(key string) []string { func (c *fakeConfigContainer) Strings(key string) []string {
v := c.getData(key) v := c.String(key)
if v == "" { if v == "" {
return nil return nil
} }

View File

@ -166,7 +166,7 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
val = bytes.Trim(val, `"`) val = bytes.Trim(val, `"`)
} }
cfg.data[section][key] = string(val) cfg.data[section][key] = ExpandValueEnv(string(val))
if comment.Len() > 0 { if comment.Len() > 0 {
cfg.keyComment[section+"."+key] = comment.String() cfg.keyComment[section+"."+key] = comment.String()
comment.Reset() comment.Reset()
@ -300,7 +300,9 @@ func (c *IniConfigContainer) GetSection(section string) (map[string]string, erro
return nil, errors.New("not exist setction") 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) { func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename. // Write configuration file by filename.
f, err := os.Create(filename) f, err := os.Create(filename)

View File

@ -42,11 +42,14 @@ needlogin = ON
enableSession = Y enableSession = Y
enableCookie = N enableCookie = N
flag = 1 flag = 1
path1 = ${GOPATH}
path2 = ${GOPATH||/home/go}
[demo] [demo]
key1="asta" key1="asta"
key2 = "xie" key2 = "xie"
CaseInsensitive = true CaseInsensitive = true
peers = one;two;three peers = one;two;three
password = ${GOPATH}
` `
keyValue = map[string]interface{}{ keyValue = map[string]interface{}{
@ -64,10 +67,13 @@ peers = one;two;three
"enableSession": true, "enableSession": true,
"enableCookie": false, "enableCookie": false,
"flag": true, "flag": true,
"path1": os.Getenv("GOPATH"),
"path2": os.Getenv("GOPATH"),
"demo::key1": "asta", "demo::key1": "asta",
"demo::key2": "xie", "demo::key2": "xie",
"demo::CaseInsensitive": true, "demo::CaseInsensitive": true,
"demo::peers": []string{"one", "two", "three"}, "demo::peers": []string{"one", "two", "three"},
"demo::password": os.Getenv("GOPATH"),
"null": "", "null": "",
"demo2::key1": "", "demo2::key1": "",
"error": "", "error": "",

View File

@ -57,6 +57,9 @@ func (js *JSONConfig) ParseData(data []byte) (Configer, error) {
} }
x.data["rootArray"] = wrappingArray x.data["rootArray"] = wrappingArray
} }
x.data = ExpandValueEnvForMap(x.data)
return x, nil return x, nil
} }

View File

@ -86,16 +86,19 @@ func TestJson(t *testing.T) {
"enableSession": "Y", "enableSession": "Y",
"enableCookie": "N", "enableCookie": "N",
"flag": 1, "flag": 1,
"path1": "${GOPATH}",
"path2": "${GOPATH||/home/go}",
"database": { "database": {
"host": "host", "host": "host",
"port": "port", "port": "port",
"database": "database", "database": "database",
"username": "username", "username": "username",
"password": "password", "password": "${GOPATH}",
"conns":{ "conns":{
"maxconnection":12, "maxconnection":12,
"autoconnect":true, "autoconnect":true,
"connectioninfo":"info" "connectioninfo":"info",
"root": "${GOPATH}"
} }
} }
}` }`
@ -115,13 +118,16 @@ func TestJson(t *testing.T) {
"enableSession": true, "enableSession": true,
"enableCookie": false, "enableCookie": false,
"flag": true, "flag": true,
"path1": os.Getenv("GOPATH"),
"path2": os.Getenv("GOPATH"),
"database::host": "host", "database::host": "host",
"database::port": "port", "database::port": "port",
"database::database": "database", "database::database": "database",
"database::password": "password", "database::password": os.Getenv("GOPATH"),
"database::conns::maxconnection": 12, "database::conns::maxconnection": 12,
"database::conns::autoconnect": true, "database::conns::autoconnect": true,
"database::conns::connectioninfo": "info", "database::conns::connectioninfo": "info",
"database::conns::root": os.Getenv("GOPATH"),
"unknown": "", "unknown": "",
} }
) )

View File

@ -12,21 +12,21 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // 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: // Usage:
// import( // import(
// _ "github.com/astaxie/beego/config/xml" // _ "github.com/astaxie/beego/config/xml"
// "github.com/astaxie/beego/config" // "github.com/astaxie/beego/config"
// ) // )
// //
// cnf, err := config.NewConfig("xml", "config.xml") // 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 package xml
import ( import (
@ -69,7 +69,7 @@ func (xc *Config) Parse(filename string) (config.Configer, error) {
return nil, err return nil, err
} }
x.data = d["config"].(map[string]interface{}) x.data = config.ExpandValueEnvForMap(d["config"].(map[string]interface{}))
return x, nil return x, nil
} }
@ -92,7 +92,7 @@ type ConfigContainer struct {
// Bool returns the boolean value for a given key. // Bool returns the boolean value for a given key.
func (c *ConfigContainer) Bool(key string) (bool, error) { 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 config.ParseBool(v)
} }
return false, fmt.Errorf("not exist key: %q", key) return false, fmt.Errorf("not exist key: %q", key)

View File

@ -15,14 +15,18 @@
package xml package xml
import ( import (
"fmt"
"os" "os"
"testing" "testing"
"github.com/astaxie/beego/config" "github.com/astaxie/beego/config"
) )
//xml parse should incluce in <config></config> tags func TestXML(t *testing.T) {
var xmlcontext = `<?xml version="1.0" encoding="UTF-8"?>
var (
//xml parse should incluce in <config></config> tags
xmlcontext = `<?xml version="1.0" encoding="UTF-8"?>
<config> <config>
<appname>beeapi</appname> <appname>beeapi</appname>
<httpport>8080</httpport> <httpport>8080</httpport>
@ -31,10 +35,25 @@ var xmlcontext = `<?xml version="1.0" encoding="UTF-8"?>
<runmode>dev</runmode> <runmode>dev</runmode>
<autorender>false</autorender> <autorender>false</autorender>
<copyrequestbody>true</copyrequestbody> <copyrequestbody>true</copyrequestbody>
<path1>${GOPATH}</path1>
<path2>${GOPATH||/home/go}</path2>
</config> </config>
` `
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") f, err := os.Create("testxml.conf")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -50,39 +69,42 @@ func TestXML(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if xmlconf.String("appname") != "beeapi" {
t.Fatal("appname not equal to beeapi") for k, v := range keyValue {
}
if port, err := xmlconf.Int("httpport"); err != nil || port != 8080 { var (
t.Error(port) value interface{}
t.Fatal(err) err error
} )
if port, err := xmlconf.Int64("mysqlport"); err != nil || port != 3600 {
t.Error(port) switch v.(type) {
t.Fatal(err) case int:
} value, err = xmlconf.Int(k)
if pi, err := xmlconf.Float("PI"); err != nil || pi != 3.1415976 { case int64:
t.Error(pi) value, err = xmlconf.Int64(k)
t.Fatal(err) case float64:
} value, err = xmlconf.Float(k)
if xmlconf.String("runmode") != "dev" { case bool:
t.Fatal("runmode not equal to dev") value, err = xmlconf.Bool(k)
} case []string:
if v, err := xmlconf.Bool("autorender"); err != nil || v != false { value = xmlconf.Strings(k)
t.Error(v) case string:
t.Fatal(err) value = xmlconf.String(k)
} default:
if v, err := xmlconf.Bool("copyrequestbody"); err != nil || v != true { value, err = xmlconf.DIY(k)
t.Error(v) }
t.Fatal(err) 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 { if err = xmlconf.Set("name", "astaxie"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if xmlconf.String("name") != "astaxie" { if xmlconf.String("name") != "astaxie" {
t.Fatal("get name error") t.Fatal("get name error")
} }
if xmlconf.Strings("emptystrings") != nil {
t.Fatal("get emtpy strings error")
}
} }

View File

@ -19,14 +19,14 @@
// go install github.com/beego/goyaml2 // go install github.com/beego/goyaml2
// //
// Usage: // Usage:
// import( // import(
// _ "github.com/astaxie/beego/config/yaml" // _ "github.com/astaxie/beego/config/yaml"
// "github.com/astaxie/beego/config" // "github.com/astaxie/beego/config"
// ) // )
// //
// cnf, err := config.NewConfig("yaml", "config.yaml") // 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 package yaml
import ( import (
@ -110,6 +110,7 @@ func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
log.Println("Not a Map? >> ", string(buf), data) log.Println("Not a Map? >> ", string(buf), data)
cnf = nil cnf = nil
} }
cnf = config.ExpandValueEnvForMap(cnf)
return return
} }
@ -121,10 +122,11 @@ type ConfigContainer struct {
// Bool returns the boolean value for a given key. // Bool returns the boolean value for a given key.
func (c *ConfigContainer) Bool(key string) (bool, error) { func (c *ConfigContainer) Bool(key string) (bool, error) {
if v, ok := c.data[key]; ok { v, err := c.getData(key)
return config.ParseBool(v) 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 // 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. // Int returns the integer value for a given key.
func (c *ConfigContainer) Int(key string) (int, error) { func (c *ConfigContainer) Int(key string) (int, error) {
if v, ok := c.data[key].(int64); ok { if v, err := c.getData(key); err != nil {
return int(v), 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") 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. // Int64 returns the int64 value for a given key.
func (c *ConfigContainer) Int64(key string) (int64, error) { func (c *ConfigContainer) Int64(key string) (int64, error) {
if v, ok := c.data[key].(int64); ok { if v, err := c.getData(key); err != nil {
return v, nil return 0, err
} else if vv, ok := v.(int64); ok {
return vv, nil
} }
return 0, errors.New("not bool value") 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. // Float returns the float value for a given key.
func (c *ConfigContainer) Float(key string) (float64, error) { func (c *ConfigContainer) Float(key string) (float64, error) {
if v, ok := c.data[key].(float64); ok { if v, err := c.getData(key); err != nil {
return v, 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") 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. // String returns the string value for a given key.
func (c *ConfigContainer) String(key string) string { func (c *ConfigContainer) String(key string) string {
if v, ok := c.data[key].(string); ok { if v, err := c.getData(key); err == nil {
return v if vv, ok := v.(string); ok {
return vv
}
} }
return "" return ""
} }
@ -230,8 +246,8 @@ func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []stri
// GetSection returns map for the given section // GetSection returns map for the given section
func (c *ConfigContainer) GetSection(section string) (map[string]string, error) { 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 v.(map[string]string), nil
} }
return nil, errors.New("not exist setction") 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. // DIY returns the raw value by a given key.
func (c *ConfigContainer) DIY(key string) (v interface{}, err error) { 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 { if v, ok := c.data[key]; ok {
return v, nil return v, nil
} }
return nil, errors.New("not exist key") return nil, fmt.Errorf("not exist key %q", key)
} }
func init() { func init() {

View File

@ -15,13 +15,17 @@
package yaml package yaml
import ( import (
"fmt"
"os" "os"
"testing" "testing"
"github.com/astaxie/beego/config" "github.com/astaxie/beego/config"
) )
var yamlcontext = ` func TestYaml(t *testing.T) {
var (
yamlcontext = `
"appname": beeapi "appname": beeapi
"httpport": 8080 "httpport": 8080
"mysqlport": 3600 "mysqlport": 3600
@ -29,9 +33,27 @@ var yamlcontext = `
"runmode": dev "runmode": dev
"autorender": false "autorender": false
"copyrequestbody": true "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") f, err := os.Create("testyaml.conf")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -47,32 +69,42 @@ func TestYaml(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if yamlconf.String("appname") != "beeapi" { if yamlconf.String("appname") != "beeapi" {
t.Fatal("appname not equal to beeapi") t.Fatal("appname not equal to beeapi")
} }
if port, err := yamlconf.Int("httpport"); err != nil || port != 8080 {
t.Error(port) for k, v := range keyValue {
t.Fatal(err)
} var (
if port, err := yamlconf.Int64("mysqlport"); err != nil || port != 3600 { value interface{}
t.Error(port) err error
t.Fatal(err) )
}
if pi, err := yamlconf.Float("PI"); err != nil || pi != 3.1415976 { switch v.(type) {
t.Error(pi) case int:
t.Fatal(err) value, err = yamlconf.Int(k)
} case int64:
if yamlconf.String("runmode") != "dev" { value, err = yamlconf.Int64(k)
t.Fatal("runmode not equal to dev") case float64:
} value, err = yamlconf.Float(k)
if v, err := yamlconf.Bool("autorender"); err != nil || v != false { case bool:
t.Error(v) value, err = yamlconf.Bool(k)
t.Fatal(err) case []string:
} value = yamlconf.Strings(k)
if v, err := yamlconf.Bool("copyrequestbody"); err != nil || v != true { case string:
t.Error(v) value = yamlconf.String(k)
t.Fatal(err) 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 { if err = yamlconf.Set("name", "astaxie"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -80,7 +112,4 @@ func TestYaml(t *testing.T) {
t.Fatal("get name error") t.Fatal("get name error")
} }
if yamlconf.Strings("emptystrings") != nil {
t.Fatal("get emtpy strings error")
}
} }