diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 51f69f3da1..276b521218 100644 --- a/cmd/compose/compose.go +++ b/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 } diff --git a/pkg/e2e/compose_environment_test.go b/pkg/e2e/compose_environment_test.go index f0538f9c11..e5d0373b47 100644 --- a/pkg/e2e/compose_environment_test.go +++ b/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 diff --git a/pkg/e2e/compose_test.go b/pkg/e2e/compose_test.go index 75d546f730..e34156e808 100644 --- a/pkg/e2e/compose_test.go +++ b/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", + }) + +} diff --git a/pkg/e2e/fixtures/environment/env-priority/compose.yaml b/pkg/e2e/fixtures/environment/env-priority/compose.yaml index 0ccade6af4..9d107d857d 100644 --- a/pkg/e2e/fixtures/environment/env-priority/compose.yaml +++ b/pkg/e2e/fixtures/environment/env-priority/compose.yaml @@ -1,3 +1,5 @@ services: env-compose-priority: image: env-compose-priority + build: + context: . diff --git a/pkg/e2e/fixtures/nested/.env b/pkg/e2e/fixtures/nested/.env new file mode 100644 index 0000000000..df2676a374 --- /dev/null +++ b/pkg/e2e/fixtures/nested/.env @@ -0,0 +1,2 @@ +ROOT=root +WIN=root \ No newline at end of file diff --git a/pkg/e2e/fixtures/nested/compose.yaml b/pkg/e2e/fixtures/nested/compose.yaml new file mode 100644 index 0000000000..d449f943f1 --- /dev/null +++ b/pkg/e2e/fixtures/nested/compose.yaml @@ -0,0 +1,4 @@ +services: + echo: + image: alpine + command: echo $ROOT $SUB win=$WIN diff --git a/pkg/e2e/fixtures/nested/sub/.env b/pkg/e2e/fixtures/nested/sub/.env new file mode 100644 index 0000000000..b930a81915 --- /dev/null +++ b/pkg/e2e/fixtures/nested/sub/.env @@ -0,0 +1,2 @@ +SUB=sub +WIN=sub