diff --git a/builtin/providers/test/provider.go b/builtin/providers/test/provider.go index 948e82f875..7ccdfa5ffb 100644 --- a/builtin/providers/test/provider.go +++ b/builtin/providers/test/provider.go @@ -29,6 +29,7 @@ func Provider() terraform.ResourceProvider { "test_resource_deprecated": testResourceDeprecated(), "test_resource_defaults": testResourceDefaults(), "test_resource_list": testResourceList(), + "test_resource_map": testResourceMap(), }, DataSourcesMap: map[string]*schema.Resource{ "test_data_source": testDataSource(), diff --git a/builtin/providers/test/resource_defaults_test.go b/builtin/providers/test/resource_defaults_test.go index 79b164e1c1..b2659966d2 100644 --- a/builtin/providers/test/resource_defaults_test.go +++ b/builtin/providers/test/resource_defaults_test.go @@ -33,6 +33,89 @@ resource "test_resource_defaults" "foo" { }) } +func TestResourceDefaults_change(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + Providers: testAccProviders, + CheckDestroy: testAccCheckResourceDestroy, + Steps: []resource.TestStep{ + { + Config: strings.TrimSpace(` +resource "test_resource_defaults" "foo" { +} + `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "test_resource_defaults.foo", "default_string", "default string", + ), + resource.TestCheckResourceAttr( + "test_resource_defaults.foo", "default_bool", "1", + ), + resource.TestCheckNoResourceAttr( + "test_resource_defaults.foo", "nested.#", + ), + ), + }, + { + Config: strings.TrimSpace(` +resource "test_resource_defaults" "foo" { + default_string = "new" + default_bool = false + nested { + optional = "nested" + } +} + `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "test_resource_defaults.foo", "default_string", "new", + ), + resource.TestCheckResourceAttr( + "test_resource_defaults.foo", "default_bool", "false", + ), + resource.TestCheckResourceAttr( + "test_resource_defaults.foo", "nested.#", "1", + ), + resource.TestCheckResourceAttr( + "test_resource_defaults.foo", "nested.2950978312.optional", "nested", + ), + resource.TestCheckResourceAttr( + "test_resource_defaults.foo", "nested.2950978312.string", "default nested", + ), + ), + }, + { + Config: strings.TrimSpace(` +resource "test_resource_defaults" "foo" { + default_string = "new" + default_bool = false + nested { + optional = "nested" + string = "new" + } +} + `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "test_resource_defaults.foo", "default_string", "new", + ), + resource.TestCheckResourceAttr( + "test_resource_defaults.foo", "default_bool", "false", + ), + resource.TestCheckResourceAttr( + "test_resource_defaults.foo", "nested.#", "1", + ), + resource.TestCheckResourceAttr( + "test_resource_defaults.foo", "nested.782850362.optional", "nested", + ), + resource.TestCheckResourceAttr( + "test_resource_defaults.foo", "nested.782850362.string", "new", + ), + ), + }, + }, + }) +} + func TestResourceDefaults_inSet(t *testing.T) { resource.UnitTest(t, resource.TestCase{ Providers: testAccProviders, diff --git a/builtin/providers/test/resource_map.go b/builtin/providers/test/resource_map.go new file mode 100644 index 0000000000..fe524f69fa --- /dev/null +++ b/builtin/providers/test/resource_map.go @@ -0,0 +1,52 @@ +package test + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" +) + +func testResourceMap() *schema.Resource { + return &schema.Resource{ + Create: testResourceMapCreate, + Read: testResourceMapRead, + Update: testResourceMapUpdate, + Delete: testResourceMapDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "map_of_three": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func testResourceMapCreate(d *schema.ResourceData, meta interface{}) error { + // make sure all elements are passed to the map + m := d.Get("map_of_three").(map[string]interface{}) + if len(m) != 3 { + return fmt.Errorf("expected 3 map values, got %#v\n", m) + } + + d.SetId("testId") + return nil +} + +func testResourceMapRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func testResourceMapUpdate(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func testResourceMapDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} diff --git a/builtin/providers/test/resource_map_test.go b/builtin/providers/test/resource_map_test.go new file mode 100644 index 0000000000..800247edab --- /dev/null +++ b/builtin/providers/test/resource_map_test.go @@ -0,0 +1,32 @@ +package test + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestResourceMap_basic(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + Providers: testAccProviders, + CheckDestroy: testAccCheckResourceDestroy, + Steps: []resource.TestStep{ + { + Config: ` +resource "test_resource_map" "foobar" { + name = "test" + map_of_three = { + one = "one" + two = "two" + empty = "" + } +}`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "test_resource_map.foobar", "map_of_three.empty", "", + ), + ), + }, + }, + }) +} diff --git a/builtin/providers/test/resource_test.go b/builtin/providers/test/resource_test.go index 12bf568faa..05fd5c0c5c 100644 --- a/builtin/providers/test/resource_test.go +++ b/builtin/providers/test/resource_test.go @@ -24,9 +24,95 @@ resource "test_resource" "foo" { } } `), - Check: func(s *terraform.State) error { - return nil - }, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckNoResourceAttr( + "test_resource.foo", "list.#", + ), + ), + }, + }, + }) +} + +func TestResource_changedList(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + Providers: testAccProviders, + CheckDestroy: testAccCheckResourceDestroy, + Steps: []resource.TestStep{ + { + Config: strings.TrimSpace(` +resource "test_resource" "foo" { + required = "yep" + required_map = { + key = "value" + } +} + `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckNoResourceAttr( + "test_resource.foo", "list.#", + ), + ), + }, + { + Config: strings.TrimSpace(` +resource "test_resource" "foo" { + required = "yep" + required_map = { + key = "value" + } + list = ["a"] +} + `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "test_resource.foo", "list.#", "1", + ), + resource.TestCheckResourceAttr( + "test_resource.foo", "list.0", "a", + ), + ), + }, + { + Config: strings.TrimSpace(` +resource "test_resource" "foo" { + required = "yep" + required_map = { + key = "value" + } + list = ["a", "b"] +} + `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "test_resource.foo", "list.#", "2", + ), + resource.TestCheckResourceAttr( + "test_resource.foo", "list.0", "a", + ), + resource.TestCheckResourceAttr( + "test_resource.foo", "list.1", "b", + ), + ), + }, + { + Config: strings.TrimSpace(` +resource "test_resource" "foo" { + required = "yep" + required_map = { + key = "value" + } + list = ["b"] +} + `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "test_resource.foo", "list.#", "1", + ), + resource.TestCheckResourceAttr( + "test_resource.foo", "list.0", "b", + ), + ), }, }, }) @@ -165,9 +251,6 @@ resource "test_resource" "foo" { } } `), - Check: func(s *terraform.State) error { - return nil - }, }, resource.TestStep{ Config: strings.TrimSpace(` diff --git a/helper/plugin/grpc_provider.go b/helper/plugin/grpc_provider.go index 1bcd491aa2..fdbf0a8407 100644 --- a/helper/plugin/grpc_provider.go +++ b/helper/plugin/grpc_provider.go @@ -697,13 +697,6 @@ func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.A } } - // strip out non-diffs - for k, v := range diff.Attributes { - if v.New == v.Old && !v.NewComputed && !v.NewRemoved { - delete(diff.Attributes, k) - } - } - // add NewExtra Fields that may have been stored in the private data if newExtra := private[newExtraKey]; newExtra != nil { for k, v := range newExtra.(map[string]interface{}) { diff --git a/terraform/diff.go b/terraform/diff.go index 04cfaa0c7d..7751794aca 100644 --- a/terraform/diff.go +++ b/terraform/diff.go @@ -454,10 +454,10 @@ func (d *InstanceDiff) Apply(attrs map[string]string, schema *configschema.Block return result, nil } - return d.blockDiff(nil, attrs, schema) + return d.applyBlockDiff(nil, attrs, schema) } -func (d *InstanceDiff) blockDiff(path []string, attrs map[string]string, schema *configschema.Block) (map[string]string, error) { +func (d *InstanceDiff) applyBlockDiff(path []string, attrs map[string]string, schema *configschema.Block) (map[string]string, error) { result := map[string]string{} name := "" @@ -554,19 +554,17 @@ func (d *InstanceDiff) blockDiff(path []string, attrs map[string]string, schema } // this must be a diff to keep - keep = true break } if !keep { - delete(candidateKeys, k) } } } for k := range candidateKeys { - newAttrs, err := d.blockDiff(append(path, n, k), attrs, &block.Block) + newAttrs, err := d.applyBlockDiff(append(path, n, k), attrs, &block.Block) if err != nil { return result, err } @@ -737,24 +735,37 @@ func (d *InstanceDiff) applyCollectionDiff(path []string, attrs map[string]strin } // collect all the keys from the diff and the old state + noDiff := true keys := map[string]bool{} for k := range d.Attributes { + if !strings.HasPrefix(k, currentKey+".") { + continue + } + noDiff = false keys[k] = true } + + noAttrs := true for k := range attrs { + if !strings.HasPrefix(k, currentKey+".") { + continue + } + noAttrs = false keys[k] = true } + // If there's no diff and no attrs, then there's no value at all. + // This prevents an unexpected zero-count attribute in the attributes. + if noDiff && noAttrs { + return result, nil + } + idx := "#" if attrSchema.Type.IsMapType() { idx = "%" } for k := range keys { - if !strings.HasPrefix(k, currentKey+".") { - continue - } - // generate an schema placeholder for the values elSchema := &configschema.Attribute{ Type: attrSchema.Type.ElementType(), @@ -772,7 +783,7 @@ func (d *InstanceDiff) applyCollectionDiff(path []string, attrs map[string]strin // Don't trust helper/schema to return a valid count, or even have one at // all. - result[idx] = countFlatmapContainerValues(name+"."+idx, result) + result[name+"."+idx] = countFlatmapContainerValues(name+"."+idx, result) return result, nil }