| # env | |||||
| env | |||||
| === | |||||
| [](https://travis-ci.org/goanywhere/env) [](http://godoc.org/github.com/goanywhere/env) | |||||
| Ease of Accessing Environment Varaibles in Golang | Ease of Accessing Environment Varaibles in Golang | ||||
| ### Installation | |||||
| ```shell | |||||
| $ go get -v github.com/goanywhere/env | |||||
| ``` | |||||
| ### Usage | |||||
| ```shell | |||||
| PORT=9394 | |||||
| SECRET_KEY=YOURSECRETKEY | |||||
| ``` | |||||
| You can double/single quote string values: | |||||
| ```shell | |||||
| PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----HkVN9…-----END DSA PRIVATE KEY-----" | |||||
| ``` | |||||
| You can use `export` in front of each line just like your shell settings, so that you can `source` the file in your terminal directly: | |||||
| ```shell | |||||
| export USERNAME=account@goanywhere.io | |||||
| export PASSWORD=AccountPasswordGoesHere | |||||
| ``` | |||||
| Export these values, you can then access them using env now. | |||||
| ``` go | |||||
| package main | |||||
| import ( | |||||
| "github.com/goanywhere/env" | |||||
| ) | |||||
| func main() { | |||||
| env.Int("PORT") // 9394 | |||||
| env.String("SECRET_KEY") // YOURSECERTKEY | |||||
| ....... | |||||
| } | |||||
| ``` | |||||
| `env` also supports custom struct for you to access the reflected values. | |||||
| ``` go | |||||
| package main | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/goanywhere/env" | |||||
| ) | |||||
| type Spec struct { | |||||
| App string | |||||
| SecretKey string `env:"SECRET_KEY"` | |||||
| } | |||||
| func main() { | |||||
| var spec Spec | |||||
| env.Set("App", "myapplication") | |||||
| env.Set("SECRET_KEY", "wGv7ELIx8P8qsUit9OuWw2zwPEF0nXtvjIKZQOioAVuI5GnHSwBAeWZ6l4-SpIPT") | |||||
| env.Map(&spec) | |||||
| fmt.Printf("App: %s", spec.App) // output: "App: myapplication" | |||||
| fmt.Printf("Secret: %s", spec.SecretKey) // output: "Secret: wGv7ELIx8P8qsUit9OuWw2zwPEF0nXtvjIKZQOioAVuI5GnHSwBAeWZ6l4-SpIPT" | |||||
| } | |||||
| ``` | |||||
| We also includes dotenv supports, simply add the application settings to file `.env` right under the root of your project: | |||||
| ``` text | |||||
| test1 = value1 | |||||
| test2 = 'value2' | |||||
| test3 = "value3" | |||||
| export test4=value4 | |||||
| ``` | |||||
| ``` go | |||||
| package main | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/goanywhere/env" | |||||
| ) | |||||
| func main() { | |||||
| // Load '.env' from current working directory. | |||||
| env.Load() | |||||
| fmt.Printf("<test1: %s>", env.String("test1")) // output: "value1" | |||||
| fmt.Printf("<test2: %s>", env.String("test2")) // output: "value2" | |||||
| } | |||||
| ``` | |||||
| ### NOTES | |||||
| Sensitive settings should **ONLY** be accessible on the machines that need access to them. **NEVER** commit them to a repository (even a private one) that is not needed by every development machine and server. |
| package env | |||||
| import ( | |||||
| _ "github.com/smartystreets/goconvey/convey" | |||||
| ) |
| package env | |||||
| /* | |||||
| env | |||||
| === | |||||
| Ease of Accessing Environment Varaibles. | |||||
| ## Installation | |||||
| ```shell | |||||
| $ go get -v github.com/goanywhere/env | |||||
| ``` | |||||
| ## Usage | |||||
| Add the application settings to file `.env` right under the root of your project: | |||||
| ```shell | |||||
| MY_SECRET_KEY=YOURSECRETKEY | |||||
| Case_Will_Be_IgNoreD=YOURSECRETKEYGOESHERE | |||||
| ``` | |||||
| You can double/single quote string values: | |||||
| ```shell | |||||
| PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----HkVN9…-----END DSA PRIVATE KEY-----" | |||||
| ``` | |||||
| You can use `export` in front of each line just like your shell settings, so that you can `source` the file in your terminal directly: | |||||
| ```shell | |||||
| export USERNAME=account@goanywhere.io | |||||
| export PASSWORD=AccountPasswordGoesHere | |||||
| ``` | |||||
| All set now, you are good to Go :-) | |||||
| ``` go | |||||
| package main | |||||
| import ( | |||||
| "github.com/goanywhere/env" | |||||
| "github.com/goanywhere/rex" | |||||
| ) | |||||
| func index (ctx *rex.Context) { | |||||
| ctx.HTML("index.html") | |||||
| } | |||||
| func main() { | |||||
| // Override default 5000 port here. | |||||
| env.Set("port", "9394") | |||||
| app := rex.New() | |||||
| app.Get("/", index) | |||||
| app.Serve() | |||||
| } | |||||
| ``` | |||||
| You will now have the HTTP server running on `0.0.0.0:9394`. | |||||
| `env` supports namespace (case-insensitive, same as the key). | |||||
| ``` go | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/goanywhere/env" | |||||
| ) | |||||
| func main() { | |||||
| env.Set("debug", "false", "production") | |||||
| fmt.Printf("debug: %s", env.Get("debug", "production")) | |||||
| } | |||||
| ``` | |||||
| `env` also supports custom struct for you to access the reflected values (the key is case-insensitive). | |||||
| ``` go | |||||
| package main | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/goanywhere/env" | |||||
| ) | |||||
| type Spec struct { | |||||
| App string | |||||
| } | |||||
| func main() { | |||||
| var spec Spec | |||||
| env.Set("app", "myapplication") | |||||
| env.Map(&spec) | |||||
| fmt.Printf("App: %s", spec.App) // output: "App: myapplication" | |||||
| } | |||||
| ``` | |||||
| We also includes dotenv supports: | |||||
| ``` text | |||||
| test1 = value1 | |||||
| test2 = 'value2' | |||||
| test3 = "value3" | |||||
| export test4=value4 | |||||
| ``` | |||||
| ``` go | |||||
| package main | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/goanywhere/env" | |||||
| ) | |||||
| func main() { | |||||
| // Load '.env' from current working directory. | |||||
| env.Load(".env") | |||||
| fmt.Printf("<test: %s>", env.Get("test")) // output: "value" | |||||
| fmt.Printf("<test2: %s>", env.Get("test2")) // output: "value2" | |||||
| } | |||||
| ``` | |||||
| ## NOTES | |||||
| Sensitive settings should **ONLY** be accessible on the machines that need access to them. **NEVER** commit them to a repository (even a private one) that is not needed by every development machine and server. | |||||
| */ |
| package env | |||||
| import ( | |||||
| "bufio" | |||||
| "fmt" | |||||
| "os" | |||||
| "path/filepath" | |||||
| "reflect" | |||||
| "regexp" | |||||
| "strconv" | |||||
| "strings" | |||||
| log "github.com/Sirupsen/logrus" | |||||
| "github.com/goanywhere/x/fs" | |||||
| ) | |||||
| const tag string = "env" | |||||
| var pattern = regexp.MustCompile(`(export\s*)?(?P<key>\w+)\s*(=)\s*(?P<value>("|')?[\w\s-$,:/\.\+]+)("|')?`) | |||||
| // findKeyValue finds '=' separated key/value pair from the given string. | |||||
| func findKeyValue(line string) (key, value string) { | |||||
| // Intentionally insert a linebreak here to avoid non-stop characters on [[:graph:]]. | |||||
| line = fmt.Sprintf("%s\n", line) | |||||
| match := pattern.FindStringSubmatch(line) | |||||
| if len(match) != 0 { | |||||
| result := make(map[string]string) | |||||
| for index, name := range pattern.SubexpNames() { | |||||
| result[name] = match[index] | |||||
| } | |||||
| // preserve those quoted spaces for value. | |||||
| key, value = result["key"], strings.TrimSpace(result["value"]) | |||||
| value = strings.Trim(value, "'") | |||||
| value = strings.Trim(value, "\"") | |||||
| } | |||||
| return | |||||
| } | |||||
| // Load parses & set the values in the given files into os environment. | |||||
| func Load(dotenv string) { | |||||
| if fs.Exists(dotenv) { | |||||
| dotenv, _ = filepath.Abs(dotenv) | |||||
| if file, err := os.Open(dotenv); err == nil { | |||||
| defer file.Close() | |||||
| scanner := bufio.NewScanner(file) | |||||
| for scanner.Scan() { | |||||
| k, v := findKeyValue(scanner.Text()) | |||||
| if k != "" && v != "" { | |||||
| Set(k, v) | |||||
| } else { | |||||
| continue | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| // Get retrieves the string value of the environment variable named by the key. | |||||
| // It returns the value, which will be empty if the variable is not present. | |||||
| func Get(key string) (value string, exists bool) { | |||||
| if v := os.Getenv(key); v != "" { | |||||
| value, exists = v, true | |||||
| } | |||||
| return | |||||
| } | |||||
| // Set stores the value of the environment variable named by the key. It returns an error, if any. | |||||
| func Set(key string, value interface{}) error { | |||||
| var sv string | |||||
| switch T := value.(type) { | |||||
| case bool: | |||||
| sv = strconv.FormatBool(T) | |||||
| case float32, float64: | |||||
| sv = strconv.FormatFloat(reflect.ValueOf(value).Float(), 'g', -1, 64) | |||||
| case int, int8, int16, int32, int64: | |||||
| sv = strconv.FormatInt(reflect.ValueOf(value).Int(), 10) | |||||
| case uint, uint8, uint16, uint32, uint64: | |||||
| sv = strconv.FormatUint(reflect.ValueOf(value).Uint(), 10) | |||||
| case string: | |||||
| sv = value.(string) | |||||
| case []string: | |||||
| sv = strings.Join(value.([]string), ",") | |||||
| default: | |||||
| return fmt.Errorf("Unsupported type: %v", T) | |||||
| } | |||||
| return os.Setenv(key, sv) | |||||
| } | |||||
| // String retrieves the string value from environment named by the key. | |||||
| func String(key string, fallback ...string) (value string) { | |||||
| if str, exists := Get(key); exists { | |||||
| value = str | |||||
| } else if len(fallback) > 0 { | |||||
| value = fallback[0] | |||||
| } | |||||
| return | |||||
| } | |||||
| // Strings retrieves the string values separated by comma from the environment. | |||||
| func Strings(key string, fallback ...[]string) (value []string) { | |||||
| if v, exists := Get(key); exists { | |||||
| for _, item := range strings.Split(strings.TrimSpace(v), ",") { | |||||
| value = append(value, strings.TrimSpace(item)) | |||||
| } | |||||
| } else if len(fallback) > 0 { | |||||
| value = fallback[0] | |||||
| } | |||||
| return | |||||
| } | |||||
| // Int retrieves the integer values separated by comma from the environment. | |||||
| func Int(key string, fallback ...int) (value int) { | |||||
| if str, exists := Get(key); exists { | |||||
| if v, e := strconv.ParseInt(str, 10, 0); e == nil { | |||||
| value = int(v) | |||||
| } | |||||
| } else if len(fallback) > 0 { | |||||
| value = fallback[0] | |||||
| } | |||||
| return | |||||
| } | |||||
| // Int retrieves the 64-bit integer values separated by comma from the environment. | |||||
| func Int64(key string, fallback ...int64) (value int64) { | |||||
| if str, exists := Get(key); exists { | |||||
| if v, e := strconv.ParseInt(str, 10, 64); e == nil { | |||||
| value = v | |||||
| } | |||||
| } else if len(fallback) > 0 { | |||||
| value = fallback[0] | |||||
| } | |||||
| return | |||||
| } | |||||
| // Uint retrieves the unsigned integer values separated by comma from the environment. | |||||
| func Uint(key string, fallback ...uint) (value uint) { | |||||
| if str, exists := Get(key); exists { | |||||
| if v, e := strconv.ParseUint(str, 10, 0); e == nil { | |||||
| value = uint(v) | |||||
| } | |||||
| } else if len(fallback) > 0 { | |||||
| value = fallback[0] | |||||
| } | |||||
| return | |||||
| } | |||||
| // Uint64 retrieves the 64-bit unsigned integer values separated by comma from the environment. | |||||
| func Uint64(key string, fallback ...uint64) (value uint64) { | |||||
| if str, exists := Get(key); exists { | |||||
| if v, e := strconv.ParseUint(str, 10, 64); e == nil { | |||||
| value = v | |||||
| } | |||||
| } else if len(fallback) > 0 { | |||||
| value = fallback[0] | |||||
| } | |||||
| return | |||||
| } | |||||
| // Bool retrieves boolean value from the environment. | |||||
| func Bool(key string, fallback ...bool) (value bool) { | |||||
| if str, exists := Get(key); exists { | |||||
| if v, e := strconv.ParseBool(str); e == nil { | |||||
| value = v | |||||
| } | |||||
| } else if len(fallback) > 0 { | |||||
| value = fallback[0] | |||||
| } | |||||
| return | |||||
| } | |||||
| // Float retrieves float (64) value from the environment. | |||||
| func Float(key string, fallback ...float64) (value float64) { | |||||
| if str, exists := Get(key); exists { | |||||
| if v, e := strconv.ParseFloat(str, 64); e == nil { | |||||
| value = v | |||||
| } | |||||
| } else if len(fallback) > 0 { | |||||
| value = fallback[0] | |||||
| } | |||||
| return | |||||
| } | |||||
| // Map fetches the key/value pair from os.Environ into the given spec. | |||||
| // Tags are supported via `env:ACTUAL_OS_KEY`. | |||||
| func Map(spec interface{}) error { | |||||
| value := reflect.ValueOf(spec) | |||||
| s := value.Elem() | |||||
| var stype reflect.Type = s.Type() | |||||
| var field reflect.Value | |||||
| for index := 0; index < s.NumField(); index++ { | |||||
| field = s.Field(index) | |||||
| if field.CanSet() { | |||||
| key := stype.Field(index).Tag.Get(tag) | |||||
| if key == "" { | |||||
| key = stype.Field(index).Name | |||||
| } | |||||
| value, exists := Get(key) | |||||
| if !exists { | |||||
| continue | |||||
| } | |||||
| // converts the environmental value from string to its real type. | |||||
| // Supports: String | Strings | Bool | Float | Integer | Unsiged Integer | |||||
| switch field.Kind() { | |||||
| case reflect.String: | |||||
| field.SetString(value) | |||||
| case reflect.Bool: | |||||
| field.SetBool(Bool(key)) | |||||
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |||||
| field.SetInt(Int64(key)) | |||||
| case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |||||
| field.SetUint(Uint64(key)) | |||||
| case reflect.Float32, reflect.Float64: | |||||
| field.SetFloat(Float(key)) | |||||
| case reflect.Slice: | |||||
| switch field.Interface().(type) { | |||||
| case []string: | |||||
| field.Set(reflect.ValueOf(Strings(key))) | |||||
| default: | |||||
| log.Fatalf("Only string slice is supported") | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } |
| package env | |||||
| import ( | |||||
| "bufio" | |||||
| "fmt" | |||||
| "os" | |||||
| "testing" | |||||
| "github.com/goanywhere/x/crypto" | |||||
| . "github.com/smartystreets/goconvey/convey" | |||||
| ) | |||||
| func TestFindKeyValue(t *testing.T) { | |||||
| Convey("env.findKeyValue", t, func() { | |||||
| k, v := findKeyValue(" test= value") | |||||
| So(k, ShouldEqual, "test") | |||||
| So(v, ShouldEqual, "value") | |||||
| k, v = findKeyValue(" test= value") | |||||
| So(k, ShouldEqual, "test") | |||||
| So(v, ShouldEqual, "value") | |||||
| k, v = findKeyValue("\ttest=\tvalue\t\n") | |||||
| So(k, ShouldEqual, "test") | |||||
| So(v, ShouldEqual, "value") | |||||
| k, v = findKeyValue("export Test=\"Example\"") | |||||
| So(k, ShouldEqual, "Test") | |||||
| So(v, ShouldEqual, "Example") | |||||
| k, v = findKeyValue(`export Secret_Keys=IK-vyX7OuiftwyasT6NXnEYyPMj8fEDssJZdppKOs8Y4hZTtWfUILer73RbsG78Q`) | |||||
| So(k, ShouldEqual, "Secret_Keys") | |||||
| So(v, ShouldEqual, "IK-vyX7OuiftwyasT6NXnEYyPMj8fEDssJZdppKOs8Y4hZTtWfUILer73RbsG78Q") | |||||
| }) | |||||
| } | |||||
| func TestLoad(t *testing.T) { | |||||
| filename := "/tmp/.env" | |||||
| // plain value without quote | |||||
| if dotenv, err := os.Create(filename); err == nil { | |||||
| defer dotenv.Close() | |||||
| defer os.Remove(filename) | |||||
| secret := crypto.Random(64) | |||||
| buffer := bufio.NewWriter(dotenv) | |||||
| buffer.WriteString(fmt.Sprintf("secret=%s\n", secret)) | |||||
| buffer.WriteString("app=myapp\n") | |||||
| buffer.WriteString("export exportation=myexports") | |||||
| buffer.Flush() | |||||
| Convey("env.Load (without quoting)", t, func() { | |||||
| Set("root", "/tmp") | |||||
| Load("/tmp/.env") | |||||
| So(String("secret"), ShouldEqual, secret) | |||||
| So(String("app"), ShouldEqual, "myapp") | |||||
| So(String("exportation"), ShouldEqual, "myexports") | |||||
| }) | |||||
| } | |||||
| // value with `` quote | |||||
| if dotenv, err := os.Create(filename); err == nil { | |||||
| defer dotenv.Close() | |||||
| defer os.Remove(filename) | |||||
| secret := crypto.Random(64) | |||||
| buffer := bufio.NewWriter(dotenv) | |||||
| buffer.WriteString(fmt.Sprintf("secret='%s'\n", secret)) | |||||
| buffer.WriteString("app='myapp'\n") | |||||
| buffer.WriteString("export account='username'\n") | |||||
| buffer.Flush() | |||||
| Convey("env.Load (with quoting)", t, func() { | |||||
| Set("root", "/tmp") | |||||
| Load("/tmp/.env") | |||||
| So(String("secret"), ShouldEqual, secret) | |||||
| So(String("app"), ShouldEqual, "myapp") | |||||
| So(String("account"), ShouldEqual, "username") | |||||
| }) | |||||
| } | |||||
| // value with `"` quote | |||||
| if dotenv, err := os.Create(filename); err == nil { | |||||
| defer dotenv.Close() | |||||
| defer os.Remove(filename) | |||||
| secret := crypto.Random(64) | |||||
| buffer := bufio.NewWriter(dotenv) | |||||
| buffer.WriteString(fmt.Sprintf("secret=\"%s\"\n", secret)) | |||||
| buffer.WriteString("app=\"myapp\"\n") | |||||
| buffer.WriteString("export account=\"username\"\n") | |||||
| buffer.Flush() | |||||
| Convey("env.Load (with single quoting)", t, func() { | |||||
| Set("root", "/tmp") | |||||
| Load("/tmp/.env") | |||||
| So(String("secret"), ShouldEqual, secret) | |||||
| So(String("app"), ShouldEqual, "myapp") | |||||
| So(String("account"), ShouldEqual, "username") | |||||
| }) | |||||
| } | |||||
| } | |||||
| func TestMap(t *testing.T) { | |||||
| type Person struct { | |||||
| Username string | |||||
| Age uint | |||||
| Kids int | |||||
| Checked bool | |||||
| Money float64 | |||||
| FirstName string `env:"FIRST_NAME"` | |||||
| Names []string | |||||
| } | |||||
| var person Person | |||||
| Convey("env.Map", t, func() { | |||||
| Set("Username", "abc") | |||||
| Set("Age", 100) | |||||
| Set("Kids", 2) | |||||
| Set("Checked", true) | |||||
| Set("Money", 1234567890.0987654321) | |||||
| Set("FIRST_NAME", "abc") | |||||
| Set("Names", "a,b,c,d,e,f,g") | |||||
| Map(&person) | |||||
| So(person.Username, ShouldEqual, "abc") | |||||
| So(person.Age, ShouldEqual, 100) | |||||
| So(person.Kids, ShouldEqual, 2) | |||||
| So(person.Checked, ShouldBeTrue) | |||||
| So(person.Money, ShouldEqual, 1234567890.0987654321) | |||||
| So(person.FirstName, ShouldEqual, "abc") | |||||
| So(person.Names, ShouldResemble, []string{"a", "b", "c", "d", "e", "f", "g"}) | |||||
| }) | |||||
| } | |||||
| func TestString(t *testing.T) { | |||||
| Convey("env.String", t, func() { | |||||
| So(String("NotFound"), ShouldEqual, "") | |||||
| Set("Found", "something") | |||||
| So(String("Found"), ShouldEqual, "something") | |||||
| So(String("NotFound", "default"), ShouldEqual, "default") | |||||
| }) | |||||
| } | |||||
| func TestStrings(t *testing.T) { | |||||
| Convey("env.Strings", t, func() { | |||||
| So(Strings("StringList"), ShouldBeNil) | |||||
| Set("StringList", "a,b,c") | |||||
| So(Strings("StringList"), ShouldResemble, []string{"a", "b", "c"}) | |||||
| So(Strings("NotFound", []string{"a", "b", "c"}), ShouldResemble, []string{"a", "b", "c"}) | |||||
| }) | |||||
| } | |||||
| func TestInt(t *testing.T) { | |||||
| Convey("env.Int", t, func() { | |||||
| So(Int("integer"), ShouldEqual, 0) | |||||
| Set("integer", 123) | |||||
| So(Int("integer"), ShouldEqual, 123) | |||||
| So(Int("NotFound", 123), ShouldEqual, 123) | |||||
| }) | |||||
| } | |||||
| func TestInt64(t *testing.T) { | |||||
| Convey("env.Int", t, func() { | |||||
| So(Int64("int64"), ShouldEqual, 0) | |||||
| Set("int64", 123) | |||||
| So(Int64("int64"), ShouldEqual, 123) | |||||
| So(Int64("NotFound", 123), ShouldEqual, 123) | |||||
| }) | |||||
| } | |||||
| func TestUint(t *testing.T) { | |||||
| Convey("env.Uint", t, func() { | |||||
| So(Uint("uint"), ShouldEqual, 0) | |||||
| Set("uint", 123) | |||||
| So(Uint("uint"), ShouldEqual, 123) | |||||
| So(Uint("NotFound", 123), ShouldEqual, 123) | |||||
| }) | |||||
| } | |||||
| func TestUint64(t *testing.T) { | |||||
| Convey("env.Uint64", t, func() { | |||||
| So(Uint64("uint64"), ShouldEqual, 0) | |||||
| Set("uint64", 123) | |||||
| So(Uint64("uint64"), ShouldEqual, 123) | |||||
| So(Uint64("NotFound", 123), ShouldEqual, 123) | |||||
| }) | |||||
| } | |||||
| func TestBool(t *testing.T) { | |||||
| Convey("env.Bool", t, func() { | |||||
| So(Bool("bool"), ShouldBeFalse) | |||||
| Set("bool", true) | |||||
| So(Bool("bool"), ShouldBeTrue) | |||||
| So(Bool("NotFound", true), ShouldBeTrue) | |||||
| }) | |||||
| } | |||||
| func TestFloat(t *testing.T) { | |||||
| Convey("env.Float", t, func() { | |||||
| So(Float("float64"), ShouldEqual, 0.0) | |||||
| Set("float64", 12345678990.0987654321) | |||||
| So(Float("float64"), ShouldEqual, 12345678990.0987654321) | |||||
| So(Float("NotFound", 12345678990.0987654321), ShouldEqual, 12345678990.0987654321) | |||||
| }) | |||||
| } |