terraform/config/merge.go

155 lines
3.7 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 {
unknowns[k] = struct{}{}
}
for _, k := range c2.unknownKeys {
unknowns[k] = struct{}{}
}
for k, _ := range unknowns {
c.unknownKeys = append(c.unknownKeys, k)
}
// 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
// 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
}