-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
241 lines (190 loc) · 6.64 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package main
import (
"io/ioutil"
"log"
"os"
"os/exec"
)
const (
aliasKey = "DOTENV_COMMAND"
strictKey = "DOTENV_STRICT"
debugKey = "DOTENV_DEBUG"
)
var (
dotenvLocations = envOrDefault("DOTENV_FOLDER_PATH", "~/.dotenv/")
dotenvUse = envOrDefault("DOTENV", "")
dotenvStrict = envOrDefault(strictKey, "")
version = "development"
knownDotenvVars = [...]string{"DOTENV_FOLDER_PATH", "DOTENV", debugKey, strictKey, aliasKey}
)
const usage = `Usage: dotenv [--environment | -e path] [command] [args...]
Place a ".env" file at the same level where the current working directory is,
then execute dotenv [command] [args...].
Additionally, use a ".env" file from ~/.dotenv/ or wherever $DOTENV_FOLDER_PATH
points to, by specifying $DOTENV or --environment=filename or -e=filename (without
the extension) and it will be used automatically. If the path passed is absolute,
then whatever file passed will be used as environment if it can be parsed as a
key=value format.
If the dotenv file sets an environment variable named DOTENV_COMMAND whose value
is a valid, runnable command, the command will be used and all the remaining
arguments will be sent to the command. For example, the following call will execute
"kubectl get pods"
$ cat ~/.dotenv/kubectl.env
DOTENV_COMMAND=kubectl
KUBECONFIG=/home/patrick/.kube/cluster.yaml
$ dotenv -e=kubectl get pods
# since the command is already set in the dotenv file, you
# don't need to specify it like "dotenv -e=kubectl kubectl get pods"
If $DOTENV_STRICT is set to any value, and set either through environment variables
or in the environment variables file, strict mode is applied, where the command
gets executed only with the environment variables from the environment file, and
without the environment variables from the environment. This mode is useful to not
leak environment variables to your commmands that don't really need them, but also
keep in mind some programs rely on $PATH to be set, or $HOME or other useful
environment variables.
A cool example with no arguments but configuration given via environment variables:
$ DOTENV=<(echo -e "DOTENV_COMMAND=env\nNAME=joe\nDOTENV_STRICT=1") dotenv
NAME=joe
dotenv will execute your command, stdin, stdout and stderr will be piped, and the
exit code will be passed to your terminal.`
func main() {
logger := log.New(ioutil.Discard, "[dotenv-debug] ", log.Lshortfile|log.LstdFlags)
if os.Getenv(debugKey) != "" {
logger.SetOutput(os.Stdout)
}
var (
command string
evfile string
)
args := os.Args[1:]
if isControlFlagSet("-h", "--help") {
os.Stdout.WriteString(usage + "\n")
return
}
if isControlFlagSet("-v", "--version") {
os.Stdout.WriteString("[dotenv] version " + version + "\n")
return
}
if dotenvUse != "" {
logger.Printf("environment variable $DOTENV set to: %q -- using that as the file", dotenvUse)
evfile = dotenvUse
}
if isControlFlagSet("--environment", "-e") {
vals := getFlagValue("--environment", "-e")
venv := ""
logger.Printf("environment parameters parsed: %v", vals)
if v, found := vals["--environment"]; found {
logger.Printf("long parameter --environment set to: %q", v)
venv = v
}
if v, found := vals["-e"]; found {
if venv != "" {
logger.Printf("exiting because both flags, --environment and -e were provided")
errexit("Both flags provided: --environment and -e -- must specify only one")
}
logger.Printf("short parameter -e set to: %q", v)
venv = v
}
if startswith(venv, "/") || startswith(venv, "./") {
logger.Printf("environment file passed %q starts with a control character, assuming full path", venv)
evfile = venv
} else {
if fp, found := envFilePresentInHome(venv); found {
logger.Printf("found a file in the user's directory with the file name matching %q: %s", venv, fp)
evfile = fp
} else {
logger.Printf("no file found in user's directory for %q, assuming full path", venv)
evfile = venv
}
}
args = getAllArgsAfter(venv)
logger.Printf("parsed arguments after environment flags to be: %#v", args)
}
if evfile == "" {
logger.Printf("no env file set, defaulting to assuming there's one in the current directory")
evfile = ".env"
}
envvars, err := loadVirtualEnv(evfile)
if err != nil {
if _, ok := err.(*filenotfound); ok {
logger.Printf("unable to find dotenv file at %q", evfile)
errexit("No dotenv file found at %q", evfile)
}
logger.Printf("unknown error while handling envfile %q: %s", evfile, err.Error())
errexit("Can't read environment variable file: %s", err.Error())
}
aliascmd, hasalias := envvars[aliasKey]
logger.Printf("found alias in env file? %v -- alias: %q", hasalias, aliascmd)
switch len(args) {
case 0:
if !hasalias {
logger.Printf("exiting just because no alias was set and no commands were passed")
errexit("missing command and / or arguments, see --help")
}
case 1:
command = args[0]
args = []string{}
default:
command = args[0]
args = args[1:]
}
logger.Printf("got command %q -- args: %#v", command, args)
if hasalias {
if command != "" {
args = append([]string{command}, args...)
}
command = aliascmd
delete(envvars, aliasKey)
logger.Printf("swapping command due to alias to %q -- args: %#v", command, args)
}
if strict, found := envvars[strictKey]; found {
dotenvStrict = strict
delete(envvars, strictKey)
}
environ := make([]string, 0, len(os.Environ()))
for _, v := range os.Environ() {
known := false
for _, m := range knownDotenvVars {
if startswith(v, m+"=") {
known = true
}
}
if !known {
logger.Printf("Adding unknown env var %q", v)
environ = append(environ, v)
}
}
vars := make([]string, 0, len(envvars)+len(environ))
logOffset := 0
if dotenvStrict == "" {
logger.Printf("strict mode environment variable not set: appending all current environment variables")
vars = append(vars, environ...)
logOffset = len(environ)
}
for k, v := range envvars {
known := false
for _, m := range knownDotenvVars {
if m == v {
known = true
}
}
if !known {
vars = append(vars, k+"="+v)
}
}
logger.Printf("environment variables to be injected to command (besides %d current env vars): %v", len(environ), vars[logOffset:])
cmd := getCommand(command, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = vars
logger.Printf("command to be executed: %s %v", command, args)
if err := cmd.Run(); err != nil {
if e, ok := err.(*exec.ExitError); ok {
logger.Printf("command exited with exit code: %v", e)
os.Exit(e.ExitCode())
}
logger.Printf("unable to execute command %q: %s", command, err.Error())
errexit("Unable to execute command %q: %s", command, err.Error())
}
}