Skip to content

Commit c6eecc5

Browse files
authored
load: support for multi-document yaml (#451)
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 8c4b79e commit c6eecc5

File tree

2 files changed

+91
-49
lines changed

2 files changed

+91
-49
lines changed

loader/loader.go

Lines changed: 75 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
package loader
1818

1919
import (
20+
"bytes"
2021
"context"
2122
"fmt"
23+
"io"
2224
"os"
2325
paths "path"
2426
"path/filepath"
@@ -166,7 +168,9 @@ func WithProfiles(profiles []string) func(*Options) {
166168
// ParseYAML reads the bytes from a file, parses the bytes into a mapping
167169
// structure, and returns it.
168170
func ParseYAML(source []byte) (map[string]interface{}, error) {
169-
m, _, err := parseYAML(source)
171+
r := bytes.NewReader(source)
172+
decoder := yaml.NewDecoder(r)
173+
m, _, err := parseYAML(decoder)
170174
return m, err
171175
}
172176

@@ -179,11 +183,11 @@ type PostProcessor interface {
179183
Apply(config *types.Config) error
180184
}
181185

182-
func parseYAML(source []byte) (map[string]interface{}, PostProcessor, error) {
186+
func parseYAML(decoder *yaml.Decoder) (map[string]interface{}, PostProcessor, error) {
183187
var cfg interface{}
184188
processor := ResetProcessor{target: &cfg}
185189

186-
if err := yaml.Unmarshal(source, &processor); err != nil {
190+
if err := decoder.Decode(&processor); err != nil {
187191
return nil, nil, err
188192
}
189193
stringMap, ok := cfg.(map[string]interface{})
@@ -251,67 +255,86 @@ func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options,
251255
loaded = append(loaded, mainFile)
252256

253257
includeRefs := make(map[string][]types.IncludeConfig)
254-
for i, file := range configDetails.ConfigFiles {
258+
first := true
259+
for _, file := range configDetails.ConfigFiles {
255260
var postProcessor PostProcessor
256261
configDict := file.Config
257-
if configDict == nil {
258-
if len(file.Content) == 0 {
259-
content, err := os.ReadFile(file.Filename)
260-
if err != nil {
261-
return nil, err
262+
263+
processYaml := func() error {
264+
if !opts.SkipValidation {
265+
if err := schema.Validate(configDict); err != nil {
266+
return fmt.Errorf("validating %s: %w", file.Filename, err)
262267
}
263-
file.Content = content
264268
}
265-
dict, p, err := parseConfig(file.Content, opts)
269+
270+
configDict = groupXFieldsIntoExtensions(configDict)
271+
272+
cfg, err := loadSections(ctx, file.Filename, configDict, configDetails, opts)
266273
if err != nil {
267-
return nil, fmt.Errorf("parsing %s: %w", file.Filename, err)
274+
return err
268275
}
269-
configDict = dict
270-
file.Config = dict
271-
configDetails.ConfigFiles[i] = file
272-
postProcessor = p
273-
}
274276

275-
if !opts.SkipValidation {
276-
if err := schema.Validate(configDict); err != nil {
277-
return nil, fmt.Errorf("validating %s: %w", file.Filename, err)
277+
if !opts.SkipInclude {
278+
var included map[string][]types.IncludeConfig
279+
cfg, included, err = loadInclude(ctx, file.Filename, configDetails, cfg, opts, loaded)
280+
if err != nil {
281+
return err
282+
}
283+
for k, v := range included {
284+
includeRefs[k] = append(includeRefs[k], v...)
285+
}
278286
}
279-
}
280-
281-
configDict = groupXFieldsIntoExtensions(configDict)
282-
283-
cfg, err := loadSections(ctx, file.Filename, configDict, configDetails, opts)
284-
if err != nil {
285-
return nil, err
286-
}
287287

288-
if !opts.SkipInclude {
289-
var included map[string][]types.IncludeConfig
290-
cfg, included, err = loadInclude(ctx, file.Filename, configDetails, cfg, opts, loaded)
288+
if first {
289+
first = false
290+
model = cfg
291+
return nil
292+
}
293+
merged, err := merge([]*types.Config{model, cfg})
291294
if err != nil {
292-
return nil, err
295+
return err
293296
}
294-
for k, v := range included {
295-
includeRefs[k] = append(includeRefs[k], v...)
297+
if postProcessor != nil {
298+
err = postProcessor.Apply(merged)
299+
if err != nil {
300+
return err
301+
}
296302
}
303+
model = merged
304+
return nil
297305
}
298306

299-
if i == 0 {
300-
model = cfg
301-
continue
302-
}
307+
if configDict == nil {
308+
if len(file.Content) == 0 {
309+
content, err := os.ReadFile(file.Filename)
310+
if err != nil {
311+
return nil, err
312+
}
313+
file.Content = content
314+
}
303315

304-
merged, err := merge([]*types.Config{model, cfg})
305-
if err != nil {
306-
return nil, err
307-
}
308-
if postProcessor != nil {
309-
err = postProcessor.Apply(merged)
310-
if err != nil {
316+
r := bytes.NewReader(file.Content)
317+
decoder := yaml.NewDecoder(r)
318+
for {
319+
dict, p, err := parseConfig(decoder, opts)
320+
if err != nil {
321+
if err != io.EOF {
322+
return nil, fmt.Errorf("parsing %s: %w", file.Filename, err)
323+
}
324+
break
325+
}
326+
configDict = dict
327+
postProcessor = p
328+
329+
if err := processYaml(); err != nil {
330+
return nil, err
331+
}
332+
}
333+
} else {
334+
if err := processYaml(); err != nil {
311335
return nil, err
312336
}
313337
}
314-
model = merged
315338
}
316339

317340
project := &types.Project{
@@ -446,8 +469,8 @@ func NormalizeProjectName(s string) string {
446469
return strings.TrimLeft(s, "_-")
447470
}
448471

449-
func parseConfig(b []byte, opts *Options) (map[string]interface{}, PostProcessor, error) {
450-
yml, postProcessor, err := parseYAML(b)
472+
func parseConfig(decoder *yaml.Decoder, opts *Options) (map[string]interface{}, PostProcessor, error) {
473+
yml, postProcessor, err := parseYAML(decoder)
451474
if err != nil {
452475
return nil, nil, err
453476
}
@@ -754,7 +777,10 @@ func loadServiceWithExtends(ctx context.Context, filename, name string, services
754777
return nil, err
755778
}
756779

757-
baseFile, _, err := parseConfig(b, opts)
780+
r := bytes.NewReader(b)
781+
decoder := yaml.NewDecoder(r)
782+
783+
baseFile, _, err := parseConfig(decoder, opts)
758784
if err != nil {
759785
return nil, err
760786
}

loader/loader_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2737,3 +2737,19 @@ services:
27372737
})
27382738
assert.ErrorContains(t, err, "Circular reference")
27392739
}
2740+
2741+
func TestLoadMulmtiDocumentYaml(t *testing.T) {
2742+
project, err := loadYAML(`
2743+
name: load-multi-docs
2744+
services:
2745+
test:
2746+
image: nginx:latest
2747+
---
2748+
services:
2749+
test:
2750+
image: nginx:override
2751+
2752+
`)
2753+
assert.NilError(t, err)
2754+
assert.Equal(t, project.Services[0].Image, "nginx:override")
2755+
}

0 commit comments

Comments
 (0)