Skip to content

Commit fad4af6

Browse files
committed
fix os.Env precedence resolving variables in .env file
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 4e62a57 commit fad4af6

File tree

4 files changed

+55
-15
lines changed

4 files changed

+55
-15
lines changed

cli/options.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,8 @@ func WithDotEnv(o *ProjectOptions) error {
255255
return err
256256
}
257257
for k, v := range envMap {
258-
o.Environment[k] = v
259-
if osVal, ok := os.LookupEnv(k); ok {
260-
o.Environment[k] = osVal
258+
if _, set := o.Environment[k]; !set {
259+
o.Environment[k] = v
261260
}
262261
}
263262
return nil
@@ -304,15 +303,12 @@ func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames [
304303
}
305304

306305
env, err := dotenv.ParseWithLookup(bytes.NewReader(b), func(k string) (string, bool) {
307-
v, ok := envMap[k]
306+
v, ok := currentEnv[k]
308307
if ok {
309308
return v, true
310309
}
311-
v, ok = currentEnv[k]
312-
if !ok {
313-
return "", false
314-
}
315-
return v, true
310+
v, ok = envMap[k]
311+
return v, ok
316312
})
317313
if err != nil {
318314
return envMap, errors.Wrapf(err, "failed to read %s", dotEnvFile)

cli/options_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,46 @@ func TestGetEnvFromFile(t *testing.T) {
322322
_, err = GetEnvFromFile(nil, wd, []string{f})
323323
assert.Check(t, strings.HasSuffix(err.Error(), ".env is a directory"))
324324
}
325+
326+
func TestEnvVariablePrecedence(t *testing.T) {
327+
testcases := []struct {
328+
name string
329+
dotEnv string
330+
osEnv []string
331+
expected map[string]string
332+
}{
333+
{
334+
"no value set in environment",
335+
"FOO=foo\nBAR=${FOO}",
336+
nil,
337+
map[string]string{
338+
"FOO": "foo",
339+
"BAR": "foo",
340+
},
341+
},
342+
{
343+
"conflict with value set in environment",
344+
"FOO=foo\nBAR=${FOO}",
345+
[]string{"FOO=zot"},
346+
map[string]string{
347+
"FOO": "zot",
348+
"BAR": "zot",
349+
},
350+
},
351+
}
352+
353+
for _, test := range testcases {
354+
t.Run(test.name, func(t *testing.T) {
355+
wd := t.TempDir()
356+
err := os.WriteFile(filepath.Join(wd, ".env"), []byte(test.dotEnv), 0o700)
357+
assert.NilError(t, err)
358+
options, err := NewProjectOptions(nil,
359+
// First load os.Env variable, higher in precedence rule
360+
WithEnv(test.osEnv),
361+
// Then load dotEnv file
362+
WithWorkingDirectory(wd), WithDotEnv)
363+
assert.NilError(t, err)
364+
assert.DeepEqual(t, test.expected, options.Environment)
365+
})
366+
}
367+
}

dotenv/godotenv.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,11 @@ func readFile(filename string, lookupFn LookupFn) (map[string]string, error) {
162162

163163
func expandVariables(value string, envMap map[string]string, lookupFn LookupFn) (string, error) {
164164
retVal, err := template.Substitute(value, func(k string) (string, bool) {
165-
if v, ok := envMap[k]; ok {
166-
return v, ok
165+
if v, ok := lookupFn(k); ok {
166+
return v, true
167167
}
168-
return lookupFn(k)
168+
v, ok := envMap[k]
169+
return v, ok
169170
})
170171
if err != nil {
171172
return value, err

dotenv/godotenv_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -587,9 +587,9 @@ func TestSubstitutionsWithEnvFilePrecedence(t *testing.T) {
587587
envFileName := "fixtures/substitutions.env"
588588
expectedValues := map[string]string{
589589
"OPTION_A": "1",
590-
"OPTION_B": "1",
591-
"OPTION_C": "1",
592-
"OPTION_D": "1_1",
590+
"OPTION_B": "5",
591+
"OPTION_C": "5",
592+
"OPTION_D": "5_5",
593593
"OPTION_E": "",
594594
}
595595

0 commit comments

Comments
 (0)