github.com/sylr/terraform@v0.11.12-beta1/helper/schema/resource_diff_test.go (about)

     1  package schema
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sort"
     7  	"testing"
     8  
     9  	"github.com/davecgh/go-spew/spew"
    10  	"github.com/hashicorp/hil/ast"
    11  	"github.com/hashicorp/terraform/config"
    12  	"github.com/hashicorp/terraform/terraform"
    13  )
    14  
    15  // testSetFunc is a very simple function we use to test a foo/bar complex set.
    16  // Both "foo" and "bar" are int values.
    17  //
    18  // This is not foolproof as since it performs sums, you can run into
    19  // collisions. Spec tests accordingly. :P
    20  func testSetFunc(v interface{}) int {
    21  	m := v.(map[string]interface{})
    22  	return m["foo"].(int) + m["bar"].(int)
    23  }
    24  
    25  // resourceDiffTestCase provides a test case struct for SetNew and SetDiff.
    26  type resourceDiffTestCase struct {
    27  	Name          string
    28  	Schema        map[string]*Schema
    29  	State         *terraform.InstanceState
    30  	Config        *terraform.ResourceConfig
    31  	Diff          *terraform.InstanceDiff
    32  	Key           string
    33  	OldValue      interface{}
    34  	NewValue      interface{}
    35  	Expected      *terraform.InstanceDiff
    36  	ExpectedKeys  []string
    37  	ExpectedError bool
    38  }
    39  
    40  // testDiffCases produces a list of test cases for use with SetNew and SetDiff.
    41  func testDiffCases(t *testing.T, oldPrefix string, oldOffset int, computed bool) []resourceDiffTestCase {
    42  	return []resourceDiffTestCase{
    43  		resourceDiffTestCase{
    44  			Name: "basic primitive diff",
    45  			Schema: map[string]*Schema{
    46  				"foo": &Schema{
    47  					Type:     TypeString,
    48  					Optional: true,
    49  					Computed: true,
    50  				},
    51  			},
    52  			State: &terraform.InstanceState{
    53  				Attributes: map[string]string{
    54  					"foo": "bar",
    55  				},
    56  			},
    57  			Config: testConfig(t, map[string]interface{}{
    58  				"foo": "baz",
    59  			}),
    60  			Diff: &terraform.InstanceDiff{
    61  				Attributes: map[string]*terraform.ResourceAttrDiff{
    62  					"foo": &terraform.ResourceAttrDiff{
    63  						Old: "bar",
    64  						New: "baz",
    65  					},
    66  				},
    67  			},
    68  			Key:      "foo",
    69  			NewValue: "qux",
    70  			Expected: &terraform.InstanceDiff{
    71  				Attributes: map[string]*terraform.ResourceAttrDiff{
    72  					"foo": &terraform.ResourceAttrDiff{
    73  						Old: "bar",
    74  						New: func() string {
    75  							if computed {
    76  								return ""
    77  							}
    78  							return "qux"
    79  						}(),
    80  						NewComputed: computed,
    81  					},
    82  				},
    83  			},
    84  		},
    85  		resourceDiffTestCase{
    86  			Name: "basic set diff",
    87  			Schema: map[string]*Schema{
    88  				"foo": &Schema{
    89  					Type:     TypeSet,
    90  					Optional: true,
    91  					Computed: true,
    92  					Elem:     &Schema{Type: TypeString},
    93  					Set:      HashString,
    94  				},
    95  			},
    96  			State: &terraform.InstanceState{
    97  				Attributes: map[string]string{
    98  					"foo.#":          "1",
    99  					"foo.1996459178": "bar",
   100  				},
   101  			},
   102  			Config: testConfig(t, map[string]interface{}{
   103  				"foo": []interface{}{"baz"},
   104  			}),
   105  			Diff: &terraform.InstanceDiff{
   106  				Attributes: map[string]*terraform.ResourceAttrDiff{
   107  					"foo.1996459178": &terraform.ResourceAttrDiff{
   108  						Old:        "bar",
   109  						New:        "",
   110  						NewRemoved: true,
   111  					},
   112  					"foo.2015626392": &terraform.ResourceAttrDiff{
   113  						Old: "",
   114  						New: "baz",
   115  					},
   116  				},
   117  			},
   118  			Key:      "foo",
   119  			NewValue: []interface{}{"qux"},
   120  			Expected: &terraform.InstanceDiff{
   121  				Attributes: func() map[string]*terraform.ResourceAttrDiff {
   122  					result := map[string]*terraform.ResourceAttrDiff{}
   123  					if computed {
   124  						result["foo.#"] = &terraform.ResourceAttrDiff{
   125  							Old:         "1",
   126  							New:         "",
   127  							NewComputed: true,
   128  						}
   129  					} else {
   130  						result["foo.2800005064"] = &terraform.ResourceAttrDiff{
   131  							Old: "",
   132  							New: "qux",
   133  						}
   134  						result["foo.1996459178"] = &terraform.ResourceAttrDiff{
   135  							Old:        "bar",
   136  							New:        "",
   137  							NewRemoved: true,
   138  						}
   139  					}
   140  					return result
   141  				}(),
   142  			},
   143  		},
   144  		resourceDiffTestCase{
   145  			Name: "basic list diff",
   146  			Schema: map[string]*Schema{
   147  				"foo": &Schema{
   148  					Type:     TypeList,
   149  					Optional: true,
   150  					Computed: true,
   151  					Elem:     &Schema{Type: TypeString},
   152  				},
   153  			},
   154  			State: &terraform.InstanceState{
   155  				Attributes: map[string]string{
   156  					"foo.#": "1",
   157  					"foo.0": "bar",
   158  				},
   159  			},
   160  			Config: testConfig(t, map[string]interface{}{
   161  				"foo": []interface{}{"baz"},
   162  			}),
   163  			Diff: &terraform.InstanceDiff{
   164  				Attributes: map[string]*terraform.ResourceAttrDiff{
   165  					"foo.0": &terraform.ResourceAttrDiff{
   166  						Old: "bar",
   167  						New: "baz",
   168  					},
   169  				},
   170  			},
   171  			Key:      "foo",
   172  			NewValue: []interface{}{"qux"},
   173  			Expected: &terraform.InstanceDiff{
   174  				Attributes: func() map[string]*terraform.ResourceAttrDiff {
   175  					result := make(map[string]*terraform.ResourceAttrDiff)
   176  					if computed {
   177  						result["foo.#"] = &terraform.ResourceAttrDiff{
   178  							Old:         "1",
   179  							New:         "",
   180  							NewComputed: true,
   181  						}
   182  					} else {
   183  						result["foo.0"] = &terraform.ResourceAttrDiff{
   184  							Old: "bar",
   185  							New: "qux",
   186  						}
   187  					}
   188  					return result
   189  				}(),
   190  			},
   191  		},
   192  		resourceDiffTestCase{
   193  			Name: "basic map diff",
   194  			Schema: map[string]*Schema{
   195  				"foo": &Schema{
   196  					Type:     TypeMap,
   197  					Optional: true,
   198  					Computed: true,
   199  				},
   200  			},
   201  			State: &terraform.InstanceState{
   202  				Attributes: map[string]string{
   203  					"foo.%":   "1",
   204  					"foo.bar": "baz",
   205  				},
   206  			},
   207  			Config: testConfig(t, map[string]interface{}{
   208  				"foo": map[string]interface{}{"bar": "qux"},
   209  			}),
   210  			Diff: &terraform.InstanceDiff{
   211  				Attributes: map[string]*terraform.ResourceAttrDiff{
   212  					"foo.bar": &terraform.ResourceAttrDiff{
   213  						Old: "baz",
   214  						New: "qux",
   215  					},
   216  				},
   217  			},
   218  			Key:      "foo",
   219  			NewValue: map[string]interface{}{"bar": "quux"},
   220  			Expected: &terraform.InstanceDiff{
   221  				Attributes: func() map[string]*terraform.ResourceAttrDiff {
   222  					result := make(map[string]*terraform.ResourceAttrDiff)
   223  					if computed {
   224  						result["foo.%"] = &terraform.ResourceAttrDiff{
   225  							Old:         "",
   226  							New:         "",
   227  							NewComputed: true,
   228  						}
   229  						result["foo.bar"] = &terraform.ResourceAttrDiff{
   230  							Old:        "baz",
   231  							New:        "",
   232  							NewRemoved: true,
   233  						}
   234  					} else {
   235  						result["foo.bar"] = &terraform.ResourceAttrDiff{
   236  							Old: "baz",
   237  							New: "quux",
   238  						}
   239  					}
   240  					return result
   241  				}(),
   242  			},
   243  		},
   244  		resourceDiffTestCase{
   245  			Name: "additional diff with primitive",
   246  			Schema: map[string]*Schema{
   247  				"foo": &Schema{
   248  					Type:     TypeString,
   249  					Optional: true,
   250  				},
   251  				"one": &Schema{
   252  					Type:     TypeString,
   253  					Optional: true,
   254  					Computed: true,
   255  				},
   256  			},
   257  			State: &terraform.InstanceState{
   258  				Attributes: map[string]string{
   259  					"foo": "bar",
   260  					"one": "two",
   261  				},
   262  			},
   263  			Config: testConfig(t, map[string]interface{}{
   264  				"foo": "baz",
   265  			}),
   266  			Diff: &terraform.InstanceDiff{
   267  				Attributes: map[string]*terraform.ResourceAttrDiff{
   268  					"foo": &terraform.ResourceAttrDiff{
   269  						Old: "bar",
   270  						New: "baz",
   271  					},
   272  				},
   273  			},
   274  			Key:      "one",
   275  			NewValue: "four",
   276  			Expected: &terraform.InstanceDiff{
   277  				Attributes: map[string]*terraform.ResourceAttrDiff{
   278  					"foo": &terraform.ResourceAttrDiff{
   279  						Old: "bar",
   280  						New: "baz",
   281  					},
   282  					"one": &terraform.ResourceAttrDiff{
   283  						Old: "two",
   284  						New: func() string {
   285  							if computed {
   286  								return ""
   287  							}
   288  							return "four"
   289  						}(),
   290  						NewComputed: computed,
   291  					},
   292  				},
   293  			},
   294  		},
   295  		resourceDiffTestCase{
   296  			Name: "additional diff with primitive computed only",
   297  			Schema: map[string]*Schema{
   298  				"foo": &Schema{
   299  					Type:     TypeString,
   300  					Optional: true,
   301  				},
   302  				"one": &Schema{
   303  					Type:     TypeString,
   304  					Computed: true,
   305  				},
   306  			},
   307  			State: &terraform.InstanceState{
   308  				Attributes: map[string]string{
   309  					"foo": "bar",
   310  					"one": "two",
   311  				},
   312  			},
   313  			Config: testConfig(t, map[string]interface{}{
   314  				"foo": "baz",
   315  			}),
   316  			Diff: &terraform.InstanceDiff{
   317  				Attributes: map[string]*terraform.ResourceAttrDiff{
   318  					"foo": &terraform.ResourceAttrDiff{
   319  						Old: "bar",
   320  						New: "baz",
   321  					},
   322  				},
   323  			},
   324  			Key:      "one",
   325  			NewValue: "three",
   326  			Expected: &terraform.InstanceDiff{
   327  				Attributes: map[string]*terraform.ResourceAttrDiff{
   328  					"foo": &terraform.ResourceAttrDiff{
   329  						Old: "bar",
   330  						New: "baz",
   331  					},
   332  					"one": &terraform.ResourceAttrDiff{
   333  						Old: "two",
   334  						New: func() string {
   335  							if computed {
   336  								return ""
   337  							}
   338  							return "three"
   339  						}(),
   340  						NewComputed: computed,
   341  					},
   342  				},
   343  			},
   344  		},
   345  		resourceDiffTestCase{
   346  			Name: "complex-ish set diff",
   347  			Schema: map[string]*Schema{
   348  				"top": &Schema{
   349  					Type:     TypeSet,
   350  					Optional: true,
   351  					Computed: true,
   352  					Elem: &Resource{
   353  						Schema: map[string]*Schema{
   354  							"foo": &Schema{
   355  								Type:     TypeInt,
   356  								Optional: true,
   357  								Computed: true,
   358  							},
   359  							"bar": &Schema{
   360  								Type:     TypeInt,
   361  								Optional: true,
   362  								Computed: true,
   363  							},
   364  						},
   365  					},
   366  					Set: testSetFunc,
   367  				},
   368  			},
   369  			State: &terraform.InstanceState{
   370  				Attributes: map[string]string{
   371  					"top.#":      "2",
   372  					"top.3.foo":  "1",
   373  					"top.3.bar":  "2",
   374  					"top.23.foo": "11",
   375  					"top.23.bar": "12",
   376  				},
   377  			},
   378  			Config: testConfig(t, map[string]interface{}{
   379  				"top": []interface{}{
   380  					map[string]interface{}{
   381  						"foo": 1,
   382  						"bar": 3,
   383  					},
   384  					map[string]interface{}{
   385  						"foo": 12,
   386  						"bar": 12,
   387  					},
   388  				},
   389  			}),
   390  			Diff: &terraform.InstanceDiff{
   391  				Attributes: map[string]*terraform.ResourceAttrDiff{
   392  					"top.4.foo": &terraform.ResourceAttrDiff{
   393  						Old: "",
   394  						New: "1",
   395  					},
   396  					"top.4.bar": &terraform.ResourceAttrDiff{
   397  						Old: "",
   398  						New: "3",
   399  					},
   400  					"top.24.foo": &terraform.ResourceAttrDiff{
   401  						Old: "",
   402  						New: "12",
   403  					},
   404  					"top.24.bar": &terraform.ResourceAttrDiff{
   405  						Old: "",
   406  						New: "12",
   407  					},
   408  				},
   409  			},
   410  			Key: "top",
   411  			NewValue: NewSet(testSetFunc, []interface{}{
   412  				map[string]interface{}{
   413  					"foo": 1,
   414  					"bar": 4,
   415  				},
   416  				map[string]interface{}{
   417  					"foo": 13,
   418  					"bar": 12,
   419  				},
   420  				map[string]interface{}{
   421  					"foo": 21,
   422  					"bar": 22,
   423  				},
   424  			}),
   425  			Expected: &terraform.InstanceDiff{
   426  				Attributes: func() map[string]*terraform.ResourceAttrDiff {
   427  					result := make(map[string]*terraform.ResourceAttrDiff)
   428  					if computed {
   429  						result["top.#"] = &terraform.ResourceAttrDiff{
   430  							Old:         "2",
   431  							New:         "",
   432  							NewComputed: true,
   433  						}
   434  					} else {
   435  						result["top.#"] = &terraform.ResourceAttrDiff{
   436  							Old: "2",
   437  							New: "3",
   438  						}
   439  						result["top.5.foo"] = &terraform.ResourceAttrDiff{
   440  							Old: "",
   441  							New: "1",
   442  						}
   443  						result["top.5.bar"] = &terraform.ResourceAttrDiff{
   444  							Old: "",
   445  							New: "4",
   446  						}
   447  						result["top.25.foo"] = &terraform.ResourceAttrDiff{
   448  							Old: "",
   449  							New: "13",
   450  						}
   451  						result["top.25.bar"] = &terraform.ResourceAttrDiff{
   452  							Old: "",
   453  							New: "12",
   454  						}
   455  						result["top.43.foo"] = &terraform.ResourceAttrDiff{
   456  							Old: "",
   457  							New: "21",
   458  						}
   459  						result["top.43.bar"] = &terraform.ResourceAttrDiff{
   460  							Old: "",
   461  							New: "22",
   462  						}
   463  					}
   464  					return result
   465  				}(),
   466  			},
   467  		},
   468  		resourceDiffTestCase{
   469  			Name: "primitive, no diff, no refresh",
   470  			Schema: map[string]*Schema{
   471  				"foo": &Schema{
   472  					Type:     TypeString,
   473  					Computed: true,
   474  				},
   475  			},
   476  			State: &terraform.InstanceState{
   477  				Attributes: map[string]string{
   478  					"foo": "bar",
   479  				},
   480  			},
   481  			Config:   testConfig(t, map[string]interface{}{}),
   482  			Diff:     &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}},
   483  			Key:      "foo",
   484  			NewValue: "baz",
   485  			Expected: &terraform.InstanceDiff{
   486  				Attributes: map[string]*terraform.ResourceAttrDiff{
   487  					"foo": &terraform.ResourceAttrDiff{
   488  						Old: "bar",
   489  						New: func() string {
   490  							if computed {
   491  								return ""
   492  							}
   493  							return "baz"
   494  						}(),
   495  						NewComputed: computed,
   496  					},
   497  				},
   498  			},
   499  		},
   500  		resourceDiffTestCase{
   501  			Name: "non-computed key, should error",
   502  			Schema: map[string]*Schema{
   503  				"foo": &Schema{
   504  					Type:     TypeString,
   505  					Required: true,
   506  				},
   507  			},
   508  			State: &terraform.InstanceState{
   509  				Attributes: map[string]string{
   510  					"foo": "bar",
   511  				},
   512  			},
   513  			Config: testConfig(t, map[string]interface{}{
   514  				"foo": "baz",
   515  			}),
   516  			Diff: &terraform.InstanceDiff{
   517  				Attributes: map[string]*terraform.ResourceAttrDiff{
   518  					"foo": &terraform.ResourceAttrDiff{
   519  						Old: "bar",
   520  						New: "baz",
   521  					},
   522  				},
   523  			},
   524  			Key:           "foo",
   525  			NewValue:      "qux",
   526  			ExpectedError: true,
   527  		},
   528  		resourceDiffTestCase{
   529  			Name: "bad key, should error",
   530  			Schema: map[string]*Schema{
   531  				"foo": &Schema{
   532  					Type:     TypeString,
   533  					Required: true,
   534  				},
   535  			},
   536  			State: &terraform.InstanceState{
   537  				Attributes: map[string]string{
   538  					"foo": "bar",
   539  				},
   540  			},
   541  			Config: testConfig(t, map[string]interface{}{
   542  				"foo": "baz",
   543  			}),
   544  			Diff: &terraform.InstanceDiff{
   545  				Attributes: map[string]*terraform.ResourceAttrDiff{
   546  					"foo": &terraform.ResourceAttrDiff{
   547  						Old: "bar",
   548  						New: "baz",
   549  					},
   550  				},
   551  			},
   552  			Key:           "bad",
   553  			NewValue:      "qux",
   554  			ExpectedError: true,
   555  		},
   556  		resourceDiffTestCase{
   557  			// NOTE: This case is technically impossible in the current
   558  			// implementation, because optional+computed values never show up in the
   559  			// diff, and we actually clear existing diffs when SetNew or
   560  			// SetNewComputed is run.  This test is here to ensure that if either of
   561  			// these behaviors change that we don't introduce regressions.
   562  			Name: "NewRemoved in diff for Optional and Computed, should be fully overridden",
   563  			Schema: map[string]*Schema{
   564  				"foo": &Schema{
   565  					Type:     TypeString,
   566  					Optional: true,
   567  					Computed: true,
   568  				},
   569  			},
   570  			State: &terraform.InstanceState{
   571  				Attributes: map[string]string{
   572  					"foo": "bar",
   573  				},
   574  			},
   575  			Config: testConfig(t, map[string]interface{}{}),
   576  			Diff: &terraform.InstanceDiff{
   577  				Attributes: map[string]*terraform.ResourceAttrDiff{
   578  					"foo": &terraform.ResourceAttrDiff{
   579  						Old:        "bar",
   580  						New:        "",
   581  						NewRemoved: true,
   582  					},
   583  				},
   584  			},
   585  			Key:      "foo",
   586  			NewValue: "qux",
   587  			Expected: &terraform.InstanceDiff{
   588  				Attributes: map[string]*terraform.ResourceAttrDiff{
   589  					"foo": &terraform.ResourceAttrDiff{
   590  						Old: "bar",
   591  						New: func() string {
   592  							if computed {
   593  								return ""
   594  							}
   595  							return "qux"
   596  						}(),
   597  						NewComputed: computed,
   598  					},
   599  				},
   600  			},
   601  		},
   602  	}
   603  }
   604  
   605  func TestSetNew(t *testing.T) {
   606  	testCases := testDiffCases(t, "", 0, false)
   607  	for _, tc := range testCases {
   608  		t.Run(tc.Name, func(t *testing.T) {
   609  			m := schemaMap(tc.Schema)
   610  			d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
   611  			err := d.SetNew(tc.Key, tc.NewValue)
   612  			switch {
   613  			case err != nil && !tc.ExpectedError:
   614  				t.Fatalf("bad: %s", err)
   615  			case err == nil && tc.ExpectedError:
   616  				t.Fatalf("Expected error, got none")
   617  			case err != nil && tc.ExpectedError:
   618  				return
   619  			}
   620  			for _, k := range d.UpdatedKeys() {
   621  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
   622  					t.Fatalf("bad: %s", err)
   623  				}
   624  			}
   625  			if !reflect.DeepEqual(tc.Expected, tc.Diff) {
   626  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
   627  			}
   628  		})
   629  	}
   630  }
   631  
   632  func TestSetNewComputed(t *testing.T) {
   633  	testCases := testDiffCases(t, "", 0, true)
   634  	for _, tc := range testCases {
   635  		t.Run(tc.Name, func(t *testing.T) {
   636  			m := schemaMap(tc.Schema)
   637  			d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
   638  			err := d.SetNewComputed(tc.Key)
   639  			switch {
   640  			case err != nil && !tc.ExpectedError:
   641  				t.Fatalf("bad: %s", err)
   642  			case err == nil && tc.ExpectedError:
   643  				t.Fatalf("Expected error, got none")
   644  			case err != nil && tc.ExpectedError:
   645  				return
   646  			}
   647  			for _, k := range d.UpdatedKeys() {
   648  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
   649  					t.Fatalf("bad: %s", err)
   650  				}
   651  			}
   652  			if !reflect.DeepEqual(tc.Expected, tc.Diff) {
   653  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
   654  			}
   655  		})
   656  	}
   657  }
   658  
   659  func TestForceNew(t *testing.T) {
   660  	cases := []resourceDiffTestCase{
   661  		resourceDiffTestCase{
   662  			Name: "basic primitive diff",
   663  			Schema: map[string]*Schema{
   664  				"foo": &Schema{
   665  					Type:     TypeString,
   666  					Optional: true,
   667  					Computed: true,
   668  				},
   669  			},
   670  			State: &terraform.InstanceState{
   671  				Attributes: map[string]string{
   672  					"foo": "bar",
   673  				},
   674  			},
   675  			Config: testConfig(t, map[string]interface{}{
   676  				"foo": "baz",
   677  			}),
   678  			Diff: &terraform.InstanceDiff{
   679  				Attributes: map[string]*terraform.ResourceAttrDiff{
   680  					"foo": &terraform.ResourceAttrDiff{
   681  						Old: "bar",
   682  						New: "baz",
   683  					},
   684  				},
   685  			},
   686  			Key: "foo",
   687  			Expected: &terraform.InstanceDiff{
   688  				Attributes: map[string]*terraform.ResourceAttrDiff{
   689  					"foo": &terraform.ResourceAttrDiff{
   690  						Old:         "bar",
   691  						New:         "baz",
   692  						RequiresNew: true,
   693  					},
   694  				},
   695  			},
   696  		},
   697  		resourceDiffTestCase{
   698  			Name: "no change, should error",
   699  			Schema: map[string]*Schema{
   700  				"foo": &Schema{
   701  					Type:     TypeString,
   702  					Optional: true,
   703  					Computed: true,
   704  				},
   705  			},
   706  			State: &terraform.InstanceState{
   707  				Attributes: map[string]string{
   708  					"foo": "bar",
   709  				},
   710  			},
   711  			Config: testConfig(t, map[string]interface{}{
   712  				"foo": "bar",
   713  			}),
   714  			ExpectedError: true,
   715  		},
   716  		resourceDiffTestCase{
   717  			Name: "basic primitive, non-computed key",
   718  			Schema: map[string]*Schema{
   719  				"foo": &Schema{
   720  					Type:     TypeString,
   721  					Required: true,
   722  				},
   723  			},
   724  			State: &terraform.InstanceState{
   725  				Attributes: map[string]string{
   726  					"foo": "bar",
   727  				},
   728  			},
   729  			Config: testConfig(t, map[string]interface{}{
   730  				"foo": "baz",
   731  			}),
   732  			Diff: &terraform.InstanceDiff{
   733  				Attributes: map[string]*terraform.ResourceAttrDiff{
   734  					"foo": &terraform.ResourceAttrDiff{
   735  						Old: "bar",
   736  						New: "baz",
   737  					},
   738  				},
   739  			},
   740  			Key: "foo",
   741  			Expected: &terraform.InstanceDiff{
   742  				Attributes: map[string]*terraform.ResourceAttrDiff{
   743  					"foo": &terraform.ResourceAttrDiff{
   744  						Old:         "bar",
   745  						New:         "baz",
   746  						RequiresNew: true,
   747  					},
   748  				},
   749  			},
   750  		},
   751  		resourceDiffTestCase{
   752  			Name: "nested field",
   753  			Schema: map[string]*Schema{
   754  				"foo": &Schema{
   755  					Type:     TypeList,
   756  					Required: true,
   757  					MaxItems: 1,
   758  					Elem: &Resource{
   759  						Schema: map[string]*Schema{
   760  							"bar": {
   761  								Type:     TypeString,
   762  								Optional: true,
   763  							},
   764  							"baz": {
   765  								Type:     TypeString,
   766  								Optional: true,
   767  							},
   768  						},
   769  					},
   770  				},
   771  			},
   772  			State: &terraform.InstanceState{
   773  				Attributes: map[string]string{
   774  					"foo.#":     "1",
   775  					"foo.0.bar": "abc",
   776  					"foo.0.baz": "xyz",
   777  				},
   778  			},
   779  			Config: testConfig(t, map[string]interface{}{
   780  				"foo": []map[string]interface{}{
   781  					map[string]interface{}{
   782  						"bar": "abcdefg",
   783  						"baz": "changed",
   784  					},
   785  				},
   786  			}),
   787  			Diff: &terraform.InstanceDiff{
   788  				Attributes: map[string]*terraform.ResourceAttrDiff{
   789  					"foo.0.bar": &terraform.ResourceAttrDiff{
   790  						Old: "abc",
   791  						New: "abcdefg",
   792  					},
   793  					"foo.0.baz": &terraform.ResourceAttrDiff{
   794  						Old: "xyz",
   795  						New: "changed",
   796  					},
   797  				},
   798  			},
   799  			Key: "foo.0.baz",
   800  			Expected: &terraform.InstanceDiff{
   801  				Attributes: map[string]*terraform.ResourceAttrDiff{
   802  					"foo.0.bar": &terraform.ResourceAttrDiff{
   803  						Old: "abc",
   804  						New: "abcdefg",
   805  					},
   806  					"foo.0.baz": &terraform.ResourceAttrDiff{
   807  						Old:         "xyz",
   808  						New:         "changed",
   809  						RequiresNew: true,
   810  					},
   811  				},
   812  			},
   813  		},
   814  		resourceDiffTestCase{
   815  			Name: "preserve NewRemoved on existing diff",
   816  			Schema: map[string]*Schema{
   817  				"foo": &Schema{
   818  					Type:     TypeString,
   819  					Optional: true,
   820  				},
   821  			},
   822  			State: &terraform.InstanceState{
   823  				Attributes: map[string]string{
   824  					"foo": "bar",
   825  				},
   826  			},
   827  			Config: testConfig(t, map[string]interface{}{}),
   828  			Diff: &terraform.InstanceDiff{
   829  				Attributes: map[string]*terraform.ResourceAttrDiff{
   830  					"foo": &terraform.ResourceAttrDiff{
   831  						Old:        "bar",
   832  						New:        "",
   833  						NewRemoved: true,
   834  					},
   835  				},
   836  			},
   837  			Key: "foo",
   838  			Expected: &terraform.InstanceDiff{
   839  				Attributes: map[string]*terraform.ResourceAttrDiff{
   840  					"foo": &terraform.ResourceAttrDiff{
   841  						Old:         "bar",
   842  						New:         "",
   843  						RequiresNew: true,
   844  						NewRemoved:  true,
   845  					},
   846  				},
   847  			},
   848  		},
   849  		resourceDiffTestCase{
   850  			Name: "nested field, preserve original diff without zero values",
   851  			Schema: map[string]*Schema{
   852  				"foo": &Schema{
   853  					Type:     TypeList,
   854  					Required: true,
   855  					MaxItems: 1,
   856  					Elem: &Resource{
   857  						Schema: map[string]*Schema{
   858  							"bar": {
   859  								Type:     TypeString,
   860  								Optional: true,
   861  							},
   862  							"baz": {
   863  								Type:     TypeInt,
   864  								Optional: true,
   865  							},
   866  						},
   867  					},
   868  				},
   869  			},
   870  			State: &terraform.InstanceState{
   871  				Attributes: map[string]string{
   872  					"foo.#":     "1",
   873  					"foo.0.bar": "abc",
   874  				},
   875  			},
   876  			Config: testConfig(t, map[string]interface{}{
   877  				"foo": []map[string]interface{}{
   878  					map[string]interface{}{
   879  						"bar": "abcdefg",
   880  					},
   881  				},
   882  			}),
   883  			Diff: &terraform.InstanceDiff{
   884  				Attributes: map[string]*terraform.ResourceAttrDiff{
   885  					"foo.0.bar": &terraform.ResourceAttrDiff{
   886  						Old: "abc",
   887  						New: "abcdefg",
   888  					},
   889  				},
   890  			},
   891  			Key: "foo.0.bar",
   892  			Expected: &terraform.InstanceDiff{
   893  				Attributes: map[string]*terraform.ResourceAttrDiff{
   894  					"foo.0.bar": &terraform.ResourceAttrDiff{
   895  						Old:         "abc",
   896  						New:         "abcdefg",
   897  						RequiresNew: true,
   898  					},
   899  				},
   900  			},
   901  		},
   902  	}
   903  	for _, tc := range cases {
   904  		t.Run(tc.Name, func(t *testing.T) {
   905  			m := schemaMap(tc.Schema)
   906  			d := newResourceDiff(m, tc.Config, tc.State, tc.Diff)
   907  			err := d.ForceNew(tc.Key)
   908  			switch {
   909  			case err != nil && !tc.ExpectedError:
   910  				t.Fatalf("bad: %s", err)
   911  			case err == nil && tc.ExpectedError:
   912  				t.Fatalf("Expected error, got none")
   913  			case err != nil && tc.ExpectedError:
   914  				return
   915  			}
   916  			for _, k := range d.UpdatedKeys() {
   917  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
   918  					t.Fatalf("bad: %s", err)
   919  				}
   920  			}
   921  			if !reflect.DeepEqual(tc.Expected, tc.Diff) {
   922  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
   923  			}
   924  		})
   925  	}
   926  }
   927  
   928  func TestClear(t *testing.T) {
   929  	cases := []resourceDiffTestCase{
   930  		resourceDiffTestCase{
   931  			Name: "basic primitive diff",
   932  			Schema: map[string]*Schema{
   933  				"foo": &Schema{
   934  					Type:     TypeString,
   935  					Optional: true,
   936  					Computed: true,
   937  				},
   938  			},
   939  			State: &terraform.InstanceState{
   940  				Attributes: map[string]string{
   941  					"foo": "bar",
   942  				},
   943  			},
   944  			Config: testConfig(t, map[string]interface{}{
   945  				"foo": "baz",
   946  			}),
   947  			Diff: &terraform.InstanceDiff{
   948  				Attributes: map[string]*terraform.ResourceAttrDiff{
   949  					"foo": &terraform.ResourceAttrDiff{
   950  						Old: "bar",
   951  						New: "baz",
   952  					},
   953  				},
   954  			},
   955  			Key:      "foo",
   956  			Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}},
   957  		},
   958  		resourceDiffTestCase{
   959  			Name: "non-computed key, should error",
   960  			Schema: map[string]*Schema{
   961  				"foo": &Schema{
   962  					Type:     TypeString,
   963  					Required: true,
   964  				},
   965  			},
   966  			State: &terraform.InstanceState{
   967  				Attributes: map[string]string{
   968  					"foo": "bar",
   969  				},
   970  			},
   971  			Config: testConfig(t, map[string]interface{}{
   972  				"foo": "baz",
   973  			}),
   974  			Diff: &terraform.InstanceDiff{
   975  				Attributes: map[string]*terraform.ResourceAttrDiff{
   976  					"foo": &terraform.ResourceAttrDiff{
   977  						Old: "bar",
   978  						New: "baz",
   979  					},
   980  				},
   981  			},
   982  			Key:           "foo",
   983  			ExpectedError: true,
   984  		},
   985  		resourceDiffTestCase{
   986  			Name: "multi-value, one removed",
   987  			Schema: map[string]*Schema{
   988  				"foo": &Schema{
   989  					Type:     TypeString,
   990  					Optional: true,
   991  					Computed: true,
   992  				},
   993  				"one": &Schema{
   994  					Type:     TypeString,
   995  					Optional: true,
   996  					Computed: true,
   997  				},
   998  			},
   999  			State: &terraform.InstanceState{
  1000  				Attributes: map[string]string{
  1001  					"foo": "bar",
  1002  					"one": "two",
  1003  				},
  1004  			},
  1005  			Config: testConfig(t, map[string]interface{}{
  1006  				"foo": "baz",
  1007  				"one": "three",
  1008  			}),
  1009  			Diff: &terraform.InstanceDiff{
  1010  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1011  					"foo": &terraform.ResourceAttrDiff{
  1012  						Old: "bar",
  1013  						New: "baz",
  1014  					},
  1015  					"one": &terraform.ResourceAttrDiff{
  1016  						Old: "two",
  1017  						New: "three",
  1018  					},
  1019  				},
  1020  			},
  1021  			Key: "one",
  1022  			Expected: &terraform.InstanceDiff{
  1023  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1024  					"foo": &terraform.ResourceAttrDiff{
  1025  						Old: "bar",
  1026  						New: "baz",
  1027  					},
  1028  				},
  1029  			},
  1030  		},
  1031  		resourceDiffTestCase{
  1032  			Name: "basic sub-block diff",
  1033  			Schema: map[string]*Schema{
  1034  				"foo": &Schema{
  1035  					Type:     TypeList,
  1036  					Optional: true,
  1037  					Computed: true,
  1038  					Elem: &Resource{
  1039  						Schema: map[string]*Schema{
  1040  							"bar": &Schema{
  1041  								Type:     TypeString,
  1042  								Optional: true,
  1043  								Computed: true,
  1044  							},
  1045  							"baz": &Schema{
  1046  								Type:     TypeString,
  1047  								Optional: true,
  1048  								Computed: true,
  1049  							},
  1050  						},
  1051  					},
  1052  				},
  1053  			},
  1054  			State: &terraform.InstanceState{
  1055  				Attributes: map[string]string{
  1056  					"foo.0.bar": "bar1",
  1057  					"foo.0.baz": "baz1",
  1058  				},
  1059  			},
  1060  			Config: testConfig(t, map[string]interface{}{
  1061  				"foo": []map[string]interface{}{
  1062  					map[string]interface{}{
  1063  						"bar": "bar2",
  1064  						"baz": "baz1",
  1065  					},
  1066  				},
  1067  			}),
  1068  			Diff: &terraform.InstanceDiff{
  1069  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1070  					"foo.0.bar": &terraform.ResourceAttrDiff{
  1071  						Old: "bar1",
  1072  						New: "bar2",
  1073  					},
  1074  				},
  1075  			},
  1076  			Key:      "foo.0.bar",
  1077  			Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}},
  1078  		},
  1079  		resourceDiffTestCase{
  1080  			Name: "sub-block diff only partial clear",
  1081  			Schema: map[string]*Schema{
  1082  				"foo": &Schema{
  1083  					Type:     TypeList,
  1084  					Optional: true,
  1085  					Computed: true,
  1086  					Elem: &Resource{
  1087  						Schema: map[string]*Schema{
  1088  							"bar": &Schema{
  1089  								Type:     TypeString,
  1090  								Optional: true,
  1091  								Computed: true,
  1092  							},
  1093  							"baz": &Schema{
  1094  								Type:     TypeString,
  1095  								Optional: true,
  1096  								Computed: true,
  1097  							},
  1098  						},
  1099  					},
  1100  				},
  1101  			},
  1102  			State: &terraform.InstanceState{
  1103  				Attributes: map[string]string{
  1104  					"foo.0.bar": "bar1",
  1105  					"foo.0.baz": "baz1",
  1106  				},
  1107  			},
  1108  			Config: testConfig(t, map[string]interface{}{
  1109  				"foo": []map[string]interface{}{
  1110  					map[string]interface{}{
  1111  						"bar": "bar2",
  1112  						"baz": "baz2",
  1113  					},
  1114  				},
  1115  			}),
  1116  			Diff: &terraform.InstanceDiff{
  1117  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1118  					"foo.0.bar": &terraform.ResourceAttrDiff{
  1119  						Old: "bar1",
  1120  						New: "bar2",
  1121  					},
  1122  					"foo.0.baz": &terraform.ResourceAttrDiff{
  1123  						Old: "baz1",
  1124  						New: "baz2",
  1125  					},
  1126  				},
  1127  			},
  1128  			Key: "foo.0.bar",
  1129  			Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{
  1130  				"foo.0.baz": &terraform.ResourceAttrDiff{
  1131  					Old: "baz1",
  1132  					New: "baz2",
  1133  				},
  1134  			}},
  1135  		},
  1136  	}
  1137  	for _, tc := range cases {
  1138  		t.Run(tc.Name, func(t *testing.T) {
  1139  			m := schemaMap(tc.Schema)
  1140  			d := newResourceDiff(m, tc.Config, tc.State, tc.Diff)
  1141  			err := d.Clear(tc.Key)
  1142  			switch {
  1143  			case err != nil && !tc.ExpectedError:
  1144  				t.Fatalf("bad: %s", err)
  1145  			case err == nil && tc.ExpectedError:
  1146  				t.Fatalf("Expected error, got none")
  1147  			case err != nil && tc.ExpectedError:
  1148  				return
  1149  			}
  1150  			for _, k := range d.UpdatedKeys() {
  1151  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
  1152  					t.Fatalf("bad: %s", err)
  1153  				}
  1154  			}
  1155  			if !reflect.DeepEqual(tc.Expected, tc.Diff) {
  1156  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
  1157  			}
  1158  		})
  1159  	}
  1160  }
  1161  
  1162  func TestGetChangedKeysPrefix(t *testing.T) {
  1163  	cases := []resourceDiffTestCase{
  1164  		resourceDiffTestCase{
  1165  			Name: "basic primitive diff",
  1166  			Schema: map[string]*Schema{
  1167  				"foo": &Schema{
  1168  					Type:     TypeString,
  1169  					Optional: true,
  1170  					Computed: true,
  1171  				},
  1172  			},
  1173  			State: &terraform.InstanceState{
  1174  				Attributes: map[string]string{
  1175  					"foo": "bar",
  1176  				},
  1177  			},
  1178  			Config: testConfig(t, map[string]interface{}{
  1179  				"foo": "baz",
  1180  			}),
  1181  			Diff: &terraform.InstanceDiff{
  1182  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1183  					"foo": &terraform.ResourceAttrDiff{
  1184  						Old: "bar",
  1185  						New: "baz",
  1186  					},
  1187  				},
  1188  			},
  1189  			Key: "foo",
  1190  			ExpectedKeys: []string{
  1191  				"foo",
  1192  			},
  1193  		},
  1194  		resourceDiffTestCase{
  1195  			Name: "nested field filtering",
  1196  			Schema: map[string]*Schema{
  1197  				"testfield": &Schema{
  1198  					Type:     TypeString,
  1199  					Required: true,
  1200  				},
  1201  				"foo": &Schema{
  1202  					Type:     TypeList,
  1203  					Required: true,
  1204  					MaxItems: 1,
  1205  					Elem: &Resource{
  1206  						Schema: map[string]*Schema{
  1207  							"bar": {
  1208  								Type:     TypeString,
  1209  								Optional: true,
  1210  							},
  1211  							"baz": {
  1212  								Type:     TypeString,
  1213  								Optional: true,
  1214  							},
  1215  						},
  1216  					},
  1217  				},
  1218  			},
  1219  			State: &terraform.InstanceState{
  1220  				Attributes: map[string]string{
  1221  					"testfield": "blablah",
  1222  					"foo.#":     "1",
  1223  					"foo.0.bar": "abc",
  1224  					"foo.0.baz": "xyz",
  1225  				},
  1226  			},
  1227  			Config: testConfig(t, map[string]interface{}{
  1228  				"testfield": "modified",
  1229  				"foo": []map[string]interface{}{
  1230  					map[string]interface{}{
  1231  						"bar": "abcdefg",
  1232  						"baz": "changed",
  1233  					},
  1234  				},
  1235  			}),
  1236  			Diff: &terraform.InstanceDiff{
  1237  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1238  					"testfield": &terraform.ResourceAttrDiff{
  1239  						Old: "blablah",
  1240  						New: "modified",
  1241  					},
  1242  					"foo.0.bar": &terraform.ResourceAttrDiff{
  1243  						Old: "abc",
  1244  						New: "abcdefg",
  1245  					},
  1246  					"foo.0.baz": &terraform.ResourceAttrDiff{
  1247  						Old: "xyz",
  1248  						New: "changed",
  1249  					},
  1250  				},
  1251  			},
  1252  			Key: "foo",
  1253  			ExpectedKeys: []string{
  1254  				"foo.0.bar",
  1255  				"foo.0.baz",
  1256  			},
  1257  		},
  1258  	}
  1259  	for _, tc := range cases {
  1260  		t.Run(tc.Name, func(t *testing.T) {
  1261  			m := schemaMap(tc.Schema)
  1262  			d := newResourceDiff(m, tc.Config, tc.State, tc.Diff)
  1263  			keys := d.GetChangedKeysPrefix(tc.Key)
  1264  
  1265  			for _, k := range d.UpdatedKeys() {
  1266  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
  1267  					t.Fatalf("bad: %s", err)
  1268  				}
  1269  			}
  1270  
  1271  			sort.Strings(keys)
  1272  
  1273  			if !reflect.DeepEqual(tc.ExpectedKeys, keys) {
  1274  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.ExpectedKeys), spew.Sdump(keys))
  1275  			}
  1276  		})
  1277  	}
  1278  }
  1279  
  1280  func TestResourceDiffGetOkExists(t *testing.T) {
  1281  	cases := []struct {
  1282  		Name   string
  1283  		Schema map[string]*Schema
  1284  		State  *terraform.InstanceState
  1285  		Config *terraform.ResourceConfig
  1286  		Diff   *terraform.InstanceDiff
  1287  		Key    string
  1288  		Value  interface{}
  1289  		Ok     bool
  1290  	}{
  1291  		/*
  1292  		 * Primitives
  1293  		 */
  1294  		{
  1295  			Name: "string-literal-empty",
  1296  			Schema: map[string]*Schema{
  1297  				"availability_zone": {
  1298  					Type:     TypeString,
  1299  					Optional: true,
  1300  					Computed: true,
  1301  					ForceNew: true,
  1302  				},
  1303  			},
  1304  
  1305  			State:  nil,
  1306  			Config: nil,
  1307  
  1308  			Diff: &terraform.InstanceDiff{
  1309  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1310  					"availability_zone": {
  1311  						Old: "",
  1312  						New: "",
  1313  					},
  1314  				},
  1315  			},
  1316  
  1317  			Key:   "availability_zone",
  1318  			Value: "",
  1319  			Ok:    true,
  1320  		},
  1321  
  1322  		{
  1323  			Name: "string-computed-empty",
  1324  			Schema: map[string]*Schema{
  1325  				"availability_zone": {
  1326  					Type:     TypeString,
  1327  					Optional: true,
  1328  					Computed: true,
  1329  					ForceNew: true,
  1330  				},
  1331  			},
  1332  
  1333  			State:  nil,
  1334  			Config: nil,
  1335  
  1336  			Diff: &terraform.InstanceDiff{
  1337  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1338  					"availability_zone": {
  1339  						Old:         "",
  1340  						New:         "",
  1341  						NewComputed: true,
  1342  					},
  1343  				},
  1344  			},
  1345  
  1346  			Key:   "availability_zone",
  1347  			Value: "",
  1348  			Ok:    false,
  1349  		},
  1350  
  1351  		{
  1352  			Name: "string-optional-computed-nil-diff",
  1353  			Schema: map[string]*Schema{
  1354  				"availability_zone": {
  1355  					Type:     TypeString,
  1356  					Optional: true,
  1357  					Computed: true,
  1358  					ForceNew: true,
  1359  				},
  1360  			},
  1361  
  1362  			State:  nil,
  1363  			Config: nil,
  1364  
  1365  			Diff: nil,
  1366  
  1367  			Key:   "availability_zone",
  1368  			Value: "",
  1369  			Ok:    false,
  1370  		},
  1371  
  1372  		/*
  1373  		 * Lists
  1374  		 */
  1375  
  1376  		{
  1377  			Name: "list-optional",
  1378  			Schema: map[string]*Schema{
  1379  				"ports": {
  1380  					Type:     TypeList,
  1381  					Optional: true,
  1382  					Elem:     &Schema{Type: TypeInt},
  1383  				},
  1384  			},
  1385  
  1386  			State:  nil,
  1387  			Config: nil,
  1388  
  1389  			Diff: nil,
  1390  
  1391  			Key:   "ports",
  1392  			Value: []interface{}{},
  1393  			Ok:    false,
  1394  		},
  1395  
  1396  		/*
  1397  		 * Map
  1398  		 */
  1399  
  1400  		{
  1401  			Name: "map-optional",
  1402  			Schema: map[string]*Schema{
  1403  				"ports": {
  1404  					Type:     TypeMap,
  1405  					Optional: true,
  1406  				},
  1407  			},
  1408  
  1409  			State:  nil,
  1410  			Config: nil,
  1411  
  1412  			Diff: nil,
  1413  
  1414  			Key:   "ports",
  1415  			Value: map[string]interface{}{},
  1416  			Ok:    false,
  1417  		},
  1418  
  1419  		/*
  1420  		 * Set
  1421  		 */
  1422  
  1423  		{
  1424  			Name: "set-optional",
  1425  			Schema: map[string]*Schema{
  1426  				"ports": {
  1427  					Type:     TypeSet,
  1428  					Optional: true,
  1429  					Elem:     &Schema{Type: TypeInt},
  1430  					Set:      func(a interface{}) int { return a.(int) },
  1431  				},
  1432  			},
  1433  
  1434  			State:  nil,
  1435  			Config: nil,
  1436  
  1437  			Diff: nil,
  1438  
  1439  			Key:   "ports",
  1440  			Value: []interface{}{},
  1441  			Ok:    false,
  1442  		},
  1443  
  1444  		{
  1445  			Name: "set-optional-key",
  1446  			Schema: map[string]*Schema{
  1447  				"ports": {
  1448  					Type:     TypeSet,
  1449  					Optional: true,
  1450  					Elem:     &Schema{Type: TypeInt},
  1451  					Set:      func(a interface{}) int { return a.(int) },
  1452  				},
  1453  			},
  1454  
  1455  			State:  nil,
  1456  			Config: nil,
  1457  
  1458  			Diff: nil,
  1459  
  1460  			Key:   "ports.0",
  1461  			Value: 0,
  1462  			Ok:    false,
  1463  		},
  1464  
  1465  		{
  1466  			Name: "bool-literal-empty",
  1467  			Schema: map[string]*Schema{
  1468  				"availability_zone": {
  1469  					Type:     TypeBool,
  1470  					Optional: true,
  1471  					Computed: true,
  1472  					ForceNew: true,
  1473  				},
  1474  			},
  1475  
  1476  			State:  nil,
  1477  			Config: nil,
  1478  			Diff: &terraform.InstanceDiff{
  1479  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1480  					"availability_zone": {
  1481  						Old: "",
  1482  						New: "",
  1483  					},
  1484  				},
  1485  			},
  1486  
  1487  			Key:   "availability_zone",
  1488  			Value: false,
  1489  			Ok:    true,
  1490  		},
  1491  
  1492  		{
  1493  			Name: "bool-literal-set",
  1494  			Schema: map[string]*Schema{
  1495  				"availability_zone": {
  1496  					Type:     TypeBool,
  1497  					Optional: true,
  1498  					Computed: true,
  1499  					ForceNew: true,
  1500  				},
  1501  			},
  1502  
  1503  			State:  nil,
  1504  			Config: nil,
  1505  
  1506  			Diff: &terraform.InstanceDiff{
  1507  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1508  					"availability_zone": {
  1509  						New: "true",
  1510  					},
  1511  				},
  1512  			},
  1513  
  1514  			Key:   "availability_zone",
  1515  			Value: true,
  1516  			Ok:    true,
  1517  		},
  1518  		{
  1519  			Name: "value-in-config",
  1520  			Schema: map[string]*Schema{
  1521  				"availability_zone": {
  1522  					Type:     TypeString,
  1523  					Optional: true,
  1524  				},
  1525  			},
  1526  
  1527  			State: &terraform.InstanceState{
  1528  				Attributes: map[string]string{
  1529  					"availability_zone": "foo",
  1530  				},
  1531  			},
  1532  			Config: testConfig(t, map[string]interface{}{
  1533  				"availability_zone": "foo",
  1534  			}),
  1535  
  1536  			Diff: &terraform.InstanceDiff{
  1537  				Attributes: map[string]*terraform.ResourceAttrDiff{},
  1538  			},
  1539  
  1540  			Key:   "availability_zone",
  1541  			Value: "foo",
  1542  			Ok:    true,
  1543  		},
  1544  		{
  1545  			Name: "new-value-in-config",
  1546  			Schema: map[string]*Schema{
  1547  				"availability_zone": {
  1548  					Type:     TypeString,
  1549  					Optional: true,
  1550  				},
  1551  			},
  1552  
  1553  			State: nil,
  1554  			Config: testConfig(t, map[string]interface{}{
  1555  				"availability_zone": "foo",
  1556  			}),
  1557  
  1558  			Diff: &terraform.InstanceDiff{
  1559  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1560  					"availability_zone": {
  1561  						Old: "",
  1562  						New: "foo",
  1563  					},
  1564  				},
  1565  			},
  1566  
  1567  			Key:   "availability_zone",
  1568  			Value: "foo",
  1569  			Ok:    true,
  1570  		},
  1571  		{
  1572  			Name: "optional-computed-value-in-config",
  1573  			Schema: map[string]*Schema{
  1574  				"availability_zone": {
  1575  					Type:     TypeString,
  1576  					Optional: true,
  1577  					Computed: true,
  1578  				},
  1579  			},
  1580  
  1581  			State: &terraform.InstanceState{
  1582  				Attributes: map[string]string{
  1583  					"availability_zone": "foo",
  1584  				},
  1585  			},
  1586  			Config: testConfig(t, map[string]interface{}{
  1587  				"availability_zone": "bar",
  1588  			}),
  1589  
  1590  			Diff: &terraform.InstanceDiff{
  1591  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1592  					"availability_zone": {
  1593  						Old: "foo",
  1594  						New: "bar",
  1595  					},
  1596  				},
  1597  			},
  1598  
  1599  			Key:   "availability_zone",
  1600  			Value: "bar",
  1601  			Ok:    true,
  1602  		},
  1603  		{
  1604  			Name: "removed-value",
  1605  			Schema: map[string]*Schema{
  1606  				"availability_zone": {
  1607  					Type:     TypeString,
  1608  					Optional: true,
  1609  				},
  1610  			},
  1611  
  1612  			State: &terraform.InstanceState{
  1613  				Attributes: map[string]string{
  1614  					"availability_zone": "foo",
  1615  				},
  1616  			},
  1617  			Config: testConfig(t, map[string]interface{}{}),
  1618  
  1619  			Diff: &terraform.InstanceDiff{
  1620  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1621  					"availability_zone": {
  1622  						Old:        "foo",
  1623  						New:        "",
  1624  						NewRemoved: true,
  1625  					},
  1626  				},
  1627  			},
  1628  
  1629  			Key:   "availability_zone",
  1630  			Value: "",
  1631  			Ok:    true,
  1632  		},
  1633  	}
  1634  
  1635  	for i, tc := range cases {
  1636  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
  1637  			d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
  1638  
  1639  			v, ok := d.GetOkExists(tc.Key)
  1640  			if s, ok := v.(*Set); ok {
  1641  				v = s.List()
  1642  			}
  1643  
  1644  			if !reflect.DeepEqual(v, tc.Value) {
  1645  				t.Fatalf("Bad %s: \n%#v", tc.Name, v)
  1646  			}
  1647  			if ok != tc.Ok {
  1648  				t.Fatalf("%s: expected ok: %t, got: %t", tc.Name, tc.Ok, ok)
  1649  			}
  1650  		})
  1651  	}
  1652  }
  1653  
  1654  func TestResourceDiffGetOkExistsSetNew(t *testing.T) {
  1655  	tc := struct {
  1656  		Schema map[string]*Schema
  1657  		State  *terraform.InstanceState
  1658  		Diff   *terraform.InstanceDiff
  1659  		Key    string
  1660  		Value  interface{}
  1661  		Ok     bool
  1662  	}{
  1663  		Schema: map[string]*Schema{
  1664  			"availability_zone": {
  1665  				Type:     TypeString,
  1666  				Optional: true,
  1667  				Computed: true,
  1668  			},
  1669  		},
  1670  
  1671  		State: nil,
  1672  
  1673  		Diff: &terraform.InstanceDiff{
  1674  			Attributes: map[string]*terraform.ResourceAttrDiff{},
  1675  		},
  1676  
  1677  		Key:   "availability_zone",
  1678  		Value: "foobar",
  1679  		Ok:    true,
  1680  	}
  1681  
  1682  	d := newResourceDiff(tc.Schema, testConfig(t, map[string]interface{}{}), tc.State, tc.Diff)
  1683  	d.SetNew(tc.Key, tc.Value)
  1684  
  1685  	v, ok := d.GetOkExists(tc.Key)
  1686  	if s, ok := v.(*Set); ok {
  1687  		v = s.List()
  1688  	}
  1689  
  1690  	if !reflect.DeepEqual(v, tc.Value) {
  1691  		t.Fatalf("Bad: \n%#v", v)
  1692  	}
  1693  	if ok != tc.Ok {
  1694  		t.Fatalf("expected ok: %t, got: %t", tc.Ok, ok)
  1695  	}
  1696  }
  1697  
  1698  func TestResourceDiffGetOkExistsSetNewComputed(t *testing.T) {
  1699  	tc := struct {
  1700  		Schema map[string]*Schema
  1701  		State  *terraform.InstanceState
  1702  		Diff   *terraform.InstanceDiff
  1703  		Key    string
  1704  		Value  interface{}
  1705  		Ok     bool
  1706  	}{
  1707  		Schema: map[string]*Schema{
  1708  			"availability_zone": {
  1709  				Type:     TypeString,
  1710  				Optional: true,
  1711  				Computed: true,
  1712  			},
  1713  		},
  1714  
  1715  		State: &terraform.InstanceState{
  1716  			Attributes: map[string]string{
  1717  				"availability_zone": "foo",
  1718  			},
  1719  		},
  1720  
  1721  		Diff: &terraform.InstanceDiff{
  1722  			Attributes: map[string]*terraform.ResourceAttrDiff{},
  1723  		},
  1724  
  1725  		Key:   "availability_zone",
  1726  		Value: "foobar",
  1727  		Ok:    false,
  1728  	}
  1729  
  1730  	d := newResourceDiff(tc.Schema, testConfig(t, map[string]interface{}{}), tc.State, tc.Diff)
  1731  	d.SetNewComputed(tc.Key)
  1732  
  1733  	_, ok := d.GetOkExists(tc.Key)
  1734  
  1735  	if ok != tc.Ok {
  1736  		t.Fatalf("expected ok: %t, got: %t", tc.Ok, ok)
  1737  	}
  1738  }
  1739  
  1740  func TestResourceDiffNewValueKnown(t *testing.T) {
  1741  	cases := []struct {
  1742  		Name     string
  1743  		Schema   map[string]*Schema
  1744  		State    *terraform.InstanceState
  1745  		Config   *terraform.ResourceConfig
  1746  		Diff     *terraform.InstanceDiff
  1747  		Key      string
  1748  		Expected bool
  1749  	}{
  1750  		{
  1751  			Name: "in config, no state",
  1752  			Schema: map[string]*Schema{
  1753  				"availability_zone": {
  1754  					Type:     TypeString,
  1755  					Optional: true,
  1756  				},
  1757  			},
  1758  			State: nil,
  1759  			Config: testConfig(t, map[string]interface{}{
  1760  				"availability_zone": "foo",
  1761  			}),
  1762  			Diff: &terraform.InstanceDiff{
  1763  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1764  					"availability_zone": {
  1765  						Old: "",
  1766  						New: "foo",
  1767  					},
  1768  				},
  1769  			},
  1770  			Key:      "availability_zone",
  1771  			Expected: true,
  1772  		},
  1773  		{
  1774  			Name: "in config, has state, no diff",
  1775  			Schema: map[string]*Schema{
  1776  				"availability_zone": {
  1777  					Type:     TypeString,
  1778  					Optional: true,
  1779  				},
  1780  			},
  1781  			State: &terraform.InstanceState{
  1782  				Attributes: map[string]string{
  1783  					"availability_zone": "foo",
  1784  				},
  1785  			},
  1786  			Config: testConfig(t, map[string]interface{}{
  1787  				"availability_zone": "foo",
  1788  			}),
  1789  			Diff: &terraform.InstanceDiff{
  1790  				Attributes: map[string]*terraform.ResourceAttrDiff{},
  1791  			},
  1792  			Key:      "availability_zone",
  1793  			Expected: true,
  1794  		},
  1795  		{
  1796  			Name: "computed attribute, in state, no diff",
  1797  			Schema: map[string]*Schema{
  1798  				"availability_zone": {
  1799  					Type:     TypeString,
  1800  					Computed: true,
  1801  				},
  1802  			},
  1803  			State: &terraform.InstanceState{
  1804  				Attributes: map[string]string{
  1805  					"availability_zone": "foo",
  1806  				},
  1807  			},
  1808  			Config: testConfig(t, map[string]interface{}{}),
  1809  			Diff: &terraform.InstanceDiff{
  1810  				Attributes: map[string]*terraform.ResourceAttrDiff{},
  1811  			},
  1812  			Key:      "availability_zone",
  1813  			Expected: true,
  1814  		},
  1815  		{
  1816  			Name: "optional and computed attribute, in state, no config",
  1817  			Schema: map[string]*Schema{
  1818  				"availability_zone": {
  1819  					Type:     TypeString,
  1820  					Optional: true,
  1821  					Computed: true,
  1822  				},
  1823  			},
  1824  			State: &terraform.InstanceState{
  1825  				Attributes: map[string]string{
  1826  					"availability_zone": "foo",
  1827  				},
  1828  			},
  1829  			Config: testConfig(t, map[string]interface{}{}),
  1830  			Diff: &terraform.InstanceDiff{
  1831  				Attributes: map[string]*terraform.ResourceAttrDiff{},
  1832  			},
  1833  			Key:      "availability_zone",
  1834  			Expected: true,
  1835  		},
  1836  		{
  1837  			Name: "optional and computed attribute, in state, with config",
  1838  			Schema: map[string]*Schema{
  1839  				"availability_zone": {
  1840  					Type:     TypeString,
  1841  					Optional: true,
  1842  					Computed: true,
  1843  				},
  1844  			},
  1845  			State: &terraform.InstanceState{
  1846  				Attributes: map[string]string{
  1847  					"availability_zone": "foo",
  1848  				},
  1849  			},
  1850  			Config: testConfig(t, map[string]interface{}{
  1851  				"availability_zone": "foo",
  1852  			}),
  1853  			Diff: &terraform.InstanceDiff{
  1854  				Attributes: map[string]*terraform.ResourceAttrDiff{},
  1855  			},
  1856  			Key:      "availability_zone",
  1857  			Expected: true,
  1858  		},
  1859  		{
  1860  			Name: "computed value, through config reader",
  1861  			Schema: map[string]*Schema{
  1862  				"availability_zone": {
  1863  					Type:     TypeString,
  1864  					Optional: true,
  1865  				},
  1866  			},
  1867  			State: &terraform.InstanceState{
  1868  				Attributes: map[string]string{
  1869  					"availability_zone": "foo",
  1870  				},
  1871  			},
  1872  			Config: testConfigInterpolate(
  1873  				t,
  1874  				map[string]interface{}{
  1875  					"availability_zone": "${var.foo}",
  1876  				},
  1877  				map[string]ast.Variable{
  1878  					"var.foo": ast.Variable{
  1879  						Value: config.UnknownVariableValue,
  1880  						Type:  ast.TypeString,
  1881  					},
  1882  				},
  1883  			),
  1884  			Diff: &terraform.InstanceDiff{
  1885  				Attributes: map[string]*terraform.ResourceAttrDiff{},
  1886  			},
  1887  			Key:      "availability_zone",
  1888  			Expected: false,
  1889  		},
  1890  		{
  1891  			Name: "computed value, through diff reader",
  1892  			Schema: map[string]*Schema{
  1893  				"availability_zone": {
  1894  					Type:     TypeString,
  1895  					Optional: true,
  1896  				},
  1897  			},
  1898  			State: &terraform.InstanceState{
  1899  				Attributes: map[string]string{
  1900  					"availability_zone": "foo",
  1901  				},
  1902  			},
  1903  			Config: testConfigInterpolate(
  1904  				t,
  1905  				map[string]interface{}{
  1906  					"availability_zone": "${var.foo}",
  1907  				},
  1908  				map[string]ast.Variable{
  1909  					"var.foo": ast.Variable{
  1910  						Value: config.UnknownVariableValue,
  1911  						Type:  ast.TypeString,
  1912  					},
  1913  				},
  1914  			),
  1915  			Diff: &terraform.InstanceDiff{
  1916  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1917  					"availability_zone": {
  1918  						Old:         "foo",
  1919  						New:         "",
  1920  						NewComputed: true,
  1921  					},
  1922  				},
  1923  			},
  1924  			Key:      "availability_zone",
  1925  			Expected: false,
  1926  		},
  1927  	}
  1928  
  1929  	for i, tc := range cases {
  1930  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
  1931  			d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
  1932  
  1933  			actual := d.NewValueKnown(tc.Key)
  1934  			if tc.Expected != actual {
  1935  				t.Fatalf("%s: expected ok: %t, got: %t", tc.Name, tc.Expected, actual)
  1936  			}
  1937  		})
  1938  	}
  1939  }
  1940  
  1941  func TestResourceDiffNewValueKnownSetNew(t *testing.T) {
  1942  	tc := struct {
  1943  		Schema   map[string]*Schema
  1944  		State    *terraform.InstanceState
  1945  		Config   *terraform.ResourceConfig
  1946  		Diff     *terraform.InstanceDiff
  1947  		Key      string
  1948  		Value    interface{}
  1949  		Expected bool
  1950  	}{
  1951  		Schema: map[string]*Schema{
  1952  			"availability_zone": {
  1953  				Type:     TypeString,
  1954  				Optional: true,
  1955  				Computed: true,
  1956  			},
  1957  		},
  1958  		State: &terraform.InstanceState{
  1959  			Attributes: map[string]string{
  1960  				"availability_zone": "foo",
  1961  			},
  1962  		},
  1963  		Config: testConfigInterpolate(
  1964  			t,
  1965  			map[string]interface{}{
  1966  				"availability_zone": "${var.foo}",
  1967  			},
  1968  			map[string]ast.Variable{
  1969  				"var.foo": ast.Variable{
  1970  					Value: config.UnknownVariableValue,
  1971  					Type:  ast.TypeString,
  1972  				},
  1973  			},
  1974  		),
  1975  		Diff: &terraform.InstanceDiff{
  1976  			Attributes: map[string]*terraform.ResourceAttrDiff{
  1977  				"availability_zone": {
  1978  					Old:         "foo",
  1979  					New:         "",
  1980  					NewComputed: true,
  1981  				},
  1982  			},
  1983  		},
  1984  		Key:      "availability_zone",
  1985  		Value:    "bar",
  1986  		Expected: true,
  1987  	}
  1988  
  1989  	d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
  1990  	d.SetNew(tc.Key, tc.Value)
  1991  
  1992  	actual := d.NewValueKnown(tc.Key)
  1993  	if tc.Expected != actual {
  1994  		t.Fatalf("expected ok: %t, got: %t", tc.Expected, actual)
  1995  	}
  1996  }
  1997  
  1998  func TestResourceDiffNewValueKnownSetNewComputed(t *testing.T) {
  1999  	tc := struct {
  2000  		Schema   map[string]*Schema
  2001  		State    *terraform.InstanceState
  2002  		Config   *terraform.ResourceConfig
  2003  		Diff     *terraform.InstanceDiff
  2004  		Key      string
  2005  		Expected bool
  2006  	}{
  2007  		Schema: map[string]*Schema{
  2008  			"availability_zone": {
  2009  				Type:     TypeString,
  2010  				Computed: true,
  2011  			},
  2012  		},
  2013  		State: &terraform.InstanceState{
  2014  			Attributes: map[string]string{
  2015  				"availability_zone": "foo",
  2016  			},
  2017  		},
  2018  		Config: testConfig(t, map[string]interface{}{}),
  2019  		Diff: &terraform.InstanceDiff{
  2020  			Attributes: map[string]*terraform.ResourceAttrDiff{},
  2021  		},
  2022  		Key:      "availability_zone",
  2023  		Expected: false,
  2024  	}
  2025  
  2026  	d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
  2027  	d.SetNewComputed(tc.Key)
  2028  
  2029  	actual := d.NewValueKnown(tc.Key)
  2030  	if tc.Expected != actual {
  2031  		t.Fatalf("expected ok: %t, got: %t", tc.Expected, actual)
  2032  	}
  2033  }