opentofu/config/merge.go
Mitchell Hashimoto 3878b8b093
config: Merge respects Terraform blocks, provider aliases, and more
Fixes #10715

`config.Merge` was not updated to support a number of new features. This
updates the codepath to merge various fields, including the `terraform`
block which was the issue in #10715.

The `Merge` API is called when an `_override` file is present to _merge_
configurations. Normally configurations are _appended_. Only an override
file triggers a _merge_.

I started working on a generic library to do this automatically awhile
back but never finished it. This might motivate me to do so. In the
interest of getting a fix out though, we'll continue the manual
approach.
2016-12-13 21:48:59 -08:00

190 lines
4.4 KiB
Go

package config
// Merge merges two configurations into a single configuration.
//
// Merge allows for the two configurations to have duplicate resources,
// because the resources will be merged. This differs from a single
// Config which must only have unique resources.
func Merge(c1, c2 *Config) (*Config, error) {
c := new(Config)
// Merge unknown keys
unknowns := make(map[string]struct{})
for _, k := range c1.unknownKeys {
_, present := unknowns[k]
if !present {
unknowns[k] = struct{}{}
c.unknownKeys = append(c.unknownKeys, k)
}
}
for _, k := range c2.unknownKeys {
_, present := unknowns[k]
if !present {
unknowns[k] = struct{}{}
c.unknownKeys = append(c.unknownKeys, k)
}
}
// Merge Atlas configuration. This is a dumb one overrides the other
// sort of merge.
c.Atlas = c1.Atlas
if c2.Atlas != nil {
c.Atlas = c2.Atlas
}
// Merge the Terraform configuration, which is a complete overwrite.
c.Terraform = c1.Terraform
if c2.Terraform != nil {
c.Terraform = c2.Terraform
}
// NOTE: Everything below is pretty gross. Due to the lack of generics
// in Go, there is some hoop-jumping involved to make this merging a
// little more test-friendly and less repetitive. Ironically, making it
// less repetitive involves being a little repetitive, but I prefer to
// be repetitive with things that are less error prone than things that
// are more error prone (more logic). Type conversions to an interface
// are pretty low-error.
var m1, m2, mresult []merger
// Modules
m1 = make([]merger, 0, len(c1.Modules))
m2 = make([]merger, 0, len(c2.Modules))
for _, v := range c1.Modules {
m1 = append(m1, v)
}
for _, v := range c2.Modules {
m2 = append(m2, v)
}
mresult = mergeSlice(m1, m2)
if len(mresult) > 0 {
c.Modules = make([]*Module, len(mresult))
for i, v := range mresult {
c.Modules[i] = v.(*Module)
}
}
// Outputs
m1 = make([]merger, 0, len(c1.Outputs))
m2 = make([]merger, 0, len(c2.Outputs))
for _, v := range c1.Outputs {
m1 = append(m1, v)
}
for _, v := range c2.Outputs {
m2 = append(m2, v)
}
mresult = mergeSlice(m1, m2)
if len(mresult) > 0 {
c.Outputs = make([]*Output, len(mresult))
for i, v := range mresult {
c.Outputs[i] = v.(*Output)
}
}
// Provider Configs
m1 = make([]merger, 0, len(c1.ProviderConfigs))
m2 = make([]merger, 0, len(c2.ProviderConfigs))
for _, v := range c1.ProviderConfigs {
m1 = append(m1, v)
}
for _, v := range c2.ProviderConfigs {
m2 = append(m2, v)
}
mresult = mergeSlice(m1, m2)
if len(mresult) > 0 {
c.ProviderConfigs = make([]*ProviderConfig, len(mresult))
for i, v := range mresult {
c.ProviderConfigs[i] = v.(*ProviderConfig)
}
}
// Resources
m1 = make([]merger, 0, len(c1.Resources))
m2 = make([]merger, 0, len(c2.Resources))
for _, v := range c1.Resources {
m1 = append(m1, v)
}
for _, v := range c2.Resources {
m2 = append(m2, v)
}
mresult = mergeSlice(m1, m2)
if len(mresult) > 0 {
c.Resources = make([]*Resource, len(mresult))
for i, v := range mresult {
c.Resources[i] = v.(*Resource)
}
}
// Variables
m1 = make([]merger, 0, len(c1.Variables))
m2 = make([]merger, 0, len(c2.Variables))
for _, v := range c1.Variables {
m1 = append(m1, v)
}
for _, v := range c2.Variables {
m2 = append(m2, v)
}
mresult = mergeSlice(m1, m2)
if len(mresult) > 0 {
c.Variables = make([]*Variable, len(mresult))
for i, v := range mresult {
c.Variables[i] = v.(*Variable)
}
}
return c, nil
}
// merger is an interface that must be implemented by types that are
// merge-able. This simplifies the implementation of Merge for the various
// components of a Config.
type merger interface {
mergerName() string
mergerMerge(merger) merger
}
// mergeSlice merges a slice of mergers.
func mergeSlice(m1, m2 []merger) []merger {
r := make([]merger, len(m1), len(m1)+len(m2))
copy(r, m1)
m := map[string]struct{}{}
for _, v2 := range m2 {
// If we already saw it, just append it because its a
// duplicate and invalid...
name := v2.mergerName()
if _, ok := m[name]; ok {
r = append(r, v2)
continue
}
m[name] = struct{}{}
// Find an original to override
var original merger
originalIndex := -1
for i, v := range m1 {
if v.mergerName() == name {
originalIndex = i
original = v
break
}
}
var v merger
if original == nil {
v = v2
} else {
v = original.mergerMerge(v2)
}
if originalIndex == -1 {
r = append(r, v)
} else {
r[originalIndex] = v
}
}
return r
}