Browse Source

allow a local .env file to override compose.yaml sibling .env

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 1 year ago
parent
commit
9e8c8caa2b

+ 27 - 9
cmd/compose/compose.go

@@ -171,7 +171,7 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
 	f.StringArrayVar(&o.Profiles, "profile", []string{}, "Specify a profile to enable")
 	f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
 	f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
-	f.StringArrayVar(&o.EnvFiles, "env-file", nil, "Specify an alternate environment file")
+	f.StringArrayVar(&o.EnvFiles, "env-file", defaultStringArrayVar(ComposeEnvFiles), "Specify an alternate environment file")
 	f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the, first specified, Compose file)")
 	f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the, first specified, Compose file)")
 	f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
@@ -180,6 +180,13 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
 	_ = f.MarkHidden("workdir")
 }
 
+// get default value for a command line flag that is set by a coma-separated value in environment variable
+func defaultStringArrayVar(env string) []string {
+	return strings.FieldsFunc(os.Getenv(env), func(c rune) bool {
+		return c == ','
+	})
+}
+
 func (o *ProjectOptions) projectOrName(ctx context.Context, dockerCli command.Cli, services ...string) (*types.Project, string, error) {
 	name := o.ProjectName
 	var project *types.Project
@@ -384,7 +391,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
 			ctx := cmd.Context()
 
 			// (1) process env vars
-			err := setEnvWithDotEnv(&opts)
+			err := setEnvWithLocalDotEnv(&opts)
 			if err != nil {
 				return err
 			}
@@ -594,18 +601,29 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
 	return c
 }
 
-func setEnvWithDotEnv(prjOpts *ProjectOptions) error {
-	if len(prjOpts.EnvFiles) == 0 {
-		if envFiles := os.Getenv(ComposeEnvFiles); envFiles != "" {
-			prjOpts.EnvFiles = strings.Split(envFiles, ",")
-		}
+// If user has a local .env file, load it as os.environment so it can be used to set COMPOSE_ variables
+// This also allows to override values set by the default .env in a compose project when ran from a distinct folder
+func setEnvWithLocalDotEnv(prjOpts *ProjectOptions) error {
+	if len(prjOpts.EnvFiles) > 0 {
+		return nil
 	}
-	options, err := prjOpts.toProjectOptions()
+
+	wd, err := os.Getwd()
 	if err != nil {
 		return compose.WrapComposeError(err)
 	}
 
-	envFromFile, err := dotenv.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), options.EnvFiles)
+	defaultDotEnv := filepath.Join(wd, ".env")
+
+	s, err := os.Stat(defaultDotEnv)
+	if os.IsNotExist(err) || s.IsDir() {
+		return nil
+	}
+	if err != nil {
+		return err
+	}
+
+	envFromFile, err := dotenv.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), []string{defaultDotEnv})
 	if err != nil {
 		return err
 	}

+ 2 - 1
pkg/e2e/compose_environment_test.go

@@ -117,7 +117,8 @@ func TestEnvPriority(t *testing.T) {
 			"run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
 		cmd.Env = append(cmd.Env, "COMPOSE_ENV_FILES=./fixtures/environment/env-priority/.env.override.with.default")
 		res := icmd.RunCmd(cmd)
-		assert.Equal(t, strings.TrimSpace(res.Stdout()), "EnvFileDefaultValue")
+		stdout := res.Stdout()
+		assert.Equal(t, strings.TrimSpace(stdout), "EnvFileDefaultValue")
 	})
 
 	// No Compose file and env variable pass to the run command

+ 21 - 0
pkg/e2e/compose_test.go

@@ -329,3 +329,24 @@ func TestResolveDotEnv(t *testing.T) {
 		Out:      "image: backend:latest",
 	})
 }
+
+func TestNestedDotEnv(t *testing.T) {
+	c := NewCLI(t)
+
+	cmd := c.NewDockerComposeCmd(t, "run", "echo")
+	cmd.Dir = filepath.Join(".", "fixtures", "nested")
+	res := icmd.RunCmd(cmd)
+	res.Assert(t, icmd.Expected{
+		ExitCode: 0,
+		Out:      "root win=root",
+	})
+
+	cmd = c.NewDockerComposeCmd(t, "run", "echo")
+	cmd.Dir = filepath.Join(".", "fixtures", "nested", "sub")
+	res = icmd.RunCmd(cmd)
+	res.Assert(t, icmd.Expected{
+		ExitCode: 0,
+		Out:      "root sub win=sub",
+	})
+
+}

+ 2 - 0
pkg/e2e/fixtures/environment/env-priority/compose.yaml

@@ -1,3 +1,5 @@
 services:
   env-compose-priority:
     image: env-compose-priority
+    build:
+      context: .

+ 2 - 0
pkg/e2e/fixtures/nested/.env

@@ -0,0 +1,2 @@
+ROOT=root
+WIN=root

+ 4 - 0
pkg/e2e/fixtures/nested/compose.yaml

@@ -0,0 +1,4 @@
+services:
+  echo:
+    image: alpine
+    command: echo $ROOT $SUB win=$WIN

+ 2 - 0
pkg/e2e/fixtures/nested/sub/.env

@@ -0,0 +1,2 @@
+SUB=sub
+WIN=sub