diff --git a/helper/schema/resource_diff.go b/helper/schema/resource_diff.go index de095514e6..7f47748b30 100644 --- a/helper/schema/resource_diff.go +++ b/helper/schema/resource_diff.go @@ -32,7 +32,7 @@ func (w *newValueWriter) WriteField(address []string, value interface{}, compute w.lock.Lock() defer w.lock.Unlock() - if w.result == nil { + if w.computedKeys == nil { w.computedKeys = make(map[string]bool) } @@ -46,7 +46,7 @@ func (w *newValueWriter) WriteField(address []string, value interface{}, compute func (w *newValueWriter) ComputedKeysMap() map[string]bool { w.lock.Lock() defer w.lock.Unlock() - if w.result == nil { + if w.computedKeys == nil { w.computedKeys = make(map[string]bool) } return w.computedKeys @@ -65,12 +65,22 @@ type newValueReader struct { // ReadField reads the values from the underlying writer, returning the // computed value if it is found as well. func (r *newValueReader) ReadField(address []string) (FieldReadResult, error) { + addrKey := strings.Join(address, ".") v, err := r.MapFieldReader.ReadField(address) if err != nil { return FieldReadResult{}, err } - if _, ok := r.computedKeys[strings.Join(address, ".")]; ok { - v.Computed = true + for computedKey := range r.computedKeys { + if strings.HasPrefix(addrKey, computedKey) { + if strings.HasSuffix(addrKey, ".#") { + // This is a count value for a list or set that has been marked as + // computed, or a sub-list/sub-set of a complex resource that has + // been marked as computed. We need to pass through to other readers + // so that an accurate previous count can be fetched for the diff. + v.Exists = false + } + v.Computed = true + } } return v, nil @@ -126,13 +136,7 @@ func newResourceDiff(schema map[string]*Schema, config *terraform.ResourceConfig config: config, state: state, diff: diff, - } - // Duplicate the passed in schema to ensure that any changes we make with - // functions like ForceNew don't affect the referenced schema. - d.schema = make(map[string]*Schema) - for k, v := range schema { - newSchema := *v - d.schema[k] = &newSchema + schema: schema, } d.oldWriter = &MapFieldWriter{Schema: d.schema} @@ -215,8 +219,17 @@ func (d *ResourceDiff) ClearAll() { // any possibility of conflicts, but can be called on its own to just remove a // specific key from the diff completely. // -// Note that this does not wipe an override. +// Note that this does not wipe an override. This function is only allowed on +// computed keys. func (d *ResourceDiff) Clear(key string) error { + if !d.schema[key].Computed { + return fmt.Errorf("Clear is allowed on computed attributes only - %s is not one", key) + } + + return d.clear(key) +} + +func (d *ResourceDiff) clear(key string) error { // Check the schema to make sure that this key exists first. if _, ok := d.schema[key]; !ok { return fmt.Errorf("%s is not a valid key", key) @@ -233,6 +246,7 @@ func (d *ResourceDiff) Clear(key string) error { // from ResourceDiff's own change data, in addition to existing diff, config, and state. func (d *ResourceDiff) diffChange(key string) (interface{}, interface{}, bool, bool) { old, new := d.getChange(key) + // log.Printf("\nkey:%s\n\nold:%s\n\nnew:%s\n", key, spew.Sdump(old), spew.Sdump(new)) if !old.Exists { old.Value = nil @@ -262,7 +276,7 @@ func (d *ResourceDiff) SetNew(key string, value interface{}) error { // // This function is only allowed on computed keys. func (d *ResourceDiff) SetNewComputed(key string) error { - return d.SetDiff(key, d.Get(key), d.schema[key].ZeroValue(), true) + return d.SetDiff(key, d.getExact(strings.Split(key, "."), "state").Value, d.schema[key].ZeroValue(), true) } // SetDiff allows the setting of both old and new values for the diff @@ -277,7 +291,11 @@ func (d *ResourceDiff) SetDiff(key string, old, new interface{}, computed bool) return fmt.Errorf("SetNew, SetNewComputed, and SetDiff are allowed on computed attributes only - %s is not one", key) } - if err := d.Clear(key); err != nil { + return d.setDiff(key, old, new, computed) +} + +func (d *ResourceDiff) setDiff(key string, old, new interface{}, computed bool) error { + if err := d.clear(key); err != nil { return err } @@ -298,8 +316,7 @@ func (d *ResourceDiff) SetDiff(key string, old, new interface{}, computed bool) // re-calculates its diff. This function is a no-op/error if there is no diff. // // Note that the change to schema is permanent for the lifecycle of this -// specific ResourceDiff instance, until ClearAll or Reset is called to start -// anew. +// specific ResourceDiff instance. func (d *ResourceDiff) ForceNew(key string) error { if !d.HasChange(key) { return fmt.Errorf("ResourceDiff.ForceNew: No changes for %s", key) @@ -307,7 +324,7 @@ func (d *ResourceDiff) ForceNew(key string) error { old, new := d.GetChange(key) d.schema[key].ForceNew = true - return d.SetDiff(key, old, new, false) + return d.setDiff(key, old, new, false) } // Get hands off to ResourceData.Get. diff --git a/helper/schema/resource_diff_test.go b/helper/schema/resource_diff_test.go index 03b93a04d9..87d6a1fb0a 100644 --- a/helper/schema/resource_diff_test.go +++ b/helper/schema/resource_diff_test.go @@ -19,19 +19,24 @@ func testSetFunc(v interface{}) int { return m["foo"].(int) + m["bar"].(int) } -func TestSetNew(t *testing.T) { - testCases := []struct { - Name string - Schema map[string]*Schema - State *terraform.InstanceState - Config *terraform.ResourceConfig - Diff *terraform.InstanceDiff - Key string - NewValue interface{} - Expected *terraform.InstanceDiff - ExpectedError bool - }{ - { +// resourceDiffTestCase provides a test case struct for SetNew and SetDiff. +type resourceDiffTestCase struct { + Name string + Schema map[string]*Schema + State *terraform.InstanceState + Config *terraform.ResourceConfig + Diff *terraform.InstanceDiff + Key string + OldValue interface{} + NewValue interface{} + Expected *terraform.InstanceDiff + ExpectedError bool +} + +// testDiffCases produces a list of test cases for use with SetNew and SetDiff. +func testDiffCases(t *testing.T, oldPrefix string, oldOffset int, computed bool) []resourceDiffTestCase { + return []resourceDiffTestCase{ + resourceDiffTestCase{ Name: "basic primitive diff", Schema: map[string]*Schema{ "foo": &Schema{ @@ -57,17 +62,24 @@ func TestSetNew(t *testing.T) { }, }, Key: "foo", + OldValue: fmt.Sprintf("%sbar", oldPrefix), NewValue: "qux", Expected: &terraform.InstanceDiff{ Attributes: map[string]*terraform.ResourceAttrDiff{ "foo": &terraform.ResourceAttrDiff{ - Old: "bar", - New: "qux", + Old: fmt.Sprintf("%sbar", oldPrefix), + New: func() string { + if computed { + return "" + } + return "qux" + }(), + NewComputed: computed, }, }, }, }, - { + resourceDiffTestCase{ Name: "basic set diff", Schema: map[string]*Schema{ "foo": &Schema{ @@ -101,22 +113,33 @@ func TestSetNew(t *testing.T) { }, }, Key: "foo", + OldValue: []interface{}{fmt.Sprintf("%sbar", oldPrefix)}, NewValue: []interface{}{"qux"}, Expected: &terraform.InstanceDiff{ - Attributes: map[string]*terraform.ResourceAttrDiff{ - "foo.1996459178": &terraform.ResourceAttrDiff{ - Old: "bar", - New: "", - NewRemoved: true, - }, - "foo.2800005064": &terraform.ResourceAttrDiff{ - Old: "", - New: "qux", - }, - }, + Attributes: func() map[string]*terraform.ResourceAttrDiff { + result := map[string]*terraform.ResourceAttrDiff{} + if computed { + result["foo.#"] = &terraform.ResourceAttrDiff{ + Old: "1", + New: "", + NewComputed: true, + } + } else { + result["foo.2800005064"] = &terraform.ResourceAttrDiff{ + Old: "", + New: "qux", + } + result[fmt.Sprintf("foo.%d", HashString(fmt.Sprintf("%sbar", oldPrefix)))] = &terraform.ResourceAttrDiff{ + Old: fmt.Sprintf("%sbar", oldPrefix), + New: "", + NewRemoved: true, + } + } + return result + }(), }, }, - { + resourceDiffTestCase{ Name: "basic list diff", Schema: map[string]*Schema{ "foo": &Schema{ @@ -144,17 +167,28 @@ func TestSetNew(t *testing.T) { }, }, Key: "foo", + OldValue: []interface{}{fmt.Sprintf("%sbar", oldPrefix)}, NewValue: []interface{}{"qux"}, Expected: &terraform.InstanceDiff{ - Attributes: map[string]*terraform.ResourceAttrDiff{ - "foo.0": &terraform.ResourceAttrDiff{ - Old: "bar", - New: "qux", - }, - }, + Attributes: func() map[string]*terraform.ResourceAttrDiff { + result := make(map[string]*terraform.ResourceAttrDiff) + if computed { + result["foo.#"] = &terraform.ResourceAttrDiff{ + Old: "1", + New: "", + NewComputed: true, + } + } else { + result["foo.0"] = &terraform.ResourceAttrDiff{ + Old: fmt.Sprintf("%sbar", oldPrefix), + New: "qux", + } + } + return result + }(), }, }, - { + resourceDiffTestCase{ Name: "basic map diff", Schema: map[string]*Schema{ "foo": &Schema{ @@ -181,17 +215,33 @@ func TestSetNew(t *testing.T) { }, }, Key: "foo", + OldValue: map[string]interface{}{"bar": fmt.Sprintf("%sbaz", oldPrefix)}, NewValue: map[string]interface{}{"bar": "quux"}, Expected: &terraform.InstanceDiff{ - Attributes: map[string]*terraform.ResourceAttrDiff{ - "foo.bar": &terraform.ResourceAttrDiff{ - Old: "baz", - New: "quux", - }, - }, + Attributes: func() map[string]*terraform.ResourceAttrDiff { + result := make(map[string]*terraform.ResourceAttrDiff) + if computed { + result["foo.%"] = &terraform.ResourceAttrDiff{ + Old: "", + New: "", + NewComputed: true, + } + result["foo.bar"] = &terraform.ResourceAttrDiff{ + Old: "baz", + New: "", + NewRemoved: true, + } + } else { + result["foo.bar"] = &terraform.ResourceAttrDiff{ + Old: fmt.Sprintf("%sbaz", oldPrefix), + New: "quux", + } + } + return result + }(), }, }, - { + resourceDiffTestCase{ Name: "additional diff with primitive", Schema: map[string]*Schema{ "foo": &Schema{ @@ -212,7 +262,6 @@ func TestSetNew(t *testing.T) { }, Config: testConfig(t, map[string]interface{}{ "foo": "baz", - "one": "three", }), Diff: &terraform.InstanceDiff{ Attributes: map[string]*terraform.ResourceAttrDiff{ @@ -220,13 +269,10 @@ func TestSetNew(t *testing.T) { Old: "bar", New: "baz", }, - "one": &terraform.ResourceAttrDiff{ - Old: "two", - New: "three", - }, }, }, Key: "one", + OldValue: fmt.Sprintf("%stwo", oldPrefix), NewValue: "four", Expected: &terraform.InstanceDiff{ Attributes: map[string]*terraform.ResourceAttrDiff{ @@ -235,13 +281,19 @@ func TestSetNew(t *testing.T) { New: "baz", }, "one": &terraform.ResourceAttrDiff{ - Old: "two", - New: "four", + Old: fmt.Sprintf("%stwo", oldPrefix), + New: func() string { + if computed { + return "" + } + return "four" + }(), + NewComputed: computed, }, }, }, }, - { + resourceDiffTestCase{ Name: "additional diff with primitive computed only", Schema: map[string]*Schema{ "foo": &Schema{ @@ -271,6 +323,7 @@ func TestSetNew(t *testing.T) { }, }, Key: "one", + OldValue: fmt.Sprintf("%stwo", oldPrefix), NewValue: "three", Expected: &terraform.InstanceDiff{ Attributes: map[string]*terraform.ResourceAttrDiff{ @@ -279,13 +332,19 @@ func TestSetNew(t *testing.T) { New: "baz", }, "one": &terraform.ResourceAttrDiff{ - Old: "two", - New: "three", + Old: fmt.Sprintf("%stwo", oldPrefix), + New: func() string { + if computed { + return "" + } + return "three" + }(), + NewComputed: computed, }, }, }, }, - { + resourceDiffTestCase{ Name: "complex-ish set diff", Schema: map[string]*Schema{ "top": &Schema{ @@ -351,6 +410,16 @@ func TestSetNew(t *testing.T) { }, }, Key: "top", + OldValue: NewSet(testSetFunc, []interface{}{ + map[string]interface{}{ + "foo": 1, + "bar": 4 + oldOffset, + }, + map[string]interface{}{ + "foo": 13 + oldOffset, + "bar": 12, + }, + }), NewValue: NewSet(testSetFunc, []interface{}{ map[string]interface{}{ "foo": 1, @@ -365,47 +434,429 @@ func TestSetNew(t *testing.T) { "bar": 22, }, }), + Expected: &terraform.InstanceDiff{ + Attributes: func() map[string]*terraform.ResourceAttrDiff { + result := make(map[string]*terraform.ResourceAttrDiff) + if computed { + result["top.#"] = &terraform.ResourceAttrDiff{ + Old: "2", + New: "", + NewComputed: true, + } + } else { + result["top.#"] = &terraform.ResourceAttrDiff{ + Old: "2", + New: "3", + } + result["top.5.foo"] = &terraform.ResourceAttrDiff{ + Old: "", + New: "1", + } + result["top.5.bar"] = &terraform.ResourceAttrDiff{ + Old: "", + New: "4", + } + result["top.25.foo"] = &terraform.ResourceAttrDiff{ + Old: "", + New: "13", + } + result["top.25.bar"] = &terraform.ResourceAttrDiff{ + Old: "", + New: "12", + } + result["top.43.foo"] = &terraform.ResourceAttrDiff{ + Old: "", + New: "21", + } + result["top.43.bar"] = &terraform.ResourceAttrDiff{ + Old: "", + New: "22", + } + } + return result + }(), + }, + }, + resourceDiffTestCase{ + Name: "primitive, no diff, no refresh", + Schema: map[string]*Schema{ + "foo": &Schema{ + Type: TypeString, + Computed: true, + }, + }, + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "foo": "bar", + }, + }, + Config: testConfig(t, map[string]interface{}{}), + Diff: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}}, + Key: "foo", + OldValue: fmt.Sprintf("%sbar", oldPrefix), + NewValue: "baz", Expected: &terraform.InstanceDiff{ Attributes: map[string]*terraform.ResourceAttrDiff{ - "top.#": &terraform.ResourceAttrDiff{ - Old: "2", - New: "3", - }, - "top.5.foo": &terraform.ResourceAttrDiff{ - Old: "", - New: "1", + "foo": &terraform.ResourceAttrDiff{ + Old: fmt.Sprintf("%sbar", oldPrefix), + New: func() string { + if computed { + return "" + } + return "baz" + }(), + NewComputed: computed, }, - "top.5.bar": &terraform.ResourceAttrDiff{ - Old: "", - New: "4", + }, + }, + }, + resourceDiffTestCase{ + Name: "non-computed key, should error", + Schema: map[string]*Schema{ + "foo": &Schema{ + Type: TypeString, + Required: true, + }, + }, + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "foo": "bar", + }, + }, + Config: testConfig(t, map[string]interface{}{ + "foo": "baz", + }), + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "foo": &terraform.ResourceAttrDiff{ + Old: "bar", + New: "baz", }, - "top.25.foo": &terraform.ResourceAttrDiff{ - Old: "", - New: "13", + }, + }, + Key: "foo", + OldValue: fmt.Sprintf("%sbar", oldPrefix), + NewValue: "qux", + ExpectedError: true, + }, + } +} + +func TestSetNew(t *testing.T) { + testCases := testDiffCases(t, "", 0, false) + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + m := schemaMap(tc.Schema) + d := newResourceDiff(tc.Schema, nil, tc.State, tc.Diff) + err := d.SetNew(tc.Key, tc.NewValue) + switch { + case err != nil && !tc.ExpectedError: + t.Fatalf("bad: %s", err) + case err == nil && tc.ExpectedError: + t.Fatalf("Expected error, got none") + case err != nil && tc.ExpectedError: + return + } + for _, k := range d.UpdatedKeys() { + if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { + t.Fatalf("bad: %s", err) + } + } + if !reflect.DeepEqual(tc.Expected, tc.Diff) { + t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff)) + } + }) + } +} + +func TestSetNewComputed(t *testing.T) { + testCases := testDiffCases(t, "", 0, true) + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + m := schemaMap(tc.Schema) + d := newResourceDiff(tc.Schema, nil, tc.State, tc.Diff) + err := d.SetNewComputed(tc.Key) + switch { + case err != nil && !tc.ExpectedError: + t.Fatalf("bad: %s", err) + case err == nil && tc.ExpectedError: + t.Fatalf("Expected error, got none") + case err != nil && tc.ExpectedError: + return + } + for _, k := range d.UpdatedKeys() { + if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { + t.Fatalf("bad: %s", err) + } + } + if !reflect.DeepEqual(tc.Expected, tc.Diff) { + t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff)) + } + }) + } +} + +func TestSetDiff(t *testing.T) { + testCases := testDiffCases(t, "testSetDiff", 1, false) + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + m := schemaMap(tc.Schema) + d := newResourceDiff(tc.Schema, nil, tc.State, tc.Diff) + err := d.SetDiff(tc.Key, tc.OldValue, tc.NewValue, false) + switch { + case err != nil && !tc.ExpectedError: + t.Fatalf("bad: %s", err) + case err == nil && tc.ExpectedError: + t.Fatalf("Expected error, got none") + case err != nil && tc.ExpectedError: + return + } + for _, k := range d.UpdatedKeys() { + if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { + t.Fatalf("bad: %s", err) + } + } + if !reflect.DeepEqual(tc.Expected, tc.Diff) { + t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff)) + } + }) + } +} + +func TestForceNew(t *testing.T) { + cases := []resourceDiffTestCase{ + resourceDiffTestCase{ + Name: "basic primitive diff", + Schema: map[string]*Schema{ + "foo": &Schema{ + Type: TypeString, + Optional: true, + Computed: true, + }, + }, + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "foo": "bar", + }, + }, + Config: testConfig(t, map[string]interface{}{ + "foo": "baz", + }), + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "foo": &terraform.ResourceAttrDiff{ + Old: "bar", + New: "baz", }, - "top.25.bar": &terraform.ResourceAttrDiff{ - Old: "", - New: "12", + }, + }, + Key: "foo", + Expected: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "foo": &terraform.ResourceAttrDiff{ + Old: "bar", + New: "baz", + RequiresNew: true, }, - "top.43.foo": &terraform.ResourceAttrDiff{ - Old: "", - New: "21", + }, + }, + }, + resourceDiffTestCase{ + Name: "no change, should error", + Schema: map[string]*Schema{ + "foo": &Schema{ + Type: TypeString, + Optional: true, + Computed: true, + }, + }, + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "foo": "bar", + }, + }, + Config: testConfig(t, map[string]interface{}{ + "foo": "bar", + }), + ExpectedError: true, + }, + resourceDiffTestCase{ + Name: "basic primitive, non-computed key", + Schema: map[string]*Schema{ + "foo": &Schema{ + Type: TypeString, + Required: true, + }, + }, + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "foo": "bar", + }, + }, + Config: testConfig(t, map[string]interface{}{ + "foo": "baz", + }), + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "foo": &terraform.ResourceAttrDiff{ + Old: "bar", + New: "baz", }, - "top.43.bar": &terraform.ResourceAttrDiff{ - Old: "", - New: "22", + }, + }, + Key: "foo", + Expected: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "foo": &terraform.ResourceAttrDiff{ + Old: "bar", + New: "baz", + RequiresNew: true, }, }, }, }, } + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + m := schemaMap(tc.Schema) + d := newResourceDiff(m, nil, tc.State, tc.Diff) + err := d.ForceNew(tc.Key) + switch { + case err != nil && !tc.ExpectedError: + t.Fatalf("bad: %s", err) + case err == nil && tc.ExpectedError: + t.Fatalf("Expected error, got none") + case err != nil && tc.ExpectedError: + return + } + for _, k := range d.UpdatedKeys() { + if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { + t.Fatalf("bad: %s", err) + } + } + if !reflect.DeepEqual(tc.Expected, tc.Diff) { + t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff)) + } + }) + } +} - for _, tc := range testCases { - t.Run(fmt.Sprintf("%s", tc.Name), func(t *testing.T) { +func TestClear(t *testing.T) { + cases := []resourceDiffTestCase{ + resourceDiffTestCase{ + Name: "basic primitive diff", + Schema: map[string]*Schema{ + "foo": &Schema{ + Type: TypeString, + Optional: true, + Computed: true, + }, + }, + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "foo": "bar", + }, + }, + Config: testConfig(t, map[string]interface{}{ + "foo": "baz", + }), + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "foo": &terraform.ResourceAttrDiff{ + Old: "bar", + New: "baz", + }, + }, + }, + Key: "foo", + Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}}, + }, + resourceDiffTestCase{ + Name: "non-computed key, should error", + Schema: map[string]*Schema{ + "foo": &Schema{ + Type: TypeString, + Required: true, + }, + }, + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "foo": "bar", + }, + }, + Config: testConfig(t, map[string]interface{}{ + "foo": "baz", + }), + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "foo": &terraform.ResourceAttrDiff{ + Old: "bar", + New: "baz", + }, + }, + }, + Key: "foo", + ExpectedError: true, + }, + resourceDiffTestCase{ + Name: "multi-value, one removed", + Schema: map[string]*Schema{ + "foo": &Schema{ + Type: TypeString, + Optional: true, + Computed: true, + }, + "one": &Schema{ + Type: TypeString, + Optional: true, + Computed: true, + }, + }, + State: &terraform.InstanceState{ + Attributes: map[string]string{ + "foo": "bar", + "one": "two", + }, + }, + Config: testConfig(t, map[string]interface{}{ + "foo": "baz", + "one": "three", + }), + Diff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "foo": &terraform.ResourceAttrDiff{ + Old: "bar", + New: "baz", + }, + "one": &terraform.ResourceAttrDiff{ + Old: "two", + New: "three", + }, + }, + }, + Key: "one", + Expected: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "foo": &terraform.ResourceAttrDiff{ + Old: "bar", + New: "baz", + }, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { m := schemaMap(tc.Schema) - d := newResourceDiff(tc.Schema, nil, tc.State, tc.Diff) - if err := d.SetNew(tc.Key, tc.NewValue); err != nil { + d := newResourceDiff(m, nil, tc.State, tc.Diff) + err := d.Clear(tc.Key) + switch { + case err != nil && !tc.ExpectedError: t.Fatalf("bad: %s", err) + case err == nil && tc.ExpectedError: + t.Fatalf("Expected error, got none") + case err != nil && tc.ExpectedError: + return } for _, k := range d.UpdatedKeys() { if err := m.diff(k, m[k], tc.Diff, d, false); err != nil { diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index d5259364ff..5a2d8190be 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -5022,3 +5022,17 @@ func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] } func (e errorSort) Less(i, j int) bool { return e[i].Error() < e[j].Error() } + +func TestSchemaMapDeepCopy(t *testing.T) { + schema := map[string]*Schema{ + "foo": &Schema{ + Type: TypeString, + }, + } + source := schemaMap(schema) + dest := source.DeepCopy() + dest["foo"].ForceNew = true + if reflect.DeepEqual(source, dest) { + t.Fatalf("source and dest should not match") + } +}