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

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package schema
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"fmt"
    10  	"reflect"
    11  	"strconv"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/google/go-cmp/cmp"
    16  	"github.com/google/go-cmp/cmp/cmpopts"
    17  	"github.com/terramate-io/tf/configs/configschema"
    18  	"github.com/terramate-io/tf/configs/hcl2shim"
    19  	"github.com/terramate-io/tf/legacy/helper/hashcode"
    20  	"github.com/terramate-io/tf/legacy/terraform"
    21  	"github.com/terramate-io/tf/providers"
    22  	"github.com/terramate-io/tf/tfdiags"
    23  	"github.com/zclconf/go-cty/cty"
    24  )
    25  
    26  var (
    27  	typeComparer  = cmp.Comparer(cty.Type.Equals)
    28  	valueComparer = cmp.Comparer(cty.Value.RawEquals)
    29  	equateEmpty   = cmpopts.EquateEmpty()
    30  )
    31  
    32  func testApplyDiff(t *testing.T,
    33  	resource *Resource,
    34  	state, expected *terraform.InstanceState,
    35  	diff *terraform.InstanceDiff) {
    36  
    37  	testSchema := providers.Schema{
    38  		Version: int64(resource.SchemaVersion),
    39  		Block:   resourceSchemaToBlock(resource.Schema),
    40  	}
    41  
    42  	stateVal, err := StateValueFromInstanceState(state, testSchema.Block.ImpliedType())
    43  	if err != nil {
    44  		t.Fatal(err)
    45  	}
    46  
    47  	newState, err := ApplyDiff(stateVal, diff, testSchema.Block)
    48  	if err != nil {
    49  		t.Fatal(err)
    50  	}
    51  
    52  	// verify that "id" is correct
    53  	id := newState.AsValueMap()["id"]
    54  
    55  	switch {
    56  	case diff.Destroy || diff.DestroyDeposed || diff.DestroyTainted:
    57  		// there should be no id
    58  		if !id.IsNull() {
    59  			t.Fatalf("destroyed instance should have no id: %#v", id)
    60  		}
    61  	default:
    62  		// the "id" field always exists and is computed, so it must have a
    63  		// valid value or be unknown.
    64  		if id.IsNull() {
    65  			t.Fatal("new instance state cannot have a null id")
    66  		}
    67  
    68  		if id.IsKnown() && id.AsString() == "" {
    69  			t.Fatal("new instance id cannot be an empty string")
    70  		}
    71  	}
    72  
    73  	// Resource.Meta will be hanlded separately, so it's OK that we lose the
    74  	// timeout values here.
    75  	expectedState, err := StateValueFromInstanceState(expected, testSchema.Block.ImpliedType())
    76  	if err != nil {
    77  		t.Fatal(err)
    78  	}
    79  
    80  	if !cmp.Equal(expectedState, newState, equateEmpty, typeComparer, valueComparer) {
    81  		t.Fatalf(cmp.Diff(expectedState, newState, equateEmpty, typeComparer, valueComparer))
    82  	}
    83  }
    84  
    85  func TestShimResourcePlan_destroyCreate(t *testing.T) {
    86  	r := &Resource{
    87  		SchemaVersion: 2,
    88  		Schema: map[string]*Schema{
    89  			"foo": &Schema{
    90  				Type:     TypeInt,
    91  				Optional: true,
    92  				ForceNew: true,
    93  			},
    94  		},
    95  	}
    96  
    97  	d := &terraform.InstanceDiff{
    98  		Attributes: map[string]*terraform.ResourceAttrDiff{
    99  			"foo": &terraform.ResourceAttrDiff{
   100  				RequiresNew: true,
   101  				Old:         "3",
   102  				New:         "42",
   103  			},
   104  		},
   105  	}
   106  
   107  	state := &terraform.InstanceState{
   108  		Attributes: map[string]string{"foo": "3"},
   109  	}
   110  
   111  	expected := &terraform.InstanceState{
   112  		ID: hcl2shim.UnknownVariableValue,
   113  		Attributes: map[string]string{
   114  			"id":  hcl2shim.UnknownVariableValue,
   115  			"foo": "42",
   116  		},
   117  		Meta: map[string]interface{}{
   118  			"schema_version": "2",
   119  		},
   120  	}
   121  
   122  	testApplyDiff(t, r, state, expected, d)
   123  }
   124  
   125  func TestShimResourceApply_create(t *testing.T) {
   126  	r := &Resource{
   127  		SchemaVersion: 2,
   128  		Schema: map[string]*Schema{
   129  			"foo": &Schema{
   130  				Type:     TypeInt,
   131  				Optional: true,
   132  			},
   133  		},
   134  	}
   135  
   136  	called := false
   137  	r.Create = func(d *ResourceData, m interface{}) error {
   138  		called = true
   139  		d.SetId("foo")
   140  		return nil
   141  	}
   142  
   143  	var s *terraform.InstanceState = nil
   144  
   145  	d := &terraform.InstanceDiff{
   146  		Attributes: map[string]*terraform.ResourceAttrDiff{
   147  			"foo": &terraform.ResourceAttrDiff{
   148  				New: "42",
   149  			},
   150  		},
   151  	}
   152  
   153  	actual, err := r.Apply(s, d, nil)
   154  	if err != nil {
   155  		t.Fatalf("err: %s", err)
   156  	}
   157  
   158  	if !called {
   159  		t.Fatal("not called")
   160  	}
   161  
   162  	expected := &terraform.InstanceState{
   163  		ID: "foo",
   164  		Attributes: map[string]string{
   165  			"id":  "foo",
   166  			"foo": "42",
   167  		},
   168  		Meta: map[string]interface{}{
   169  			"schema_version": "2",
   170  		},
   171  	}
   172  
   173  	if !reflect.DeepEqual(actual, expected) {
   174  		t.Fatalf("bad: %#v", actual)
   175  	}
   176  
   177  	// Shim
   178  	// now that we have our diff and desired state, see if we can reproduce
   179  	// that with the shim
   180  	// we're not testing Resource.Create, so we need to start with the "created" state
   181  	createdState := &terraform.InstanceState{
   182  		ID:         "foo",
   183  		Attributes: map[string]string{"id": "foo"},
   184  	}
   185  
   186  	testApplyDiff(t, r, createdState, expected, d)
   187  }
   188  
   189  func TestShimResourceApply_Timeout_state(t *testing.T) {
   190  	r := &Resource{
   191  		SchemaVersion: 2,
   192  		Schema: map[string]*Schema{
   193  			"foo": &Schema{
   194  				Type:     TypeInt,
   195  				Optional: true,
   196  			},
   197  		},
   198  		Timeouts: &ResourceTimeout{
   199  			Create: DefaultTimeout(40 * time.Minute),
   200  			Update: DefaultTimeout(80 * time.Minute),
   201  			Delete: DefaultTimeout(40 * time.Minute),
   202  		},
   203  	}
   204  
   205  	called := false
   206  	r.Create = func(d *ResourceData, m interface{}) error {
   207  		called = true
   208  		d.SetId("foo")
   209  		return nil
   210  	}
   211  
   212  	var s *terraform.InstanceState = nil
   213  
   214  	d := &terraform.InstanceDiff{
   215  		Attributes: map[string]*terraform.ResourceAttrDiff{
   216  			"foo": &terraform.ResourceAttrDiff{
   217  				New: "42",
   218  			},
   219  		},
   220  	}
   221  
   222  	diffTimeout := &ResourceTimeout{
   223  		Create: DefaultTimeout(40 * time.Minute),
   224  		Update: DefaultTimeout(80 * time.Minute),
   225  		Delete: DefaultTimeout(40 * time.Minute),
   226  	}
   227  
   228  	if err := diffTimeout.DiffEncode(d); err != nil {
   229  		t.Fatalf("Error encoding timeout to diff: %s", err)
   230  	}
   231  
   232  	actual, err := r.Apply(s, d, nil)
   233  	if err != nil {
   234  		t.Fatalf("err: %s", err)
   235  	}
   236  
   237  	if !called {
   238  		t.Fatal("not called")
   239  	}
   240  
   241  	expected := &terraform.InstanceState{
   242  		ID: "foo",
   243  		Attributes: map[string]string{
   244  			"id":  "foo",
   245  			"foo": "42",
   246  		},
   247  		Meta: map[string]interface{}{
   248  			"schema_version": "2",
   249  			TimeoutKey:       expectedForValues(40, 0, 80, 40, 0),
   250  		},
   251  	}
   252  
   253  	if !reflect.DeepEqual(actual, expected) {
   254  		t.Fatalf("Not equal in Timeout State:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
   255  	}
   256  
   257  	// Shim
   258  	// we're not testing Resource.Create, so we need to start with the "created" state
   259  	createdState := &terraform.InstanceState{
   260  		ID:         "foo",
   261  		Attributes: map[string]string{"id": "foo"},
   262  	}
   263  
   264  	testApplyDiff(t, r, createdState, expected, d)
   265  }
   266  
   267  func TestShimResourceDiff_Timeout_diff(t *testing.T) {
   268  	r := &Resource{
   269  		Schema: map[string]*Schema{
   270  			"foo": &Schema{
   271  				Type:     TypeInt,
   272  				Optional: true,
   273  			},
   274  		},
   275  		Timeouts: &ResourceTimeout{
   276  			Create: DefaultTimeout(40 * time.Minute),
   277  			Update: DefaultTimeout(80 * time.Minute),
   278  			Delete: DefaultTimeout(40 * time.Minute),
   279  		},
   280  	}
   281  
   282  	r.Create = func(d *ResourceData, m interface{}) error {
   283  		d.SetId("foo")
   284  		return nil
   285  	}
   286  
   287  	conf := terraform.NewResourceConfigRaw(map[string]interface{}{
   288  		"foo": 42,
   289  		TimeoutsConfigKey: map[string]interface{}{
   290  			"create": "2h",
   291  		},
   292  	})
   293  	var s *terraform.InstanceState
   294  
   295  	actual, err := r.Diff(s, conf, nil)
   296  	if err != nil {
   297  		t.Fatalf("err: %s", err)
   298  	}
   299  
   300  	expected := &terraform.InstanceDiff{
   301  		Attributes: map[string]*terraform.ResourceAttrDiff{
   302  			"foo": &terraform.ResourceAttrDiff{
   303  				New: "42",
   304  			},
   305  		},
   306  	}
   307  
   308  	diffTimeout := &ResourceTimeout{
   309  		Create: DefaultTimeout(120 * time.Minute),
   310  		Update: DefaultTimeout(80 * time.Minute),
   311  		Delete: DefaultTimeout(40 * time.Minute),
   312  	}
   313  
   314  	if err := diffTimeout.DiffEncode(expected); err != nil {
   315  		t.Fatalf("Error encoding timeout to diff: %s", err)
   316  	}
   317  
   318  	if !reflect.DeepEqual(actual, expected) {
   319  		t.Fatalf("Not equal in Timeout Diff:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
   320  	}
   321  
   322  	// Shim
   323  	// apply this diff, so we have a state to compare
   324  	applied, err := r.Apply(s, actual, nil)
   325  	if err != nil {
   326  		t.Fatal(err)
   327  	}
   328  
   329  	// we're not testing Resource.Create, so we need to start with the "created" state
   330  	createdState := &terraform.InstanceState{
   331  		ID:         "foo",
   332  		Attributes: map[string]string{"id": "foo"},
   333  	}
   334  
   335  	testSchema := providers.Schema{
   336  		Version: int64(r.SchemaVersion),
   337  		Block:   resourceSchemaToBlock(r.Schema),
   338  	}
   339  
   340  	initialVal, err := StateValueFromInstanceState(createdState, testSchema.Block.ImpliedType())
   341  	if err != nil {
   342  		t.Fatal(err)
   343  	}
   344  
   345  	appliedVal, err := StateValueFromInstanceState(applied, testSchema.Block.ImpliedType())
   346  	if err != nil {
   347  		t.Fatal(err)
   348  	}
   349  
   350  	d, err := DiffFromValues(initialVal, appliedVal, r)
   351  	if err != nil {
   352  		t.Fatal(err)
   353  	}
   354  	if eq, _ := d.Same(expected); !eq {
   355  		t.Fatal(cmp.Diff(d, expected))
   356  	}
   357  }
   358  
   359  func TestShimResourceApply_destroy(t *testing.T) {
   360  	r := &Resource{
   361  		Schema: map[string]*Schema{
   362  			"foo": &Schema{
   363  				Type:     TypeInt,
   364  				Optional: true,
   365  			},
   366  		},
   367  	}
   368  
   369  	called := false
   370  	r.Delete = func(d *ResourceData, m interface{}) error {
   371  		called = true
   372  		return nil
   373  	}
   374  
   375  	s := &terraform.InstanceState{
   376  		ID: "bar",
   377  	}
   378  
   379  	d := &terraform.InstanceDiff{
   380  		Destroy: true,
   381  	}
   382  
   383  	actual, err := r.Apply(s, d, nil)
   384  	if err != nil {
   385  		t.Fatalf("err: %s", err)
   386  	}
   387  
   388  	if !called {
   389  		t.Fatal("delete not called")
   390  	}
   391  
   392  	if actual != nil {
   393  		t.Fatalf("bad: %#v", actual)
   394  	}
   395  
   396  	// Shim
   397  	// now that we have our diff and desired state, see if we can reproduce
   398  	// that with the shim
   399  	testApplyDiff(t, r, s, actual, d)
   400  }
   401  
   402  func TestShimResourceApply_destroyCreate(t *testing.T) {
   403  	r := &Resource{
   404  		Schema: map[string]*Schema{
   405  			"foo": &Schema{
   406  				Type:     TypeInt,
   407  				Optional: true,
   408  				ForceNew: true,
   409  			},
   410  
   411  			"tags": &Schema{
   412  				Type:     TypeMap,
   413  				Optional: true,
   414  				Computed: true,
   415  			},
   416  		},
   417  	}
   418  
   419  	change := false
   420  	r.Create = func(d *ResourceData, m interface{}) error {
   421  		change = d.HasChange("tags")
   422  		d.SetId("foo")
   423  		return nil
   424  	}
   425  	r.Delete = func(d *ResourceData, m interface{}) error {
   426  		return nil
   427  	}
   428  
   429  	var s *terraform.InstanceState = &terraform.InstanceState{
   430  		ID: "bar",
   431  		Attributes: map[string]string{
   432  			"foo":       "7",
   433  			"tags.Name": "foo",
   434  		},
   435  	}
   436  
   437  	d := &terraform.InstanceDiff{
   438  		Attributes: map[string]*terraform.ResourceAttrDiff{
   439  			"id": &terraform.ResourceAttrDiff{
   440  				New: "foo",
   441  			},
   442  			"foo": &terraform.ResourceAttrDiff{
   443  				Old:         "7",
   444  				New:         "42",
   445  				RequiresNew: true,
   446  			},
   447  			"tags.Name": &terraform.ResourceAttrDiff{
   448  				Old:         "foo",
   449  				New:         "foo",
   450  				RequiresNew: true,
   451  			},
   452  		},
   453  	}
   454  
   455  	actual, err := r.Apply(s, d, nil)
   456  	if err != nil {
   457  		t.Fatalf("err: %s", err)
   458  	}
   459  
   460  	if !change {
   461  		t.Fatal("should have change")
   462  	}
   463  
   464  	expected := &terraform.InstanceState{
   465  		ID: "foo",
   466  		Attributes: map[string]string{
   467  			"id":        "foo",
   468  			"foo":       "42",
   469  			"tags.%":    "1",
   470  			"tags.Name": "foo",
   471  		},
   472  	}
   473  
   474  	if !reflect.DeepEqual(actual, expected) {
   475  		cmp.Diff(actual, expected)
   476  	}
   477  
   478  	// Shim
   479  	// now that we have our diff and desired state, see if we can reproduce
   480  	// that with the shim
   481  	// we're not testing Resource.Create, so we need to start with the "created" state
   482  	createdState := &terraform.InstanceState{
   483  		ID: "foo",
   484  		Attributes: map[string]string{
   485  			"id":        "foo",
   486  			"foo":       "7",
   487  			"tags.%":    "1",
   488  			"tags.Name": "foo",
   489  		},
   490  	}
   491  
   492  	testApplyDiff(t, r, createdState, expected, d)
   493  }
   494  
   495  func TestShimSchemaMap_Diff(t *testing.T) {
   496  	cases := []struct {
   497  		Name          string
   498  		Schema        map[string]*Schema
   499  		State         *terraform.InstanceState
   500  		Config        map[string]interface{}
   501  		CustomizeDiff CustomizeDiffFunc
   502  		Diff          *terraform.InstanceDiff
   503  		Err           bool
   504  	}{
   505  		{
   506  			Name: "diff-1",
   507  			Schema: map[string]*Schema{
   508  				"availability_zone": &Schema{
   509  					Type:     TypeString,
   510  					Optional: true,
   511  					Computed: true,
   512  					ForceNew: true,
   513  				},
   514  			},
   515  
   516  			State: nil,
   517  
   518  			Config: map[string]interface{}{
   519  				"availability_zone": "foo",
   520  			},
   521  
   522  			Diff: &terraform.InstanceDiff{
   523  				Attributes: map[string]*terraform.ResourceAttrDiff{
   524  					"availability_zone": &terraform.ResourceAttrDiff{
   525  						Old:         "",
   526  						New:         "foo",
   527  						RequiresNew: true,
   528  					},
   529  				},
   530  			},
   531  
   532  			Err: false,
   533  		},
   534  
   535  		{
   536  			Name: "diff-2",
   537  			Schema: map[string]*Schema{
   538  				"availability_zone": &Schema{
   539  					Type:     TypeString,
   540  					Optional: true,
   541  					Computed: true,
   542  					ForceNew: true,
   543  				},
   544  			},
   545  
   546  			State: nil,
   547  
   548  			Config: map[string]interface{}{},
   549  
   550  			Diff: &terraform.InstanceDiff{
   551  				Attributes: map[string]*terraform.ResourceAttrDiff{
   552  					"availability_zone": &terraform.ResourceAttrDiff{
   553  						Old:         "",
   554  						NewComputed: true,
   555  						RequiresNew: true,
   556  					},
   557  				},
   558  			},
   559  
   560  			Err: false,
   561  		},
   562  
   563  		{
   564  			Name: "diff-3",
   565  			Schema: map[string]*Schema{
   566  				"availability_zone": &Schema{
   567  					Type:     TypeString,
   568  					Optional: true,
   569  					Computed: true,
   570  					ForceNew: true,
   571  				},
   572  			},
   573  
   574  			State: &terraform.InstanceState{
   575  				ID: "foo",
   576  			},
   577  
   578  			Config: map[string]interface{}{},
   579  
   580  			Diff: nil,
   581  
   582  			Err: false,
   583  		},
   584  
   585  		{
   586  			Name: "Computed, but set in config",
   587  			Schema: map[string]*Schema{
   588  				"availability_zone": &Schema{
   589  					Type:     TypeString,
   590  					Optional: true,
   591  					Computed: true,
   592  				},
   593  			},
   594  
   595  			State: &terraform.InstanceState{
   596  				ID: "id",
   597  				Attributes: map[string]string{
   598  					"availability_zone": "foo",
   599  				},
   600  			},
   601  
   602  			Config: map[string]interface{}{
   603  				"availability_zone": "bar",
   604  			},
   605  
   606  			Diff: &terraform.InstanceDiff{
   607  				Attributes: map[string]*terraform.ResourceAttrDiff{
   608  					"availability_zone": &terraform.ResourceAttrDiff{
   609  						Old: "foo",
   610  						New: "bar",
   611  					},
   612  				},
   613  			},
   614  
   615  			Err: false,
   616  		},
   617  
   618  		{
   619  			Name: "Default",
   620  			Schema: map[string]*Schema{
   621  				"availability_zone": &Schema{
   622  					Type:     TypeString,
   623  					Optional: true,
   624  					Default:  "foo",
   625  				},
   626  			},
   627  
   628  			State: nil,
   629  
   630  			Config: nil,
   631  
   632  			Diff: &terraform.InstanceDiff{
   633  				Attributes: map[string]*terraform.ResourceAttrDiff{
   634  					"availability_zone": &terraform.ResourceAttrDiff{
   635  						Old: "",
   636  						New: "foo",
   637  					},
   638  				},
   639  			},
   640  
   641  			Err: false,
   642  		},
   643  
   644  		{
   645  			Name: "DefaultFunc, value",
   646  			Schema: map[string]*Schema{
   647  				"availability_zone": &Schema{
   648  					Type:     TypeString,
   649  					Optional: true,
   650  					DefaultFunc: func() (interface{}, error) {
   651  						return "foo", nil
   652  					},
   653  				},
   654  			},
   655  
   656  			State: nil,
   657  
   658  			Config: nil,
   659  
   660  			Diff: &terraform.InstanceDiff{
   661  				Attributes: map[string]*terraform.ResourceAttrDiff{
   662  					"availability_zone": &terraform.ResourceAttrDiff{
   663  						Old: "",
   664  						New: "foo",
   665  					},
   666  				},
   667  			},
   668  
   669  			Err: false,
   670  		},
   671  
   672  		{
   673  			Name: "DefaultFunc, configuration set",
   674  			Schema: map[string]*Schema{
   675  				"availability_zone": &Schema{
   676  					Type:     TypeString,
   677  					Optional: true,
   678  					DefaultFunc: func() (interface{}, error) {
   679  						return "foo", nil
   680  					},
   681  				},
   682  			},
   683  
   684  			State: nil,
   685  
   686  			Config: map[string]interface{}{
   687  				"availability_zone": "bar",
   688  			},
   689  
   690  			Diff: &terraform.InstanceDiff{
   691  				Attributes: map[string]*terraform.ResourceAttrDiff{
   692  					"availability_zone": &terraform.ResourceAttrDiff{
   693  						Old: "",
   694  						New: "bar",
   695  					},
   696  				},
   697  			},
   698  
   699  			Err: false,
   700  		},
   701  
   702  		{
   703  			Name: "String with StateFunc",
   704  			Schema: map[string]*Schema{
   705  				"availability_zone": &Schema{
   706  					Type:     TypeString,
   707  					Optional: true,
   708  					Computed: true,
   709  					StateFunc: func(a interface{}) string {
   710  						return a.(string) + "!"
   711  					},
   712  				},
   713  			},
   714  
   715  			State: nil,
   716  
   717  			Config: map[string]interface{}{
   718  				"availability_zone": "foo",
   719  			},
   720  
   721  			Diff: &terraform.InstanceDiff{
   722  				Attributes: map[string]*terraform.ResourceAttrDiff{
   723  					"availability_zone": &terraform.ResourceAttrDiff{
   724  						Old:      "",
   725  						New:      "foo!",
   726  						NewExtra: "foo",
   727  					},
   728  				},
   729  			},
   730  
   731  			Err: false,
   732  		},
   733  
   734  		{
   735  			Name: "StateFunc not called with nil value",
   736  			Schema: map[string]*Schema{
   737  				"availability_zone": &Schema{
   738  					Type:     TypeString,
   739  					Optional: true,
   740  					Computed: true,
   741  					StateFunc: func(a interface{}) string {
   742  						t.Error("should not get here!")
   743  						return ""
   744  					},
   745  				},
   746  			},
   747  
   748  			State: nil,
   749  
   750  			Config: map[string]interface{}{},
   751  
   752  			Diff: &terraform.InstanceDiff{
   753  				Attributes: map[string]*terraform.ResourceAttrDiff{
   754  					"availability_zone": &terraform.ResourceAttrDiff{
   755  						Old:         "",
   756  						New:         "",
   757  						NewComputed: true,
   758  					},
   759  				},
   760  			},
   761  
   762  			Err: false,
   763  		},
   764  
   765  		{
   766  			Name: "Variable computed",
   767  			Schema: map[string]*Schema{
   768  				"availability_zone": &Schema{
   769  					Type:     TypeString,
   770  					Optional: true,
   771  				},
   772  			},
   773  
   774  			State: nil,
   775  
   776  			Config: map[string]interface{}{
   777  				"availability_zone": hcl2shim.UnknownVariableValue,
   778  			},
   779  
   780  			Diff: &terraform.InstanceDiff{
   781  				Attributes: map[string]*terraform.ResourceAttrDiff{
   782  					"availability_zone": &terraform.ResourceAttrDiff{
   783  						Old:         "",
   784  						New:         hcl2shim.UnknownVariableValue,
   785  						NewComputed: true,
   786  					},
   787  				},
   788  			},
   789  
   790  			Err: false,
   791  		},
   792  
   793  		{
   794  			Name: "Int decode",
   795  			Schema: map[string]*Schema{
   796  				"port": &Schema{
   797  					Type:     TypeInt,
   798  					Optional: true,
   799  					Computed: true,
   800  					ForceNew: true,
   801  				},
   802  			},
   803  
   804  			State: nil,
   805  
   806  			Config: map[string]interface{}{
   807  				"port": 27,
   808  			},
   809  
   810  			Diff: &terraform.InstanceDiff{
   811  				Attributes: map[string]*terraform.ResourceAttrDiff{
   812  					"port": &terraform.ResourceAttrDiff{
   813  						Old:         "",
   814  						New:         "27",
   815  						RequiresNew: true,
   816  					},
   817  				},
   818  			},
   819  
   820  			Err: false,
   821  		},
   822  
   823  		{
   824  			Name: "bool decode",
   825  			Schema: map[string]*Schema{
   826  				"port": &Schema{
   827  					Type:     TypeBool,
   828  					Optional: true,
   829  					Computed: true,
   830  					ForceNew: true,
   831  				},
   832  			},
   833  
   834  			State: nil,
   835  
   836  			Config: map[string]interface{}{
   837  				"port": false,
   838  			},
   839  
   840  			Diff: &terraform.InstanceDiff{
   841  				Attributes: map[string]*terraform.ResourceAttrDiff{
   842  					"port": &terraform.ResourceAttrDiff{
   843  						Old:         "",
   844  						New:         "false",
   845  						RequiresNew: true,
   846  					},
   847  				},
   848  			},
   849  
   850  			Err: false,
   851  		},
   852  
   853  		{
   854  			Name: "Bool",
   855  			Schema: map[string]*Schema{
   856  				"delete": &Schema{
   857  					Type:     TypeBool,
   858  					Optional: true,
   859  					Default:  false,
   860  				},
   861  			},
   862  
   863  			State: &terraform.InstanceState{
   864  				ID: "id",
   865  				Attributes: map[string]string{
   866  					"delete": "false",
   867  				},
   868  			},
   869  
   870  			Config: nil,
   871  
   872  			Diff: nil,
   873  
   874  			Err: false,
   875  		},
   876  
   877  		{
   878  			Name: "List decode",
   879  			Schema: map[string]*Schema{
   880  				"ports": &Schema{
   881  					Type:     TypeList,
   882  					Required: true,
   883  					Elem:     &Schema{Type: TypeInt},
   884  				},
   885  			},
   886  
   887  			State: nil,
   888  
   889  			Config: map[string]interface{}{
   890  				"ports": []interface{}{1, 2, 5},
   891  			},
   892  
   893  			Diff: &terraform.InstanceDiff{
   894  				Attributes: map[string]*terraform.ResourceAttrDiff{
   895  					"ports.#": &terraform.ResourceAttrDiff{
   896  						Old: "0",
   897  						New: "3",
   898  					},
   899  					"ports.0": &terraform.ResourceAttrDiff{
   900  						Old: "",
   901  						New: "1",
   902  					},
   903  					"ports.1": &terraform.ResourceAttrDiff{
   904  						Old: "",
   905  						New: "2",
   906  					},
   907  					"ports.2": &terraform.ResourceAttrDiff{
   908  						Old: "",
   909  						New: "5",
   910  					},
   911  				},
   912  			},
   913  
   914  			Err: false,
   915  		},
   916  
   917  		{
   918  			Name: "List decode with promotion with list",
   919  			Schema: map[string]*Schema{
   920  				"ports": &Schema{
   921  					Type:          TypeList,
   922  					Required:      true,
   923  					Elem:          &Schema{Type: TypeInt},
   924  					PromoteSingle: true,
   925  				},
   926  			},
   927  
   928  			State: nil,
   929  
   930  			Config: map[string]interface{}{
   931  				"ports": []interface{}{"5"},
   932  			},
   933  
   934  			Diff: &terraform.InstanceDiff{
   935  				Attributes: map[string]*terraform.ResourceAttrDiff{
   936  					"ports.#": &terraform.ResourceAttrDiff{
   937  						Old: "0",
   938  						New: "1",
   939  					},
   940  					"ports.0": &terraform.ResourceAttrDiff{
   941  						Old: "",
   942  						New: "5",
   943  					},
   944  				},
   945  			},
   946  
   947  			Err: false,
   948  		},
   949  
   950  		{
   951  			Schema: map[string]*Schema{
   952  				"ports": &Schema{
   953  					Type:     TypeList,
   954  					Required: true,
   955  					Elem:     &Schema{Type: TypeInt},
   956  				},
   957  			},
   958  
   959  			State: nil,
   960  
   961  			Config: map[string]interface{}{
   962  				"ports": []interface{}{1, 2, 5},
   963  			},
   964  
   965  			Diff: &terraform.InstanceDiff{
   966  				Attributes: map[string]*terraform.ResourceAttrDiff{
   967  					"ports.#": &terraform.ResourceAttrDiff{
   968  						Old: "0",
   969  						New: "3",
   970  					},
   971  					"ports.0": &terraform.ResourceAttrDiff{
   972  						Old: "",
   973  						New: "1",
   974  					},
   975  					"ports.1": &terraform.ResourceAttrDiff{
   976  						Old: "",
   977  						New: "2",
   978  					},
   979  					"ports.2": &terraform.ResourceAttrDiff{
   980  						Old: "",
   981  						New: "5",
   982  					},
   983  				},
   984  			},
   985  
   986  			Err: false,
   987  		},
   988  
   989  		{
   990  			Schema: map[string]*Schema{
   991  				"ports": &Schema{
   992  					Type:     TypeList,
   993  					Required: true,
   994  					Elem:     &Schema{Type: TypeInt},
   995  				},
   996  			},
   997  
   998  			State: nil,
   999  
  1000  			Config: map[string]interface{}{
  1001  				"ports": []interface{}{1, hcl2shim.UnknownVariableValue, "5"},
  1002  			},
  1003  
  1004  			Diff: &terraform.InstanceDiff{
  1005  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1006  					"ports.#": &terraform.ResourceAttrDiff{
  1007  						Old:         "0",
  1008  						New:         "",
  1009  						NewComputed: true,
  1010  					},
  1011  				},
  1012  			},
  1013  
  1014  			Err: false,
  1015  		},
  1016  
  1017  		{
  1018  			Schema: map[string]*Schema{
  1019  				"ports": &Schema{
  1020  					Type:     TypeList,
  1021  					Required: true,
  1022  					Elem:     &Schema{Type: TypeInt},
  1023  				},
  1024  			},
  1025  
  1026  			State: &terraform.InstanceState{
  1027  				ID: "id",
  1028  				Attributes: map[string]string{
  1029  					"ports.#": "3",
  1030  					"ports.0": "1",
  1031  					"ports.1": "2",
  1032  					"ports.2": "5",
  1033  				},
  1034  			},
  1035  
  1036  			Config: map[string]interface{}{
  1037  				"ports": []interface{}{1, 2, 5},
  1038  			},
  1039  
  1040  			Diff: nil,
  1041  
  1042  			Err: false,
  1043  		},
  1044  
  1045  		{
  1046  			Name: "",
  1047  			Schema: map[string]*Schema{
  1048  				"ports": &Schema{
  1049  					Type:     TypeList,
  1050  					Required: true,
  1051  					Elem:     &Schema{Type: TypeInt},
  1052  				},
  1053  			},
  1054  
  1055  			State: &terraform.InstanceState{
  1056  				ID: "id",
  1057  				Attributes: map[string]string{
  1058  					"ports.#": "2",
  1059  					"ports.0": "1",
  1060  					"ports.1": "2",
  1061  				},
  1062  			},
  1063  
  1064  			Config: map[string]interface{}{
  1065  				"ports": []interface{}{1, 2, 5},
  1066  			},
  1067  
  1068  			Diff: &terraform.InstanceDiff{
  1069  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1070  					"ports.#": &terraform.ResourceAttrDiff{
  1071  						Old: "2",
  1072  						New: "3",
  1073  					},
  1074  					"ports.2": &terraform.ResourceAttrDiff{
  1075  						Old: "",
  1076  						New: "5",
  1077  					},
  1078  				},
  1079  			},
  1080  
  1081  			Err: false,
  1082  		},
  1083  
  1084  		{
  1085  			Name: "",
  1086  			Schema: map[string]*Schema{
  1087  				"ports": &Schema{
  1088  					Type:     TypeList,
  1089  					Required: true,
  1090  					Elem:     &Schema{Type: TypeInt},
  1091  					ForceNew: true,
  1092  				},
  1093  			},
  1094  
  1095  			State: nil,
  1096  
  1097  			Config: map[string]interface{}{
  1098  				"ports": []interface{}{1, 2, 5},
  1099  			},
  1100  
  1101  			Diff: &terraform.InstanceDiff{
  1102  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1103  					"ports.#": &terraform.ResourceAttrDiff{
  1104  						Old:         "0",
  1105  						New:         "3",
  1106  						RequiresNew: true,
  1107  					},
  1108  					"ports.0": &terraform.ResourceAttrDiff{
  1109  						Old:         "",
  1110  						New:         "1",
  1111  						RequiresNew: true,
  1112  					},
  1113  					"ports.1": &terraform.ResourceAttrDiff{
  1114  						Old:         "",
  1115  						New:         "2",
  1116  						RequiresNew: true,
  1117  					},
  1118  					"ports.2": &terraform.ResourceAttrDiff{
  1119  						Old:         "",
  1120  						New:         "5",
  1121  						RequiresNew: true,
  1122  					},
  1123  				},
  1124  			},
  1125  
  1126  			Err: false,
  1127  		},
  1128  
  1129  		{
  1130  			Name: "",
  1131  			Schema: map[string]*Schema{
  1132  				"ports": &Schema{
  1133  					Type:     TypeList,
  1134  					Optional: true,
  1135  					Computed: true,
  1136  					Elem:     &Schema{Type: TypeInt},
  1137  				},
  1138  			},
  1139  
  1140  			State: nil,
  1141  
  1142  			Config: map[string]interface{}{},
  1143  
  1144  			Diff: &terraform.InstanceDiff{
  1145  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1146  					"ports.#": &terraform.ResourceAttrDiff{
  1147  						Old:         "",
  1148  						NewComputed: true,
  1149  					},
  1150  				},
  1151  			},
  1152  
  1153  			Err: false,
  1154  		},
  1155  
  1156  		{
  1157  			Name: "List with computed set",
  1158  			Schema: map[string]*Schema{
  1159  				"config": &Schema{
  1160  					Type:     TypeList,
  1161  					Optional: true,
  1162  					ForceNew: true,
  1163  					MinItems: 1,
  1164  					Elem: &Resource{
  1165  						Schema: map[string]*Schema{
  1166  							"name": {
  1167  								Type:     TypeString,
  1168  								Required: true,
  1169  							},
  1170  
  1171  							"rules": {
  1172  								Type:     TypeSet,
  1173  								Computed: true,
  1174  								Elem:     &Schema{Type: TypeString},
  1175  								Set:      HashString,
  1176  							},
  1177  						},
  1178  					},
  1179  				},
  1180  			},
  1181  
  1182  			State: nil,
  1183  
  1184  			Config: map[string]interface{}{
  1185  				"config": []interface{}{
  1186  					map[string]interface{}{
  1187  						"name": "hello",
  1188  					},
  1189  				},
  1190  			},
  1191  
  1192  			Diff: &terraform.InstanceDiff{
  1193  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1194  					"config.#": &terraform.ResourceAttrDiff{
  1195  						Old:         "0",
  1196  						New:         "1",
  1197  						RequiresNew: true,
  1198  					},
  1199  
  1200  					"config.0.name": &terraform.ResourceAttrDiff{
  1201  						Old: "",
  1202  						New: "hello",
  1203  					},
  1204  
  1205  					"config.0.rules.#": &terraform.ResourceAttrDiff{
  1206  						Old:         "",
  1207  						NewComputed: true,
  1208  					},
  1209  				},
  1210  			},
  1211  
  1212  			Err: false,
  1213  		},
  1214  
  1215  		{
  1216  			Name: "Set-1",
  1217  			Schema: map[string]*Schema{
  1218  				"ports": &Schema{
  1219  					Type:     TypeSet,
  1220  					Required: true,
  1221  					Elem:     &Schema{Type: TypeInt},
  1222  					Set: func(a interface{}) int {
  1223  						return a.(int)
  1224  					},
  1225  				},
  1226  			},
  1227  
  1228  			State: nil,
  1229  
  1230  			Config: map[string]interface{}{
  1231  				"ports": []interface{}{5, 2, 1},
  1232  			},
  1233  
  1234  			Diff: &terraform.InstanceDiff{
  1235  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1236  					"ports.#": &terraform.ResourceAttrDiff{
  1237  						Old: "0",
  1238  						New: "3",
  1239  					},
  1240  					"ports.1": &terraform.ResourceAttrDiff{
  1241  						Old: "",
  1242  						New: "1",
  1243  					},
  1244  					"ports.2": &terraform.ResourceAttrDiff{
  1245  						Old: "",
  1246  						New: "2",
  1247  					},
  1248  					"ports.5": &terraform.ResourceAttrDiff{
  1249  						Old: "",
  1250  						New: "5",
  1251  					},
  1252  				},
  1253  			},
  1254  
  1255  			Err: false,
  1256  		},
  1257  
  1258  		{
  1259  			Name: "Set-2",
  1260  			Schema: map[string]*Schema{
  1261  				"ports": &Schema{
  1262  					Type:     TypeSet,
  1263  					Computed: true,
  1264  					Required: true,
  1265  					Elem:     &Schema{Type: TypeInt},
  1266  					Set: func(a interface{}) int {
  1267  						return a.(int)
  1268  					},
  1269  				},
  1270  			},
  1271  
  1272  			State: &terraform.InstanceState{
  1273  				ID: "id",
  1274  				Attributes: map[string]string{
  1275  					"ports.#": "0",
  1276  				},
  1277  			},
  1278  
  1279  			Config: nil,
  1280  
  1281  			Diff: nil,
  1282  
  1283  			Err: false,
  1284  		},
  1285  
  1286  		{
  1287  			Name: "Set-3",
  1288  			Schema: map[string]*Schema{
  1289  				"ports": &Schema{
  1290  					Type:     TypeSet,
  1291  					Optional: true,
  1292  					Computed: true,
  1293  					Elem:     &Schema{Type: TypeInt},
  1294  					Set: func(a interface{}) int {
  1295  						return a.(int)
  1296  					},
  1297  				},
  1298  			},
  1299  
  1300  			State: nil,
  1301  
  1302  			Config: nil,
  1303  
  1304  			Diff: &terraform.InstanceDiff{
  1305  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1306  					"ports.#": &terraform.ResourceAttrDiff{
  1307  						Old:         "",
  1308  						NewComputed: true,
  1309  					},
  1310  				},
  1311  			},
  1312  
  1313  			Err: false,
  1314  		},
  1315  
  1316  		{
  1317  			Name: "Set-4",
  1318  			Schema: map[string]*Schema{
  1319  				"ports": &Schema{
  1320  					Type:     TypeSet,
  1321  					Required: true,
  1322  					Elem:     &Schema{Type: TypeInt},
  1323  					Set: func(a interface{}) int {
  1324  						return a.(int)
  1325  					},
  1326  				},
  1327  			},
  1328  
  1329  			State: nil,
  1330  
  1331  			Config: map[string]interface{}{
  1332  				"ports": []interface{}{"2", "5", 1},
  1333  			},
  1334  
  1335  			Diff: &terraform.InstanceDiff{
  1336  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1337  					"ports.#": &terraform.ResourceAttrDiff{
  1338  						Old: "0",
  1339  						New: "3",
  1340  					},
  1341  					"ports.1": &terraform.ResourceAttrDiff{
  1342  						Old: "",
  1343  						New: "1",
  1344  					},
  1345  					"ports.2": &terraform.ResourceAttrDiff{
  1346  						Old: "",
  1347  						New: "2",
  1348  					},
  1349  					"ports.5": &terraform.ResourceAttrDiff{
  1350  						Old: "",
  1351  						New: "5",
  1352  					},
  1353  				},
  1354  			},
  1355  
  1356  			Err: false,
  1357  		},
  1358  
  1359  		{
  1360  			Name: "Set-5",
  1361  			Schema: map[string]*Schema{
  1362  				"ports": &Schema{
  1363  					Type:     TypeSet,
  1364  					Required: true,
  1365  					Elem:     &Schema{Type: TypeInt},
  1366  					Set: func(a interface{}) int {
  1367  						return a.(int)
  1368  					},
  1369  				},
  1370  			},
  1371  
  1372  			State: nil,
  1373  
  1374  			Config: map[string]interface{}{
  1375  				"ports": []interface{}{1, hcl2shim.UnknownVariableValue, 5},
  1376  			},
  1377  
  1378  			Diff: &terraform.InstanceDiff{
  1379  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1380  					"ports.#": &terraform.ResourceAttrDiff{
  1381  						Old:         "",
  1382  						New:         "",
  1383  						NewComputed: true,
  1384  					},
  1385  				},
  1386  			},
  1387  
  1388  			Err: false,
  1389  		},
  1390  
  1391  		{
  1392  			Name: "Set-6",
  1393  			Schema: map[string]*Schema{
  1394  				"ports": &Schema{
  1395  					Type:     TypeSet,
  1396  					Required: true,
  1397  					Elem:     &Schema{Type: TypeInt},
  1398  					Set: func(a interface{}) int {
  1399  						return a.(int)
  1400  					},
  1401  				},
  1402  			},
  1403  
  1404  			State: &terraform.InstanceState{
  1405  				ID: "id",
  1406  				Attributes: map[string]string{
  1407  					"ports.#": "2",
  1408  					"ports.1": "1",
  1409  					"ports.2": "2",
  1410  				},
  1411  			},
  1412  
  1413  			Config: map[string]interface{}{
  1414  				"ports": []interface{}{5, 2, 1},
  1415  			},
  1416  
  1417  			Diff: &terraform.InstanceDiff{
  1418  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1419  					"ports.#": &terraform.ResourceAttrDiff{
  1420  						Old: "2",
  1421  						New: "3",
  1422  					},
  1423  					"ports.1": &terraform.ResourceAttrDiff{
  1424  						Old: "1",
  1425  						New: "1",
  1426  					},
  1427  					"ports.2": &terraform.ResourceAttrDiff{
  1428  						Old: "2",
  1429  						New: "2",
  1430  					},
  1431  					"ports.5": &terraform.ResourceAttrDiff{
  1432  						Old: "",
  1433  						New: "5",
  1434  					},
  1435  				},
  1436  			},
  1437  
  1438  			Err: false,
  1439  		},
  1440  
  1441  		{
  1442  			Name: "Set-8",
  1443  			Schema: map[string]*Schema{
  1444  				"ports": &Schema{
  1445  					Type:     TypeSet,
  1446  					Optional: true,
  1447  					Computed: true,
  1448  					Elem:     &Schema{Type: TypeInt},
  1449  					Set: func(a interface{}) int {
  1450  						return a.(int)
  1451  					},
  1452  				},
  1453  			},
  1454  
  1455  			State: &terraform.InstanceState{
  1456  				ID: "id",
  1457  				Attributes: map[string]string{
  1458  					"availability_zone": "bar",
  1459  					"ports.#":           "1",
  1460  					"ports.80":          "80",
  1461  				},
  1462  			},
  1463  
  1464  			Config: map[string]interface{}{},
  1465  
  1466  			Diff: nil,
  1467  
  1468  			Err: false,
  1469  		},
  1470  
  1471  		{
  1472  			Name: "Set-9",
  1473  			Schema: map[string]*Schema{
  1474  				"ingress": &Schema{
  1475  					Type:     TypeSet,
  1476  					Required: true,
  1477  					Elem: &Resource{
  1478  						Schema: map[string]*Schema{
  1479  							"ports": &Schema{
  1480  								Type:     TypeList,
  1481  								Optional: true,
  1482  								Elem:     &Schema{Type: TypeInt},
  1483  							},
  1484  						},
  1485  					},
  1486  					Set: func(v interface{}) int {
  1487  						m := v.(map[string]interface{})
  1488  						ps := m["ports"].([]interface{})
  1489  						result := 0
  1490  						for _, p := range ps {
  1491  							result += p.(int)
  1492  						}
  1493  						return result
  1494  					},
  1495  				},
  1496  			},
  1497  
  1498  			State: &terraform.InstanceState{
  1499  				ID: "id",
  1500  				Attributes: map[string]string{
  1501  					"ingress.#":           "2",
  1502  					"ingress.80.ports.#":  "1",
  1503  					"ingress.80.ports.0":  "80",
  1504  					"ingress.443.ports.#": "1",
  1505  					"ingress.443.ports.0": "443",
  1506  				},
  1507  			},
  1508  
  1509  			Config: map[string]interface{}{
  1510  				"ingress": []interface{}{
  1511  					map[string]interface{}{
  1512  						"ports": []interface{}{443},
  1513  					},
  1514  					map[string]interface{}{
  1515  						"ports": []interface{}{80},
  1516  					},
  1517  				},
  1518  			},
  1519  
  1520  			Diff: nil,
  1521  
  1522  			Err: false,
  1523  		},
  1524  
  1525  		{
  1526  			Name: "List of structure decode",
  1527  			Schema: map[string]*Schema{
  1528  				"ingress": &Schema{
  1529  					Type:     TypeList,
  1530  					Required: true,
  1531  					Elem: &Resource{
  1532  						Schema: map[string]*Schema{
  1533  							"from": &Schema{
  1534  								Type:     TypeInt,
  1535  								Required: true,
  1536  							},
  1537  						},
  1538  					},
  1539  				},
  1540  			},
  1541  
  1542  			State: nil,
  1543  
  1544  			Config: map[string]interface{}{
  1545  				"ingress": []interface{}{
  1546  					map[string]interface{}{
  1547  						"from": 8080,
  1548  					},
  1549  				},
  1550  			},
  1551  
  1552  			Diff: &terraform.InstanceDiff{
  1553  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1554  					"ingress.#": &terraform.ResourceAttrDiff{
  1555  						Old: "0",
  1556  						New: "1",
  1557  					},
  1558  					"ingress.0.from": &terraform.ResourceAttrDiff{
  1559  						Old: "",
  1560  						New: "8080",
  1561  					},
  1562  				},
  1563  			},
  1564  
  1565  			Err: false,
  1566  		},
  1567  
  1568  		{
  1569  			Name: "ComputedWhen",
  1570  			Schema: map[string]*Schema{
  1571  				"availability_zone": &Schema{
  1572  					Type:         TypeString,
  1573  					Computed:     true,
  1574  					ComputedWhen: []string{"port"},
  1575  				},
  1576  
  1577  				"port": &Schema{
  1578  					Type:     TypeInt,
  1579  					Optional: true,
  1580  				},
  1581  			},
  1582  
  1583  			State: &terraform.InstanceState{
  1584  				ID: "id",
  1585  				Attributes: map[string]string{
  1586  					"availability_zone": "foo",
  1587  					"port":              "80",
  1588  				},
  1589  			},
  1590  
  1591  			Config: map[string]interface{}{
  1592  				"port": 80,
  1593  			},
  1594  
  1595  			Diff: nil,
  1596  
  1597  			Err: false,
  1598  		},
  1599  
  1600  		{
  1601  			Name: "computed",
  1602  			Schema: map[string]*Schema{
  1603  				"availability_zone": &Schema{
  1604  					Type:         TypeString,
  1605  					Computed:     true,
  1606  					ComputedWhen: []string{"port"},
  1607  				},
  1608  
  1609  				"port": &Schema{
  1610  					Type:     TypeInt,
  1611  					Optional: true,
  1612  				},
  1613  			},
  1614  
  1615  			State: nil,
  1616  
  1617  			Config: map[string]interface{}{
  1618  				"port": 80,
  1619  			},
  1620  
  1621  			Diff: &terraform.InstanceDiff{
  1622  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1623  					"availability_zone": &terraform.ResourceAttrDiff{
  1624  						NewComputed: true,
  1625  					},
  1626  					"port": &terraform.ResourceAttrDiff{
  1627  						New: "80",
  1628  					},
  1629  				},
  1630  			},
  1631  
  1632  			Err: false,
  1633  		},
  1634  
  1635  		{
  1636  			Name: "computed, exists",
  1637  			Schema: map[string]*Schema{
  1638  				"availability_zone": &Schema{
  1639  					Type:         TypeString,
  1640  					Computed:     true,
  1641  					ComputedWhen: []string{"port"},
  1642  				},
  1643  
  1644  				"port": &Schema{
  1645  					Type:     TypeInt,
  1646  					Optional: true,
  1647  				},
  1648  			},
  1649  
  1650  			State: &terraform.InstanceState{
  1651  				ID: "id",
  1652  				Attributes: map[string]string{
  1653  					"port": "80",
  1654  				},
  1655  			},
  1656  
  1657  			Config: map[string]interface{}{
  1658  				"port": 80,
  1659  			},
  1660  
  1661  			// there is no computed diff when the instance exists already
  1662  			Diff: nil,
  1663  
  1664  			Err: false,
  1665  		},
  1666  
  1667  		{
  1668  			Name: "Maps-1",
  1669  			Schema: map[string]*Schema{
  1670  				"config_vars": &Schema{
  1671  					Type: TypeMap,
  1672  				},
  1673  			},
  1674  
  1675  			State: nil,
  1676  
  1677  			Config: map[string]interface{}{
  1678  				"config_vars": map[string]interface{}{
  1679  					"bar": "baz",
  1680  				},
  1681  			},
  1682  
  1683  			Diff: &terraform.InstanceDiff{
  1684  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1685  					"config_vars.%": &terraform.ResourceAttrDiff{
  1686  						Old: "0",
  1687  						New: "1",
  1688  					},
  1689  
  1690  					"config_vars.bar": &terraform.ResourceAttrDiff{
  1691  						Old: "",
  1692  						New: "baz",
  1693  					},
  1694  				},
  1695  			},
  1696  
  1697  			Err: false,
  1698  		},
  1699  
  1700  		{
  1701  			Name: "Maps-2",
  1702  			Schema: map[string]*Schema{
  1703  				"config_vars": &Schema{
  1704  					Type: TypeMap,
  1705  				},
  1706  			},
  1707  
  1708  			State: &terraform.InstanceState{
  1709  				ID: "id",
  1710  				Attributes: map[string]string{
  1711  					"config_vars.%":   "1",
  1712  					"config_vars.foo": "bar",
  1713  				},
  1714  			},
  1715  
  1716  			Config: map[string]interface{}{
  1717  				"config_vars": map[string]interface{}{
  1718  					"bar": "baz",
  1719  				},
  1720  			},
  1721  
  1722  			Diff: &terraform.InstanceDiff{
  1723  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1724  					"config_vars.foo": &terraform.ResourceAttrDiff{
  1725  						Old:        "bar",
  1726  						NewRemoved: true,
  1727  					},
  1728  					"config_vars.bar": &terraform.ResourceAttrDiff{
  1729  						Old: "",
  1730  						New: "baz",
  1731  					},
  1732  				},
  1733  			},
  1734  
  1735  			Err: false,
  1736  		},
  1737  
  1738  		{
  1739  			Name: "Maps-3",
  1740  			Schema: map[string]*Schema{
  1741  				"vars": &Schema{
  1742  					Type:     TypeMap,
  1743  					Optional: true,
  1744  					Computed: true,
  1745  				},
  1746  			},
  1747  
  1748  			State: &terraform.InstanceState{
  1749  				ID: "id",
  1750  				Attributes: map[string]string{
  1751  					"vars.%":   "1",
  1752  					"vars.foo": "bar",
  1753  				},
  1754  			},
  1755  
  1756  			Config: map[string]interface{}{
  1757  				"vars": map[string]interface{}{
  1758  					"bar": "baz",
  1759  				},
  1760  			},
  1761  
  1762  			Diff: &terraform.InstanceDiff{
  1763  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1764  					"vars.foo": &terraform.ResourceAttrDiff{
  1765  						Old:        "bar",
  1766  						New:        "",
  1767  						NewRemoved: true,
  1768  					},
  1769  					"vars.bar": &terraform.ResourceAttrDiff{
  1770  						Old: "",
  1771  						New: "baz",
  1772  					},
  1773  				},
  1774  			},
  1775  
  1776  			Err: false,
  1777  		},
  1778  
  1779  		{
  1780  			Name: "Maps-4",
  1781  			Schema: map[string]*Schema{
  1782  				"vars": &Schema{
  1783  					Type:     TypeMap,
  1784  					Computed: true,
  1785  				},
  1786  			},
  1787  
  1788  			State: &terraform.InstanceState{
  1789  				ID: "id",
  1790  				Attributes: map[string]string{
  1791  					"vars.%":   "1",
  1792  					"vars.foo": "bar",
  1793  				},
  1794  			},
  1795  
  1796  			Config: nil,
  1797  
  1798  			Diff: nil,
  1799  
  1800  			Err: false,
  1801  		},
  1802  
  1803  		{
  1804  			Name: "Maps-5",
  1805  			Schema: map[string]*Schema{
  1806  				"config_vars": &Schema{
  1807  					Type: TypeList,
  1808  					Elem: &Schema{Type: TypeMap},
  1809  				},
  1810  			},
  1811  
  1812  			State: &terraform.InstanceState{
  1813  				ID: "id",
  1814  				Attributes: map[string]string{
  1815  					"config_vars.#":     "1",
  1816  					"config_vars.0.%":   "1",
  1817  					"config_vars.0.foo": "bar",
  1818  				},
  1819  			},
  1820  
  1821  			Config: map[string]interface{}{
  1822  				"config_vars": []interface{}{
  1823  					map[string]interface{}{
  1824  						"bar": "baz",
  1825  					},
  1826  				},
  1827  			},
  1828  
  1829  			Diff: &terraform.InstanceDiff{
  1830  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1831  					"config_vars.0.foo": &terraform.ResourceAttrDiff{
  1832  						Old:        "bar",
  1833  						NewRemoved: true,
  1834  					},
  1835  					"config_vars.0.bar": &terraform.ResourceAttrDiff{
  1836  						Old: "",
  1837  						New: "baz",
  1838  					},
  1839  				},
  1840  			},
  1841  
  1842  			Err: false,
  1843  		},
  1844  
  1845  		{
  1846  			Name: "Maps-6",
  1847  			Schema: map[string]*Schema{
  1848  				"config_vars": &Schema{
  1849  					Type:     TypeList,
  1850  					Elem:     &Schema{Type: TypeMap},
  1851  					Optional: true,
  1852  				},
  1853  			},
  1854  
  1855  			State: &terraform.InstanceState{
  1856  				ID: "id",
  1857  				Attributes: map[string]string{
  1858  					"config_vars.#":     "1",
  1859  					"config_vars.0.%":   "2",
  1860  					"config_vars.0.foo": "bar",
  1861  					"config_vars.0.bar": "baz",
  1862  				},
  1863  			},
  1864  
  1865  			Config: map[string]interface{}{},
  1866  
  1867  			Diff: &terraform.InstanceDiff{
  1868  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1869  					"config_vars.#": &terraform.ResourceAttrDiff{
  1870  						Old: "1",
  1871  						New: "0",
  1872  					},
  1873  					"config_vars.0.%": &terraform.ResourceAttrDiff{
  1874  						Old: "2",
  1875  						New: "0",
  1876  					},
  1877  					"config_vars.0.foo": &terraform.ResourceAttrDiff{
  1878  						Old:        "bar",
  1879  						NewRemoved: true,
  1880  					},
  1881  					"config_vars.0.bar": &terraform.ResourceAttrDiff{
  1882  						Old:        "baz",
  1883  						NewRemoved: true,
  1884  					},
  1885  				},
  1886  			},
  1887  
  1888  			Err: false,
  1889  		},
  1890  
  1891  		{
  1892  			Name: "ForceNews",
  1893  			Schema: map[string]*Schema{
  1894  				"availability_zone": &Schema{
  1895  					Type:     TypeString,
  1896  					Optional: true,
  1897  					ForceNew: true,
  1898  				},
  1899  
  1900  				"address": &Schema{
  1901  					Type:     TypeString,
  1902  					Optional: true,
  1903  					Computed: true,
  1904  				},
  1905  			},
  1906  
  1907  			State: &terraform.InstanceState{
  1908  				ID: "id",
  1909  				Attributes: map[string]string{
  1910  					"availability_zone": "bar",
  1911  					"address":           "foo",
  1912  				},
  1913  			},
  1914  
  1915  			Config: map[string]interface{}{
  1916  				"availability_zone": "foo",
  1917  			},
  1918  
  1919  			Diff: &terraform.InstanceDiff{
  1920  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1921  					"availability_zone": &terraform.ResourceAttrDiff{
  1922  						Old:         "bar",
  1923  						New:         "foo",
  1924  						RequiresNew: true,
  1925  					},
  1926  				},
  1927  			},
  1928  
  1929  			Err: false,
  1930  		},
  1931  
  1932  		{
  1933  			Name: "Set-10",
  1934  			Schema: map[string]*Schema{
  1935  				"availability_zone": &Schema{
  1936  					Type:     TypeString,
  1937  					Optional: true,
  1938  					ForceNew: true,
  1939  				},
  1940  
  1941  				"ports": &Schema{
  1942  					Type:     TypeSet,
  1943  					Optional: true,
  1944  					Computed: true,
  1945  					Elem:     &Schema{Type: TypeInt},
  1946  					Set: func(a interface{}) int {
  1947  						return a.(int)
  1948  					},
  1949  				},
  1950  			},
  1951  
  1952  			State: &terraform.InstanceState{
  1953  				ID: "id",
  1954  				Attributes: map[string]string{
  1955  					"availability_zone": "bar",
  1956  					"ports.#":           "1",
  1957  					"ports.80":          "80",
  1958  				},
  1959  			},
  1960  
  1961  			Config: map[string]interface{}{
  1962  				"availability_zone": "foo",
  1963  			},
  1964  
  1965  			Diff: &terraform.InstanceDiff{
  1966  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1967  					"availability_zone": &terraform.ResourceAttrDiff{
  1968  						Old:         "bar",
  1969  						New:         "foo",
  1970  						RequiresNew: true,
  1971  					},
  1972  				},
  1973  			},
  1974  
  1975  			Err: false,
  1976  		},
  1977  
  1978  		{
  1979  			Name: "Set-11",
  1980  			Schema: map[string]*Schema{
  1981  				"instances": &Schema{
  1982  					Type:     TypeSet,
  1983  					Elem:     &Schema{Type: TypeString},
  1984  					Optional: true,
  1985  					Computed: true,
  1986  					Set: func(v interface{}) int {
  1987  						return len(v.(string))
  1988  					},
  1989  				},
  1990  			},
  1991  
  1992  			State: &terraform.InstanceState{
  1993  				ID: "id",
  1994  				Attributes: map[string]string{
  1995  					"instances.#": "0",
  1996  				},
  1997  			},
  1998  
  1999  			Config: map[string]interface{}{
  2000  				"instances": []interface{}{hcl2shim.UnknownVariableValue},
  2001  			},
  2002  
  2003  			Diff: &terraform.InstanceDiff{
  2004  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2005  					"instances.#": &terraform.ResourceAttrDiff{
  2006  						NewComputed: true,
  2007  					},
  2008  				},
  2009  			},
  2010  
  2011  			Err: false,
  2012  		},
  2013  
  2014  		{
  2015  			Name: "Set-12",
  2016  			Schema: map[string]*Schema{
  2017  				"route": &Schema{
  2018  					Type:     TypeSet,
  2019  					Optional: true,
  2020  					Elem: &Resource{
  2021  						Schema: map[string]*Schema{
  2022  							"index": &Schema{
  2023  								Type:     TypeInt,
  2024  								Required: true,
  2025  							},
  2026  
  2027  							"gateway": &Schema{
  2028  								Type:     TypeString,
  2029  								Optional: true,
  2030  							},
  2031  						},
  2032  					},
  2033  					Set: func(v interface{}) int {
  2034  						m := v.(map[string]interface{})
  2035  						return m["index"].(int)
  2036  					},
  2037  				},
  2038  			},
  2039  
  2040  			State: nil,
  2041  
  2042  			Config: map[string]interface{}{
  2043  				"route": []interface{}{
  2044  					map[string]interface{}{
  2045  						"index":   "1",
  2046  						"gateway": hcl2shim.UnknownVariableValue,
  2047  					},
  2048  				},
  2049  			},
  2050  
  2051  			Diff: &terraform.InstanceDiff{
  2052  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2053  					"route.#": &terraform.ResourceAttrDiff{
  2054  						Old: "0",
  2055  						New: "1",
  2056  					},
  2057  					"route.~1.index": &terraform.ResourceAttrDiff{
  2058  						Old: "",
  2059  						New: "1",
  2060  					},
  2061  					"route.~1.gateway": &terraform.ResourceAttrDiff{
  2062  						Old:         "",
  2063  						New:         hcl2shim.UnknownVariableValue,
  2064  						NewComputed: true,
  2065  					},
  2066  				},
  2067  			},
  2068  
  2069  			Err: false,
  2070  		},
  2071  
  2072  		{
  2073  			Name: "Set-13",
  2074  			Schema: map[string]*Schema{
  2075  				"route": &Schema{
  2076  					Type:     TypeSet,
  2077  					Optional: true,
  2078  					Elem: &Resource{
  2079  						Schema: map[string]*Schema{
  2080  							"index": &Schema{
  2081  								Type:     TypeInt,
  2082  								Required: true,
  2083  							},
  2084  
  2085  							"gateway": &Schema{
  2086  								Type:     TypeSet,
  2087  								Optional: true,
  2088  								Elem:     &Schema{Type: TypeInt},
  2089  								Set: func(a interface{}) int {
  2090  									return a.(int)
  2091  								},
  2092  							},
  2093  						},
  2094  					},
  2095  					Set: func(v interface{}) int {
  2096  						m := v.(map[string]interface{})
  2097  						return m["index"].(int)
  2098  					},
  2099  				},
  2100  			},
  2101  
  2102  			State: nil,
  2103  
  2104  			Config: map[string]interface{}{
  2105  				"route": []interface{}{
  2106  					map[string]interface{}{
  2107  						"index": "1",
  2108  						"gateway": []interface{}{
  2109  							hcl2shim.UnknownVariableValue,
  2110  						},
  2111  					},
  2112  				},
  2113  			},
  2114  
  2115  			Diff: &terraform.InstanceDiff{
  2116  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2117  					"route.#": &terraform.ResourceAttrDiff{
  2118  						Old: "0",
  2119  						New: "1",
  2120  					},
  2121  					"route.~1.index": &terraform.ResourceAttrDiff{
  2122  						Old: "",
  2123  						New: "1",
  2124  					},
  2125  					"route.~1.gateway.#": &terraform.ResourceAttrDiff{
  2126  						NewComputed: true,
  2127  					},
  2128  				},
  2129  			},
  2130  
  2131  			Err: false,
  2132  		},
  2133  
  2134  		{
  2135  			Name: "Computed maps",
  2136  			Schema: map[string]*Schema{
  2137  				"vars": &Schema{
  2138  					Type:     TypeMap,
  2139  					Computed: true,
  2140  				},
  2141  			},
  2142  
  2143  			State: nil,
  2144  
  2145  			Config: nil,
  2146  
  2147  			Diff: &terraform.InstanceDiff{
  2148  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2149  					"vars.%": &terraform.ResourceAttrDiff{
  2150  						Old:         "",
  2151  						NewComputed: true,
  2152  					},
  2153  				},
  2154  			},
  2155  
  2156  			Err: false,
  2157  		},
  2158  
  2159  		{
  2160  			Name: "Computed maps",
  2161  			Schema: map[string]*Schema{
  2162  				"vars": &Schema{
  2163  					Type:     TypeMap,
  2164  					Computed: true,
  2165  				},
  2166  			},
  2167  
  2168  			State: &terraform.InstanceState{
  2169  				ID: "id",
  2170  				Attributes: map[string]string{
  2171  					"vars.%": "0",
  2172  				},
  2173  			},
  2174  
  2175  			Config: map[string]interface{}{
  2176  				"vars": map[string]interface{}{
  2177  					"bar": hcl2shim.UnknownVariableValue,
  2178  				},
  2179  			},
  2180  
  2181  			Diff: &terraform.InstanceDiff{
  2182  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2183  					"vars.%": &terraform.ResourceAttrDiff{
  2184  						Old:         "",
  2185  						NewComputed: true,
  2186  					},
  2187  				},
  2188  			},
  2189  
  2190  			Err: false,
  2191  		},
  2192  
  2193  		{
  2194  			Name:   "Empty",
  2195  			Schema: map[string]*Schema{},
  2196  
  2197  			State: &terraform.InstanceState{},
  2198  
  2199  			Config: map[string]interface{}{},
  2200  
  2201  			Diff: nil,
  2202  
  2203  			Err: false,
  2204  		},
  2205  
  2206  		{
  2207  			Name: "Float",
  2208  			Schema: map[string]*Schema{
  2209  				"some_threshold": &Schema{
  2210  					Type: TypeFloat,
  2211  				},
  2212  			},
  2213  
  2214  			State: &terraform.InstanceState{
  2215  				ID: "id",
  2216  				Attributes: map[string]string{
  2217  					"some_threshold": "567.8",
  2218  				},
  2219  			},
  2220  
  2221  			Config: map[string]interface{}{
  2222  				"some_threshold": 12.34,
  2223  			},
  2224  
  2225  			Diff: &terraform.InstanceDiff{
  2226  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2227  					"some_threshold": &terraform.ResourceAttrDiff{
  2228  						Old: "567.8",
  2229  						New: "12.34",
  2230  					},
  2231  				},
  2232  			},
  2233  
  2234  			Err: false,
  2235  		},
  2236  
  2237  		{
  2238  			Name: "https://github.com/terramate-io/tf/issues/824",
  2239  			Schema: map[string]*Schema{
  2240  				"block_device": &Schema{
  2241  					Type:     TypeSet,
  2242  					Optional: true,
  2243  					Computed: true,
  2244  					Elem: &Resource{
  2245  						Schema: map[string]*Schema{
  2246  							"device_name": &Schema{
  2247  								Type:     TypeString,
  2248  								Required: true,
  2249  							},
  2250  							"delete_on_termination": &Schema{
  2251  								Type:     TypeBool,
  2252  								Optional: true,
  2253  								Default:  true,
  2254  							},
  2255  						},
  2256  					},
  2257  					Set: func(v interface{}) int {
  2258  						var buf bytes.Buffer
  2259  						m := v.(map[string]interface{})
  2260  						buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
  2261  						buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool)))
  2262  						return hashcode.String(buf.String())
  2263  					},
  2264  				},
  2265  			},
  2266  
  2267  			State: &terraform.InstanceState{
  2268  				ID: "id",
  2269  				Attributes: map[string]string{
  2270  					"block_device.#": "2",
  2271  					"block_device.616397234.delete_on_termination":  "true",
  2272  					"block_device.616397234.device_name":            "/dev/sda1",
  2273  					"block_device.2801811477.delete_on_termination": "true",
  2274  					"block_device.2801811477.device_name":           "/dev/sdx",
  2275  				},
  2276  			},
  2277  
  2278  			Config: map[string]interface{}{
  2279  				"block_device": []interface{}{
  2280  					map[string]interface{}{
  2281  						"device_name": "/dev/sda1",
  2282  					},
  2283  					map[string]interface{}{
  2284  						"device_name": "/dev/sdx",
  2285  					},
  2286  				},
  2287  			},
  2288  			Diff: nil,
  2289  			Err:  false,
  2290  		},
  2291  
  2292  		{
  2293  			Name: "Zero value in state shouldn't result in diff",
  2294  			Schema: map[string]*Schema{
  2295  				"port": &Schema{
  2296  					Type:     TypeBool,
  2297  					Optional: true,
  2298  					ForceNew: true,
  2299  				},
  2300  			},
  2301  
  2302  			State: &terraform.InstanceState{
  2303  				ID: "id",
  2304  				Attributes: map[string]string{
  2305  					"port": "false",
  2306  				},
  2307  			},
  2308  
  2309  			Config: map[string]interface{}{},
  2310  
  2311  			Diff: nil,
  2312  
  2313  			Err: false,
  2314  		},
  2315  
  2316  		{
  2317  			Name: "Same as prev, but for sets",
  2318  			Schema: map[string]*Schema{
  2319  				"route": &Schema{
  2320  					Type:     TypeSet,
  2321  					Optional: true,
  2322  					Elem: &Resource{
  2323  						Schema: map[string]*Schema{
  2324  							"index": &Schema{
  2325  								Type:     TypeInt,
  2326  								Required: true,
  2327  							},
  2328  
  2329  							"gateway": &Schema{
  2330  								Type:     TypeSet,
  2331  								Optional: true,
  2332  								Elem:     &Schema{Type: TypeInt},
  2333  								Set: func(a interface{}) int {
  2334  									return a.(int)
  2335  								},
  2336  							},
  2337  						},
  2338  					},
  2339  					Set: func(v interface{}) int {
  2340  						m := v.(map[string]interface{})
  2341  						return m["index"].(int)
  2342  					},
  2343  				},
  2344  			},
  2345  
  2346  			State: &terraform.InstanceState{
  2347  				ID: "id",
  2348  				Attributes: map[string]string{
  2349  					"route.#": "0",
  2350  				},
  2351  			},
  2352  
  2353  			Config: map[string]interface{}{},
  2354  
  2355  			Diff: nil,
  2356  
  2357  			Err: false,
  2358  		},
  2359  
  2360  		{
  2361  			Name: "A set computed element shouldn't cause a diff",
  2362  			Schema: map[string]*Schema{
  2363  				"active": &Schema{
  2364  					Type:     TypeBool,
  2365  					Computed: true,
  2366  					ForceNew: true,
  2367  				},
  2368  			},
  2369  
  2370  			State: &terraform.InstanceState{
  2371  				ID: "id",
  2372  				Attributes: map[string]string{
  2373  					"active": "true",
  2374  				},
  2375  			},
  2376  
  2377  			Config: map[string]interface{}{},
  2378  
  2379  			Diff: nil,
  2380  
  2381  			Err: false,
  2382  		},
  2383  
  2384  		{
  2385  			Name: "An empty set should show up in the diff",
  2386  			Schema: map[string]*Schema{
  2387  				"instances": &Schema{
  2388  					Type:     TypeSet,
  2389  					Elem:     &Schema{Type: TypeString},
  2390  					Optional: true,
  2391  					ForceNew: true,
  2392  					Set: func(v interface{}) int {
  2393  						return len(v.(string))
  2394  					},
  2395  				},
  2396  			},
  2397  
  2398  			State: &terraform.InstanceState{
  2399  				ID: "id",
  2400  				Attributes: map[string]string{
  2401  					"instances.#": "1",
  2402  					"instances.3": "foo",
  2403  				},
  2404  			},
  2405  
  2406  			Config: map[string]interface{}{},
  2407  
  2408  			Diff: &terraform.InstanceDiff{
  2409  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2410  					"instances.#": &terraform.ResourceAttrDiff{
  2411  						Old:         "1",
  2412  						New:         "0",
  2413  						RequiresNew: true,
  2414  					},
  2415  					"instances.3": &terraform.ResourceAttrDiff{
  2416  						Old:         "foo",
  2417  						New:         "",
  2418  						NewRemoved:  true,
  2419  						RequiresNew: true,
  2420  					},
  2421  				},
  2422  			},
  2423  
  2424  			Err: false,
  2425  		},
  2426  
  2427  		{
  2428  			Name: "Map with empty value",
  2429  			Schema: map[string]*Schema{
  2430  				"vars": &Schema{
  2431  					Type: TypeMap,
  2432  				},
  2433  			},
  2434  
  2435  			State: nil,
  2436  
  2437  			Config: map[string]interface{}{
  2438  				"vars": map[string]interface{}{
  2439  					"foo": "",
  2440  				},
  2441  			},
  2442  
  2443  			Diff: &terraform.InstanceDiff{
  2444  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2445  					"vars.%": &terraform.ResourceAttrDiff{
  2446  						Old: "0",
  2447  						New: "1",
  2448  					},
  2449  					"vars.foo": &terraform.ResourceAttrDiff{
  2450  						Old: "",
  2451  						New: "",
  2452  					},
  2453  				},
  2454  			},
  2455  
  2456  			Err: false,
  2457  		},
  2458  
  2459  		{
  2460  			Name: "Unset bool, not in state",
  2461  			Schema: map[string]*Schema{
  2462  				"force": &Schema{
  2463  					Type:     TypeBool,
  2464  					Optional: true,
  2465  					ForceNew: true,
  2466  				},
  2467  			},
  2468  
  2469  			State: nil,
  2470  
  2471  			Config: map[string]interface{}{},
  2472  
  2473  			Diff: nil,
  2474  
  2475  			Err: false,
  2476  		},
  2477  
  2478  		{
  2479  			Name: "Unset set, not in state",
  2480  			Schema: map[string]*Schema{
  2481  				"metadata_keys": &Schema{
  2482  					Type:     TypeSet,
  2483  					Optional: true,
  2484  					ForceNew: true,
  2485  					Elem:     &Schema{Type: TypeInt},
  2486  					Set:      func(interface{}) int { return 0 },
  2487  				},
  2488  			},
  2489  
  2490  			State: nil,
  2491  
  2492  			Config: map[string]interface{}{},
  2493  
  2494  			Diff: nil,
  2495  
  2496  			Err: false,
  2497  		},
  2498  
  2499  		{
  2500  			Name: "Unset list in state, should not show up computed",
  2501  			Schema: map[string]*Schema{
  2502  				"metadata_keys": &Schema{
  2503  					Type:     TypeList,
  2504  					Optional: true,
  2505  					Computed: true,
  2506  					ForceNew: true,
  2507  					Elem:     &Schema{Type: TypeInt},
  2508  				},
  2509  			},
  2510  
  2511  			State: &terraform.InstanceState{
  2512  				ID: "id",
  2513  				Attributes: map[string]string{
  2514  					"metadata_keys.#": "0",
  2515  				},
  2516  			},
  2517  
  2518  			Config: map[string]interface{}{},
  2519  
  2520  			Diff: nil,
  2521  
  2522  			Err: false,
  2523  		},
  2524  
  2525  		{
  2526  			Name: "Computed map without config that's known to be empty does not generate diff",
  2527  			Schema: map[string]*Schema{
  2528  				"tags": &Schema{
  2529  					Type:     TypeMap,
  2530  					Computed: true,
  2531  				},
  2532  			},
  2533  
  2534  			Config: nil,
  2535  
  2536  			State: &terraform.InstanceState{
  2537  				ID: "id",
  2538  				Attributes: map[string]string{
  2539  					"tags.%": "0",
  2540  				},
  2541  			},
  2542  
  2543  			Diff: nil,
  2544  
  2545  			Err: false,
  2546  		},
  2547  
  2548  		{
  2549  			Name: "Set with hyphen keys",
  2550  			Schema: map[string]*Schema{
  2551  				"route": &Schema{
  2552  					Type:     TypeSet,
  2553  					Optional: true,
  2554  					Elem: &Resource{
  2555  						Schema: map[string]*Schema{
  2556  							"index": &Schema{
  2557  								Type:     TypeInt,
  2558  								Required: true,
  2559  							},
  2560  
  2561  							"gateway-name": &Schema{
  2562  								Type:     TypeString,
  2563  								Optional: true,
  2564  							},
  2565  						},
  2566  					},
  2567  					Set: func(v interface{}) int {
  2568  						m := v.(map[string]interface{})
  2569  						return m["index"].(int)
  2570  					},
  2571  				},
  2572  			},
  2573  
  2574  			State: nil,
  2575  
  2576  			Config: map[string]interface{}{
  2577  				"route": []interface{}{
  2578  					map[string]interface{}{
  2579  						"index":        "1",
  2580  						"gateway-name": "hello",
  2581  					},
  2582  				},
  2583  			},
  2584  
  2585  			Diff: &terraform.InstanceDiff{
  2586  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2587  					"route.#": &terraform.ResourceAttrDiff{
  2588  						Old: "0",
  2589  						New: "1",
  2590  					},
  2591  					"route.1.index": &terraform.ResourceAttrDiff{
  2592  						Old: "",
  2593  						New: "1",
  2594  					},
  2595  					"route.1.gateway-name": &terraform.ResourceAttrDiff{
  2596  						Old: "",
  2597  						New: "hello",
  2598  					},
  2599  				},
  2600  			},
  2601  
  2602  			Err: false,
  2603  		},
  2604  
  2605  		{
  2606  			Name: "StateFunc in nested set (#1759)",
  2607  			Schema: map[string]*Schema{
  2608  				"service_account": &Schema{
  2609  					Type:     TypeList,
  2610  					Optional: true,
  2611  					ForceNew: true,
  2612  					Elem: &Resource{
  2613  						Schema: map[string]*Schema{
  2614  							"scopes": &Schema{
  2615  								Type:     TypeSet,
  2616  								Required: true,
  2617  								ForceNew: true,
  2618  								Elem: &Schema{
  2619  									Type: TypeString,
  2620  									StateFunc: func(v interface{}) string {
  2621  										return v.(string) + "!"
  2622  									},
  2623  								},
  2624  								Set: func(v interface{}) int {
  2625  									i, err := strconv.Atoi(v.(string))
  2626  									if err != nil {
  2627  										t.Fatalf("err: %s", err)
  2628  									}
  2629  									return i
  2630  								},
  2631  							},
  2632  						},
  2633  					},
  2634  				},
  2635  			},
  2636  
  2637  			State: nil,
  2638  
  2639  			Config: map[string]interface{}{
  2640  				"service_account": []interface{}{
  2641  					map[string]interface{}{
  2642  						"scopes": []interface{}{"123"},
  2643  					},
  2644  				},
  2645  			},
  2646  
  2647  			Diff: &terraform.InstanceDiff{
  2648  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2649  					"service_account.#": &terraform.ResourceAttrDiff{
  2650  						Old:         "0",
  2651  						New:         "1",
  2652  						RequiresNew: true,
  2653  					},
  2654  					"service_account.0.scopes.#": &terraform.ResourceAttrDiff{
  2655  						Old:         "0",
  2656  						New:         "1",
  2657  						RequiresNew: true,
  2658  					},
  2659  					"service_account.0.scopes.123": &terraform.ResourceAttrDiff{
  2660  						Old:         "",
  2661  						New:         "123!",
  2662  						NewExtra:    "123",
  2663  						RequiresNew: true,
  2664  					},
  2665  				},
  2666  			},
  2667  
  2668  			Err: false,
  2669  		},
  2670  
  2671  		{
  2672  			Name: "Removing set elements",
  2673  			Schema: map[string]*Schema{
  2674  				"instances": &Schema{
  2675  					Type:     TypeSet,
  2676  					Elem:     &Schema{Type: TypeString},
  2677  					Optional: true,
  2678  					ForceNew: true,
  2679  					Set: func(v interface{}) int {
  2680  						return len(v.(string))
  2681  					},
  2682  				},
  2683  			},
  2684  
  2685  			State: &terraform.InstanceState{
  2686  				ID: "id",
  2687  				Attributes: map[string]string{
  2688  					"instances.#": "2",
  2689  					"instances.3": "333",
  2690  					"instances.2": "22",
  2691  				},
  2692  			},
  2693  
  2694  			Config: map[string]interface{}{
  2695  				"instances": []interface{}{"333", "4444"},
  2696  			},
  2697  
  2698  			Diff: &terraform.InstanceDiff{
  2699  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2700  					"instances.2": &terraform.ResourceAttrDiff{
  2701  						Old:         "22",
  2702  						New:         "",
  2703  						NewRemoved:  true,
  2704  						RequiresNew: true,
  2705  					},
  2706  					"instances.3": &terraform.ResourceAttrDiff{
  2707  						Old: "333",
  2708  						New: "333",
  2709  					},
  2710  					"instances.4": &terraform.ResourceAttrDiff{
  2711  						Old:         "",
  2712  						New:         "4444",
  2713  						RequiresNew: true,
  2714  					},
  2715  				},
  2716  			},
  2717  
  2718  			Err: false,
  2719  		},
  2720  
  2721  		{
  2722  			Name: "Bools can be set with 0/1 in config, still get true/false",
  2723  			Schema: map[string]*Schema{
  2724  				"one": &Schema{
  2725  					Type:     TypeBool,
  2726  					Optional: true,
  2727  				},
  2728  				"two": &Schema{
  2729  					Type:     TypeBool,
  2730  					Optional: true,
  2731  				},
  2732  				"three": &Schema{
  2733  					Type:     TypeBool,
  2734  					Optional: true,
  2735  				},
  2736  			},
  2737  
  2738  			State: &terraform.InstanceState{
  2739  				ID: "id",
  2740  				Attributes: map[string]string{
  2741  					"one":   "false",
  2742  					"two":   "true",
  2743  					"three": "true",
  2744  				},
  2745  			},
  2746  
  2747  			Config: map[string]interface{}{
  2748  				"one": "1",
  2749  				"two": "0",
  2750  			},
  2751  
  2752  			Diff: &terraform.InstanceDiff{
  2753  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2754  					"one": &terraform.ResourceAttrDiff{
  2755  						Old: "false",
  2756  						New: "true",
  2757  					},
  2758  					"two": &terraform.ResourceAttrDiff{
  2759  						Old: "true",
  2760  						New: "false",
  2761  					},
  2762  					"three": &terraform.ResourceAttrDiff{
  2763  						Old:        "true",
  2764  						New:        "false",
  2765  						NewRemoved: true,
  2766  					},
  2767  				},
  2768  			},
  2769  
  2770  			Err: false,
  2771  		},
  2772  
  2773  		{
  2774  			Name:   "tainted in state w/ no attr changes is still a replacement",
  2775  			Schema: map[string]*Schema{},
  2776  
  2777  			State: &terraform.InstanceState{
  2778  				ID: "id",
  2779  				Attributes: map[string]string{
  2780  					"id": "someid",
  2781  				},
  2782  				Tainted: true,
  2783  			},
  2784  
  2785  			Config: map[string]interface{}{},
  2786  
  2787  			Diff: &terraform.InstanceDiff{
  2788  				Attributes:     map[string]*terraform.ResourceAttrDiff{},
  2789  				DestroyTainted: true,
  2790  			},
  2791  		},
  2792  
  2793  		{
  2794  			Name: "Set ForceNew only marks the changing element as ForceNew",
  2795  			Schema: map[string]*Schema{
  2796  				"ports": &Schema{
  2797  					Type:     TypeSet,
  2798  					Required: true,
  2799  					ForceNew: true,
  2800  					Elem:     &Schema{Type: TypeInt},
  2801  					Set: func(a interface{}) int {
  2802  						return a.(int)
  2803  					},
  2804  				},
  2805  			},
  2806  
  2807  			State: &terraform.InstanceState{
  2808  				ID: "id",
  2809  				Attributes: map[string]string{
  2810  					"ports.#": "3",
  2811  					"ports.1": "1",
  2812  					"ports.2": "2",
  2813  					"ports.4": "4",
  2814  				},
  2815  			},
  2816  
  2817  			Config: map[string]interface{}{
  2818  				"ports": []interface{}{5, 2, 1},
  2819  			},
  2820  
  2821  			Diff: &terraform.InstanceDiff{
  2822  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2823  					"ports.1": &terraform.ResourceAttrDiff{
  2824  						Old: "1",
  2825  						New: "1",
  2826  					},
  2827  					"ports.2": &terraform.ResourceAttrDiff{
  2828  						Old: "2",
  2829  						New: "2",
  2830  					},
  2831  					"ports.5": &terraform.ResourceAttrDiff{
  2832  						Old:         "",
  2833  						New:         "5",
  2834  						RequiresNew: true,
  2835  					},
  2836  					"ports.4": &terraform.ResourceAttrDiff{
  2837  						Old:         "4",
  2838  						New:         "0",
  2839  						NewRemoved:  true,
  2840  						RequiresNew: true,
  2841  					},
  2842  				},
  2843  			},
  2844  		},
  2845  
  2846  		{
  2847  			Name: "removed optional items should trigger ForceNew",
  2848  			Schema: map[string]*Schema{
  2849  				"description": &Schema{
  2850  					Type:     TypeString,
  2851  					ForceNew: true,
  2852  					Optional: true,
  2853  				},
  2854  			},
  2855  
  2856  			State: &terraform.InstanceState{
  2857  				ID: "id",
  2858  				Attributes: map[string]string{
  2859  					"description": "foo",
  2860  				},
  2861  			},
  2862  
  2863  			Config: map[string]interface{}{},
  2864  
  2865  			Diff: &terraform.InstanceDiff{
  2866  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2867  					"description": &terraform.ResourceAttrDiff{
  2868  						Old:         "foo",
  2869  						New:         "",
  2870  						RequiresNew: true,
  2871  						NewRemoved:  true,
  2872  					},
  2873  				},
  2874  			},
  2875  
  2876  			Err: false,
  2877  		},
  2878  
  2879  		// GH-7715
  2880  		{
  2881  			Name: "computed value for boolean field",
  2882  			Schema: map[string]*Schema{
  2883  				"foo": &Schema{
  2884  					Type:     TypeBool,
  2885  					ForceNew: true,
  2886  					Computed: true,
  2887  					Optional: true,
  2888  				},
  2889  			},
  2890  
  2891  			State: &terraform.InstanceState{
  2892  				ID: "id",
  2893  			},
  2894  
  2895  			Config: map[string]interface{}{
  2896  				"foo": hcl2shim.UnknownVariableValue,
  2897  			},
  2898  
  2899  			Diff: &terraform.InstanceDiff{
  2900  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2901  					"foo": &terraform.ResourceAttrDiff{
  2902  						Old:         "",
  2903  						New:         "false",
  2904  						NewComputed: true,
  2905  						RequiresNew: true,
  2906  					},
  2907  				},
  2908  			},
  2909  
  2910  			Err: false,
  2911  		},
  2912  
  2913  		{
  2914  			Name: "Set ForceNew marks count as ForceNew if computed",
  2915  			Schema: map[string]*Schema{
  2916  				"ports": &Schema{
  2917  					Type:     TypeSet,
  2918  					Required: true,
  2919  					ForceNew: true,
  2920  					Elem:     &Schema{Type: TypeInt},
  2921  					Set: func(a interface{}) int {
  2922  						return a.(int)
  2923  					},
  2924  				},
  2925  			},
  2926  
  2927  			State: &terraform.InstanceState{
  2928  				ID: "id",
  2929  				Attributes: map[string]string{
  2930  					"ports.#": "3",
  2931  					"ports.1": "1",
  2932  					"ports.2": "2",
  2933  					"ports.4": "4",
  2934  				},
  2935  			},
  2936  
  2937  			Config: map[string]interface{}{
  2938  				"ports": []interface{}{hcl2shim.UnknownVariableValue, 2, 1},
  2939  			},
  2940  
  2941  			Diff: &terraform.InstanceDiff{
  2942  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2943  					"ports.#": &terraform.ResourceAttrDiff{
  2944  						NewComputed: true,
  2945  						RequiresNew: true,
  2946  					},
  2947  				},
  2948  			},
  2949  		},
  2950  
  2951  		{
  2952  			Name: "List with computed schema and ForceNew",
  2953  			Schema: map[string]*Schema{
  2954  				"config": &Schema{
  2955  					Type:     TypeList,
  2956  					Optional: true,
  2957  					ForceNew: true,
  2958  					Elem: &Schema{
  2959  						Type: TypeString,
  2960  					},
  2961  				},
  2962  			},
  2963  
  2964  			State: &terraform.InstanceState{
  2965  				ID: "id",
  2966  				Attributes: map[string]string{
  2967  					"config.#": "2",
  2968  					"config.0": "a",
  2969  					"config.1": "b",
  2970  				},
  2971  			},
  2972  
  2973  			Config: map[string]interface{}{
  2974  				"config": []interface{}{hcl2shim.UnknownVariableValue, hcl2shim.UnknownVariableValue},
  2975  			},
  2976  
  2977  			Diff: &terraform.InstanceDiff{
  2978  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2979  					"config.#": &terraform.ResourceAttrDiff{
  2980  						Old:         "2",
  2981  						New:         "",
  2982  						RequiresNew: true,
  2983  						NewComputed: true,
  2984  					},
  2985  				},
  2986  			},
  2987  
  2988  			Err: false,
  2989  		},
  2990  
  2991  		{
  2992  			Name: "overridden diff with a CustomizeDiff function, ForceNew not in schema",
  2993  			Schema: map[string]*Schema{
  2994  				"availability_zone": &Schema{
  2995  					Type:     TypeString,
  2996  					Optional: true,
  2997  					Computed: true,
  2998  				},
  2999  			},
  3000  
  3001  			State: nil,
  3002  
  3003  			Config: map[string]interface{}{
  3004  				"availability_zone": "foo",
  3005  			},
  3006  
  3007  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3008  				if err := d.SetNew("availability_zone", "bar"); err != nil {
  3009  					return err
  3010  				}
  3011  				if err := d.ForceNew("availability_zone"); err != nil {
  3012  					return err
  3013  				}
  3014  				return nil
  3015  			},
  3016  
  3017  			Diff: &terraform.InstanceDiff{
  3018  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3019  					"availability_zone": &terraform.ResourceAttrDiff{
  3020  						Old:         "",
  3021  						New:         "bar",
  3022  						RequiresNew: true,
  3023  					},
  3024  				},
  3025  			},
  3026  
  3027  			Err: false,
  3028  		},
  3029  
  3030  		{
  3031  			// NOTE: This case is technically impossible in the current
  3032  			// implementation, because optional+computed values never show up in the
  3033  			// diff. In the event behavior changes this test should ensure that the
  3034  			// intended diff still shows up.
  3035  			Name: "overridden removed attribute diff with a CustomizeDiff function, ForceNew not in schema",
  3036  			Schema: map[string]*Schema{
  3037  				"availability_zone": &Schema{
  3038  					Type:     TypeString,
  3039  					Optional: true,
  3040  					Computed: true,
  3041  				},
  3042  			},
  3043  
  3044  			State: nil,
  3045  
  3046  			Config: map[string]interface{}{},
  3047  
  3048  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3049  				if err := d.SetNew("availability_zone", "bar"); err != nil {
  3050  					return err
  3051  				}
  3052  				if err := d.ForceNew("availability_zone"); err != nil {
  3053  					return err
  3054  				}
  3055  				return nil
  3056  			},
  3057  
  3058  			Diff: &terraform.InstanceDiff{
  3059  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3060  					"availability_zone": &terraform.ResourceAttrDiff{
  3061  						Old:         "",
  3062  						New:         "bar",
  3063  						RequiresNew: true,
  3064  					},
  3065  				},
  3066  			},
  3067  
  3068  			Err: false,
  3069  		},
  3070  
  3071  		{
  3072  
  3073  			Name: "overridden diff with a CustomizeDiff function, ForceNew in schema",
  3074  			Schema: map[string]*Schema{
  3075  				"availability_zone": &Schema{
  3076  					Type:     TypeString,
  3077  					Optional: true,
  3078  					Computed: true,
  3079  					ForceNew: true,
  3080  				},
  3081  			},
  3082  
  3083  			State: nil,
  3084  
  3085  			Config: map[string]interface{}{
  3086  				"availability_zone": "foo",
  3087  			},
  3088  
  3089  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3090  				if err := d.SetNew("availability_zone", "bar"); err != nil {
  3091  					return err
  3092  				}
  3093  				return nil
  3094  			},
  3095  
  3096  			Diff: &terraform.InstanceDiff{
  3097  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3098  					"availability_zone": &terraform.ResourceAttrDiff{
  3099  						Old:         "",
  3100  						New:         "bar",
  3101  						RequiresNew: true,
  3102  					},
  3103  				},
  3104  			},
  3105  
  3106  			Err: false,
  3107  		},
  3108  
  3109  		{
  3110  			Name: "required field with computed diff added with CustomizeDiff function",
  3111  			Schema: map[string]*Schema{
  3112  				"ami_id": &Schema{
  3113  					Type:     TypeString,
  3114  					Required: true,
  3115  				},
  3116  				"instance_id": &Schema{
  3117  					Type:     TypeString,
  3118  					Computed: true,
  3119  				},
  3120  			},
  3121  
  3122  			State: nil,
  3123  
  3124  			Config: map[string]interface{}{
  3125  				"ami_id": "foo",
  3126  			},
  3127  
  3128  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3129  				if err := d.SetNew("instance_id", "bar"); err != nil {
  3130  					return err
  3131  				}
  3132  				return nil
  3133  			},
  3134  
  3135  			Diff: &terraform.InstanceDiff{
  3136  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3137  					"ami_id": &terraform.ResourceAttrDiff{
  3138  						Old: "",
  3139  						New: "foo",
  3140  					},
  3141  					"instance_id": &terraform.ResourceAttrDiff{
  3142  						Old: "",
  3143  						New: "bar",
  3144  					},
  3145  				},
  3146  			},
  3147  
  3148  			Err: false,
  3149  		},
  3150  
  3151  		{
  3152  			Name: "Set ForceNew only marks the changing element as ForceNew - CustomizeDiffFunc edition",
  3153  			Schema: map[string]*Schema{
  3154  				"ports": &Schema{
  3155  					Type:     TypeSet,
  3156  					Optional: true,
  3157  					Computed: true,
  3158  					Elem:     &Schema{Type: TypeInt},
  3159  					Set: func(a interface{}) int {
  3160  						return a.(int)
  3161  					},
  3162  				},
  3163  			},
  3164  
  3165  			State: &terraform.InstanceState{
  3166  				ID: "id",
  3167  				Attributes: map[string]string{
  3168  					"ports.#": "3",
  3169  					"ports.1": "1",
  3170  					"ports.2": "2",
  3171  					"ports.4": "4",
  3172  				},
  3173  			},
  3174  
  3175  			Config: map[string]interface{}{
  3176  				"ports": []interface{}{5, 2, 6},
  3177  			},
  3178  
  3179  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3180  				if err := d.SetNew("ports", []interface{}{5, 2, 1}); err != nil {
  3181  					return err
  3182  				}
  3183  				if err := d.ForceNew("ports"); err != nil {
  3184  					return err
  3185  				}
  3186  				return nil
  3187  			},
  3188  
  3189  			Diff: &terraform.InstanceDiff{
  3190  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3191  					"ports.1": &terraform.ResourceAttrDiff{
  3192  						Old: "1",
  3193  						New: "1",
  3194  					},
  3195  					"ports.2": &terraform.ResourceAttrDiff{
  3196  						Old: "2",
  3197  						New: "2",
  3198  					},
  3199  					"ports.5": &terraform.ResourceAttrDiff{
  3200  						Old:         "",
  3201  						New:         "5",
  3202  						RequiresNew: true,
  3203  					},
  3204  					"ports.4": &terraform.ResourceAttrDiff{
  3205  						Old:         "4",
  3206  						New:         "0",
  3207  						NewRemoved:  true,
  3208  						RequiresNew: true,
  3209  					},
  3210  				},
  3211  			},
  3212  		},
  3213  
  3214  		{
  3215  			Name:   "tainted resource does not run CustomizeDiffFunc",
  3216  			Schema: map[string]*Schema{},
  3217  
  3218  			State: &terraform.InstanceState{
  3219  				ID: "someid",
  3220  				Attributes: map[string]string{
  3221  					"id": "someid",
  3222  				},
  3223  				Tainted: true,
  3224  			},
  3225  
  3226  			Config: map[string]interface{}{},
  3227  
  3228  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3229  				return errors.New("diff customization should not have run")
  3230  			},
  3231  
  3232  			Diff: &terraform.InstanceDiff{
  3233  				Attributes:     map[string]*terraform.ResourceAttrDiff{},
  3234  				DestroyTainted: true,
  3235  			},
  3236  
  3237  			Err: false,
  3238  		},
  3239  
  3240  		{
  3241  			Name: "NewComputed based on a conditional with CustomizeDiffFunc",
  3242  			Schema: map[string]*Schema{
  3243  				"etag": &Schema{
  3244  					Type:     TypeString,
  3245  					Optional: true,
  3246  					Computed: true,
  3247  				},
  3248  				"version_id": &Schema{
  3249  					Type:     TypeString,
  3250  					Computed: true,
  3251  				},
  3252  			},
  3253  
  3254  			State: &terraform.InstanceState{
  3255  				ID: "id",
  3256  				Attributes: map[string]string{
  3257  					"etag":       "foo",
  3258  					"version_id": "1",
  3259  				},
  3260  			},
  3261  
  3262  			Config: map[string]interface{}{
  3263  				"etag": "bar",
  3264  			},
  3265  
  3266  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3267  				if d.HasChange("etag") {
  3268  					d.SetNewComputed("version_id")
  3269  				}
  3270  				return nil
  3271  			},
  3272  
  3273  			Diff: &terraform.InstanceDiff{
  3274  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3275  					"etag": &terraform.ResourceAttrDiff{
  3276  						Old: "foo",
  3277  						New: "bar",
  3278  					},
  3279  					"version_id": &terraform.ResourceAttrDiff{
  3280  						Old:         "1",
  3281  						New:         "",
  3282  						NewComputed: true,
  3283  					},
  3284  				},
  3285  			},
  3286  
  3287  			Err: false,
  3288  		},
  3289  
  3290  		{
  3291  			Name: "vetoing a diff",
  3292  			Schema: map[string]*Schema{
  3293  				"foo": &Schema{
  3294  					Type:     TypeString,
  3295  					Optional: true,
  3296  					Computed: true,
  3297  				},
  3298  			},
  3299  
  3300  			State: &terraform.InstanceState{
  3301  				ID: "id",
  3302  				Attributes: map[string]string{
  3303  					"foo": "bar",
  3304  				},
  3305  			},
  3306  
  3307  			Config: map[string]interface{}{
  3308  				"foo": "baz",
  3309  			},
  3310  
  3311  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3312  				return fmt.Errorf("diff vetoed")
  3313  			},
  3314  
  3315  			Err: true,
  3316  		},
  3317  
  3318  		// A lot of resources currently depended on using the empty string as a
  3319  		// nil/unset value.
  3320  		{
  3321  			Name: "optional, computed, empty string",
  3322  			Schema: map[string]*Schema{
  3323  				"attr": &Schema{
  3324  					Type:     TypeString,
  3325  					Optional: true,
  3326  					Computed: true,
  3327  				},
  3328  			},
  3329  
  3330  			State: &terraform.InstanceState{
  3331  				ID: "id",
  3332  				Attributes: map[string]string{
  3333  					"attr": "bar",
  3334  				},
  3335  			},
  3336  
  3337  			Config: map[string]interface{}{
  3338  				"attr": "",
  3339  			},
  3340  		},
  3341  
  3342  		{
  3343  			Name: "optional, computed, empty string should not crash in CustomizeDiff",
  3344  			Schema: map[string]*Schema{
  3345  				"unrelated_set": {
  3346  					Type:     TypeSet,
  3347  					Optional: true,
  3348  					Elem:     &Schema{Type: TypeString},
  3349  				},
  3350  				"stream_enabled": {
  3351  					Type:     TypeBool,
  3352  					Optional: true,
  3353  				},
  3354  				"stream_view_type": {
  3355  					Type:     TypeString,
  3356  					Optional: true,
  3357  					Computed: true,
  3358  				},
  3359  			},
  3360  
  3361  			State: &terraform.InstanceState{
  3362  				ID: "id",
  3363  				Attributes: map[string]string{
  3364  					"unrelated_set.#":  "0",
  3365  					"stream_enabled":   "true",
  3366  					"stream_view_type": "KEYS_ONLY",
  3367  				},
  3368  			},
  3369  			Config: map[string]interface{}{
  3370  				"stream_enabled":   false,
  3371  				"stream_view_type": "",
  3372  			},
  3373  			CustomizeDiff: func(diff *ResourceDiff, v interface{}) error {
  3374  				v, ok := diff.GetOk("unrelated_set")
  3375  				if ok {
  3376  					return fmt.Errorf("Didn't expect unrelated_set: %#v", v)
  3377  				}
  3378  				return nil
  3379  			},
  3380  			Diff: &terraform.InstanceDiff{
  3381  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3382  					"stream_enabled": {
  3383  						Old: "true",
  3384  						New: "false",
  3385  					},
  3386  				},
  3387  			},
  3388  		},
  3389  	}
  3390  
  3391  	for i, tc := range cases {
  3392  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
  3393  			c := terraform.NewResourceConfigRaw(tc.Config)
  3394  
  3395  			{
  3396  				d, err := schemaMap(tc.Schema).Diff(tc.State, c, tc.CustomizeDiff, nil, false)
  3397  				if err != nil != tc.Err {
  3398  					t.Fatalf("err: %s", err)
  3399  				}
  3400  				if !cmp.Equal(d, tc.Diff, equateEmpty) {
  3401  					t.Fatal(cmp.Diff(d, tc.Diff, equateEmpty))
  3402  				}
  3403  			}
  3404  			// up to here is already tested in helper/schema; we're just
  3405  			// verify that we haven't broken any tests in transition.
  3406  
  3407  			// create a schema from the schemaMap
  3408  			testSchema := resourceSchemaToBlock(tc.Schema)
  3409  
  3410  			// get our initial state cty.Value
  3411  			stateVal, err := StateValueFromInstanceState(tc.State, testSchema.ImpliedType())
  3412  			if err != nil {
  3413  				t.Fatal(err)
  3414  			}
  3415  
  3416  			// this is the desired cty.Value from the configuration
  3417  			configVal := hcl2shim.HCL2ValueFromConfigValue(c.Config)
  3418  
  3419  			// verify that we can round-trip the config
  3420  			origConfig := hcl2shim.ConfigValueFromHCL2(configVal)
  3421  			if !cmp.Equal(c.Config, origConfig, equateEmpty) {
  3422  				t.Fatal(cmp.Diff(c.Config, origConfig, equateEmpty))
  3423  			}
  3424  
  3425  			// make sure our config conforms precisely to the schema
  3426  			configVal, err = testSchema.CoerceValue(configVal)
  3427  			if err != nil {
  3428  				t.Fatal(tfdiags.FormatError(err))
  3429  			}
  3430  
  3431  			// The new API requires returning the desired state rather than a
  3432  			// diff, so we need to verify that we can combine the state and
  3433  			// diff and recreate a new state.
  3434  
  3435  			// now verify that we can create diff, using the new config and state values
  3436  			// customize isn't run on tainted resources
  3437  			tainted := tc.State != nil && tc.State.Tainted
  3438  			if tainted {
  3439  				tc.CustomizeDiff = nil
  3440  			}
  3441  
  3442  			res := &Resource{Schema: tc.Schema}
  3443  
  3444  			d, err := diffFromValues(stateVal, configVal, res, tc.CustomizeDiff)
  3445  			if err != nil {
  3446  				if !tc.Err {
  3447  					t.Fatal(err)
  3448  				}
  3449  			}
  3450  
  3451  			// In a real "apply" operation there would be no unknown values,
  3452  			// so for tests containing unknowns we'll stop here: the steps
  3453  			// after this point apply only to the apply phase.
  3454  			if !configVal.IsWhollyKnown() {
  3455  				return
  3456  			}
  3457  
  3458  			// our diff function can't set DestroyTainted, but match the
  3459  			// expected value here for the test fixtures
  3460  			if tainted {
  3461  				if d == nil {
  3462  					d = &terraform.InstanceDiff{}
  3463  				}
  3464  				d.DestroyTainted = true
  3465  			}
  3466  
  3467  			if eq, _ := d.Same(tc.Diff); !eq {
  3468  				t.Fatal(cmp.Diff(d, tc.Diff))
  3469  			}
  3470  
  3471  		})
  3472  	}
  3473  }
  3474  
  3475  func resourceSchemaToBlock(s map[string]*Schema) *configschema.Block {
  3476  	return (&Resource{Schema: s}).CoreConfigSchema()
  3477  }
  3478  
  3479  func TestRemoveConfigUnknowns(t *testing.T) {
  3480  	cfg := map[string]interface{}{
  3481  		"id": "74D93920-ED26-11E3-AC10-0800200C9A66",
  3482  		"route_rules": []interface{}{
  3483  			map[string]interface{}{
  3484  				"cidr_block":        "74D93920-ED26-11E3-AC10-0800200C9A66",
  3485  				"destination":       "0.0.0.0/0",
  3486  				"destination_type":  "CIDR_BLOCK",
  3487  				"network_entity_id": "1",
  3488  			},
  3489  			map[string]interface{}{
  3490  				"cidr_block":       "74D93920-ED26-11E3-AC10-0800200C9A66",
  3491  				"destination":      "0.0.0.0/0",
  3492  				"destination_type": "CIDR_BLOCK",
  3493  				"sub_block": []interface{}{
  3494  					map[string]interface{}{
  3495  						"computed": "74D93920-ED26-11E3-AC10-0800200C9A66",
  3496  					},
  3497  				},
  3498  			},
  3499  		},
  3500  	}
  3501  
  3502  	expect := map[string]interface{}{
  3503  		"route_rules": []interface{}{
  3504  			map[string]interface{}{
  3505  				"destination":       "0.0.0.0/0",
  3506  				"destination_type":  "CIDR_BLOCK",
  3507  				"network_entity_id": "1",
  3508  			},
  3509  			map[string]interface{}{
  3510  				"destination":      "0.0.0.0/0",
  3511  				"destination_type": "CIDR_BLOCK",
  3512  				"sub_block": []interface{}{
  3513  					map[string]interface{}{},
  3514  				},
  3515  			},
  3516  		},
  3517  	}
  3518  
  3519  	removeConfigUnknowns(cfg)
  3520  
  3521  	if !reflect.DeepEqual(cfg, expect) {
  3522  		t.Fatalf("\nexpected: %#v\ngot:      %#v", expect, cfg)
  3523  	}
  3524  }