Skip to content

Commit 65600ce

Browse files
committed
run interpolation after merge, but for required attributes
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent f4d8eb8 commit 65600ce

File tree

7 files changed

+106
-45
lines changed

7 files changed

+106
-45
lines changed

cli/options_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ func TestProjectWithDotEnv(t *testing.T) {
253253
"compose-with-variables.yaml",
254254
}, WithName("my_project"), WithEnvFiles(), WithDotEnv)
255255
assert.NilError(t, err)
256-
p, err := ProjectFromOptions(context.TODO(), opts)
256+
p, err := opts.LoadProject(context.TODO())
257257
assert.NilError(t, err)
258258
service, err := p.GetService("simple")
259259
assert.NilError(t, err)

cli/testdata/env-file/compose-with-env-files.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
version: "3"
21
services:
32
simple:
43
image: nginx

loader/extends.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ import (
2222
"path/filepath"
2323

2424
"github.com/compose-spec/compose-go/v2/consts"
25+
"github.com/compose-spec/compose-go/v2/interpolation"
2526
"github.com/compose-spec/compose-go/v2/override"
2627
"github.com/compose-spec/compose-go/v2/paths"
28+
"github.com/compose-spec/compose-go/v2/template"
29+
"github.com/compose-spec/compose-go/v2/transform"
2730
"github.com/compose-spec/compose-go/v2/types"
2831
)
2932

@@ -68,10 +71,22 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
6871
)
6972
switch v := extends.(type) {
7073
case map[string]any:
74+
if opts.Interpolate != nil {
75+
v, err = interpolation.Interpolate(v, *opts.Interpolate)
76+
if err != nil {
77+
return nil, err
78+
}
79+
}
7180
ref = v["service"].(string)
7281
file = v["file"]
7382
opts.ProcessEvent("extends", v)
7483
case string:
84+
if opts.Interpolate != nil {
85+
v, err = opts.Interpolate.Substitute(v, template.Mapping(opts.Interpolate.LookupValue))
86+
if err != nil {
87+
return nil, err
88+
}
89+
}
7590
ref = v
7691
opts.ProcessEvent("extends", map[string]any{"service": ref})
7792
}
@@ -175,6 +190,12 @@ func getExtendsBaseFromFile(
175190
)
176191
}
177192

193+
// Attempt to make a canonical model so ResolveRelativePaths can operate on source:target short syntaxes
194+
source, err = transform.Canonical(source, true)
195+
if err != nil {
196+
return nil, nil, err
197+
}
198+
178199
var remotes []paths.RemoteResource
179200
for _, loader := range opts.RemoteResourceLoaders() {
180201
remotes = append(remotes, loader.Accept)

loader/include.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import (
3030
)
3131

3232
// loadIncludeConfig parse the required config from raw yaml
33-
func loadIncludeConfig(source any) ([]types.IncludeConfig, error) {
33+
func loadIncludeConfig(source any, options *Options) ([]types.IncludeConfig, error) {
3434
if source == nil {
3535
return nil, nil
3636
}
@@ -45,13 +45,23 @@ func loadIncludeConfig(source any) ([]types.IncludeConfig, error) {
4545
}
4646
}
4747
}
48+
if options.Interpolate != nil {
49+
for i, config := range configs {
50+
interpolated, err := interp.Interpolate(config.(map[string]any), *options.Interpolate)
51+
if err != nil {
52+
return nil, err
53+
}
54+
configs[i] = interpolated
55+
}
56+
}
57+
4858
var requires []types.IncludeConfig
4959
err := Transform(source, &requires)
5060
return requires, err
5161
}
5262

5363
func ApplyInclude(ctx context.Context, workingDir string, environment types.Mapping, model map[string]any, options *Options, included []string) error {
54-
includeConfig, err := loadIncludeConfig(model["include"])
64+
includeConfig, err := loadIncludeConfig(model["include"], options)
5565
if err != nil {
5666
return err
5767
}

loader/loader.go

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,23 @@ func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Option
367367
}
368368
}
369369

370+
if opts.Interpolate != nil && !opts.SkipInterpolation {
371+
dict, err = interp.Interpolate(dict, *opts.Interpolate)
372+
if err != nil {
373+
return nil, err
374+
}
375+
}
376+
377+
dict, err = transform.Canonical(dict, opts.SkipInterpolation)
378+
if err != nil {
379+
return nil, err
380+
}
381+
382+
dict, err = override.EnforceUnicity(dict)
383+
if err != nil {
384+
return nil, err
385+
}
386+
370387
if !opts.SkipDefaultValues {
371388
dict, err = transform.SetDefaultValues(dict)
372389
if err != nil {
@@ -415,13 +432,6 @@ func loadYamlFile(ctx context.Context, file types.ConfigFile, opts *Options, wor
415432
return errors.New("Top-level object must be a mapping")
416433
}
417434

418-
if opts.Interpolate != nil && !opts.SkipInterpolation {
419-
cfg, err = interp.Interpolate(cfg, *opts.Interpolate)
420-
if err != nil {
421-
return err
422-
}
423-
}
424-
425435
fixEmptyNotNull(cfg)
426436

427437
if !opts.SkipExtends {
@@ -450,11 +460,6 @@ func loadYamlFile(ctx context.Context, file types.ConfigFile, opts *Options, wor
450460
return err
451461
}
452462

453-
dict, err = override.EnforceUnicity(dict)
454-
if err != nil {
455-
return err
456-
}
457-
458463
if !opts.SkipValidation {
459464
if err := schema.Validate(dict); err != nil {
460465
return fmt.Errorf("validating %s: %w", file.Filename, err)
@@ -464,15 +469,7 @@ func loadYamlFile(ctx context.Context, file types.ConfigFile, opts *Options, wor
464469
delete(dict, "version")
465470
}
466471
}
467-
468-
dict, err = transform.Canonical(dict, opts.SkipInterpolation)
469-
if err != nil {
470-
return err
471-
}
472-
473-
// Canonical transformation can reveal duplicates, typically as ports can be a range and conflict with an override
474-
dict, err = override.EnforceUnicity(dict)
475-
return err
472+
return nil
476473
}
477474

478475
var processor PostProcessor

loader/loader_yaml_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,46 @@ services:
5656
})
5757
}
5858

