Skip to content

Commit 2ebf7a5

Browse files
committed
env_file can be declared optional
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 81e1e90 commit 2ebf7a5

20 files changed

+288
-41
lines changed

cli/options_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ func TestProjectWithDiscardEnvFile(t *testing.T) {
270270
service, err := p.GetService("simple")
271271
assert.NilError(t, err)
272272
assert.Equal(t, *service.Environment["DEFAULT_PORT"], "8080")
273-
assert.Assert(t, service.EnvFile == nil)
273+
assert.Assert(t, len(service.EnvFiles) == 0)
274274
assert.Equal(t, service.Ports[0].Published, "8000")
275275
}
276276

@@ -287,7 +287,7 @@ func TestProjectWithMultipleEnvFile(t *testing.T) {
287287
service, err := p.GetService("simple")
288288
assert.NilError(t, err)
289289
assert.Equal(t, *service.Environment["DEFAULT_PORT"], "9090")
290-
assert.Assert(t, service.EnvFile == nil)
290+
assert.Assert(t, len(service.EnvFiles) == 0)
291291
assert.Equal(t, service.Ports[0].Published, "9000")
292292
}
293293

loader/full-example.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ services:
141141
# env_file: .env
142142
env_file:
143143
- ./example1.env
144-
- ./example2.env
144+
- path: ./example2.env
145+
required: false
145146

146147
# Mapping or list
147148
# Mapping values can be strings, numbers or null

