Skip to content

Commit

Permalink
helper/schema: Allow ResourceDiff.ForceNew on nested fields (avoid cr…
Browse files Browse the repository at this point in the history
…ash)
  • Loading branch information
radeksimko committed Mar 1, 2018
1 parent 66ff8f8 commit 7ebf93a
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 4 deletions.
34 changes: 30 additions & 4 deletions helper/schema/resource_diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,11 @@ func (d *ResourceDiff) Clear(key string) error {

func (d *ResourceDiff) clear(key string) error {
// Check the schema to make sure that this key exists first.
if _, ok := d.schema[key]; !ok {
schemaL := addrToSchema(strings.Split(key, "."), d.schema)
if len(schemaL) == 0 {
return fmt.Errorf("%s is not a valid key", key)
}

for k := range d.diff.Attributes {
if strings.HasPrefix(k, key) {
delete(d.diff.Attributes, k)
Expand All @@ -234,6 +236,19 @@ func (d *ResourceDiff) clear(key string) error {
return nil
}

// GetChangedKeys helps to implement Resource.CustomizeDiff
// where we need to act on all nested fields
// without calling out each one separately
func (d *ResourceDiff) GetChangedKeys(prefix string) []string {
keys := make([]string, 0)
for k := range d.diff.Attributes {
if strings.HasPrefix(k, prefix) {
keys = append(keys, k)
}
}
return keys
}

// diffChange helps to implement resourceDiffer and derives its change values
// from ResourceDiff's own change data, in addition to existing diff, config, and state.
func (d *ResourceDiff) diffChange(key string) (interface{}, interface{}, bool, bool, bool) {
Expand Down Expand Up @@ -309,9 +324,20 @@ func (d *ResourceDiff) ForceNew(key string) error {
return fmt.Errorf("ForceNew: No changes for %s", key)
}

_, new := d.GetChange(key)
d.schema[key].ForceNew = true
return d.setDiff(key, new, false)
keyParts := strings.Split(key, ".")
var schema *Schema
schemaL := addrToSchema(keyParts, d.schema)
if len(schemaL) > 0 {
schema = schemaL[len(schemaL)-1]
} else {
return fmt.Errorf("ForceNew: %s is not a valid key", key)
}

schema.ForceNew = true

// We need to set whole lists/sets/maps here
_, new := d.GetChange(keyParts[0])
return d.setDiff(keyParts[0], new, false)
}

// Get hands off to ResourceData.Get.
Expand Down
179 changes: 179 additions & 0 deletions helper/schema/resource_diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type resourceDiffTestCase struct {
OldValue interface{}
NewValue interface{}
Expected *terraform.InstanceDiff
ExpectedKeys []string
ExpectedError bool
}

Expand Down Expand Up @@ -697,6 +698,69 @@ func TestForceNew(t *testing.T) {
},
},
},
resourceDiffTestCase{
Name: "nested field",
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeList,
Required: true,
MaxItems: 1,
Elem: &Resource{
Schema: map[string]*Schema{
"bar": {
Type: TypeString,
Optional: true,
},
"baz": {
Type: TypeString,
Optional: true,
},
},
},
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"foo.#": "1",
"foo.0.bar": "abc",
"foo.0.baz": "xyz",
},
},
Config: testConfig(t, map[string]interface{}{
"foo": []map[string]interface{}{
map[string]interface{}{
"bar": "abcdefg",
"baz": "changed",
},
},
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo.0.bar": &terraform.ResourceAttrDiff{
Old: "abc",
New: "abcdefg",
},
"foo.0.baz": &terraform.ResourceAttrDiff{
Old: "xyz",
New: "changed",
},
},
},
Key: "foo.0.baz",
Expected: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo.0.bar": &terraform.ResourceAttrDiff{
Old: "abc",
New: "abcdefg",
},
"foo.0.baz": &terraform.ResourceAttrDiff{
Old: "xyz",
New: "changed",
RequiresNew: true,
},
},
},
},
}
for _, tc := range cases {
t.Run(tc.Name, func(t *testing.T) {
Expand Down Expand Up @@ -851,3 +915,118 @@ func TestClear(t *testing.T) {
})
}
}

func TestGetChangedKeys(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",
ExpectedKeys: []string{
"foo",
},
},
resourceDiffTestCase{
Name: "nested field filtering",
Schema: map[string]*Schema{
"testfield": &Schema{
Type: TypeString,
Required: true,
},
"foo": &Schema{
Type: TypeList,
Required: true,
MaxItems: 1,
Elem: &Resource{
Schema: map[string]*Schema{
"bar": {
Type: TypeString,
Optional: true,
},
"baz": {
Type: TypeString,
Optional: true,
},
},
},
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"testfield": "blablah",
"foo.#": "1",
"foo.0.bar": "abc",
"foo.0.baz": "xyz",
},
},
Config: testConfig(t, map[string]interface{}{
"testfield": "modified",
"foo": []map[string]interface{}{
map[string]interface{}{
"bar": "abcdefg",
"baz": "changed",
},
},
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"testfield": &terraform.ResourceAttrDiff{
Old: "blablah",
New: "modified",
},
"foo.0.bar": &terraform.ResourceAttrDiff{
Old: "abc",
New: "abcdefg",
},
"foo.0.baz": &terraform.ResourceAttrDiff{
Old: "xyz",
New: "changed",
},
},
},
Key: "foo",
ExpectedKeys: []string{
"foo.0.bar",
"foo.0.baz",
},
},
}
for _, tc := range cases {
t.Run(tc.Name, func(t *testing.T) {
m := schemaMap(tc.Schema)
d := newResourceDiff(m, tc.Config, tc.State, tc.Diff)
keys := d.GetChangedKeys(tc.Key)

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.ExpectedKeys, keys) {
t.Fatalf("Expected %s, got %s", spew.Sdump(tc.ExpectedKeys), spew.Sdump(keys))
}
})
}
}

0 comments on commit 7ebf93a

Please sign in to comment.