Skip to content

Commit 1252033

Browse files
authored
[devbox.json] support env_from dotenv files (#2174)
## Summary NOTE: Docs update PR will follow. As per request from users, I added support for `env_from` field being able to take a path to `*.env` file and have those env variables available in devbox shell and run. If a duplicate env variable is specified in both `.env` and in the `"env"` section of devbox.json, the env section from devbox.json takes priority and overwrites. This I believe is the correct behavior but I'm open to discussion. ## How was it tested? see test file at `testscripts/run/envfrom.test.txt`
1 parent 7d7741b commit 1252033

File tree

3 files changed

+110
-2
lines changed

3 files changed

+110
-2
lines changed

internal/devbox/devbox.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,21 @@ func (d *Devbox) configEnvs(
951951
}
952952
}
953953
}
954+
} else if d.cfg.Root.IsdotEnvEnabled() {
955+
// if env_from points to a .env file, parse and add it
956+
parsedEnvs, err := d.cfg.Root.ParseEnvsFromDotEnv()
957+
if err != nil {
958+
// it's fine to include the error ParseEnvsFromDotEnv here because
959+
// the error message is relevant to the user
960+
return nil, usererr.New(
961+
"failed parsing %s file. Error: %v",
962+
d.cfg.Root.EnvFrom,
963+
err,
964+
)
965+
}
966+
for k, v := range parsedEnvs {
967+
env[k] = v
968+
}
954969
} else if d.cfg.Root.EnvFrom != "" {
955970
return nil, usererr.New(
956971
"unknown from_env value: %s. Supported value is: %q.",
Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,63 @@
11
package configfile
22

3+
import (
4+
"bufio"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
)
10+
311
func (c *ConfigFile) IsEnvsecEnabled() bool {
4-
// envsec for legacy.
5-
return c.EnvFrom == "envsec" || c.EnvFrom == "jetpack-cloud"
12+
// envsec for legacy. jetpack-cloud for legacy
13+
return c.EnvFrom == "envsec" || c.EnvFrom == "jetpack-cloud" || c.EnvFrom == "jetify-cloud"
14+
}
15+
16+
func (c *ConfigFile) IsdotEnvEnabled() bool {
17+
// filename has to end with .env
18+
return filepath.Ext(c.EnvFrom) == ".env"
19+
}
20+
21+
func (c *ConfigFile) ParseEnvsFromDotEnv() (map[string]string, error) {
22+
// This check should never happen because we call IsdotEnvEnabled
23+
// before calling this method. But having it makes it more robust
24+
// in case if anyone uses this method without the IsdotEnvEnabled
25+
if !c.IsdotEnvEnabled() {
26+
return nil, fmt.Errorf("env file does not have a .env extension")
27+
}
28+
29+
file, err := os.Open(c.EnvFrom)
30+
if err != nil {
31+
return nil, fmt.Errorf("failed to open file: %s", c.EnvFrom)
32+
}
33+
defer file.Close()
34+
35+
envMap := map[string]string{}
36+
37+
// Read the file line by line
38+
scanner := bufio.NewScanner(file)
39+
for scanner.Scan() {
40+
line := scanner.Text()
41+
// Ideally .env file shouldn't have empty lines and comments but
42+
// this check makes it allowed.
43+
if strings.TrimSpace(line) == "" || strings.HasPrefix(line, "#") {
44+
continue
45+
}
46+
parts := strings.SplitN(line, "=", 2)
47+
if len(parts) != 2 {
48+
return nil, fmt.Errorf("invalid line in .env file: %s", line)
49+
}
50+
// Also ideally, .env files should not have space in their `key=value` format
51+
// but this allows `key = value` to pass through as well
52+
key := strings.TrimSpace(parts[0])
53+
value := strings.TrimSpace(parts[1])
54+
55+
// Add the parsed key-value pair to the map
56+
envMap[key] = value
57+
}
58+
59+
if err := scanner.Err(); err != nil {
60+
return nil, fmt.Errorf("failed to read env file: %v", err)
61+
}
62+
return envMap, nil
663
}

testscripts/run/envfrom.test.txt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Tests related to setting the env_from for devbox run.
2+
3+
exec devbox run test
4+
stdout 'BAR'
5+
6+
exec devbox run test2
7+
stdout 'BAZ'
8+
9+
exec devbox run test3
10+
stdout 'BAS'
11+
12+
exec devbox run test4
13+
stdout ''
14+
15+
-- test.env --
16+
FOO=BAR
17+
FOO2 = BAZ
18+
FOO3=ToBeOverwrittenByDevboxJSON
19+
# FOO4=comment shouldn't be processed
20+
21+
-- devbox.json --
22+
{
23+
"packages": [],
24+
"env": {
25+
"FOO3": "BAS"
26+
},
27+
"shell": {
28+
"scripts": {
29+
"test": "echo $FOO",
30+
"test2": "echo $FOO2",
31+
"test3": "echo $FOO3",
32+
"test4": "echo $FOO4"
33+
}
34+
},
35+
"env_from": "test.env"
36+
}

0 commit comments

Comments
 (0)