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.
This commit is contained in:
Mitchell Hashimoto 2016-12-13 21:48:59 -08:00
parent 014b414839
commit 3878b8b093
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
4 changed files with 309 additions and 2 deletions

View File

@ -915,7 +915,10 @@ func (o *Output) mergerMerge(m merger) merger {
result := *o
result.Name = o2.Name
result.Description = o2.Description
result.RawConfig = result.RawConfig.merge(o2.RawConfig)
result.Sensitive = o2.Sensitive
result.DependsOn = o2.DependsOn
return &result
}
@ -943,6 +946,10 @@ func (c *ProviderConfig) mergerMerge(m merger) merger {
result.Name = c2.Name
result.RawConfig = result.RawConfig.merge(c2.RawConfig)
if c2.Alias != "" {
result.Alias = c2.Alias
}
return &result
}
@ -978,6 +985,9 @@ func (v *Variable) Merge(v2 *Variable) *Variable {
// The names should be the same, but the second name always wins.
result.Name = v2.Name
if v2.DeclaredType != "" {
result.DeclaredType = v2.DeclaredType
}
if v2.Default != nil {
result.Default = v2.Default
}

View File

@ -32,6 +32,12 @@ func Merge(c1, c2 *Config) (*Config, error) {
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

View File

@ -153,6 +153,287 @@ func TestMerge(t *testing.T) {
false,
},
// Terraform block
{
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
&Config{},
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
false,
},
{
&Config{},
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
&Config{
Terraform: &Terraform{
RequiredVersion: "A",
},
},
false,
},
// Provider alias
{
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "foo"},
},
},
&Config{},
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "foo"},
},
},
false,
},
{
&Config{},
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "foo"},
},
},
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "foo"},
},
},
false,
},
{
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "bar"},
},
},
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "foo"},
},
},
&Config{
ProviderConfigs: []*ProviderConfig{
&ProviderConfig{Alias: "foo"},
},
},
false,
},
// Variable type
{
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "foo"},
},
},
&Config{},
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "foo"},
},
},
false,
},
{
&Config{},
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "foo"},
},
},
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "foo"},
},
},
false,
},
{
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "bar"},
},
},
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "foo"},
},
},
&Config{
Variables: []*Variable{
&Variable{DeclaredType: "foo"},
},
},
false,
},
// Output description
{
&Config{
Outputs: []*Output{
&Output{Description: "foo"},
},
},
&Config{},
&Config{
Outputs: []*Output{
&Output{Description: "foo"},
},
},
false,
},
{
&Config{},
&Config{
Outputs: []*Output{
&Output{Description: "foo"},
},
},
&Config{
Outputs: []*Output{
&Output{Description: "foo"},
},
},
false,
},
{
&Config{
Outputs: []*Output{
&Output{Description: "bar"},
},
},
&Config{
Outputs: []*Output{
&Output{Description: "foo"},
},
},
&Config{
Outputs: []*Output{
&Output{Description: "foo"},
},
},
false,
},
// Output depends_on
{
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"foo"}},
},
},
&Config{},
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"foo"}},
},
},
false,
},
{
&Config{},
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"foo"}},
},
},
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"foo"}},
},
},
false,
},
{
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"bar"}},
},
},
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"foo"}},
},
},
&Config{
Outputs: []*Output{
&Output{DependsOn: []string{"foo"}},
},
},
false,
},
// Output sensitive
{
&Config{
Outputs: []*Output{
&Output{Sensitive: true},
},
},
&Config{},
&Config{
Outputs: []*Output{
&Output{Sensitive: true},
},
},
false,
},
{
&Config{},
&Config{
Outputs: []*Output{
&Output{Sensitive: true},
},
},
&Config{
Outputs: []*Output{
&Output{Sensitive: true},
},
},
false,
},
{
&Config{
Outputs: []*Output{
&Output{Sensitive: false},
},
},
&Config{
Outputs: []*Output{
&Output{Sensitive: true},
},
},
&Config{
Outputs: []*Output{
&Output{Sensitive: true},
},
},
false,
},
}
for i, tc := range cases {

View File

@ -240,15 +240,25 @@ func (r *RawConfig) interpolate(fn interpolationWalkerFunc) error {
}
func (r *RawConfig) merge(r2 *RawConfig) *RawConfig {
if r == nil && r2 == nil {
return nil
}
if r == nil {
r = &RawConfig{}
}
rawRaw, err := copystructure.Copy(r.Raw)
if err != nil {
panic(err)
}
raw := rawRaw.(map[string]interface{})
if r2 != nil {
for k, v := range r2.Raw {
raw[k] = v
}
}
result, err := NewRawConfig(raw)
if err != nil {