loader/full-struct_test.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,15 @@ func services(workingDir, homeDir string) types.Services {
176176
"ENV.WITH.DOT": strPtr("ok"),
177177
"ENV_WITH_UNDERSCORE": strPtr("ok"),
178178
},
179-
EnvFile: []string{
180-
filepath.Join(workingDir, "example1.env"),
181-
filepath.Join(workingDir, "example2.env"),
179+
EnvFiles: []types.EnvFile{
180+
{
181+
Path: filepath.Join(workingDir, "example1.env"),
182+
Required: true,
183+
},
184+
{
185+
Path: filepath.Join(workingDir, "example2.env"),
186+
Required: false,
187+
},
182188
},
183189
Expose: []string{"3000", "8000"},
184190
ExternalLinks: []string{
@@ -729,7 +735,8 @@ services:
729735
QUX: qux_from_environment
730736
env_file:
731737
- %s
732-
- %s
738+
- path: %s
739+
required: false
733740
expose:
734741
- "3000"
735742
- "8000"
@@ -1324,7 +1331,10 @@ func fullExampleJSON(workingDir, homeDir string) string {
13241331
},
13251332
"env_file": [
13261333
"%s",
1327-
"%s"
1334+
{
1335+
"path": "%s",
1336+
"required": false
1337+
}
13281338
],
13291339
"expose": [
13301340
"3000",

loader/loader_test.go

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,8 @@ services:
907907
image: nginx
908908
env_file:
909909
- example1.env
910-
- example2.env
910+
- path: example2.env
911+
required: false
911912
`
912913
expectedEnvironmentMap := types.MappingWithEquals{
913914
"FOO": strPtr("foo_from_env_file"),
@@ -925,14 +926,21 @@ services:
925926
options.ResolvePaths = false
926927
})
927928
assert.NilError(t, err)
928-
assert.DeepEqual(t, configWithEnvFiles.Services["web"].EnvFile, types.StringList{"example1.env",
929-
"example2.env"})
929+
assert.DeepEqual(t, configWithEnvFiles.Services["web"].EnvFiles, []types.EnvFile{
930+
{
931+
Path: "example1.env",
932+
Required: true,
933+
},
934+
{
935+
Path: "example2.env",
936+
Required: false,
937+
}})
930938
assert.DeepEqual(t, configWithEnvFiles.Services["web"].Environment, expectedEnvironmentMap)
931939

932940
// Custom behavior removes the `env_file` entries
933941
configWithoutEnvFiles, err := Load(configDetails, WithDiscardEnvFiles)
934942
assert.NilError(t, err)
935-
assert.DeepEqual(t, configWithoutEnvFiles.Services["web"].EnvFile, types.StringList(nil))
943+
assert.Equal(t, len(configWithoutEnvFiles.Services["web"].EnvFiles), 0)
936944
assert.DeepEqual(t, configWithoutEnvFiles.Services["web"].Environment, expectedEnvironmentMap)
937945
}
938946

@@ -1929,7 +1937,11 @@ func TestLoadWithExtends(t *testing.T) {
19291937
Environment: types.MappingWithEquals{
19301938
"SOURCE": strPtr("extends"),
19311939
},
1932-
EnvFile: []string{expectedEnvFilePath},
1940+
EnvFiles: []types.EnvFile{
1941+
{
1942+
Path: expectedEnvFilePath,
1943+
Required: true,
1944+
}},
19331945
Networks: map[string]*types.ServiceNetworkConfig{"default": nil},
19341946
Volumes: []types.ServiceVolumeConfig{{
19351947
Type: "bind",
@@ -2090,8 +2102,10 @@ func TestLoadServiceWithEnvFile(t *testing.T) {
20902102
},
20912103
Services: types.Services{
20922104
"test": {
2093-
Name: "test",
2094-
EnvFile: []string{file.Name()},
2105+
Name: "test",
2106+
EnvFiles: []types.EnvFile{
2107+
{Path: file.Name(), Required: true},
2108+
},
20952109
},
20962110
},
20972111
}
@@ -2450,8 +2464,11 @@ services:
24502464
Name: "imported",
24512465
ContainerName: "extends", // as defined by ./testdata/subdir/extra.env
24522466
Environment: types.MappingWithEquals{"SOURCE": strPtr("extends")},
2453-
EnvFile: types.StringList{
2454-
filepath.Join(workingDir, "testdata", "subdir", "extra.env"),
2467+
EnvFiles: []types.EnvFile{
2468+
{
2469+
Path: filepath.Join(workingDir, "testdata", "subdir", "extra.env"),
2470+
Required: true,
2471+
},
24552472
},
24562473
Image: "nginx",
24572474
Volumes: []types.ServiceVolumeConfig{
@@ -2630,8 +2647,11 @@ services:
26302647
Name: "foo",
26312648
Image: "foo",
26322649
Environment: types.MappingWithEquals{"FOO": strPtr("BAR")},
2633-
EnvFile: types.StringList{
2634-
filepath.Join(config.WorkingDir, "testdata", "remote", "env"),
2650+
EnvFiles: []types.EnvFile{
2651+
{
2652+
Path: filepath.Join(config.WorkingDir, "testdata", "remote", "env"),
2653+
Required: true,
2654+
},
26352655
},
26362656
Volumes: []types.ServiceVolumeConfig{
26372657
{

paths/resolve.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func ResolveRelativePaths(project map[string]any, base string) error {
3333
r.resolvers = map[tree.Path]resolver{
3434
"services.*.build.context": r.absContextPath,
3535
"services.*.build.additional_contexts.*": r.absContextPath,
36-
"services.*.env_file": r.absPath,
36+
"services.*.env_file.*.path": r.absPath,
3737
"services.*.extends.file": r.absPath,
3838
"services.*.develop.watch.*.path": r.absPath,
3939
"services.*.volumes.*": r.absVolumeMount,

schema/compose-spec.json

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@
215215
"dns_search": {"$ref": "#/definitions/string_or_list"},
216216
"domainname": {"type": "string"},
217217
"entrypoint": {"$ref": "#/definitions/command"},
218-
"env_file": {"$ref": "#/definitions/string_or_list"},
218+
"env_file": {"$ref": "#/definitions/env_file"},
219219
"environment": {"$ref": "#/definitions/list_or_dict"},
220220

221221
"expose": {
@@ -775,6 +775,36 @@
775775
]
776776
},
777777

778+
"env_file": {
779+
"oneOf": [
780+
{"type": "string"},
781+
{
782+
"type": "array",
783+
"items": {
784+
"oneOf": [
785+
{"type": "string"},
786+
{
787+
"type": "object",
788+
"additionalProperties": false,
789+
"properties": {
790+
"path": {
791+
"type": "string"
792+
},
793+
"required": {
794+
"type": "boolean",
795+
"default": true
796+
}
797+
},
798+
"required": [
799+
"path"
800+
]
801+
}
802+
]
803+
}
804+
}
805+
]
806+
},
807+
778808
"string_or_list": {
779809
"oneOf": [
780810
{"type": "string"},

transform/build.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@ func transformBuild(data any, p tree.Path) (any, error) {
3333
"context": v,
3434
}, nil
3535
default:
36-
return data, errors.Errorf("invalid type %T for build", v)
36+
return data, errors.Errorf("%s: invalid type %T for build", p, v)
3737
}
3838
}

transform/canonical.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func init() {
2828
transformers["services.*"] = transformService
2929
transformers["services.*.build.secrets.*"] = transformFileMount
3030
transformers["services.*.depends_on"] = transformDependsOn
31+
transformers["services.*.env_file"] = transformEnvFile
3132
transformers["services.*.extends"] = transformExtends
3233
transformers["services.*.networks"] = transformServiceNetworks
3334
transformers["services.*.volumes.*"] = transformVolumeMount

transform/dependson.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,6 @@ func transformDependsOn(data any, p tree.Path) (any, error) {
4949
}
5050
return d, nil
5151
default:
52-
return data, errors.Errorf("invalid type %T for depend_on", v)
52+
return data, errors.Errorf("%s: invalid type %T for depend_on", p, v)
5353
}
5454
}

transform/envfile.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright 2020 The Compose Specification Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package transform
18+
19+
import (
20+
"github.com/compose-spec/compose-go/v2/tree"
21+
"github.com/pkg/errors"
22+
)
23+
24+
func transformEnvFile(data any, p tree.Path) (any, error) {
25+
switch v := data.(type) {
26+
case string:
27+
return []any{
28+
transformEnvFileValue(v),
29+
}, nil
30+
case []any:
31+
for i, e := range v {
32+
v[i] = transformEnvFileValue(e)
33+
}
34+
return v, nil
35+
default:
36+
return nil, errors.Errorf("%s: invalid type %T for env_file", p, v)
37+
}
38+
}
39+
40+
func transformEnvFileValue(data any) any {
41+
switch v := data.(type) {
42+
case string:
43+
return map[string]any{
44+
"path": v,
45+
"required": true,
46+
}
47+
case map[string]any:
48+
if _, ok := v["required"]; !ok {
49+
v["required"] = true
50+
}
51+
return v
52+
}
53+
return nil
54+
}

0 commit comments

Comments
 (0)