Repository: compose-spec/compose-go
Pull request #269 (2022-05-30 14:34:00)

Make dotEnv resolution plublic


Patch:
diff --git a/cli/options.go b/cli/options.go
index 7365bac..4bb3c2a 100644
--- a/cli/options.go
+++ b/cli/options.go
@@ -145,7 +145,7 @@ func WithDefaultConfigPath(o *ProjectOptions) error {
 // WithEnv defines a key=value set of variables used for compose file interpolation
 func WithEnv(env []string) ProjectOptionsFn {
 	return func(o *ProjectOptions) error {
-		for k, v := range getAsEqualsMap(env) {
+		for k, v := range utils.GetAsEqualsMap(env) {
 			o.Environment[k] = v
 		}
 		return nil
@@ -169,7 +169,7 @@ func WithLoadOptions(loadOptions ...func(*loader.Options)) ProjectOptionsFn {
 
 // WithOsEnv imports environment variables from OS
 func WithOsEnv(o *ProjectOptions) error {
-	for k, v := range getAsEqualsMap(os.Environ()) {
+	for k, v := range utils.GetAsEqualsMap(os.Environ()) {
 		if _, set := o.Environment[k]; set {
 			continue
 		}
@@ -188,46 +188,59 @@ func WithEnvFile(file string) ProjectOptionsFn {
 
 // WithDotEnv imports environment variables from .env file
 func WithDotEnv(o *ProjectOptions) error {
-	dotEnvFile := o.EnvFile
+	wd, err := o.GetWorkingDir()
+	if err != nil {
+		return err
+	}
+	envMap, err := GetEnvFromFile(o.Environment, wd, o.EnvFile)
+	if err != nil {
+		return err
+	}
+	for k, v := range envMap {
+		o.Environment[k] = v
+	}
+	return nil
+}
+
+func GetEnvFromFile(currentEnv map[string]string, workingDir string, filename string) (map[string]string, error) {
+	envMap := make(map[string]string)
+
+	dotEnvFile := filename
 	if dotEnvFile == "" {
-		wd, err := o.GetWorkingDir()
-		if err != nil {
-			return err
-		}
-		dotEnvFile = filepath.Join(wd, ".env")
+		dotEnvFile = filepath.Join(workingDir, ".env")
 	}
 	abs, err := filepath.Abs(dotEnvFile)
 	if err != nil {
-		return err
+		return envMap, err
 	}
 	dotEnvFile = abs
 
 	s, err := os.Stat(dotEnvFile)
 	if os.IsNotExist(err) {
-		if o.EnvFile != "" {
-			return errors.Errorf("Couldn't find env file: %s", o.EnvFile)
+		if filename != "" {
+			return nil, errors.Errorf("Couldn't find env file: %s", filename)
 		}
-		return nil
+		return envMap, nil
 	}
 	if err != nil {
-		return err
+		return envMap, err
 	}
 
 	if s.IsDir() {
-		if o.EnvFile == "" {
-			return nil
+		if filename == "" {
+			return envMap, nil
 		}
-		return errors.Errorf("%s is a directory", dotEnvFile)
+		return envMap, errors.Errorf("%s is a directory", dotEnvFile)
 	}
 
 	file, err := os.Open(dotEnvFile)
 	if err != nil {
-		return err
+		return envMap, err
 	}
 	defer file.Close()
 
 	env, err := dotenv.ParseWithLookup(file, func(k string) (string, bool) {
-		v, ok := o.Environment[k]
+		v, ok := currentEnv[k]
 		if !ok {
 
 			return "", false
@@ -235,15 +248,16 @@ func WithDotEnv(o *ProjectOptions) error {
 		return v, true
 	})
 	if err != nil {
-		return err
+		return envMap, err
 	}
 	for k, v := range env {
-		if _, set := o.Environment[k]; set {
+		if _, set := currentEnv[k]; set {
 			continue
 		}
-		o.Environment[k] = v
+		envMap[k] = v
 	}
-	return nil
+
+	return envMap, nil
 }
 
 // WithInterpolation set ProjectOptions to enable/skip interpolation
@@ -412,22 +426,3 @@ func absolutePaths(p []string) ([]string, error) {
 	}
 	return paths, nil
 }
-
-// getAsEqualsMap split key=value formatted strings into a key : value map
-func getAsEqualsMap(em []string) map[string]string {
-	m := make(map[string]string)
-	for _, v := range em {
-		kv := strings.SplitN(v, "=", 2)
-		m[kv[0]] = kv[1]
-	}
-	return m
-}
-
-// getAsEqualsMap format a key : value map into key=value strings
-func getAsStringList(em map[string]string) []string {
-	m := make([]string, 0, len(em))
-	for k, v := range em {
-		m = append(m, fmt.Sprintf("%s=%s", k, v))
-	}
-	return m
-}
diff --git a/utils/stringutils.go b/utils/stringutils.go
index bd95e7b..182ddf8 100644
--- a/utils/stringutils.go
+++ b/utils/stringutils.go
@@ -17,6 +17,7 @@
 package utils
 
 import (
+	"fmt"
 	"strconv"
 	"strings"
 )
@@ -36,3 +37,22 @@ func StringToBool(s string) bool {
 	b, _ := strconv.ParseBool(strings.ToLower(strings.TrimSpace(s)))
 	return b
 }
+
+// GetAsEqualsMap split key=value formatted strings into a key : value map
+func GetAsEqualsMap(em []string) map[string]string {
+	m := make(map[string]string)
+	for _, v := range em {
+		kv := strings.SplitN(v, "=", 2)
+		m[kv[0]] = kv[1]
+	}
+	return m
+}
+
+// GetAsEqualsMap format a key : value map into key=value strings
+func GetAsStringList(em map[string]string) []string {
+	m := make([]string, 0, len(em))
+	for k, v := range em {
+		m = append(m, fmt.Sprintf("%s=%s", k, v))
+	}
+	return m
+}


Test patch:
diff --git a/cli/options_test.go b/cli/options_test.go
index 8429c44..9d4c65d 100644
--- a/cli/options_test.go
+++ b/cli/options_test.go
@@ -23,6 +23,7 @@ import (
 	"testing"
 
 	"github.com/compose-spec/compose-go/consts"
+	"github.com/compose-spec/compose-go/utils"
 	"gotest.tools/v3/assert"
 )
 
@@ -231,8 +232,8 @@ func TestProjectNameFromWorkingDir(t *testing.T) {
 func TestEnvMap(t *testing.T) {
 	m := map[string]string{}
 	m["foo"] = "bar"
-	l := getAsStringList(m)
+	l := utils.GetAsStringList(m)
 	assert.Equal(t, l[0], "foo=bar")
-	m = getAsEqualsMap(l)
+	m = utils.GetAsEqualsMap(l)
 	assert.Equal(t, m["foo"], "bar")
 }