diff --git a/loader/include.go b/loader/include.go index 3e49b8d8..04c9ff7f 100644 --- a/loader/include.go +++ b/loader/include.go @@ -21,11 +21,12 @@ import ( "fmt" "os" "path/filepath" - "reflect" "strings" "github.com/compose-spec/compose-go/v2/dotenv" interp "github.com/compose-spec/compose-go/v2/interpolation" + "github.com/compose-spec/compose-go/v2/override" + "github.com/compose-spec/compose-go/v2/tree" "github.com/compose-spec/compose-go/v2/types" ) @@ -50,7 +51,7 @@ func loadIncludeConfig(source any) ([]types.IncludeConfig, error) { return requires, err } -func ApplyInclude(ctx context.Context, workingDir string, environment types.Mapping, model map[string]any, options *Options, included []string) error { +func ApplyInclude(ctx context.Context, workingDir string, environment types.Mapping, model map[string]any, options *Options, included []string, processor PostProcessor) error { includeConfig, err := loadIncludeConfig(model["include"]) if err != nil { return err @@ -151,7 +152,7 @@ func ApplyInclude(ctx context.Context, workingDir string, environment types.Mapp if err != nil { return err } - err = importResources(imported, model) + err = importResources(imported, model, processor) if err != nil { return err } @@ -161,29 +162,29 @@ func ApplyInclude(ctx context.Context, workingDir string, environment types.Mapp } // importResources import into model all resources defined by imported, and report error on conflict -func importResources(source map[string]any, target map[string]any) error { - if err := importResource(source, target, "services"); err != nil { +func importResources(source map[string]any, target map[string]any, processor PostProcessor) error { + if err := importResource(source, target, "services", processor); err != nil { return err } - if err := importResource(source, target, "volumes"); err != nil { + if err := importResource(source, target, "volumes", processor); err != nil { return err } - if err := importResource(source, target, "networks"); err != nil { + if err := importResource(source, target, "networks", processor); err != nil { return err } - if err := importResource(source, target, "secrets"); err != nil { + if err := importResource(source, target, "secrets", processor); err != nil { return err } - if err := importResource(source, target, "configs"); err != nil { + if err := importResource(source, target, "configs", processor); err != nil { return err } - if err := importResource(source, target, "models"); err != nil { + if err := importResource(source, target, "models", processor); err != nil { return err } return nil } -func importResource(source map[string]any, target map[string]any, key string) error { +func importResource(source map[string]any, target map[string]any, key string, processor PostProcessor) error { from := source[key] if from != nil { var to map[string]any @@ -193,13 +194,25 @@ func importResource(source map[string]any, target map[string]any, key string) er to = map[string]any{} } for name, a := range from.(map[string]any) { - if conflict, ok := to[name]; ok { - if reflect.DeepEqual(a, conflict) { - continue - } - return fmt.Errorf("%s.%s conflicts with imported resource", key, name) + conflict, ok := to[name] + if !ok { + to[name] = a + continue + } + err := processor.Apply(map[string]any{ + key: map[string]any{ + name: a, + }, + }) + if err != nil { + return err + } + + merged, err := override.MergeYaml(a, conflict, tree.NewPath(key, name)) + if err != nil { + return err } - to[name] = a + to[name] = merged } target[key] = to } diff --git a/loader/include_test.go b/loader/include_test.go index dbce5f04..775fa88c 100644 --- a/loader/include_test.go +++ b/loader/include_test.go @@ -81,12 +81,21 @@ include: services: bar: image: busybox + environment: !override + - ZOT=QIX `, map[string]string{"SOURCE": "override"}) - _, err := LoadWithContext(context.TODO(), details, func(options *Options) { + p, err := LoadWithContext(context.TODO(), details, func(options *Options) { options.SkipNormalization = true options.ResolvePaths = true }) - assert.ErrorContains(t, err, "services.bar conflicts with imported resource", err) + assert.NilError(t, err) + assert.DeepEqual(t, p.Services["bar"], types.ServiceConfig{ + Name: "bar", + Image: "busybox", + Environment: types.MappingWithEquals{ + "ZOT": strPtr("QIX"), + }, + }) } func TestIncludeRelative(t *testing.T) { diff --git a/loader/loader.go b/loader/loader.go index 91f687f9..68dfea44 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -459,7 +459,7 @@ func loadYamlFile(ctx context.Context, if !opts.SkipInclude { included = append(included, file.Filename) - err = ApplyInclude(ctx, workingDir, environment, cfg, opts, included) + err = ApplyInclude(ctx, workingDir, environment, cfg, opts, included, processor) if err != nil { return err } diff --git a/loader/testdata/compose-include.yaml b/loader/testdata/compose-include.yaml index e201ab52..67a7ebfa 100644 --- a/loader/testdata/compose-include.yaml +++ b/loader/testdata/compose-include.yaml @@ -3,4 +3,6 @@ include: services: bar: - image: bar \ No newline at end of file + image: bar + environment: + - FOO=BAR \ No newline at end of file diff --git a/override/extends.go b/override/extends.go index f47912dd..de92fd29 100644 --- a/override/extends.go +++ b/override/extends.go @@ -19,7 +19,7 @@ package override import "github.com/compose-spec/compose-go/v2/tree" func ExtendService(base, override map[string]any) (map[string]any, error) { - yaml, err := mergeYaml(base, override, tree.NewPath("services.x")) + yaml, err := MergeYaml(base, override, tree.NewPath("services.x")) if err != nil { return nil, err } diff --git a/override/merge.go b/override/merge.go index 6fae6e5f..525299cd 100644 --- a/override/merge.go +++ b/override/merge.go @@ -26,7 +26,7 @@ import ( // Merge applies overrides to a config model func Merge(right, left map[string]any) (map[string]any, error) { - merged, err := mergeYaml(right, left, tree.NewPath()) + merged, err := MergeYaml(right, left, tree.NewPath()) if err != nil { return nil, err } @@ -70,8 +70,8 @@ func init() { mergeSpecials["services.*.ulimits.*"] = mergeUlimit } -// mergeYaml merges map[string]any yaml trees handling special rules -func mergeYaml(e any, o any, p tree.Path) (any, error) { +// MergeYaml merges map[string]any yaml trees handling special rules +func MergeYaml(e any, o any, p tree.Path) (any, error) { for pattern, merger := range mergeSpecials { if p.Matches(pattern) { merged, err := merger(e, o, p) @@ -110,7 +110,7 @@ func mergeMappings(mapping map[string]any, other map[string]any, p tree.Path) (m continue } next := p.Next(k) - merged, err := mergeYaml(e, v, next) + merged, err := MergeYaml(e, v, next) if err != nil { return nil, err }