From 37995e7ff892595eb6baeba28683612ef0bba985 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 1 Jul 2014 16:06:06 -0700 Subject: [PATCH] helper/diff: work with complex data types --- helper/diff/resource_builder.go | 86 ++++++++++++++++++++-------- helper/diff/resource_builder_test.go | 68 ++++++++++++++++++++-- 2 files changed, 123 insertions(+), 31 deletions(-) diff --git a/helper/diff/resource_builder.go b/helper/diff/resource_builder.go index b07f4aa63..7fe565ac6 100644 --- a/helper/diff/resource_builder.go +++ b/helper/diff/resource_builder.go @@ -1,14 +1,43 @@ package diff import ( + "strings" + + "github.com/hashicorp/terraform/flatmap" "github.com/hashicorp/terraform/terraform" ) +// AttrType is an enum that tells the ResourceBuilder what type of attribute +// an attribute is, affecting the overall diff output. +// +// The valid values are: +// +// * AttrTypeCreate - This attribute can only be set or updated on create. +// This means that if this attribute is changed, it will require a new +// resource to be created if it is already created. +// +// * AttrTypeUpdate - This attribute can be set at create time or updated +// in-place. Changing this attribute does not require a new resource. +// +type AttrType byte + +const ( + AttrTypeUnknown AttrType = iota + AttrTypeCreate + AttrTypeUpdate +) + // ResourceBuilder is a helper that knows about how a single resource // changes and how those changes affect the diff. type ResourceBuilder struct { - CreateComputedAttrs []string - RequiresNewAttrs []string + // Attrs are the mapping of attributes that can be set from the + // configuration, and the affect they have. See the documentation for + // AttrType for more info. + Attrs map[string]AttrType + + // ComputedAttrs are the attributes that are computed at + // resource creation time. + ComputedAttrs []string } // Diff returns the ResourceDiff for a resource given its state and @@ -18,45 +47,52 @@ func (b *ResourceBuilder) Diff( c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) { attrs := make(map[string]*terraform.ResourceAttrDiff) - requiresNewSet := make(map[string]struct{}) - for _, k := range b.RequiresNewAttrs { - requiresNewSet[k] = struct{}{} - } - // We require a new resource if the ID is empty. Or, later, we set // this to true if any configuration changed that triggers a new resource. requiresNew := s.ID == "" - // Go through the configuration and find the changed attributes - for k, v := range c.Raw { - newV := v.(string) + // Flatten the raw and processed configuration + flatRaw := flatmap.Flatten(c.Raw) + flatConfig := flatmap.Flatten(c.Config) + + for k, v := range flatRaw { + // Make sure this is an attribute that actually affects + // the diff in some way. + var attr AttrType + for ak, at := range b.Attrs { + if strings.HasPrefix(k, ak) { + attr = at + break + } + } + if attr == AttrTypeUnknown { + continue + } // If this key is in the cleaned config, then use that value // because it'll have its variables properly interpolated - if cleanV, ok := c.Config[k]; ok { - newV = cleanV.(string) + if cleanV, ok := flatConfig[k]; ok { + v = cleanV } - var oldV string - var ok bool - if oldV, ok = s.Attributes[k]; ok { - // Old value exists! We check to see if there is a change - if oldV == newV { - continue - } + oldV, ok := s.Attributes[k] + + // If there is an old value and they're the same, no change + if ok && oldV == v { + continue } - // There has been a change. Record it + // Record the change attrs[k] = &terraform.ResourceAttrDiff{ - Old: oldV, - New: newV, + Old: oldV, + New: v, + Type: terraform.DiffAttrInput, } // If this requires a new resource, record that and flag our // boolean. - if _, ok := requiresNewSet[k]; ok { + if attr == AttrTypeCreate { attrs[k].RequiresNew = true - attrs[k].Type = terraform.DiffAttrInput requiresNew = true } } @@ -64,7 +100,7 @@ func (b *ResourceBuilder) Diff( // If we require a new resource, then process all the attributes // that will be changing due to the creation of the resource. if requiresNew { - for _, k := range b.CreateComputedAttrs { + for _, k := range b.ComputedAttrs { old := s.Attributes[k] attrs[k] = &terraform.ResourceAttrDiff{ Old: old, diff --git a/helper/diff/resource_builder_test.go b/helper/diff/resource_builder_test.go index 1cd8a685a..d62f674a3 100644 --- a/helper/diff/resource_builder_test.go +++ b/helper/diff/resource_builder_test.go @@ -7,9 +7,51 @@ import ( "github.com/hashicorp/terraform/terraform" ) +func TestResourceBuilder_complex(t *testing.T) { + rb := &ResourceBuilder{ + Attrs: map[string]AttrType{ + "listener": AttrTypeUpdate, + }, + } + + state := &terraform.ResourceState{ + ID: "foo", + Attributes: map[string]string{ + "ignore": "1", + "listener.#": "1", + "listener.0.port": "80", + }, + } + + c := testConfig(t, map[string]interface{}{ + "listener": []interface{}{ + map[interface{}]interface{}{ + "port": 3000, + }, + }, + }, nil) + + diff, err := rb.Diff(state, c) + if err != nil { + t.Fatalf("err: %s", err) + } + if diff == nil { + t.Fatal("should not be nil") + } + + actual := testResourceDiffStr(diff) + expected := testRBComplexDiff + if actual != expected { + t.Fatalf("bad: %s", actual) + } +} + func TestResourceBuilder_new(t *testing.T) { rb := &ResourceBuilder{ - CreateComputedAttrs: []string{"private_ip"}, + Attrs: map[string]AttrType{ + "foo": AttrTypeUpdate, + }, + ComputedAttrs: []string{"private_ip"}, } state := &terraform.ResourceState{} @@ -35,8 +77,10 @@ func TestResourceBuilder_new(t *testing.T) { func TestResourceBuilder_requiresNew(t *testing.T) { rb := &ResourceBuilder{ - CreateComputedAttrs: []string{"private_ip"}, - RequiresNewAttrs: []string{"ami"}, + ComputedAttrs: []string{"private_ip"}, + Attrs: map[string]AttrType{ + "ami": AttrTypeCreate, + }, } state := &terraform.ResourceState{ @@ -68,7 +112,7 @@ func TestResourceBuilder_requiresNew(t *testing.T) { func TestResourceBuilder_same(t *testing.T) { rb := &ResourceBuilder{ - CreateComputedAttrs: []string{"private_ip"}, + ComputedAttrs: []string{"private_ip"}, } state := &terraform.ResourceState{ @@ -92,7 +136,11 @@ func TestResourceBuilder_same(t *testing.T) { } func TestResourceBuilder_unknown(t *testing.T) { - rb := &ResourceBuilder{} + rb := &ResourceBuilder{ + Attrs: map[string]AttrType{ + "foo": AttrTypeUpdate, + }, + } state := &terraform.ResourceState{} @@ -119,7 +167,11 @@ func TestResourceBuilder_unknown(t *testing.T) { } func TestResourceBuilder_vars(t *testing.T) { - rb := &ResourceBuilder{} + rb := &ResourceBuilder{ + Attrs: map[string]AttrType{ + "foo": AttrTypeUpdate, + }, + } state := &terraform.ResourceState{} @@ -144,6 +196,10 @@ func TestResourceBuilder_vars(t *testing.T) { } } +const testRBComplexDiff = `UPDATE + IN listener.0.port: "80" => "3000" +` + const testRBNewDiff = `UPDATE IN foo: "" => "bar" OUT private_ip: "" => ""