github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/resource_diff_test.go (about)

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