Skip to content

Commit

Permalink
Fix env file read from include
Browse files Browse the repository at this point in the history
Signed-off-by: jhrotko <joana.hrotko@docker.com>
  • Loading branch information
jhrotko committed Feb 21, 2024
1 parent ef78476 commit 11c5003
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 4 deletions.
6 changes: 3 additions & 3 deletions cli/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ func ProjectFromOptions(ctx context.Context, options *ProjectOptions) (*types.Pr
options.loadOptions = append(options.loadOptions,
withNamePrecedenceLoad(workingDir, options),
withConvertWindowsPaths(options),
withListener(options))
withListeners(options))

project, err := loader.LoadWithContext(ctx, types.ConfigDetails{
ConfigFiles: configs,
Expand Down Expand Up @@ -484,9 +484,9 @@ func withConvertWindowsPaths(options *ProjectOptions) func(*loader.Options) {
}

// save listeners from ProjectOptions (compose) to loader.Options
func withListener(options *ProjectOptions) func(*loader.Options) {
func withListeners(options *ProjectOptions) func(*loader.Options) {
return func(opts *loader.Options) {
opts.Listeners = options.Listeners
opts.Listeners = append(opts.Listeners, options.Listeners...)
}
}

Expand Down
43 changes: 43 additions & 0 deletions loader/environment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package loader

import (
"fmt"

"github.com/compose-spec/compose-go/v2/types"
)

// Will update the environment variables for the format {- VAR} (without interpolation)
// This function should resolve context environment vars for include (passed in env_file)
func resolveServicesEnvironment(dict map[string]any, config types.ConfigDetails) {
services, ok := dict["services"].(map[string]any)
if !ok {
return
}

for service, cfg := range services {
serviceConfig, ok := cfg.(map[string]any)
if !ok {
continue
}
serviceEnv, ok := serviceConfig["environment"].([]any)
if !ok {
continue
}
envs := []any{}
for _, env := range serviceEnv {
varEnv, ok := env.(string)
if !ok {
continue
}
if found, ok := config.Environment[varEnv]; ok {
envs = append(envs, fmt.Sprintf("%s=%s", varEnv, found))
} else {
// either does not exist or it was already resolved in interpolation
envs = append(envs, varEnv)
}
}
serviceConfig["environment"] = envs
services[service] = serviceConfig
}
dict["services"] = services
}
16 changes: 16 additions & 0 deletions loader/include.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,22 @@ func ApplyInclude(ctx context.Context, configDetails types.ConfigDetails, model
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(configDetails.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 = append(envFile, f)
}
r.EnvFile = envFile
}

envFromFile, err := dotenv.GetEnvFromFile(configDetails.Environment, r.EnvFile)
Expand Down
71 changes: 70 additions & 1 deletion loader/include_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package loader

import (
"context"
"os"
"path/filepath"
"testing"

Expand Down Expand Up @@ -50,7 +51,7 @@ services:
assert.Equal(t, imported.ContainerName, "override")

// include 2 different services with same name should trigger an error
p, err = Load(buildConfigDetails(`
_, err = Load(buildConfigDetails(`
name: 'test-multi-include'
include:
Expand Down Expand Up @@ -89,3 +90,71 @@ func TestIncludeRelative(t *testing.T) {
assert.Equal(t, included.Build.Context, ".")
assert.Equal(t, included.Volumes[0].Source, ".")
}

func TestLoadWithIncludeEnv(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 = `
services:
b:
image: alpine
environment:
- VAR_NAME
c:
image: alpine
environment:
- VAR_NAME`
createFileSubDir(t, tmpdir, "module", 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")

}

func createFile(t *testing.T, rootDir, content, fileName string) string {
path := filepath.Join(rootDir, fileName)
assert.NilError(t, os.WriteFile(path, []byte(content), 0o600))
return path
}

func createFileSubDir(t *testing.T, rootDir, subDir, content, fileName string) string {
subDirPath := filepath.Join(rootDir, subDir)
assert.NilError(t, os.Mkdir(subDirPath, 0o700))
path := filepath.Join(subDirPath, fileName)
assert.NilError(t, os.WriteFile(path, []byte(content), 0o600))
return path
}
1 change: 1 addition & 0 deletions loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Option
return nil, err
}
}
resolveServicesEnvironment(dict, config)

return dict, nil
}
Expand Down

0 comments on commit 11c5003

Please sign in to comment.