config: Merge works properly
This commit is contained in:
parent
cf1f3a9e73
commit
9d2e83d56d
|
@ -251,6 +251,84 @@ func (c *Config) allVariables() map[string][]InterpolatedVariable {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Output) mergerName() string {
|
||||||
|
return o.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Output) mergerMerge(m merger) merger {
|
||||||
|
o2 := m.(*Output)
|
||||||
|
|
||||||
|
result := *o
|
||||||
|
result.Name = o2.Name
|
||||||
|
result.RawConfig = result.RawConfig.merge(o2.RawConfig)
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProviderConfig) mergerName() string {
|
||||||
|
return c.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProviderConfig) mergerMerge(m merger) merger {
|
||||||
|
c2 := m.(*ProviderConfig)
|
||||||
|
|
||||||
|
result := *c
|
||||||
|
result.Name = c2.Name
|
||||||
|
result.RawConfig = result.RawConfig.merge(c2.RawConfig)
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) mergerName() string {
|
||||||
|
return fmt.Sprintf("%s.%s", r.Type, r.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) mergerMerge(m merger) merger {
|
||||||
|
r2 := m.(*Resource)
|
||||||
|
|
||||||
|
result := *r
|
||||||
|
result.Name = r2.Name
|
||||||
|
result.Type = r2.Type
|
||||||
|
result.RawConfig = result.RawConfig.merge(r2.RawConfig)
|
||||||
|
|
||||||
|
if r2.Count > 0 {
|
||||||
|
result.Count = r2.Count
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r2.Provisioners) > 0 {
|
||||||
|
result.Provisioners = r2.Provisioners
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges two variables to create a new third variable.
|
||||||
|
func (v *Variable) Merge(v2 *Variable) *Variable {
|
||||||
|
// Shallow copy the variable
|
||||||
|
result := *v
|
||||||
|
|
||||||
|
// The names should be the same, but the second name always wins.
|
||||||
|
result.Name = v2.Name
|
||||||
|
|
||||||
|
if v2.defaultSet {
|
||||||
|
result.Default = v2.Default
|
||||||
|
result.defaultSet = true
|
||||||
|
}
|
||||||
|
if v2.Description != "" {
|
||||||
|
result.Description = v2.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Variable) mergerName() string {
|
||||||
|
return v.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Variable) mergerMerge(m merger) merger {
|
||||||
|
return v.Merge(m.(*Variable))
|
||||||
|
}
|
||||||
|
|
||||||
// Required tests whether a variable is required or not.
|
// Required tests whether a variable is required or not.
|
||||||
func (v *Variable) Required() bool {
|
func (v *Variable) Required() bool {
|
||||||
return !v.defaultSet
|
return !v.defaultSet
|
||||||
|
|
|
@ -505,6 +505,7 @@ foo
|
||||||
|
|
||||||
const importProvidersStr = `
|
const importProvidersStr = `
|
||||||
aws
|
aws
|
||||||
|
bar
|
||||||
foo
|
foo
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
168
config/merge.go
168
config/merge.go
|
@ -1,9 +1,5 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Merge merges two configurations into a single configuration.
|
// Merge merges two configurations into a single configuration.
|
||||||
//
|
//
|
||||||
// Merge allows for the two configurations to have duplicate resources,
|
// Merge allows for the two configurations to have duplicate resources,
|
||||||
|
@ -24,81 +20,135 @@ func Merge(c1, c2 *Config) (*Config, error) {
|
||||||
c.unknownKeys = append(c.unknownKeys, k)
|
c.unknownKeys = append(c.unknownKeys, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge variables: Variable merging is quite simple. Set fields in
|
// NOTE: Everything below is pretty gross. Due to the lack of generics
|
||||||
// later set variables override those earlier.
|
// in Go, there is some hoop-jumping involved to make this merging a
|
||||||
if len(c1.Variables) > 0 || len(c2.Variables) > 0 {
|
// little more test-friendly and less repetitive. Ironically, making it
|
||||||
c.Variables = make([]*Variable, 0, len(c1.Variables)+len(c2.Variables))
|
// less repetitive involves being a little repetitive, but I prefer to
|
||||||
varMap := make(map[string]*Variable)
|
// be repetitive with things that are less error prone than things that
|
||||||
for _, v := range c1.Variables {
|
// are more error prone (more logic). Type conversions to an interface
|
||||||
varMap[v.Name] = v
|
// are pretty low-error.
|
||||||
}
|
|
||||||
for _, v2 := range c2.Variables {
|
|
||||||
v1, ok := varMap[v2.Name]
|
|
||||||
if ok {
|
|
||||||
if v2.Default == "" {
|
|
||||||
v2.Default = v1.Default
|
|
||||||
}
|
|
||||||
if v2.Description == "" {
|
|
||||||
v2.Description = v1.Description
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
varMap[v2.Name] = v2
|
var m1, m2, mresult []merger
|
||||||
}
|
|
||||||
for _, v := range varMap {
|
|
||||||
c.Variables = append(c.Variables, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge outputs: If they collide, just take the latest one for now. In
|
// Outputs
|
||||||
// the future, we might provide smarter merge functionality.
|
m1 = make([]merger, 0, len(c1.Outputs))
|
||||||
if len(c1.Outputs) > 0 || len(c2.Outputs) > 0 {
|
m2 = make([]merger, 0, len(c2.Outputs))
|
||||||
c.Outputs = make([]*Output, 0, len(c1.Outputs)+len(c2.Outputs))
|
|
||||||
m := make(map[string]*Output)
|
|
||||||
for _, v := range c1.Outputs {
|
for _, v := range c1.Outputs {
|
||||||
m[v.Name] = v
|
m1 = append(m1, v)
|
||||||
}
|
}
|
||||||
for _, v := range c2.Outputs {
|
for _, v := range c2.Outputs {
|
||||||
m[v.Name] = v
|
m2 = append(m2, v)
|
||||||
}
|
}
|
||||||
for _, v := range m {
|
mresult = mergeSlice(m1, m2)
|
||||||
c.Outputs = append(c.Outputs, v)
|
if len(mresult) > 0 {
|
||||||
|
c.Outputs = make([]*Output, len(mresult))
|
||||||
|
for i, v := range mresult {
|
||||||
|
c.Outputs[i] = v.(*Output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge provider configs: If they collide, we just take the latest one
|
// Provider Configs
|
||||||
// for now. In the future, we might provide smarter merge functionality.
|
m1 = make([]merger, 0, len(c1.ProviderConfigs))
|
||||||
if len(c1.ProviderConfigs) > 0 || len(c2.ProviderConfigs) > 0 {
|
m2 = make([]merger, 0, len(c2.ProviderConfigs))
|
||||||
m := make(map[string]*ProviderConfig)
|
|
||||||
for _, v := range c1.ProviderConfigs {
|
for _, v := range c1.ProviderConfigs {
|
||||||
m[v.Name] = v
|
m1 = append(m1, v)
|
||||||
}
|
}
|
||||||
for _, v := range c2.ProviderConfigs {
|
for _, v := range c2.ProviderConfigs {
|
||||||
m[v.Name] = v
|
m2 = append(m2, v)
|
||||||
}
|
}
|
||||||
|
mresult = mergeSlice(m1, m2)
|
||||||
c.ProviderConfigs = make([]*ProviderConfig, 0, len(m))
|
if len(mresult) > 0 {
|
||||||
for _, v := range m {
|
c.ProviderConfigs = make([]*ProviderConfig, len(mresult))
|
||||||
c.ProviderConfigs = append(c.ProviderConfigs, v)
|
for i, v := range mresult {
|
||||||
|
c.ProviderConfigs[i] = v.(*ProviderConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge resources: If they collide, we just take the latest one
|
// Resources
|
||||||
// for now. In the future, we might provide smarter merge functionality.
|
m1 = make([]merger, 0, len(c1.Resources))
|
||||||
resources := make(map[string]*Resource)
|
m2 = make([]merger, 0, len(c2.Resources))
|
||||||
for _, r := range c1.Resources {
|
for _, v := range c1.Resources {
|
||||||
id := fmt.Sprintf("%s[%s]", r.Type, r.Name)
|
m1 = append(m1, v)
|
||||||
resources[id] = r
|
}
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
for _, r := range c2.Resources {
|
|
||||||
id := fmt.Sprintf("%s[%s]", r.Type, r.Name)
|
|
||||||
resources[id] = r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Resources = make([]*Resource, 0, len(resources))
|
// Variables
|
||||||
for _, r := range resources {
|
m1 = make([]merger, 0, len(c1.Variables))
|
||||||
c.Resources = append(c.Resources, r)
|
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
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMerge(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
c1, c2, result *Config
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
// Normal good case.
|
||||||
|
{
|
||||||
|
&Config{
|
||||||
|
Outputs: []*Output{
|
||||||
|
&Output{Name: "foo"},
|
||||||
|
},
|
||||||
|
ProviderConfigs: []*ProviderConfig{
|
||||||
|
&ProviderConfig{Name: "foo"},
|
||||||
|
},
|
||||||
|
Resources: []*Resource{
|
||||||
|
&Resource{Name: "foo"},
|
||||||
|
},
|
||||||
|
Variables: []*Variable{
|
||||||
|
&Variable{Name: "foo"},
|
||||||
|
},
|
||||||
|
|
||||||
|
unknownKeys: []string{"foo"},
|
||||||
|
},
|
||||||
|
|
||||||
|
&Config{
|
||||||
|
Outputs: []*Output{
|
||||||
|
&Output{Name: "bar"},
|
||||||
|
},
|
||||||
|
ProviderConfigs: []*ProviderConfig{
|
||||||
|
&ProviderConfig{Name: "bar"},
|
||||||
|
},
|
||||||
|
Resources: []*Resource{
|
||||||
|
&Resource{Name: "bar"},
|
||||||
|
},
|
||||||
|
Variables: []*Variable{
|
||||||
|
&Variable{Name: "bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
unknownKeys: []string{"bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
&Config{
|
||||||
|
Outputs: []*Output{
|
||||||
|
&Output{Name: "foo"},
|
||||||
|
&Output{Name: "bar"},
|
||||||
|
},
|
||||||
|
ProviderConfigs: []*ProviderConfig{
|
||||||
|
&ProviderConfig{Name: "foo"},
|
||||||
|
&ProviderConfig{Name: "bar"},
|
||||||
|
},
|
||||||
|
Resources: []*Resource{
|
||||||
|
&Resource{Name: "foo"},
|
||||||
|
&Resource{Name: "bar"},
|
||||||
|
},
|
||||||
|
Variables: []*Variable{
|
||||||
|
&Variable{Name: "foo"},
|
||||||
|
&Variable{Name: "bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
unknownKeys: []string{"foo", "bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Test that when merging duplicates, it merges into the
|
||||||
|
// first, but keeps the duplicates so that errors still
|
||||||
|
// happen.
|
||||||
|
{
|
||||||
|
&Config{
|
||||||
|
Outputs: []*Output{
|
||||||
|
&Output{Name: "foo"},
|
||||||
|
},
|
||||||
|
ProviderConfigs: []*ProviderConfig{
|
||||||
|
&ProviderConfig{Name: "foo"},
|
||||||
|
},
|
||||||
|
Resources: []*Resource{
|
||||||
|
&Resource{Name: "foo"},
|
||||||
|
},
|
||||||
|
Variables: []*Variable{
|
||||||
|
&Variable{Name: "foo", Default: "foo"},
|
||||||
|
&Variable{Name: "foo"},
|
||||||
|
},
|
||||||
|
|
||||||
|
unknownKeys: []string{"foo"},
|
||||||
|
},
|
||||||
|
|
||||||
|
&Config{
|
||||||
|
Outputs: []*Output{
|
||||||
|
&Output{Name: "bar"},
|
||||||
|
},
|
||||||
|
ProviderConfigs: []*ProviderConfig{
|
||||||
|
&ProviderConfig{Name: "bar"},
|
||||||
|
},
|
||||||
|
Resources: []*Resource{
|
||||||
|
&Resource{Name: "bar"},
|
||||||
|
},
|
||||||
|
Variables: []*Variable{
|
||||||
|
&Variable{Name: "foo", Default: "bar", defaultSet: true},
|
||||||
|
&Variable{Name: "bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
unknownKeys: []string{"bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
&Config{
|
||||||
|
Outputs: []*Output{
|
||||||
|
&Output{Name: "foo"},
|
||||||
|
&Output{Name: "bar"},
|
||||||
|
},
|
||||||
|
ProviderConfigs: []*ProviderConfig{
|
||||||
|
&ProviderConfig{Name: "foo"},
|
||||||
|
&ProviderConfig{Name: "bar"},
|
||||||
|
},
|
||||||
|
Resources: []*Resource{
|
||||||
|
&Resource{Name: "foo"},
|
||||||
|
&Resource{Name: "bar"},
|
||||||
|
},
|
||||||
|
Variables: []*Variable{
|
||||||
|
&Variable{Name: "foo", Default: "bar", defaultSet: true},
|
||||||
|
&Variable{Name: "foo"},
|
||||||
|
&Variable{Name: "bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
unknownKeys: []string{"foo", "bar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
actual, err := Merge(tc.c1, tc.c2)
|
||||||
|
if (err != nil) != tc.err {
|
||||||
|
t.Fatalf("%d: error fail", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, tc.result) {
|
||||||
|
t.Fatalf("%d: bad:\n\n%#v", i, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -92,6 +92,25 @@ func (r *RawConfig) init() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RawConfig) merge(r2 *RawConfig) *RawConfig {
|
||||||
|
rawRaw, err := copystructure.Copy(r.Raw)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := rawRaw.(map[string]interface{})
|
||||||
|
for k, v := range r2.Raw {
|
||||||
|
raw[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := NewRawConfig(raw)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// UnknownKeys returns the keys of the configuration that are unknown
|
// UnknownKeys returns the keys of the configuration that are unknown
|
||||||
// because they had interpolated variables that must be computed.
|
// because they had interpolated variables that must be computed.
|
||||||
func (r *RawConfig) UnknownKeys() []string {
|
func (r *RawConfig) UnknownKeys() []string {
|
||||||
|
|
Loading…
Reference in New Issue