diff --git a/loader/extends.go b/loader/extends.go index 4bfd2d6d..a5614f43 100644 --- a/loader/extends.go +++ b/loader/extends.go @@ -26,7 +26,7 @@ import ( "github.com/compose-spec/compose-go/v2/types" ) -func ApplyExtends(ctx context.Context, dict map[string]any, workingdir string, opts *Options, ct *cycleTracker, post ...PostProcessor) error { +func ApplyExtends(ctx context.Context, dict map[string]any, workingdir string, opts *Options, tracker *cycleTracker, post ...PostProcessor) error { a, ok := dict["services"] if !ok { return nil @@ -38,7 +38,8 @@ func ApplyExtends(ctx context.Context, dict map[string]any, workingdir string, o if !ok { continue } - if err := ct.Add(ctx.Value(consts.ComposeFileKey{}).(string), name); err != nil { + ct, err := tracker.Add(ctx.Value(consts.ComposeFileKey{}).(string), name) + if err != nil { return err } var ( diff --git a/loader/extends_test.go b/loader/extends_test.go new file mode 100644 index 00000000..6fa90605 --- /dev/null +++ b/loader/extends_test.go @@ -0,0 +1,66 @@ +/* + Copyright 2020 The Compose Specification Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package loader + +import ( + "context" + "path/filepath" + "testing" + + "github.com/compose-spec/compose-go/v2/types" + "gotest.tools/v3/assert" +) + +func TestExtends(t *testing.T) { + yaml := ` +name: test-extends +services: + test1: + extends: + file: testdata/extends/base.yaml + service: base + hostname: test1 + + test2: + extends: + file: testdata/extends/base.yaml + service: base + hostname: test2 + + test3: + extends: + file: testdata/extends/base.yaml + service: another + hostname: test3 +` + abs, err := filepath.Abs(".") + assert.NilError(t, err) + + p, err := LoadWithContext(context.Background(), types.ConfigDetails{ + ConfigFiles: []types.ConfigFile{ + { + Content: []byte(yaml), + Filename: "(inline)", + }, + }, + WorkingDir: abs, + }) + assert.NilError(t, err) + assert.DeepEqual(t, p.Services["test1"].Hostname, "test1") + assert.Equal(t, p.Services["test2"].Hostname, "test2") + assert.Equal(t, p.Services["test3"].Hostname, "test3") +} diff --git a/loader/loader.go b/loader/loader.go index e0756680..58ca98aa 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -144,7 +144,7 @@ type cycleTracker struct { loaded []serviceRef } -func (ct *cycleTracker) Add(filename, service string) error { +func (ct *cycleTracker) Add(filename, service string) (*cycleTracker, error) { toAdd := serviceRef{filename: filename, service: service} for _, loaded := range ct.loaded { if toAdd == loaded { @@ -161,12 +161,16 @@ func (ct *cycleTracker) Add(filename, service string) error { errLines = append(errLines, fmt.Sprintf(" extends %s in %s", service.service, service.filename)) } - return errors.New(strings.Join(errLines, "\n")) + return nil, errors.New(strings.Join(errLines, "\n")) } } - ct.loaded = append(ct.loaded, toAdd) - return nil + var branch []serviceRef + branch = append(branch, ct.loaded...) + branch = append(branch, toAdd) + return &cycleTracker{ + loaded: branch, + }, nil } // WithDiscardEnvFiles sets the Options to discard the `env_file` section after resolving to diff --git a/loader/testdata/extends/base.yaml b/loader/testdata/extends/base.yaml new file mode 100644 index 00000000..8cc2f4a8 --- /dev/null +++ b/loader/testdata/extends/base.yaml @@ -0,0 +1,7 @@ +services: + base: + image: base + + another: + extends: base +