terraform/helper/schema/resource_diff_test.go

2046 lines
45 KiB
Go
Raw Normal View History

package schema
import (
"fmt"
"reflect"
"sort"
"testing"
"github.com/davecgh/go-spew/spew"
2019-08-07 01:58:58 +02:00
"github.com/hashicorp/terraform/configs/hcl2shim"
"github.com/hashicorp/terraform/terraform"
)
// testSetFunc is a very simple function we use to test a foo/bar complex set.
// Both "foo" and "bar" are int values.
//
// This is not foolproof as since it performs sums, you can run into
// collisions. Spec tests accordingly. :P
func testSetFunc(v interface{}) int {
m := v.(map[string]interface{})
return m["foo"].(int) + m["bar"].(int)
}
// 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
ExpectedKeys []string
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{
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",
NewValue: "qux",
Expected: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
Old: "bar",
New: func() string {
if computed {
return ""
}
return "qux"
}(),
NewComputed: computed,
},
},
},
},
resourceDiffTestCase{
Name: "basic set diff",
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeSet,
Optional: true,
Computed: true,
Elem: &Schema{Type: TypeString},
Set: HashString,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"foo.#": "1",
"foo.1996459178": "bar",
},
},
Config: testConfig(t, map[string]interface{}{
"foo": []interface{}{"baz"},
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo.1996459178": &terraform.ResourceAttrDiff{
Old: "bar",
New: "",
NewRemoved: true,
},
"foo.2015626392": &terraform.ResourceAttrDiff{
Old: "",
New: "baz",
},
},
},
Key: "foo",
NewValue: []interface{}{"qux"},
Expected: &terraform.InstanceDiff{
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["foo.1996459178"] = &terraform.ResourceAttrDiff{
Old: "bar",
New: "",
NewRemoved: true,
}
}
return result
}(),
},
},
resourceDiffTestCase{
Name: "basic list diff",
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeList,
Optional: true,
Computed: true,
Elem: &Schema{Type: TypeString},
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"foo.#": "1",
"foo.0": "bar",
},
},
Config: testConfig(t, map[string]interface{}{
"foo": []interface{}{"baz"},
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo.0": &terraform.ResourceAttrDiff{
Old: "bar",
New: "baz",
},
},
},
Key: "foo",
NewValue: []interface{}{"qux"},
Expected: &terraform.InstanceDiff{
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: "bar",
New: "qux",
}
}
return result
}(),
},
},
resourceDiffTestCase{
Name: "basic map diff",
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeMap,
Optional: true,
Computed: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"foo.%": "1",
"foo.bar": "baz",
},
},
Config: testConfig(t, map[string]interface{}{
"foo": map[string]interface{}{"bar": "qux"},
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo.bar": &terraform.ResourceAttrDiff{
Old: "baz",
New: "qux",
},
},
},
Key: "foo",
NewValue: map[string]interface{}{"bar": "quux"},
Expected: &terraform.InstanceDiff{
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: "baz",
New: "quux",
}
}
return result
}(),
},
},
resourceDiffTestCase{
Name: "additional diff with primitive",
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeString,
Optional: 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",
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
Old: "bar",
New: "baz",
},
},
},
Key: "one",
NewValue: "four",
Expected: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
Old: "bar",
New: "baz",
},
"one": &terraform.ResourceAttrDiff{
Old: "two",
New: func() string {
if computed {
return ""
}
return "four"
}(),
NewComputed: computed,
},
},
},
},
resourceDiffTestCase{
Name: "additional diff with primitive computed only",
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeString,
Optional: true,
},
"one": &Schema{
Type: TypeString,
Computed: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"foo": "bar",
"one": "two",
},
},
Config: testConfig(t, map[string]interface{}{
"foo": "baz",
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
Old: "bar",
New: "baz",
},
},
},
Key: "one",
NewValue: "three",
Expected: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
Old: "bar",
New: "baz",
},
"one": &terraform.ResourceAttrDiff{
Old: "two",
New: func() string {
if computed {
return ""
}
return "three"
}(),
NewComputed: computed,
},
},
},
},
resourceDiffTestCase{
Name: "complex-ish set diff",
Schema: map[string]*Schema{
"top": &Schema{
Type: TypeSet,
Optional: true,
Computed: true,
Elem: &Resource{
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeInt,
Optional: true,
Computed: true,
},
"bar": &Schema{
Type: TypeInt,
Optional: true,
Computed: true,
},
},
},
Set: testSetFunc,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"top.#": "2",
"top.3.foo": "1",
"top.3.bar": "2",
"top.23.foo": "11",
"top.23.bar": "12",
},
},
Config: testConfig(t, map[string]interface{}{
"top": []interface{}{
map[string]interface{}{
"foo": 1,
"bar": 3,
},
map[string]interface{}{
"foo": 12,
"bar": 12,
},
},
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"top.4.foo": &terraform.ResourceAttrDiff{
Old: "",
New: "1",
},
"top.4.bar": &terraform.ResourceAttrDiff{
Old: "",
New: "3",
},
"top.24.foo": &terraform.ResourceAttrDiff{
Old: "",
New: "12",
},
"top.24.bar": &terraform.ResourceAttrDiff{
Old: "",
New: "12",
},
},
},
Key: "top",
NewValue: NewSet(testSetFunc, []interface{}{
map[string]interface{}{
"foo": 1,
"bar": 4,
},
map[string]interface{}{
"foo": 13,
"bar": 12,
},
map[string]interface{}{
"foo": 21,
"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",
NewValue: "baz",
Expected: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
Old: "bar",
New: func() string {
if computed {
return ""
}
return "baz"
}(),
NewComputed: computed,
},
},
},
},
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",
NewValue: "qux",
ExpectedError: true,
},
resourceDiffTestCase{
Name: "bad 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: "bad",
NewValue: "qux",
ExpectedError: true,
},
helper/schema: ResourceDiff ForceNew attribute correctness A couple of bugs have been discovered in ResourceDiff.ForceNew: * NewRemoved is not preserved when a diff for a key is already present. This is because the second diff that happens after customization performs a second getChange on not just state and config, but also on the pre-existing diff. This results in Exists == true, meaning nil is never returned as a new value. * ForceNew was doing the work of adding the key to the list of changed keys by doing a full SetNew on the existing value. This has a side effect of fetching zero values from what were otherwise undefined values and creating diffs for these values where there should not have been (example: "" => "0"). This update fixes these scenarios by: * Adding a new private function to check the existing diff for NewRemoved keys. This is included in the check on new values in diffChange. * Keys that have been flagged as ForceNew (or parent keys of lists and sets that have been flagged as ForceNew) are now maintained in a separate map. UpdatedKeys now returns the results of both of these maps, but otherwise these keys are ignored by ResourceDiff. * Pursuant the above, values are no longer pushed into the newDiff writer by ForceNew. This prevents the zero value problem, and makes for a cleaner implementation where the provider has to "manually" SetNew to update the appropriate values in the writer. It also prevents non-computed keys from winding up in the diff, which ResourceDiff normally blocks by design. There are also a couple of tests for cases that should never come up right now involving Optional/Computed values and NewRemoved, for which explanations are given in annotations of each test. These are here to guard against future regressions.
2018-04-07 22:06:47 +02:00
resourceDiffTestCase{
// NOTE: This case is technically impossible in the current
// implementation, because optional+computed values never show up in the
// diff, and we actually clear existing diffs when SetNew or
// SetNewComputed is run. This test is here to ensure that if either of
// these behaviors change that we don't introduce regressions.
Name: "NewRemoved in diff for Optional and Computed, should be fully overridden",
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{}{}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
Old: "bar",
New: "",
NewRemoved: true,
},
},
},
Key: "foo",
NewValue: "qux",
Expected: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
Old: "bar",
New: func() string {
if computed {
return ""
}
return "qux"
}(),
NewComputed: computed,
},
},
},
},
helper/schema: Always propagate NewComputed for previously zero value primative type attributes When the following conditions were met: * Schema attribute with a primative type (e.g. Type: TypeString) and Computed: true * Old state of attribute set to zero value for type (e.g. "") * Old state ID of resource set to non-empty (e.g. existing resource) Attempting to use CustomizeDiff with SetNewComputed() would result in the difference previously being discarded. This update ensures that previous zero values or resource existence does not influence the propagation of the computed update. Previously: ``` --- FAIL: TestSetNewComputed (0.00s) --- FAIL: TestSetNewComputed/NewComputed_should_always_propagate (0.00s) resource_diff_test.go:684: Expected (*terraform.InstanceDiff)(0xc00051cea0)({ mu: (sync.Mutex) { state: (int32) 0, sema: (uint32) 0 }, Attributes: (map[string]*terraform.ResourceAttrDiff) (len=1) { (string) (len=3) "foo": (*terraform.ResourceAttrDiff)(0xc0003dcec0)({ Old: (string) "", New: (string) "", NewComputed: (bool) true, NewRemoved: (bool) false, NewExtra: (interface {}) <nil>, RequiresNew: (bool) false, Sensitive: (bool) false, Type: (terraform.DiffAttrType) 0 }) }, Destroy: (bool) false, DestroyDeposed: (bool) false, DestroyTainted: (bool) false, Meta: (map[string]interface {}) <nil> }) , got (*terraform.InstanceDiff)(0xc00051ce80)({ mu: (sync.Mutex) { state: (int32) 0, sema: (uint32) 0 }, Attributes: (map[string]*terraform.ResourceAttrDiff) { }, Destroy: (bool) false, DestroyDeposed: (bool) false, DestroyTainted: (bool) false, Meta: (map[string]interface {}) <nil> }) --- FAIL: TestSchemaMap_Diff (0.01s) --- FAIL: TestSchemaMap_Diff/79-NewComputed_should_always_propagate_with_CustomizeDiff (0.00s) schema_test.go:3289: expected: *terraform.InstanceDiff{mu:sync.Mutex{state:0, sema:0x0}, Attributes:map[string]*terraform.ResourceAttrDiff{"foo":*terraform.ResourceAttrDiff{Old:"", New:"", NewComputed:true, NewRemoved:false, NewExtra:interface {}(nil), RequiresNew:false, Sensitive:false, Type:0x0}}, Destroy:false, DestroyDeposed:false, DestroyTainted:false, Meta:map[string]interface {}(nil)} got: <nil> FAIL FAIL github.com/hashicorp/terraform/helper/schema 0.825s ```
2018-12-05 04:48:30 +01:00
resourceDiffTestCase{
Name: "NewComputed should always propagate",
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeString,
Computed: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"foo": "",
},
ID: "pre-existing",
},
Config: testConfig(t, map[string]interface{}{}),
Diff: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}},
Key: "foo",
NewValue: "",
Expected: &terraform.InstanceDiff{
Attributes: func() map[string]*terraform.ResourceAttrDiff {
if computed {
return map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
NewComputed: computed,
},
}
}
return map[string]*terraform.ResourceAttrDiff{}
}(),
},
},
}
}
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, tc.Config, 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, tc.Config, 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 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",
},
},
},
Key: "foo",
Expected: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
Old: "bar",
New: "baz",
RequiresNew: true,
},
},
},
},
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",
},
},
},
Key: "foo",
Expected: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
Old: "bar",
New: "baz",
RequiresNew: true,
},
},
},
},
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": []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,
},
},
},
},
helper/schema: ResourceDiff ForceNew attribute correctness A couple of bugs have been discovered in ResourceDiff.ForceNew: * NewRemoved is not preserved when a diff for a key is already present. This is because the second diff that happens after customization performs a second getChange on not just state and config, but also on the pre-existing diff. This results in Exists == true, meaning nil is never returned as a new value. * ForceNew was doing the work of adding the key to the list of changed keys by doing a full SetNew on the existing value. This has a side effect of fetching zero values from what were otherwise undefined values and creating diffs for these values where there should not have been (example: "" => "0"). This update fixes these scenarios by: * Adding a new private function to check the existing diff for NewRemoved keys. This is included in the check on new values in diffChange. * Keys that have been flagged as ForceNew (or parent keys of lists and sets that have been flagged as ForceNew) are now maintained in a separate map. UpdatedKeys now returns the results of both of these maps, but otherwise these keys are ignored by ResourceDiff. * Pursuant the above, values are no longer pushed into the newDiff writer by ForceNew. This prevents the zero value problem, and makes for a cleaner implementation where the provider has to "manually" SetNew to update the appropriate values in the writer. It also prevents non-computed keys from winding up in the diff, which ResourceDiff normally blocks by design. There are also a couple of tests for cases that should never come up right now involving Optional/Computed values and NewRemoved, for which explanations are given in annotations of each test. These are here to guard against future regressions.
2018-04-07 22:06:47 +02:00
resourceDiffTestCase{
Name: "preserve NewRemoved on existing diff",
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeString,
Optional: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"foo": "bar",
},
},
Config: testConfig(t, map[string]interface{}{}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
Old: "bar",
New: "",
NewRemoved: true,
},
},
},
Key: "foo",
Expected: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
Old: "bar",
New: "",
RequiresNew: true,
NewRemoved: true,
},
},
},
},
resourceDiffTestCase{
Name: "nested field, preserve original diff without zero values",
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: TypeInt,
Optional: true,
},
},
},
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"foo.#": "1",
"foo.0.bar": "abc",
},
},
Config: testConfig(t, map[string]interface{}{
"foo": []interface{}{
helper/schema: ResourceDiff ForceNew attribute correctness A couple of bugs have been discovered in ResourceDiff.ForceNew: * NewRemoved is not preserved when a diff for a key is already present. This is because the second diff that happens after customization performs a second getChange on not just state and config, but also on the pre-existing diff. This results in Exists == true, meaning nil is never returned as a new value. * ForceNew was doing the work of adding the key to the list of changed keys by doing a full SetNew on the existing value. This has a side effect of fetching zero values from what were otherwise undefined values and creating diffs for these values where there should not have been (example: "" => "0"). This update fixes these scenarios by: * Adding a new private function to check the existing diff for NewRemoved keys. This is included in the check on new values in diffChange. * Keys that have been flagged as ForceNew (or parent keys of lists and sets that have been flagged as ForceNew) are now maintained in a separate map. UpdatedKeys now returns the results of both of these maps, but otherwise these keys are ignored by ResourceDiff. * Pursuant the above, values are no longer pushed into the newDiff writer by ForceNew. This prevents the zero value problem, and makes for a cleaner implementation where the provider has to "manually" SetNew to update the appropriate values in the writer. It also prevents non-computed keys from winding up in the diff, which ResourceDiff normally blocks by design. There are also a couple of tests for cases that should never come up right now involving Optional/Computed values and NewRemoved, for which explanations are given in annotations of each test. These are here to guard against future regressions.
2018-04-07 22:06:47 +02:00
map[string]interface{}{
"bar": "abcdefg",
},
},
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo.0.bar": &terraform.ResourceAttrDiff{
Old: "abc",
New: "abcdefg",
},
},
},
Key: "foo.0.bar",
Expected: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo.0.bar": &terraform.ResourceAttrDiff{
Old: "abc",
New: "abcdefg",
RequiresNew: true,
},
},
},
},
}
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)
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))
}
})
}
}
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",
},
},
},
},
resourceDiffTestCase{
Name: "basic sub-block diff",
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeList,
Optional: true,
Computed: true,
Elem: &Resource{
Schema: map[string]*Schema{
"bar": &Schema{
Type: TypeString,
Optional: true,
Computed: true,
},
"baz": &Schema{
Type: TypeString,
Optional: true,
Computed: true,
},
},
},
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"foo.0.bar": "bar1",
"foo.0.baz": "baz1",
},
},
Config: testConfig(t, map[string]interface{}{
"foo": []interface{}{
map[string]interface{}{
"bar": "bar2",
"baz": "baz1",
},
},
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo.0.bar": &terraform.ResourceAttrDiff{
Old: "bar1",
New: "bar2",
},
},
},
Key: "foo.0.bar",
Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}},
},
resourceDiffTestCase{
Name: "sub-block diff only partial clear",
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeList,
Optional: true,
Computed: true,
Elem: &Resource{
Schema: map[string]*Schema{
"bar": &Schema{
Type: TypeString,
Optional: true,
Computed: true,
},
"baz": &Schema{
Type: TypeString,
Optional: true,
Computed: true,
},
},
},
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"foo.0.bar": "bar1",
"foo.0.baz": "baz1",
},
},
Config: testConfig(t, map[string]interface{}{
"foo": []interface{}{
map[string]interface{}{
"bar": "bar2",
"baz": "baz2",
},
},
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo.0.bar": &terraform.ResourceAttrDiff{
Old: "bar1",
New: "bar2",
},
"foo.0.baz": &terraform.ResourceAttrDiff{
Old: "baz1",
New: "baz2",
},
},
},
Key: "foo.0.bar",
Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{
"foo.0.baz": &terraform.ResourceAttrDiff{
Old: "baz1",
New: "baz2",
},
}},
},
}
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)
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 {
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 TestGetChangedKeysPrefix(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": []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.GetChangedKeysPrefix(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)
}
}
sort.Strings(keys)
if !reflect.DeepEqual(tc.ExpectedKeys, keys) {
t.Fatalf("Expected %s, got %s", spew.Sdump(tc.ExpectedKeys), spew.Sdump(keys))
}
})
}
}
func TestResourceDiffGetOkExists(t *testing.T) {
cases := []struct {
Name string
Schema map[string]*Schema
State *terraform.InstanceState
Config *terraform.ResourceConfig
Diff *terraform.InstanceDiff
Key string
Value interface{}
Ok bool
}{
/*
* Primitives
*/
{
Name: "string-literal-empty",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
},
State: nil,
Config: nil,
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"availability_zone": {
Old: "",
New: "",
},
},
},
Key: "availability_zone",
Value: "",
Ok: true,
},
{
Name: "string-computed-empty",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
},
State: nil,
Config: nil,
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"availability_zone": {
Old: "",
New: "",
NewComputed: true,
},
},
},
Key: "availability_zone",
Value: "",
Ok: false,
},
{
Name: "string-optional-computed-nil-diff",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
},
State: nil,
Config: nil,
Diff: nil,
Key: "availability_zone",
Value: "",
Ok: false,
},
/*
* Lists
*/
{
Name: "list-optional",
Schema: map[string]*Schema{
"ports": {
Type: TypeList,
Optional: true,
Elem: &Schema{Type: TypeInt},
},
},
State: nil,
Config: nil,
Diff: nil,
Key: "ports",
Value: []interface{}{},
Ok: false,
},
/*
* Map
*/
{
Name: "map-optional",
Schema: map[string]*Schema{
"ports": {
Type: TypeMap,
Optional: true,
},
},
State: nil,
Config: nil,
Diff: nil,
Key: "ports",
Value: map[string]interface{}{},
Ok: false,
},
/*
* Set
*/
{
Name: "set-optional",
Schema: map[string]*Schema{
"ports": {
Type: TypeSet,
Optional: true,
Elem: &Schema{Type: TypeInt},
Set: func(a interface{}) int { return a.(int) },
},
},
State: nil,
Config: nil,
Diff: nil,
Key: "ports",
Value: []interface{}{},
Ok: false,
},
{
Name: "set-optional-key",
Schema: map[string]*Schema{
"ports": {
Type: TypeSet,
Optional: true,
Elem: &Schema{Type: TypeInt},
Set: func(a interface{}) int { return a.(int) },
},
},
State: nil,
Config: nil,
Diff: nil,
Key: "ports.0",
Value: 0,
Ok: false,
},
{
Name: "bool-literal-empty",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeBool,
Optional: true,
Computed: true,
ForceNew: true,
},
},
State: nil,
Config: nil,
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"availability_zone": {
Old: "",
New: "",
},
},
},
Key: "availability_zone",
Value: false,
Ok: true,
},
{
Name: "bool-literal-set",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeBool,
Optional: true,
Computed: true,
ForceNew: true,
},
},
State: nil,
Config: nil,
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"availability_zone": {
New: "true",
},
},
},
Key: "availability_zone",
Value: true,
Ok: true,
},
{
Name: "value-in-config",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"availability_zone": "foo",
},
},
Config: testConfig(t, map[string]interface{}{
"availability_zone": "foo",
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{},
},
Key: "availability_zone",
Value: "foo",
Ok: true,
},
{
Name: "new-value-in-config",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
},
},
State: nil,
Config: testConfig(t, map[string]interface{}{
"availability_zone": "foo",
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"availability_zone": {
Old: "",
New: "foo",
},
},
},
Key: "availability_zone",
Value: "foo",
Ok: true,
},
{
Name: "optional-computed-value-in-config",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
Computed: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"availability_zone": "foo",
},
},
Config: testConfig(t, map[string]interface{}{
"availability_zone": "bar",
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"availability_zone": {
Old: "foo",
New: "bar",
},
},
},
Key: "availability_zone",
Value: "bar",
Ok: true,
},
{
Name: "removed-value",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"availability_zone": "foo",
},
},
Config: testConfig(t, map[string]interface{}{}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"availability_zone": {
Old: "foo",
New: "",
NewRemoved: true,
},
},
},
Key: "availability_zone",
Value: "",
Ok: true,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
v, ok := d.GetOkExists(tc.Key)
if s, ok := v.(*Set); ok {
v = s.List()
}
if !reflect.DeepEqual(v, tc.Value) {
t.Fatalf("Bad %s: \n%#v", tc.Name, v)
}
if ok != tc.Ok {
t.Fatalf("%s: expected ok: %t, got: %t", tc.Name, tc.Ok, ok)
}
})
}
}
func TestResourceDiffGetOkExistsSetNew(t *testing.T) {
tc := struct {
Schema map[string]*Schema
State *terraform.InstanceState
Diff *terraform.InstanceDiff
Key string
Value interface{}
Ok bool
}{
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
Computed: true,
},
},
State: nil,
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{},
},
Key: "availability_zone",
Value: "foobar",
Ok: true,
}
d := newResourceDiff(tc.Schema, testConfig(t, map[string]interface{}{}), tc.State, tc.Diff)
d.SetNew(tc.Key, tc.Value)
v, ok := d.GetOkExists(tc.Key)
if s, ok := v.(*Set); ok {
v = s.List()
}
if !reflect.DeepEqual(v, tc.Value) {
t.Fatalf("Bad: \n%#v", v)
}
if ok != tc.Ok {
t.Fatalf("expected ok: %t, got: %t", tc.Ok, ok)
}
}
func TestResourceDiffGetOkExistsSetNewComputed(t *testing.T) {
tc := struct {
Schema map[string]*Schema
State *terraform.InstanceState
Diff *terraform.InstanceDiff
Key string
Value interface{}
Ok bool
}{
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
Computed: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"availability_zone": "foo",
},
},
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{},
},
Key: "availability_zone",
Value: "foobar",
Ok: false,
}
d := newResourceDiff(tc.Schema, testConfig(t, map[string]interface{}{}), tc.State, tc.Diff)
d.SetNewComputed(tc.Key)
_, ok := d.GetOkExists(tc.Key)
if ok != tc.Ok {
t.Fatalf("expected ok: %t, got: %t", tc.Ok, ok)
}
}
func TestResourceDiffNewValueKnown(t *testing.T) {
cases := []struct {
Name string
Schema map[string]*Schema
State *terraform.InstanceState
Config *terraform.ResourceConfig
Diff *terraform.InstanceDiff
Key string
Expected bool
}{
{
Name: "in config, no state",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
},
},
State: nil,
Config: testConfig(t, map[string]interface{}{
"availability_zone": "foo",
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"availability_zone": {
Old: "",
New: "foo",
},
},
},
Key: "availability_zone",
Expected: true,
},
{
Name: "in config, has state, no diff",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"availability_zone": "foo",
},
},
Config: testConfig(t, map[string]interface{}{
"availability_zone": "foo",
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{},
},
Key: "availability_zone",
Expected: true,
},
{
Name: "computed attribute, in state, no diff",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Computed: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"availability_zone": "foo",
},
},
Config: testConfig(t, map[string]interface{}{}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{},
},
Key: "availability_zone",
Expected: true,
},
{
Name: "optional and computed attribute, in state, no config",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
Computed: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"availability_zone": "foo",
},
},
Config: testConfig(t, map[string]interface{}{}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{},
},
Key: "availability_zone",
Expected: true,
},
{
Name: "optional and computed attribute, in state, with config",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
Computed: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"availability_zone": "foo",
},
},
Config: testConfig(t, map[string]interface{}{
"availability_zone": "foo",
}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{},
},
Key: "availability_zone",
Expected: true,
},
{
Name: "computed value, through config reader",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"availability_zone": "foo",
},
},
Config: testConfig(
t,
map[string]interface{}{
"availability_zone": hcl2shim.UnknownVariableValue,
},
),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{},
},
Key: "availability_zone",
Expected: false,
},
{
Name: "computed value, through diff reader",
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"availability_zone": "foo",
},
},
Config: testConfig(
t,
map[string]interface{}{
"availability_zone": hcl2shim.UnknownVariableValue,
},
),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"availability_zone": {
Old: "foo",
New: "",
NewComputed: true,
},
},
},
Key: "availability_zone",
Expected: false,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
actual := d.NewValueKnown(tc.Key)
if tc.Expected != actual {
t.Fatalf("%s: expected ok: %t, got: %t", tc.Name, tc.Expected, actual)
}
})
}
}
func TestResourceDiffNewValueKnownSetNew(t *testing.T) {
tc := struct {
Schema map[string]*Schema
State *terraform.InstanceState
Config *terraform.ResourceConfig
Diff *terraform.InstanceDiff
Key string
Value interface{}
Expected bool
}{
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Optional: true,
Computed: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"availability_zone": "foo",
},
},
Config: testConfig(
t,
map[string]interface{}{
"availability_zone": hcl2shim.UnknownVariableValue,
},
),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"availability_zone": {
Old: "foo",
New: "",
NewComputed: true,
},
},
},
Key: "availability_zone",
Value: "bar",
Expected: true,
}
d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
d.SetNew(tc.Key, tc.Value)
actual := d.NewValueKnown(tc.Key)
if tc.Expected != actual {
t.Fatalf("expected ok: %t, got: %t", tc.Expected, actual)
}
}
func TestResourceDiffNewValueKnownSetNewComputed(t *testing.T) {
tc := struct {
Schema map[string]*Schema
State *terraform.InstanceState
Config *terraform.ResourceConfig
Diff *terraform.InstanceDiff
Key string
Expected bool
}{
Schema: map[string]*Schema{
"availability_zone": {
Type: TypeString,
Computed: true,
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"availability_zone": "foo",
},
},
Config: testConfig(t, map[string]interface{}{}),
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{},
},
Key: "availability_zone",
Expected: false,
}
d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
d.SetNewComputed(tc.Key)
actual := d.NewValueKnown(tc.Key)
if tc.Expected != actual {
t.Fatalf("expected ok: %t, got: %t", tc.Expected, actual)
}
}