diff --git a/loader/include.go b/loader/include.go index ea784144..aaebfd30 100644 --- a/loader/include.go +++ b/loader/include.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "path/filepath" + "reflect" "github.com/compose-spec/compose-go/dotenv" interp "github.com/compose-spec/compose-go/interpolation" @@ -109,37 +110,55 @@ func loadInclude(ctx context.Context, filename string, configDetails types.Confi func importResources(model *types.Config, imported *types.Project, path []string) error { services := mapByName(model.Services) for _, service := range imported.Services { - if _, ok := services[service.Name]; ok { + if present, ok := services[service.Name]; ok { + if reflect.DeepEqual(present, service) { + continue + } return fmt.Errorf("imported compose file %s defines conflicting service %s", path, service.Name) } model.Services = append(model.Services, service) } for _, service := range imported.DisabledServices { - if _, ok := services[service.Name]; ok { + if disabled, ok := services[service.Name]; ok { + if reflect.DeepEqual(disabled, service) { + continue + } return fmt.Errorf("imported compose file %s defines conflicting service %s", path, service.Name) } model.Services = append(model.Services, service) } for n, network := range imported.Networks { - if _, ok := model.Networks[n]; ok { + if present, ok := model.Networks[n]; ok { + if reflect.DeepEqual(present, network) { + continue + } return fmt.Errorf("imported compose file %s defines conflicting network %s", path, n) } model.Networks[n] = network } for n, volume := range imported.Volumes { - if _, ok := model.Volumes[n]; ok { + if present, ok := model.Volumes[n]; ok { + if reflect.DeepEqual(present, volume) { + continue + } return fmt.Errorf("imported compose file %s defines conflicting volume %s", path, n) } model.Volumes[n] = volume } for n, secret := range imported.Secrets { - if _, ok := model.Secrets[n]; ok { + if present, ok := model.Secrets[n]; ok { + if reflect.DeepEqual(present, secret) { + continue + } return fmt.Errorf("imported compose file %s defines conflicting secret %s", path, n) } model.Secrets[n] = secret } for n, config := range imported.Configs { - if _, ok := model.Configs[n]; ok { + if present, ok := model.Configs[n]; ok { + if reflect.DeepEqual(present, config) { + continue + } return fmt.Errorf("imported compose file %s defines conflicting config %s", path, n) } model.Configs[n] = config diff --git a/loader/loader_test.go b/loader/loader_test.go index b40c447c..44ffc156 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -2604,13 +2604,56 @@ func TestLoadWithIncludeCycle(t *testing.T) { WorkingDir: filepath.Join(workingDir, "testdata"), ConfigFiles: []types.ConfigFile{ { - Filename: filepath.Join(workingDir, "testdata", "compose-include.yaml"), + Filename: filepath.Join(workingDir, "testdata", "compose-include-cycle.yaml"), }, }, }) assert.Check(t, strings.HasPrefix(err.Error(), "include cycle detected")) } +func TestLoadWithMultipleInclude(t *testing.T) { + // include same service twice should not trigger an error + p, err := Load(buildConfigDetails(` +name: 'test-multi-include' + +include: + - path: ./testdata/subdir/compose-test-extends-imported.yaml + env_file: ./testdata/subdir/extra.env + - path: ./testdata/compose-include.yaml + +services: + foo: + image: busybox + depends_on: + - imported +`, map[string]string{"SOURCE": "override"}), func(options *Options) { + options.SkipNormalization = true + options.ResolvePaths = true + }) + assert.NilError(t, err) + assert.Equal(t, p.Services[1].ContainerName, "override") + + // include 2 different services with same name should trigger an error + p, err = Load(buildConfigDetails(` +name: 'test-multi-include' + +include: + - path: ./testdata/subdir/compose-test-extends-imported.yaml + env_file: ./testdata/subdir/extra.env + - path: ./testdata/compose-include.yaml + env_file: ./testdata/subdir/extra.env + + +services: + bar: + image: busybox +`, map[string]string{"SOURCE": "override"}), func(options *Options) { + options.SkipNormalization = true + options.ResolvePaths = true + }) + assert.ErrorContains(t, err, "defines conflicting service bar", err) +} + func TestLoadWithDependsOn(t *testing.T) { p, err := loadYAML(` name: test-depends-on diff --git a/loader/testdata/compose-include-cycle.yaml b/loader/testdata/compose-include-cycle.yaml new file mode 100644 index 00000000..a34172b3 --- /dev/null +++ b/loader/testdata/compose-include-cycle.yaml @@ -0,0 +1,6 @@ +include: + - compose-include-cycle.yaml + +services: + foo: + image: foo diff --git a/loader/testdata/compose-include.yaml b/loader/testdata/compose-include.yaml index 6fa52404..e201ab52 100644 --- a/loader/testdata/compose-include.yaml +++ b/loader/testdata/compose-include.yaml @@ -1,6 +1,6 @@ include: - - compose-include.yaml + - path: ./subdir/compose-test-extends-imported.yaml services: - foo: - image: foo + bar: + image: bar \ No newline at end of file