Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions cmd/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/e2e/compose_environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions pkg/e2e/compose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 2 additions & 0 deletions pkg/e2e/fixtures/environment/env-priority/compose.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
services:
env-compose-priority:
image: env-compose-priority
build:
context: .
2 changes: 2 additions & 0 deletions pkg/e2e/fixtures/nested/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ROOT=root
WIN=root
4 changes: 4 additions & 0 deletions pkg/e2e/fixtures/nested/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
services:
echo:
image: alpine
command: echo $ROOT $SUB win=$WIN
2 changes: 2 additions & 0 deletions pkg/e2e/fixtures/nested/sub/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SUB=sub
WIN=sub
Loading