-
Notifications
You must be signed in to change notification settings - Fork 114
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
Fix env file read from include #582
Fix env file read from include #582
Conversation
e9735eb
to
9b5ad02
Compare
loader/include.go
Outdated
} else { | ||
envFile := []string{} | ||
for _, f := range r.EnvFile { | ||
f := filepath.Join(configDetails.WorkingDir, f) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to guard this with filepath.IsAbs(r.Envfile)
as env_file can also be set to an absolute path, or relative to user's home ~
loader/include.go
Outdated
envFile := []string{} | ||
for _, f := range r.EnvFile { | ||
f := filepath.Join(configDetails.WorkingDir, f) | ||
if s, err := os.Stat(f); err == nil && !s.IsDir() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if explicitly selected env file is a Dir, then this is an error. We only ignore when selecting the default .env
file.
loader/include.go
Outdated
} | ||
|
||
envFromFile, err := dotenv.GetEnvFromFile(configDetails.Environment, r.EnvFile) | ||
if err != nil { | ||
return err | ||
} | ||
configDetails.Environment.Merge(envFromFile) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change ?
Need to clone before merge, otherwise env_file selected to parse the included model will still be present in configDetails
and will have impacts on next include being processed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It took me some time to understand what is the error. Currently, the the only environment variables that are resolved DURING the parse is the Interpolation ($VAR
) and in this case environment: - VAR
will only be solved AFTER the load in - at the project level -
Line 528 in 4c12ed6
project, err = project.WithServicesEnvironmentResolved(opts.discardEnvFiles) |
That's why changing the
Merge
to also be applied to the higher level configDetails
works. However, this is not correct, the environment variables of custom.env
(from the test and the issue example) should be only within the context of the include
. I propose to apply a resolveEnvironment
at the end of the loadYamlModel
to have this case into consideration, without changing the overall project.Environment
as the first fix
17844c5
to
3a3d39e
Compare
loader/loader.go
Outdated
@@ -787,3 +788,29 @@ func convertVolumePath(volume types.ServiceVolumeConfig) types.ServiceVolumeConf | |||
volume.Source = convertedSource | |||
return volume | |||
} | |||
|
|||
func resolveEnvironment(dict map[string]any, opts *Options) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was confused reviewing this PR about this func name, which is close to Project.ResolveEnvironment
but serve a very different scenario, but I don't have a better name for now 😅
Will create a follow-up PR if I find one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I'm not alone 😅
loader/loader.go
Outdated
if !ok { | ||
continue | ||
} | ||
if found, ok := opts.Interpolate.LookupValue(env); ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interpolate
being involved here is a bit confusing. I understand the actual goal is to access environment map, which LookupValue
captured in options. So this basically is a hack - We should better add an explicit Environment
(or Variables
?) attribute to loader.Options
so we make it explicit we rely on the ConfigDetails.Environment
here (and maybe in other places in future)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should directly get the value from the map and avoid looping the whole dict
Edit: I didn't see the recursive call of the method, so I was wrong
loader/loader.go
Outdated
@@ -787,3 +788,29 @@ func convertVolumePath(volume types.ServiceVolumeConfig) types.ServiceVolumeConf | |||
volume.Source = convertedSource | |||
return volume | |||
} | |||
|
|||
func resolveEnvironment(dict map[string]any, opts *Options) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I'm not alone 😅
loader/loader.go
Outdated
for key, value := range dict { | ||
if key == "environment" { | ||
if environment, ok := value.([]any); ok { | ||
serviceEnvironment := []any{} | ||
for _, env := range environment { | ||
env, ok := env.(string) | ||
if !ok { | ||
continue | ||
} | ||
if found, ok := opts.Interpolate.LookupValue(env); ok { | ||
serviceEnvironment = append(serviceEnvironment, fmt.Sprintf("%s=%s", env, found)) | ||
} else { | ||
serviceEnvironment = append(serviceEnvironment, env) | ||
} | ||
|
||
} | ||
dict["environment"] = serviceEnvironment | ||
} | ||
} | ||
if v, ok := value.(map[string]any); ok { | ||
resolveEnvironment(v, opts) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can also do
switch environment := value.(type) {
case []any:
for _, env := range environment { ... }
case maps[string]any:
resolveEnvironment(v, opts)
loader/loader.go
Outdated
for key, value := range dict { | ||
if key == "environment" { | ||
if environment, ok := value.([]any); ok { | ||
serviceEnvironment := []any{} | ||
for _, env := range environment { | ||
env, ok := env.(string) | ||
if !ok { | ||
continue | ||
} | ||
if found, ok := opts.Interpolate.LookupValue(env); ok { | ||
serviceEnvironment = append(serviceEnvironment, fmt.Sprintf("%s=%s", env, found)) | ||
} else { | ||
serviceEnvironment = append(serviceEnvironment, env) | ||
} | ||
|
||
} | ||
dict["environment"] = serviceEnvironment | ||
} | ||
} | ||
if v, ok := value.(map[string]any); ok { | ||
resolveEnvironment(v, opts) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can also do
switch environment := value.(type) {
case []any:
for _, env := range environment { ... }
case maps[string]any:
resolveEnvironment(v, opts)
c2d05a7
to
15225af
Compare
d3a0d73
to
11c5003
Compare
Signed-off-by: jhrotko <joana.hrotko@docker.com>
11c5003
to
9ae13bd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
When reading a custom env file from include, we were not passing the read enviroenmnt variables to the config Environments, thus the read vars were being "forgotten".
Also fixed the default directory for environment variables, which should be the same as the compose.yml that is invoking the
include
withListeners
change forgotten in previous PRFixes:
docker/compose#11509
https://docker.atlassian.net/browse/COMP-438