59+
func TestParseYAMLFilesInterpolateAfterMerge(t *testing.T) {
60+
model, err := loadYamlModel(
61+
context.TODO(), types.ConfigDetails{
62+
ConfigFiles: []types.ConfigFile{
63+
{
64+
Filename: "test.yaml",
65+
Content: []byte(`
66+
services:
67+
test:
68+
image: foo
69+
environment:
70+
my_env: ${my_env?my_env must be set}
71+
`),
72+
},
73+
{
74+
Filename: "override.yaml",
75+
Content: []byte(`
76+
services:
77+
test:
78+
image: bar
79+
environment:
80+
my_env: ${my_env:-default}
81+
`),
82+
},
83+
},
84+
}, &Options{}, &cycleTracker{}, nil,
85+
)
86+
assert.NilError(t, err)
87+
assert.DeepEqual(
88+
t, model, map[string]interface{}{
89+
"services": map[string]interface{}{
90+
"test": map[string]interface{}{
91+
"image": "bar",
92+
"environment": []any{"my_env=${my_env:-default}"},
93+
},
94+
},
95+
},
96+
)
97+
}
98+
5999
func TestParseYAMLFilesMergeOverride(t *testing.T) {
60100
model, err := loadYamlModel(context.TODO(), types.ConfigDetails{
61101
ConfigFiles: []types.ConfigFile{

schema/compose-spec.json

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,8 @@
155155
},
156156
"additionalProperties": false
157157
},
158-
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
159-
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
158+
"cap_add": {"type": "array", "items": {"type": "string"}},
159+
"cap_drop": {"type": "array", "items": {"type": "string"}},
160160
"cgroup": {"type": "string", "enum": ["host", "private"]},
161161
"cgroup_parent": {"type": "string"},
162162
"command": {"$ref": "#/definitions/command"},
@@ -215,9 +215,9 @@
215215
]
216216
},
217217
"device_cgroup_rules": {"$ref": "#/definitions/list_of_strings"},
218-
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
218+
"devices": {"type": "array", "items": {"type": "string"}},
219219
"dns": {"$ref": "#/definitions/string_or_list"},
220-
"dns_opt": {"type": "array","items": {"type": "string"}, "uniqueItems": true},
220+
"dns_opt": {"type": "array","items": {"type": "string"}},
221221
"dns_search": {"$ref": "#/definitions/string_or_list"},
222222
"domainname": {"type": "string"},
223223
"entrypoint": {"$ref": "#/definitions/command"},
@@ -229,8 +229,7 @@
229229
"items": {
230230
"type": ["string", "number"],
231231
"format": "expose"
232-
},
233-
"uniqueItems": true
232+
}
234233
},
235234
"extends": {
236235
"oneOf": [
@@ -247,14 +246,13 @@
247246
}
248247
]
249248
},
250-
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
249+
"external_links": {"type": "array", "items": {"type": "string"}},
251250
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
252251
"group_add": {
253252
"type": "array",
254253
"items": {
255254
"type": ["string", "number"]
256-
},
257-
"uniqueItems": true
255+
}
258256
},
259257
"healthcheck": {"$ref": "#/definitions/healthcheck"},
260258
"hostname": {"type": "string"},
@@ -263,7 +261,7 @@
263261
"ipc": {"type": "string"},
264262
"isolation": {"type": "string"},
265263
"labels": {"$ref": "#/definitions/list_or_dict"},
266-
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
264+
"links": {"type": "array", "items": {"type": "string"}},
267265
"logging": {
268266
"type": "object",
269267

@@ -349,8 +347,7 @@
349347
"patternProperties": {"^x-": {}}
350348
}
351349
]
352-
},
353-
"uniqueItems": true
350+
}
354351
},
355352
"privileged": {"type": ["boolean", "string"]},
356353
"profiles": {"$ref": "#/definitions/list_of_strings"},
@@ -365,7 +362,7 @@
365362
"scale": {
366363
"type": ["integer", "string"]
367364
},
368-
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
365+
"security_opt": {"type": "array", "items": {"type": "string"}},
369366
"shm_size": {"type": ["number", "string"]},
370367
"secrets": {"$ref": "#/definitions/service_config_or_secret"},
371368
"sysctls": {"$ref": "#/definitions/list_or_dict"},
@@ -431,13 +428,11 @@
431428
"patternProperties": {"^x-": {}}
432429
}
433430
]
434-
},
435-
"uniqueItems": true
431+
}
436432
},
437433
"volumes_from": {
438434
"type": "array",
439-
"items": {"type": "string"},
440-
"uniqueItems": true
435+
"items": {"type": "string"}
441436
},
442437
"working_dir": {"type": "string"}
443438
},
@@ -832,8 +827,7 @@
832827

833828
"list_of_strings": {
834829
"type": "array",
835-
"items": {"type": "string"},
836-
"uniqueItems": true
830+
"items": {"type": "string"}
837831
},
838832

839833
"list_or_dict": {
@@ -847,7 +841,7 @@
847841
},
848842
"additionalProperties": false
849843
},
850-
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
844+
{"type": "array", "items": {"type": "string"}}
851845
]
852846
},
853847

0 commit comments

Comments
 (0)