259 lines
6.0 KiB
Go
259 lines
6.0 KiB
Go
|
/*
|
||
|
package envy makes working with ENV variables in Go trivial.
|
||
|
|
||
|
* Get ENV variables with default values.
|
||
|
* Set ENV variables safely without affecting the underlying system.
|
||
|
* Temporarily change ENV vars; useful for testing.
|
||
|
* Map all of the key/values in the ENV.
|
||
|
* Loads .env files (by using [godotenv](https://github.com/joho/godotenv/))
|
||
|
* More!
|
||
|
*/
|
||
|
package envy
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"flag"
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"os/exec"
|
||
|
"path/filepath"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/joho/godotenv"
|
||
|
"github.com/rogpeppe/go-internal/modfile"
|
||
|
)
|
||
|
|
||
|
var gil = &sync.RWMutex{}
|
||
|
var env = map[string]string{}
|
||
|
|
||
|
// GO111MODULE is ENV for turning mods on/off
|
||
|
const GO111MODULE = "GO111MODULE"
|
||
|
|
||
|
func init() {
|
||
|
Load()
|
||
|
loadEnv()
|
||
|
}
|
||
|
|
||
|
// Load the ENV variables to the env map
|
||
|
func loadEnv() {
|
||
|
gil.Lock()
|
||
|
defer gil.Unlock()
|
||
|
|
||
|
if os.Getenv("GO_ENV") == "" {
|
||
|
// if the flag "test.v" is *defined*, we're running as a unit test. Note that we don't care
|
||
|
// about v.Value (verbose test mode); we just want to know if the test environment has defined
|
||
|
// it. It's also possible that the flags are not yet fully parsed (i.e. flag.Parsed() == false),
|
||
|
// so we could not depend on v.Value anyway.
|
||
|
//
|
||
|
if v := flag.Lookup("test.v"); v != nil {
|
||
|
env["GO_ENV"] = "test"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// set the GOPATH if using >= 1.8 and the GOPATH isn't set
|
||
|
if os.Getenv("GOPATH") == "" {
|
||
|
out, err := exec.Command("go", "env", "GOPATH").Output()
|
||
|
if err == nil {
|
||
|
gp := strings.TrimSpace(string(out))
|
||
|
os.Setenv("GOPATH", gp)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, e := range os.Environ() {
|
||
|
pair := strings.Split(e, "=")
|
||
|
env[pair[0]] = os.Getenv(pair[0])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Mods() bool {
|
||
|
return Get(GO111MODULE, "off") == "on"
|
||
|
}
|
||
|
|
||
|
// Reload the ENV variables. Useful if
|
||
|
// an external ENV manager has been used
|
||
|
func Reload() {
|
||
|
env = map[string]string{}
|
||
|
loadEnv()
|
||
|
}
|
||
|
|
||
|
// Load .env files. Files will be loaded in the same order that are received.
|
||
|
// Redefined vars will override previously existing values.
|
||
|
// IE: envy.Load(".env", "test_env/.env") will result in DIR=test_env
|
||
|
// If no arg passed, it will try to load a .env file.
|
||
|
func Load(files ...string) error {
|
||
|
|
||
|
// If no files received, load the default one
|
||
|
if len(files) == 0 {
|
||
|
err := godotenv.Overload()
|
||
|
if err == nil {
|
||
|
Reload()
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// We received a list of files
|
||
|
for _, file := range files {
|
||
|
|
||
|
// Check if it exists or we can access
|
||
|
if _, err := os.Stat(file); err != nil {
|
||
|
// It does not exist or we can not access.
|
||
|
// Return and stop loading
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// It exists and we have permission. Load it
|
||
|
if err := godotenv.Overload(file); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Reload the env so all new changes are noticed
|
||
|
Reload()
|
||
|
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Get a value from the ENV. If it doesn't exist the
|
||
|
// default value will be returned.
|
||
|
func Get(key string, value string) string {
|
||
|
gil.RLock()
|
||
|
defer gil.RUnlock()
|
||
|
if v, ok := env[key]; ok {
|
||
|
return v
|
||
|
}
|
||
|
return value
|
||
|
}
|
||
|
|
||
|
// Get a value from the ENV. If it doesn't exist
|
||
|
// an error will be returned
|
||
|
func MustGet(key string) (string, error) {
|
||
|
gil.RLock()
|
||
|
defer gil.RUnlock()
|
||
|
if v, ok := env[key]; ok {
|
||
|
return v, nil
|
||
|
}
|
||
|
return "", fmt.Errorf("could not find ENV var with %s", key)
|
||
|
}
|
||
|
|
||
|
// Set a value into the ENV. This is NOT permanent. It will
|
||
|
// only affect values accessed through envy.
|
||
|
func Set(key string, value string) {
|
||
|
gil.Lock()
|
||
|
defer gil.Unlock()
|
||
|
env[key] = value
|
||
|
}
|
||
|
|
||
|
// MustSet the value into the underlying ENV, as well as envy.
|
||
|
// This may return an error if there is a problem setting the
|
||
|
// underlying ENV value.
|
||
|
func MustSet(key string, value string) error {
|
||
|
gil.Lock()
|
||
|
defer gil.Unlock()
|
||
|
err := os.Setenv(key, value)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
env[key] = value
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Map all of the keys/values set in envy.
|
||
|
func Map() map[string]string {
|
||
|
gil.RLock()
|
||
|
defer gil.RUnlock()
|
||
|
cp := map[string]string{}
|
||
|
for k, v := range env {
|
||
|
cp[k] = v
|
||
|
}
|
||
|
return env
|
||
|
}
|
||
|
|
||
|
// Temp makes a copy of the values and allows operation on
|
||
|
// those values temporarily during the run of the function.
|
||
|
// At the end of the function run the copy is discarded and
|
||
|
// the original values are replaced. This is useful for testing.
|
||
|
// Warning: This function is NOT safe to use from a goroutine or
|
||
|
// from code which may access any Get or Set function from a goroutine
|
||
|
func Temp(f func()) {
|
||
|
oenv := env
|
||
|
env = map[string]string{}
|
||
|
for k, v := range oenv {
|
||
|
env[k] = v
|
||
|
}
|
||
|
defer func() { env = oenv }()
|
||
|
f()
|
||
|
}
|
||
|
|
||
|
func GoPath() string {
|
||
|
return Get("GOPATH", "")
|
||
|
}
|
||
|
|
||
|
func GoBin() string {
|
||
|
return Get("GO_BIN", "go")
|
||
|
}
|
||
|
|
||
|
// GoPaths returns all possible GOPATHS that are set.
|
||
|
func GoPaths() []string {
|
||
|
gp := Get("GOPATH", "")
|
||
|
if runtime.GOOS == "windows" {
|
||
|
return strings.Split(gp, ";") // Windows uses a different separator
|
||
|
}
|
||
|
return strings.Split(gp, ":")
|
||
|
}
|
||
|
|
||
|
func importPath(path string) string {
|
||
|
path = strings.TrimPrefix(path, "/private")
|
||
|
for _, gopath := range GoPaths() {
|
||
|
srcpath := filepath.Join(gopath, "src")
|
||
|
rel, err := filepath.Rel(srcpath, path)
|
||
|
if err == nil {
|
||
|
return filepath.ToSlash(rel)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// fallback to trim
|
||
|
rel := strings.TrimPrefix(path, filepath.Join(GoPath(), "src"))
|
||
|
rel = strings.TrimPrefix(rel, string(filepath.Separator))
|
||
|
return filepath.ToSlash(rel)
|
||
|
}
|
||
|
|
||
|
// CurrentModule will attempt to return the module name from `go.mod` if
|
||
|
// modules are enabled.
|
||
|
// If modules are not enabled it will fallback to using CurrentPackage instead.
|
||
|
func CurrentModule() (string, error) {
|
||
|
if !Mods() {
|
||
|
return CurrentPackage(), nil
|
||
|
}
|
||
|
moddata, err := ioutil.ReadFile("go.mod")
|
||
|
if err != nil {
|
||
|
return "", errors.New("go.mod cannot be read or does not exist while go module is enabled")
|
||
|
}
|
||
|
packagePath := modfile.ModulePath(moddata)
|
||
|
if packagePath == "" {
|
||
|
return "", errors.New("go.mod is malformed")
|
||
|
}
|
||
|
return packagePath, nil
|
||
|
}
|
||
|
|
||
|
// CurrentPackage attempts to figure out the current package name from the PWD
|
||
|
// Use CurrentModule for a more accurate package name.
|
||
|
func CurrentPackage() string {
|
||
|
if Mods() {
|
||
|
}
|
||
|
pwd, _ := os.Getwd()
|
||
|
return importPath(pwd)
|
||
|
}
|
||
|
|
||
|
func Environ() []string {
|
||
|
gil.RLock()
|
||
|
defer gil.RUnlock()
|
||
|
var e []string
|
||
|
for k, v := range env {
|
||
|
e = append(e, fmt.Sprintf("%s=%s", k, v))
|
||
|
}
|
||
|
return e
|
||
|
}
|