diff --git a/loader/include.go b/loader/include.go index 823c2f7a..9244f2b3 100644 --- a/loader/include.go +++ b/loader/include.go @@ -109,28 +109,36 @@ func ApplyInclude(ctx context.Context, workingDir string, environment types.Mapp WorkingDir: r.ProjectDirectory, }) + envFile := r.EnvFile if len(r.EnvFile) == 0 { - f := filepath.Join(r.ProjectDirectory, ".env") - if s, err := os.Stat(f); err == nil && !s.IsDir() { - r.EnvFile = types.StringList{f} - } - } else { - envFile := []string{} - for _, f := range r.EnvFile { - if !filepath.IsAbs(f) { - f = filepath.Join(workingDir, f) - s, err := os.Stat(f) - if err != nil { - return err - } - if s.IsDir() { - return fmt.Errorf("%s is not a file", f) - } + envFile = types.StringList{".env"} + } + + resolvedEnvFile := []string{} + for _, f := range envFile { + for _, loader := range options.ResourceLoaders { + if !loader.Accept(f) { + continue + } + path, err := loader.Load(ctx, f) + if err != nil { + return err + } + s, err := os.Stat(path) + if os.IsNotExist(err) && len(r.EnvFile) == 0 { + break // Skip if default .env not found + } + if err != nil { + return err + } + if s.IsDir() { + return fmt.Errorf("%s is not a file", f) } - envFile = append(envFile, f) + resolvedEnvFile = append(resolvedEnvFile, path) + break } - r.EnvFile = envFile } + r.EnvFile = resolvedEnvFile envFromFile, err := dotenv.GetEnvFromFile(environment, r.EnvFile) if err != nil { diff --git a/loader/include_test.go b/loader/include_test.go index f44d417b..b2383c8d 100644 --- a/loader/include_test.go +++ b/loader/include_test.go @@ -163,6 +163,85 @@ services: } +func TestLoadWithIncludeEnvTripleTimes(t *testing.T) { + fileName := "compose.yml" + tmpdir := t.TempDir() + // file in root + yaml := ` +include: + - path: + - ./module/compose.yml + env_file: + - ./custom.env +services: + a: + image: alpine + environment: + - VAR_NAME` + createFile(t, tmpdir, `VAR_NAME=value`, "custom.env") + path := createFile(t, tmpdir, yaml, fileName) + // file in /module + yaml = ` +include: + - path: + - ./submodule/compose.yml + env_file: + - ../custom.env +services: + b: + image: alpine + environment: + - VAR_NAME` + createFileSubDir(t, tmpdir, "module", yaml, fileName) + + yaml = ` +include: + - path: + - ./subsubmodule/compose.yml + env_file: + - ../../custom.env +services: + c: + image: alpine + environment: + - VAR_NAME` + createFileSubDir(t, tmpdir, "module/submodule", yaml, fileName) + + yaml = ` +services: + d: + image: alpine + environment: + - VAR_NAME` + createFileSubDir(t, tmpdir, "module/submodule/subsubmodule", yaml, fileName) + + p, err := Load(types.ConfigDetails{ + WorkingDir: tmpdir, + ConfigFiles: []types.ConfigFile{{ + Filename: path, + }}, + Environment: nil, + }, func(options *Options) { + options.SkipNormalization = true + options.ResolvePaths = true + options.SetProjectName("project", true) + }) + assert.NilError(t, err) + a := p.Services["a"] + // make sure VAR_NAME is only accessible in include context + assert.Check(t, a.Environment["VAR_NAME"] == nil, "VAR_NAME should not be defined in environment") + b := p.Services["b"] + assert.Check(t, b.Environment["VAR_NAME"] != nil, "VAR_NAME is not defined in environment") + assert.Equal(t, *b.Environment["VAR_NAME"], "value") + c := p.Services["c"] + assert.Check(t, c.Environment["VAR_NAME"] != nil, "VAR_NAME is not defined in environment") + assert.Equal(t, *c.Environment["VAR_NAME"], "value") + d := p.Services["d"] + assert.Check(t, d.Environment["VAR_NAME"] != nil, "VAR_NAME is not defined in environment") + assert.Equal(t, *d.Environment["VAR_NAME"], "value") + +} + func TestIncludeWithProjectDirectory(t *testing.T) { var envs map[string]string if runtime.GOOS == "windows" {