mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-25 16:31:10 -06:00
205 lines
4.9 KiB
Go
205 lines
4.9 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
|
|
if c1.Terraform != nil {
|
|
c.Terraform = c1.Terraform
|
|
if c2.Terraform != nil {
|
|
c.Terraform.Merge(c2.Terraform)
|
|
}
|
|
} else {
|
|
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)
|
|
}
|
|
}
|
|
|
|
// Local Values
|
|
// These are simpler than the other config elements because they are just
|
|
// flat values and so no deep merging is required.
|
|
if localsCount := len(c1.Locals) + len(c2.Locals); localsCount != 0 {
|
|
// Explicit length check above because we want c.Locals to remain
|
|
// nil if the result would be empty.
|
|
c.Locals = make([]*Local, 0, len(c1.Locals)+len(c2.Locals))
|
|
c.Locals = append(c.Locals, c1.Locals...)
|
|
c.Locals = append(c.Locals, c2.Locals...)
|
|
}
|
|
|
|
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
|
|
}
|