github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/schema/resource_test.go (about)

     1  package schema
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"reflect"
     7  	"strconv"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/google/go-cmp/cmp"
    12  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim"
    13  	"github.com/hashicorp/terraform-plugin-sdk/terraform"
    14  
    15  	"github.com/zclconf/go-cty/cty"
    16  	ctyjson "github.com/zclconf/go-cty/cty/json"
    17  )
    18  
    19  func TestResourceApply_create(t *testing.T) {
    20  	r := &Resource{
    21  		SchemaVersion: 2,
    22  		Schema: map[string]*Schema{
    23  			"foo": {
    24  				Type:     TypeInt,
    25  				Optional: true,
    26  			},
    27  		},
    28  	}
    29  
    30  	called := false
    31  	r.Create = func(d *ResourceData, m interface{}) error {
    32  		called = true
    33  		d.SetId("foo")
    34  		return nil
    35  	}
    36  
    37  	var s *terraform.InstanceState = nil
    38  
    39  	d := &terraform.InstanceDiff{
    40  		Attributes: map[string]*terraform.ResourceAttrDiff{
    41  			"foo": {
    42  				New: "42",
    43  			},
    44  		},
    45  	}
    46  
    47  	actual, err := r.Apply(s, d, nil)
    48  	if err != nil {
    49  		t.Fatalf("err: %s", err)
    50  	}
    51  
    52  	if !called {
    53  		t.Fatal("not called")
    54  	}
    55  
    56  	expected := &terraform.InstanceState{
    57  		ID: "foo",
    58  		Attributes: map[string]string{
    59  			"id":  "foo",
    60  			"foo": "42",
    61  		},
    62  		Meta: map[string]interface{}{
    63  			"schema_version": "2",
    64  		},
    65  	}
    66  
    67  	if !reflect.DeepEqual(actual, expected) {
    68  		t.Fatalf("bad: %#v", actual)
    69  	}
    70  }
    71  
    72  func TestResourceApply_Timeout_state(t *testing.T) {
    73  	r := &Resource{
    74  		SchemaVersion: 2,
    75  		Schema: map[string]*Schema{
    76  			"foo": {
    77  				Type:     TypeInt,
    78  				Optional: true,
    79  			},
    80  		},
    81  		Timeouts: &ResourceTimeout{
    82  			Create: DefaultTimeout(40 * time.Minute),
    83  			Update: DefaultTimeout(80 * time.Minute),
    84  			Delete: DefaultTimeout(40 * time.Minute),
    85  		},
    86  	}
    87  
    88  	called := false
    89  	r.Create = func(d *ResourceData, m interface{}) error {
    90  		called = true
    91  		d.SetId("foo")
    92  		return nil
    93  	}
    94  
    95  	var s *terraform.InstanceState = nil
    96  
    97  	d := &terraform.InstanceDiff{
    98  		Attributes: map[string]*terraform.ResourceAttrDiff{
    99  			"foo": {
   100  				New: "42",
   101  			},
   102  		},
   103  	}
   104  
   105  	diffTimeout := &ResourceTimeout{
   106  		Create: DefaultTimeout(40 * time.Minute),
   107  		Update: DefaultTimeout(80 * time.Minute),
   108  		Delete: DefaultTimeout(40 * time.Minute),
   109  	}
   110  
   111  	if err := diffTimeout.DiffEncode(d); err != nil {
   112  		t.Fatalf("Error encoding timeout to diff: %s", err)
   113  	}
   114  
   115  	actual, err := r.Apply(s, d, nil)
   116  	if err != nil {
   117  		t.Fatalf("err: %s", err)
   118  	}
   119  
   120  	if !called {
   121  		t.Fatal("not called")
   122  	}
   123  
   124  	expected := &terraform.InstanceState{
   125  		ID: "foo",
   126  		Attributes: map[string]string{
   127  			"id":  "foo",
   128  			"foo": "42",
   129  		},
   130  		Meta: map[string]interface{}{
   131  			"schema_version": "2",
   132  			TimeoutKey:       expectedForValues(40, 0, 80, 40, 0),
   133  		},
   134  	}
   135  
   136  	if !reflect.DeepEqual(actual, expected) {
   137  		t.Fatalf("Not equal in Timeout State:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
   138  	}
   139  }
   140  
   141  // Regression test to ensure that the meta data is read from state, if a
   142  // resource is destroyed and the timeout meta is no longer available from the
   143  // config
   144  func TestResourceApply_Timeout_destroy(t *testing.T) {
   145  	timeouts := &ResourceTimeout{
   146  		Create: DefaultTimeout(40 * time.Minute),
   147  		Update: DefaultTimeout(80 * time.Minute),
   148  		Delete: DefaultTimeout(40 * time.Minute),
   149  	}
   150  
   151  	r := &Resource{
   152  		Schema: map[string]*Schema{
   153  			"foo": {
   154  				Type:     TypeInt,
   155  				Optional: true,
   156  			},
   157  		},
   158  		Timeouts: timeouts,
   159  	}
   160  
   161  	called := false
   162  	var delTimeout time.Duration
   163  	r.Delete = func(d *ResourceData, m interface{}) error {
   164  		delTimeout = d.Timeout(TimeoutDelete)
   165  		called = true
   166  		return nil
   167  	}
   168  
   169  	s := &terraform.InstanceState{
   170  		ID: "bar",
   171  	}
   172  
   173  	if err := timeouts.StateEncode(s); err != nil {
   174  		t.Fatalf("Error encoding to state: %s", err)
   175  	}
   176  
   177  	d := &terraform.InstanceDiff{
   178  		Destroy: true,
   179  	}
   180  
   181  	actual, err := r.Apply(s, d, nil)
   182  	if err != nil {
   183  		t.Fatalf("err: %s", err)
   184  	}
   185  
   186  	if !called {
   187  		t.Fatal("delete not called")
   188  	}
   189  
   190  	if *timeouts.Delete != delTimeout {
   191  		t.Fatalf("timeouts don't match, expected (%#v), got (%#v)", timeouts.Delete, delTimeout)
   192  	}
   193  
   194  	if actual != nil {
   195  		t.Fatalf("bad: %#v", actual)
   196  	}
   197  }
   198  
   199  func TestResourceDiff_Timeout_diff(t *testing.T) {
   200  	r := &Resource{
   201  		Schema: map[string]*Schema{
   202  			"foo": {
   203  				Type:     TypeInt,
   204  				Optional: true,
   205  			},
   206  		},
   207  		Timeouts: &ResourceTimeout{
   208  			Create: DefaultTimeout(40 * time.Minute),
   209  			Update: DefaultTimeout(80 * time.Minute),
   210  			Delete: DefaultTimeout(40 * time.Minute),
   211  		},
   212  	}
   213  
   214  	r.Create = func(d *ResourceData, m interface{}) error {
   215  		d.SetId("foo")
   216  		return nil
   217  	}
   218  
   219  	conf := terraform.NewResourceConfigRaw(
   220  		map[string]interface{}{
   221  			"foo": 42,
   222  			TimeoutsConfigKey: map[string]interface{}{
   223  				"create": "2h",
   224  			},
   225  		},
   226  	)
   227  	var s *terraform.InstanceState
   228  
   229  	actual, err := r.Diff(s, conf, nil)
   230  	if err != nil {
   231  		t.Fatalf("err: %s", err)
   232  	}
   233  
   234  	expected := &terraform.InstanceDiff{
   235  		Attributes: map[string]*terraform.ResourceAttrDiff{
   236  			"foo": {
   237  				New: "42",
   238  			},
   239  		},
   240  	}
   241  
   242  	diffTimeout := &ResourceTimeout{
   243  		Create: DefaultTimeout(120 * time.Minute),
   244  		Update: DefaultTimeout(80 * time.Minute),
   245  		Delete: DefaultTimeout(40 * time.Minute),
   246  	}
   247  
   248  	if err := diffTimeout.DiffEncode(expected); err != nil {
   249  		t.Fatalf("Error encoding timeout to diff: %s", err)
   250  	}
   251  
   252  	if !reflect.DeepEqual(actual, expected) {
   253  		t.Fatalf("Not equal Meta in Timeout Diff:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
   254  	}
   255  }
   256  
   257  func TestResourceDiff_CustomizeFunc(t *testing.T) {
   258  	r := &Resource{
   259  		Schema: map[string]*Schema{
   260  			"foo": {
   261  				Type:     TypeInt,
   262  				Optional: true,
   263  			},
   264  		},
   265  	}
   266  
   267  	var called bool
   268  
   269  	r.CustomizeDiff = func(d *ResourceDiff, m interface{}) error {
   270  		called = true
   271  		return nil
   272  	}
   273  
   274  	conf := terraform.NewResourceConfigRaw(
   275  		map[string]interface{}{
   276  			"foo": 42,
   277  		},
   278  	)
   279  
   280  	var s *terraform.InstanceState
   281  
   282  	_, err := r.Diff(s, conf, nil)
   283  	if err != nil {
   284  		t.Fatalf("err: %s", err)
   285  	}
   286  
   287  	if !called {
   288  		t.Fatalf("diff customization not called")
   289  	}
   290  }
   291  
   292  func TestResourceApply_destroy(t *testing.T) {
   293  	r := &Resource{
   294  		Schema: map[string]*Schema{
   295  			"foo": {
   296  				Type:     TypeInt,
   297  				Optional: true,
   298  			},
   299  		},
   300  	}
   301  
   302  	called := false
   303  	r.Delete = func(d *ResourceData, m interface{}) error {
   304  		called = true
   305  		return nil
   306  	}
   307  
   308  	s := &terraform.InstanceState{
   309  		ID: "bar",
   310  	}
   311  
   312  	d := &terraform.InstanceDiff{
   313  		Destroy: true,
   314  	}
   315  
   316  	actual, err := r.Apply(s, d, nil)
   317  	if err != nil {
   318  		t.Fatalf("err: %s", err)
   319  	}
   320  
   321  	if !called {
   322  		t.Fatal("delete not called")
   323  	}
   324  
   325  	if actual != nil {
   326  		t.Fatalf("bad: %#v", actual)
   327  	}
   328  }
   329  
   330  func TestResourceApply_destroyCreate(t *testing.T) {
   331  	r := &Resource{
   332  		Schema: map[string]*Schema{
   333  			"foo": {
   334  				Type:     TypeInt,
   335  				Optional: true,
   336  			},
   337  
   338  			"tags": {
   339  				Type:     TypeMap,
   340  				Optional: true,
   341  				Computed: true,
   342  			},
   343  		},
   344  	}
   345  
   346  	change := false
   347  	r.Create = func(d *ResourceData, m interface{}) error {
   348  		change = d.HasChange("tags")
   349  		d.SetId("foo")
   350  		return nil
   351  	}
   352  	r.Delete = func(d *ResourceData, m interface{}) error {
   353  		return nil
   354  	}
   355  
   356  	var s *terraform.InstanceState = &terraform.InstanceState{
   357  		ID: "bar",
   358  		Attributes: map[string]string{
   359  			"foo":       "bar",
   360  			"tags.Name": "foo",
   361  		},
   362  	}
   363  
   364  	d := &terraform.InstanceDiff{
   365  		Attributes: map[string]*terraform.ResourceAttrDiff{
   366  			"foo": {
   367  				New:         "42",
   368  				RequiresNew: true,
   369  			},
   370  			"tags.Name": {
   371  				Old:         "foo",
   372  				New:         "foo",
   373  				RequiresNew: true,
   374  			},
   375  		},
   376  	}
   377  
   378  	actual, err := r.Apply(s, d, nil)
   379  	if err != nil {
   380  		t.Fatalf("err: %s", err)
   381  	}
   382  
   383  	if !change {
   384  		t.Fatal("should have change")
   385  	}
   386  
   387  	expected := &terraform.InstanceState{
   388  		ID: "foo",
   389  		Attributes: map[string]string{
   390  			"id":        "foo",
   391  			"foo":       "42",
   392  			"tags.%":    "1",
   393  			"tags.Name": "foo",
   394  		},
   395  	}
   396  
   397  	if !reflect.DeepEqual(actual, expected) {
   398  		t.Fatalf("bad: %#v", actual)
   399  	}
   400  }
   401  
   402  func TestResourceApply_destroyPartial(t *testing.T) {
   403  	r := &Resource{
   404  		Schema: map[string]*Schema{
   405  			"foo": {
   406  				Type:     TypeInt,
   407  				Optional: true,
   408  			},
   409  		},
   410  		SchemaVersion: 3,
   411  	}
   412  
   413  	r.Delete = func(d *ResourceData, m interface{}) error {
   414  		d.Set("foo", 42)
   415  		return fmt.Errorf("some error")
   416  	}
   417  
   418  	s := &terraform.InstanceState{
   419  		ID: "bar",
   420  		Attributes: map[string]string{
   421  			"foo": "12",
   422  		},
   423  	}
   424  
   425  	d := &terraform.InstanceDiff{
   426  		Destroy: true,
   427  	}
   428  
   429  	actual, err := r.Apply(s, d, nil)
   430  	if err == nil {
   431  		t.Fatal("should error")
   432  	}
   433  
   434  	expected := &terraform.InstanceState{
   435  		ID: "bar",
   436  		Attributes: map[string]string{
   437  			"id":  "bar",
   438  			"foo": "42",
   439  		},
   440  		Meta: map[string]interface{}{
   441  			"schema_version": "3",
   442  		},
   443  	}
   444  
   445  	if !reflect.DeepEqual(actual, expected) {
   446  		t.Fatalf("expected:\n%#v\n\ngot:\n%#v", expected, actual)
   447  	}
   448  }
   449  
   450  func TestResourceApply_update(t *testing.T) {
   451  	r := &Resource{
   452  		Schema: map[string]*Schema{
   453  			"foo": {
   454  				Type:     TypeInt,
   455  				Optional: true,
   456  			},
   457  		},
   458  	}
   459  
   460  	r.Update = func(d *ResourceData, m interface{}) error {
   461  		d.Set("foo", 42)
   462  		return nil
   463  	}
   464  
   465  	s := &terraform.InstanceState{
   466  		ID: "foo",
   467  		Attributes: map[string]string{
   468  			"foo": "12",
   469  		},
   470  	}
   471  
   472  	d := &terraform.InstanceDiff{
   473  		Attributes: map[string]*terraform.ResourceAttrDiff{
   474  			"foo": {
   475  				New: "13",
   476  			},
   477  		},
   478  	}
   479  
   480  	actual, err := r.Apply(s, d, nil)
   481  	if err != nil {
   482  		t.Fatalf("err: %s", err)
   483  	}
   484  
   485  	expected := &terraform.InstanceState{
   486  		ID: "foo",
   487  		Attributes: map[string]string{
   488  			"id":  "foo",
   489  			"foo": "42",
   490  		},
   491  	}
   492  
   493  	if !reflect.DeepEqual(actual, expected) {
   494  		t.Fatalf("bad: %#v", actual)
   495  	}
   496  }
   497  
   498  func TestResourceApply_updateNoCallback(t *testing.T) {
   499  	r := &Resource{
   500  		Schema: map[string]*Schema{
   501  			"foo": {
   502  				Type:     TypeInt,
   503  				Optional: true,
   504  			},
   505  		},
   506  	}
   507  
   508  	r.Update = nil
   509  
   510  	s := &terraform.InstanceState{
   511  		ID: "foo",
   512  		Attributes: map[string]string{
   513  			"foo": "12",
   514  		},
   515  	}
   516  
   517  	d := &terraform.InstanceDiff{
   518  		Attributes: map[string]*terraform.ResourceAttrDiff{
   519  			"foo": {
   520  				New: "13",
   521  			},
   522  		},
   523  	}
   524  
   525  	actual, err := r.Apply(s, d, nil)
   526  	if err == nil {
   527  		t.Fatal("should error")
   528  	}
   529  
   530  	expected := &terraform.InstanceState{
   531  		ID: "foo",
   532  		Attributes: map[string]string{
   533  			"foo": "12",
   534  		},
   535  	}
   536  
   537  	if !reflect.DeepEqual(actual, expected) {
   538  		t.Fatalf("bad: %#v", actual)
   539  	}
   540  }
   541  
   542  func TestResourceApply_isNewResource(t *testing.T) {
   543  	r := &Resource{
   544  		Schema: map[string]*Schema{
   545  			"foo": {
   546  				Type:     TypeString,
   547  				Optional: true,
   548  			},
   549  		},
   550  	}
   551  
   552  	updateFunc := func(d *ResourceData, m interface{}) error {
   553  		d.Set("foo", "updated")
   554  		if d.IsNewResource() {
   555  			d.Set("foo", "new-resource")
   556  		}
   557  		return nil
   558  	}
   559  	r.Create = func(d *ResourceData, m interface{}) error {
   560  		d.SetId("foo")
   561  		d.Set("foo", "created")
   562  		return updateFunc(d, m)
   563  	}
   564  	r.Update = updateFunc
   565  
   566  	d := &terraform.InstanceDiff{
   567  		Attributes: map[string]*terraform.ResourceAttrDiff{
   568  			"foo": {
   569  				New: "bla-blah",
   570  			},
   571  		},
   572  	}
   573  
   574  	// positive test
   575  	var s *terraform.InstanceState = nil
   576  
   577  	actual, err := r.Apply(s, d, nil)
   578  	if err != nil {
   579  		t.Fatalf("err: %s", err)
   580  	}
   581  
   582  	expected := &terraform.InstanceState{
   583  		ID: "foo",
   584  		Attributes: map[string]string{
   585  			"id":  "foo",
   586  			"foo": "new-resource",
   587  		},
   588  	}
   589  
   590  	if !reflect.DeepEqual(actual, expected) {
   591  		t.Fatalf("actual: %#v\nexpected: %#v",
   592  			actual, expected)
   593  	}
   594  
   595  	// negative test
   596  	s = &terraform.InstanceState{
   597  		ID: "foo",
   598  		Attributes: map[string]string{
   599  			"id":  "foo",
   600  			"foo": "new-resource",
   601  		},
   602  	}
   603  
   604  	actual, err = r.Apply(s, d, nil)
   605  	if err != nil {
   606  		t.Fatalf("err: %s", err)
   607  	}
   608  
   609  	expected = &terraform.InstanceState{
   610  		ID: "foo",
   611  		Attributes: map[string]string{
   612  			"id":  "foo",
   613  			"foo": "updated",
   614  		},
   615  	}
   616  
   617  	if !reflect.DeepEqual(actual, expected) {
   618  		t.Fatalf("actual: %#v\nexpected: %#v",
   619  			actual, expected)
   620  	}
   621  }
   622  
   623  func TestResourceInternalValidate(t *testing.T) {
   624  	cases := []struct {
   625  		In       *Resource
   626  		Writable bool
   627  		Err      bool
   628  	}{
   629  		0: {
   630  			nil,
   631  			true,
   632  			true,
   633  		},
   634  
   635  		// No optional and no required
   636  		1: {
   637  			&Resource{
   638  				Schema: map[string]*Schema{
   639  					"foo": {
   640  						Type:     TypeInt,
   641  						Optional: true,
   642  						Required: true,
   643  					},
   644  				},
   645  			},
   646  			true,
   647  			true,
   648  		},
   649  
   650  		// Update undefined for non-ForceNew field
   651  		2: {
   652  			&Resource{
   653  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   654  				Schema: map[string]*Schema{
   655  					"boo": {
   656  						Type:     TypeInt,
   657  						Optional: true,
   658  					},
   659  				},
   660  			},
   661  			true,
   662  			true,
   663  		},
   664  
   665  		// Update defined for ForceNew field
   666  		3: {
   667  			&Resource{
   668  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   669  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   670  				Schema: map[string]*Schema{
   671  					"goo": {
   672  						Type:     TypeInt,
   673  						Optional: true,
   674  						ForceNew: true,
   675  					},
   676  				},
   677  			},
   678  			true,
   679  			true,
   680  		},
   681  
   682  		// non-writable doesn't need Update, Create or Delete
   683  		4: {
   684  			&Resource{
   685  				Schema: map[string]*Schema{
   686  					"goo": {
   687  						Type:     TypeInt,
   688  						Optional: true,
   689  					},
   690  				},
   691  			},
   692  			false,
   693  			false,
   694  		},
   695  
   696  		// non-writable *must not* have Create
   697  		5: {
   698  			&Resource{
   699  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   700  				Schema: map[string]*Schema{
   701  					"goo": {
   702  						Type:     TypeInt,
   703  						Optional: true,
   704  					},
   705  				},
   706  			},
   707  			false,
   708  			true,
   709  		},
   710  
   711  		// writable must have Read
   712  		6: {
   713  			&Resource{
   714  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   715  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   716  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   717  				Schema: map[string]*Schema{
   718  					"goo": {
   719  						Type:     TypeInt,
   720  						Optional: true,
   721  					},
   722  				},
   723  			},
   724  			true,
   725  			true,
   726  		},
   727  
   728  		// writable must have Delete
   729  		7: {
   730  			&Resource{
   731  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   732  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   733  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   734  				Schema: map[string]*Schema{
   735  					"goo": {
   736  						Type:     TypeInt,
   737  						Optional: true,
   738  					},
   739  				},
   740  			},
   741  			true,
   742  			true,
   743  		},
   744  
   745  		8: { // Reserved name at root should be disallowed
   746  			&Resource{
   747  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   748  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   749  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   750  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   751  				Schema: map[string]*Schema{
   752  					"count": {
   753  						Type:     TypeInt,
   754  						Optional: true,
   755  					},
   756  				},
   757  			},
   758  			true,
   759  			true,
   760  		},
   761  
   762  		9: { // Reserved name at nested levels should be allowed
   763  			&Resource{
   764  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   765  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   766  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   767  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   768  				Schema: map[string]*Schema{
   769  					"parent_list": {
   770  						Type:     TypeString,
   771  						Optional: true,
   772  						Elem: &Resource{
   773  							Schema: map[string]*Schema{
   774  								"provisioner": {
   775  									Type:     TypeString,
   776  									Optional: true,
   777  								},
   778  							},
   779  						},
   780  					},
   781  				},
   782  			},
   783  			true,
   784  			false,
   785  		},
   786  
   787  		10: { // Provider reserved name should be allowed in resource
   788  			&Resource{
   789  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   790  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   791  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   792  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   793  				Schema: map[string]*Schema{
   794  					"alias": {
   795  						Type:     TypeString,
   796  						Optional: true,
   797  					},
   798  				},
   799  			},
   800  			true,
   801  			false,
   802  		},
   803  
   804  		11: { // ID should be allowed in data source
   805  			&Resource{
   806  				Read: func(d *ResourceData, meta interface{}) error { return nil },
   807  				Schema: map[string]*Schema{
   808  					"id": {
   809  						Type:     TypeString,
   810  						Optional: true,
   811  					},
   812  				},
   813  			},
   814  			false,
   815  			false,
   816  		},
   817  
   818  		12: { // Deprecated ID should be allowed in resource
   819  			&Resource{
   820  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   821  				Read:   func(d *ResourceData, meta interface{}) error { return nil },
   822  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   823  				Delete: func(d *ResourceData, meta interface{}) error { return nil },
   824  				Schema: map[string]*Schema{
   825  					"id": {
   826  						Type:       TypeString,
   827  						Optional:   true,
   828  						Deprecated: "Use x_id instead",
   829  					},
   830  				},
   831  			},
   832  			true,
   833  			false,
   834  		},
   835  
   836  		13: { // non-writable must not define CustomizeDiff
   837  			&Resource{
   838  				Read: func(d *ResourceData, meta interface{}) error { return nil },
   839  				Schema: map[string]*Schema{
   840  					"goo": {
   841  						Type:     TypeInt,
   842  						Optional: true,
   843  					},
   844  				},
   845  				CustomizeDiff: func(*ResourceDiff, interface{}) error { return nil },
   846  			},
   847  			false,
   848  			true,
   849  		},
   850  		14: { // Deprecated resource
   851  			&Resource{
   852  				Read: func(d *ResourceData, meta interface{}) error { return nil },
   853  				Schema: map[string]*Schema{
   854  					"goo": {
   855  						Type:     TypeInt,
   856  						Optional: true,
   857  					},
   858  				},
   859  				DeprecationMessage: "This resource has been deprecated.",
   860  			},
   861  			true,
   862  			true,
   863  		},
   864  	}
   865  
   866  	for i, tc := range cases {
   867  		t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
   868  			sm := schemaMap{}
   869  			if tc.In != nil {
   870  				sm = schemaMap(tc.In.Schema)
   871  			}
   872  
   873  			err := tc.In.InternalValidate(sm, tc.Writable)
   874  			if err != nil && !tc.Err {
   875  				t.Fatalf("%d: expected validation to pass: %s", i, err)
   876  			}
   877  			if err == nil && tc.Err {
   878  				t.Fatalf("%d: expected validation to fail", i)
   879  			}
   880  		})
   881  	}
   882  }
   883  
   884  func TestResourceRefresh(t *testing.T) {
   885  	r := &Resource{
   886  		SchemaVersion: 2,
   887  		Schema: map[string]*Schema{
   888  			"foo": {
   889  				Type:     TypeInt,
   890  				Optional: true,
   891  			},
   892  		},
   893  	}
   894  
   895  	r.Read = func(d *ResourceData, m interface{}) error {
   896  		if m != 42 {
   897  			return fmt.Errorf("meta not passed")
   898  		}
   899  
   900  		return d.Set("foo", d.Get("foo").(int)+1)
   901  	}
   902  
   903  	s := &terraform.InstanceState{
   904  		ID: "bar",
   905  		Attributes: map[string]string{
   906  			"foo": "12",
   907  		},
   908  	}
   909  
   910  	expected := &terraform.InstanceState{
   911  		ID: "bar",
   912  		Attributes: map[string]string{
   913  			"id":  "bar",
   914  			"foo": "13",
   915  		},
   916  		Meta: map[string]interface{}{
   917  			"schema_version": "2",
   918  		},
   919  	}
   920  
   921  	actual, err := r.Refresh(s, 42)
   922  	if err != nil {
   923  		t.Fatalf("err: %s", err)
   924  	}
   925  
   926  	if !reflect.DeepEqual(actual, expected) {
   927  		t.Fatalf("bad: %#v", actual)
   928  	}
   929  }
   930  
   931  func TestResourceRefresh_blankId(t *testing.T) {
   932  	r := &Resource{
   933  		Schema: map[string]*Schema{
   934  			"foo": {
   935  				Type:     TypeInt,
   936  				Optional: true,
   937  			},
   938  		},
   939  	}
   940  
   941  	r.Read = func(d *ResourceData, m interface{}) error {
   942  		d.SetId("foo")
   943  		return nil
   944  	}
   945  
   946  	s := &terraform.InstanceState{
   947  		ID:         "",
   948  		Attributes: map[string]string{},
   949  	}
   950  
   951  	actual, err := r.Refresh(s, 42)
   952  	if err != nil {
   953  		t.Fatalf("err: %s", err)
   954  	}
   955  	if actual != nil {
   956  		t.Fatalf("bad: %#v", actual)
   957  	}
   958  }
   959  
   960  func TestResourceRefresh_delete(t *testing.T) {
   961  	r := &Resource{
   962  		Schema: map[string]*Schema{
   963  			"foo": {
   964  				Type:     TypeInt,
   965  				Optional: true,
   966  			},
   967  		},
   968  	}
   969  
   970  	r.Read = func(d *ResourceData, m interface{}) error {
   971  		d.SetId("")
   972  		return nil
   973  	}
   974  
   975  	s := &terraform.InstanceState{
   976  		ID: "bar",
   977  		Attributes: map[string]string{
   978  			"foo": "12",
   979  		},
   980  	}
   981  
   982  	actual, err := r.Refresh(s, 42)
   983  	if err != nil {
   984  		t.Fatalf("err: %s", err)
   985  	}
   986  
   987  	if actual != nil {
   988  		t.Fatalf("bad: %#v", actual)
   989  	}
   990  }
   991  
   992  func TestResourceRefresh_existsError(t *testing.T) {
   993  	r := &Resource{
   994  		Schema: map[string]*Schema{
   995  			"foo": {
   996  				Type:     TypeInt,
   997  				Optional: true,
   998  			},
   999  		},
  1000  	}
  1001  
  1002  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
  1003  		return false, fmt.Errorf("error")
  1004  	}
  1005  
  1006  	r.Read = func(d *ResourceData, m interface{}) error {
  1007  		panic("shouldn't be called")
  1008  	}
  1009  
  1010  	s := &terraform.InstanceState{
  1011  		ID: "bar",
  1012  		Attributes: map[string]string{
  1013  			"foo": "12",
  1014  		},
  1015  	}
  1016  
  1017  	actual, err := r.Refresh(s, 42)
  1018  	if err == nil {
  1019  		t.Fatalf("should error")
  1020  	}
  1021  	if !reflect.DeepEqual(actual, s) {
  1022  		t.Fatalf("bad: %#v", actual)
  1023  	}
  1024  }
  1025  
  1026  func TestResourceRefresh_noExists(t *testing.T) {
  1027  	r := &Resource{
  1028  		Schema: map[string]*Schema{
  1029  			"foo": {
  1030  				Type:     TypeInt,
  1031  				Optional: true,
  1032  			},
  1033  		},
  1034  	}
  1035  
  1036  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
  1037  		return false, nil
  1038  	}
  1039  
  1040  	r.Read = func(d *ResourceData, m interface{}) error {
  1041  		panic("shouldn't be called")
  1042  	}
  1043  
  1044  	s := &terraform.InstanceState{
  1045  		ID: "bar",
  1046  		Attributes: map[string]string{
  1047  			"foo": "12",
  1048  		},
  1049  	}
  1050  
  1051  	actual, err := r.Refresh(s, 42)
  1052  	if err != nil {
  1053  		t.Fatalf("err: %s", err)
  1054  	}
  1055  	if actual != nil {
  1056  		t.Fatalf("should have no state")
  1057  	}
  1058  }
  1059  
  1060  func TestResourceRefresh_needsMigration(t *testing.T) {
  1061  	// Schema v2 it deals only in newfoo, which tracks foo as an int
  1062  	r := &Resource{
  1063  		SchemaVersion: 2,
  1064  		Schema: map[string]*Schema{
  1065  			"newfoo": {
  1066  				Type:     TypeInt,
  1067  				Optional: true,
  1068  			},
  1069  		},
  1070  	}
  1071  
  1072  	r.Read = func(d *ResourceData, m interface{}) error {
  1073  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
  1074  	}
  1075  
  1076  	r.MigrateState = func(
  1077  		v int,
  1078  		s *terraform.InstanceState,
  1079  		meta interface{}) (*terraform.InstanceState, error) {
  1080  		// Real state migration functions will probably switch on this value,
  1081  		// but we'll just assert on it for now.
  1082  		if v != 1 {
  1083  			t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v)
  1084  		}
  1085  
  1086  		if meta != 42 {
  1087  			t.Fatal("Expected meta to be passed through to the migration function")
  1088  		}
  1089  
  1090  		oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64)
  1091  		if err != nil {
  1092  			t.Fatalf("err: %#v", err)
  1093  		}
  1094  		s.Attributes["newfoo"] = strconv.Itoa(int(oldfoo * 10))
  1095  		delete(s.Attributes, "oldfoo")
  1096  
  1097  		return s, nil
  1098  	}
  1099  
  1100  	// State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th
  1101  	// the scale of newfoo
  1102  	s := &terraform.InstanceState{
  1103  		ID: "bar",
  1104  		Attributes: map[string]string{
  1105  			"oldfoo": "1.2",
  1106  		},
  1107  		Meta: map[string]interface{}{
  1108  			"schema_version": "1",
  1109  		},
  1110  	}
  1111  
  1112  	actual, err := r.Refresh(s, 42)
  1113  	if err != nil {
  1114  		t.Fatalf("err: %s", err)
  1115  	}
  1116  
  1117  	expected := &terraform.InstanceState{
  1118  		ID: "bar",
  1119  		Attributes: map[string]string{
  1120  			"id":     "bar",
  1121  			"newfoo": "13",
  1122  		},
  1123  		Meta: map[string]interface{}{
  1124  			"schema_version": "2",
  1125  		},
  1126  	}
  1127  
  1128  	if !reflect.DeepEqual(actual, expected) {
  1129  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1130  	}
  1131  }
  1132  
  1133  func TestResourceRefresh_noMigrationNeeded(t *testing.T) {
  1134  	r := &Resource{
  1135  		SchemaVersion: 2,
  1136  		Schema: map[string]*Schema{
  1137  			"newfoo": {
  1138  				Type:     TypeInt,
  1139  				Optional: true,
  1140  			},
  1141  		},
  1142  	}
  1143  
  1144  	r.Read = func(d *ResourceData, m interface{}) error {
  1145  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
  1146  	}
  1147  
  1148  	r.MigrateState = func(
  1149  		v int,
  1150  		s *terraform.InstanceState,
  1151  		meta interface{}) (*terraform.InstanceState, error) {
  1152  		t.Fatal("Migrate function shouldn't be called!")
  1153  		return nil, nil
  1154  	}
  1155  
  1156  	s := &terraform.InstanceState{
  1157  		ID: "bar",
  1158  		Attributes: map[string]string{
  1159  			"newfoo": "12",
  1160  		},
  1161  		Meta: map[string]interface{}{
  1162  			"schema_version": "2",
  1163  		},
  1164  	}
  1165  
  1166  	actual, err := r.Refresh(s, nil)
  1167  	if err != nil {
  1168  		t.Fatalf("err: %s", err)
  1169  	}
  1170  
  1171  	expected := &terraform.InstanceState{
  1172  		ID: "bar",
  1173  		Attributes: map[string]string{
  1174  			"id":     "bar",
  1175  			"newfoo": "13",
  1176  		},
  1177  		Meta: map[string]interface{}{
  1178  			"schema_version": "2",
  1179  		},
  1180  	}
  1181  
  1182  	if !reflect.DeepEqual(actual, expected) {
  1183  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1184  	}
  1185  }
  1186  
  1187  func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) {
  1188  	r := &Resource{
  1189  		// Version 1 > Version 0
  1190  		SchemaVersion: 1,
  1191  		Schema: map[string]*Schema{
  1192  			"newfoo": {
  1193  				Type:     TypeInt,
  1194  				Optional: true,
  1195  			},
  1196  		},
  1197  	}
  1198  
  1199  	r.Read = func(d *ResourceData, m interface{}) error {
  1200  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
  1201  	}
  1202  
  1203  	r.MigrateState = func(
  1204  		v int,
  1205  		s *terraform.InstanceState,
  1206  		meta interface{}) (*terraform.InstanceState, error) {
  1207  		s.Attributes["newfoo"] = s.Attributes["oldfoo"]
  1208  		return s, nil
  1209  	}
  1210  
  1211  	s := &terraform.InstanceState{
  1212  		ID: "bar",
  1213  		Attributes: map[string]string{
  1214  			"oldfoo": "12",
  1215  		},
  1216  	}
  1217  
  1218  	actual, err := r.Refresh(s, nil)
  1219  	if err != nil {
  1220  		t.Fatalf("err: %s", err)
  1221  	}
  1222  
  1223  	expected := &terraform.InstanceState{
  1224  		ID: "bar",
  1225  		Attributes: map[string]string{
  1226  			"id":     "bar",
  1227  			"newfoo": "13",
  1228  		},
  1229  		Meta: map[string]interface{}{
  1230  			"schema_version": "1",
  1231  		},
  1232  	}
  1233  
  1234  	if !reflect.DeepEqual(actual, expected) {
  1235  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1236  	}
  1237  }
  1238  
  1239  func TestResourceRefresh_migrateStateErr(t *testing.T) {
  1240  	r := &Resource{
  1241  		SchemaVersion: 2,
  1242  		Schema: map[string]*Schema{
  1243  			"newfoo": {
  1244  				Type:     TypeInt,
  1245  				Optional: true,
  1246  			},
  1247  		},
  1248  	}
  1249  
  1250  	r.Read = func(d *ResourceData, m interface{}) error {
  1251  		t.Fatal("Read should never be called!")
  1252  		return nil
  1253  	}
  1254  
  1255  	r.MigrateState = func(
  1256  		v int,
  1257  		s *terraform.InstanceState,
  1258  		meta interface{}) (*terraform.InstanceState, error) {
  1259  		return s, fmt.Errorf("triggering an error")
  1260  	}
  1261  
  1262  	s := &terraform.InstanceState{
  1263  		ID: "bar",
  1264  		Attributes: map[string]string{
  1265  			"oldfoo": "12",
  1266  		},
  1267  	}
  1268  
  1269  	_, err := r.Refresh(s, nil)
  1270  	if err == nil {
  1271  		t.Fatal("expected error, but got none!")
  1272  	}
  1273  }
  1274  
  1275  func TestResourceData(t *testing.T) {
  1276  	r := &Resource{
  1277  		SchemaVersion: 2,
  1278  		Schema: map[string]*Schema{
  1279  			"foo": {
  1280  				Type:     TypeInt,
  1281  				Optional: true,
  1282  			},
  1283  		},
  1284  	}
  1285  
  1286  	state := &terraform.InstanceState{
  1287  		ID: "foo",
  1288  		Attributes: map[string]string{
  1289  			"id":  "foo",
  1290  			"foo": "42",
  1291  		},
  1292  	}
  1293  
  1294  	data := r.Data(state)
  1295  	if data.Id() != "foo" {
  1296  		t.Fatalf("err: %s", data.Id())
  1297  	}
  1298  	if v := data.Get("foo"); v != 42 {
  1299  		t.Fatalf("bad: %#v", v)
  1300  	}
  1301  
  1302  	// Set expectations
  1303  	state.Meta = map[string]interface{}{
  1304  		"schema_version": "2",
  1305  	}
  1306  
  1307  	result := data.State()
  1308  	if !reflect.DeepEqual(result, state) {
  1309  		t.Fatalf("bad: %#v", result)
  1310  	}
  1311  }
  1312  
  1313  func TestResourceData_blank(t *testing.T) {
  1314  	r := &Resource{
  1315  		SchemaVersion: 2,
  1316  		Schema: map[string]*Schema{
  1317  			"foo": {
  1318  				Type:     TypeInt,
  1319  				Optional: true,
  1320  			},
  1321  		},
  1322  	}
  1323  
  1324  	data := r.Data(nil)
  1325  	if data.Id() != "" {
  1326  		t.Fatalf("err: %s", data.Id())
  1327  	}
  1328  	if v := data.Get("foo"); v != 0 {
  1329  		t.Fatalf("bad: %#v", v)
  1330  	}
  1331  }
  1332  
  1333  func TestResourceData_timeouts(t *testing.T) {
  1334  	one := 1 * time.Second
  1335  	two := 2 * time.Second
  1336  	three := 3 * time.Second
  1337  	four := 4 * time.Second
  1338  	five := 5 * time.Second
  1339  
  1340  	timeouts := &ResourceTimeout{
  1341  		Create:  &one,
  1342  		Read:    &two,
  1343  		Update:  &three,
  1344  		Delete:  &four,
  1345  		Default: &five,
  1346  	}
  1347  
  1348  	r := &Resource{
  1349  		SchemaVersion: 2,
  1350  		Schema: map[string]*Schema{
  1351  			"foo": {
  1352  				Type:     TypeInt,
  1353  				Optional: true,
  1354  			},
  1355  		},
  1356  		Timeouts: timeouts,
  1357  	}
  1358  
  1359  	data := r.Data(nil)
  1360  	if data.Id() != "" {
  1361  		t.Fatalf("err: %s", data.Id())
  1362  	}
  1363  
  1364  	if !reflect.DeepEqual(timeouts, data.timeouts) {
  1365  		t.Fatalf("incorrect ResourceData timeouts: %#v\n", *data.timeouts)
  1366  	}
  1367  }
  1368  
  1369  func TestResource_UpgradeState(t *testing.T) {
  1370  	// While this really only calls itself and therefore doesn't test any of
  1371  	// the Resource code directly, it still serves as an example of registering
  1372  	// a StateUpgrader.
  1373  	r := &Resource{
  1374  		SchemaVersion: 2,
  1375  		Schema: map[string]*Schema{
  1376  			"newfoo": {
  1377  				Type:     TypeInt,
  1378  				Optional: true,
  1379  			},
  1380  		},
  1381  	}
  1382  
  1383  	r.StateUpgraders = []StateUpgrader{
  1384  		{
  1385  			Version: 1,
  1386  			Type: cty.Object(map[string]cty.Type{
  1387  				"id":     cty.String,
  1388  				"oldfoo": cty.Number,
  1389  			}),
  1390  			Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
  1391  
  1392  				oldfoo, ok := m["oldfoo"].(float64)
  1393  				if !ok {
  1394  					t.Fatalf("expected 1.2, got %#v", m["oldfoo"])
  1395  				}
  1396  				m["newfoo"] = int(oldfoo * 10)
  1397  				delete(m, "oldfoo")
  1398  
  1399  				return m, nil
  1400  			},
  1401  		},
  1402  	}
  1403  
  1404  	oldStateAttrs := map[string]string{
  1405  		"id":     "bar",
  1406  		"oldfoo": "1.2",
  1407  	}
  1408  
  1409  	// convert the legacy flatmap state to the json equivalent
  1410  	ty := r.StateUpgraders[0].Type
  1411  	val, err := hcl2shim.HCL2ValueFromFlatmap(oldStateAttrs, ty)
  1412  	if err != nil {
  1413  		t.Fatal(err)
  1414  	}
  1415  	js, err := ctyjson.Marshal(val, ty)
  1416  	if err != nil {
  1417  		t.Fatal(err)
  1418  	}
  1419  
  1420  	// unmarshal the state using the json default types
  1421  	var m map[string]interface{}
  1422  	if err := json.Unmarshal(js, &m); err != nil {
  1423  		t.Fatal(err)
  1424  	}
  1425  
  1426  	actual, err := r.StateUpgraders[0].Upgrade(m, nil)
  1427  	if err != nil {
  1428  		t.Fatalf("err: %s", err)
  1429  	}
  1430  
  1431  	expected := map[string]interface{}{
  1432  		"id":     "bar",
  1433  		"newfoo": 12,
  1434  	}
  1435  
  1436  	if !reflect.DeepEqual(expected, actual) {
  1437  		t.Fatalf("expected: %#v\ngot: %#v\n", expected, actual)
  1438  	}
  1439  }
  1440  
  1441  func TestResource_ValidateUpgradeState(t *testing.T) {
  1442  	r := &Resource{
  1443  		SchemaVersion: 3,
  1444  		Schema: map[string]*Schema{
  1445  			"newfoo": {
  1446  				Type:     TypeInt,
  1447  				Optional: true,
  1448  			},
  1449  		},
  1450  	}
  1451  
  1452  	if err := r.InternalValidate(nil, true); err != nil {
  1453  		t.Fatal(err)
  1454  	}
  1455  
  1456  	r.StateUpgraders = append(r.StateUpgraders, StateUpgrader{
  1457  		Version: 2,
  1458  		Type: cty.Object(map[string]cty.Type{
  1459  			"id": cty.String,
  1460  		}),
  1461  		Upgrade: func(m map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
  1462  			return m, nil
  1463  		},
  1464  	})
  1465  	if err := r.InternalValidate(nil, true); err != nil {
  1466  		t.Fatal(err)
  1467  	}
  1468  
  1469  	// check for missing type
  1470  	r.StateUpgraders[0].Type = cty.Type{}
  1471  	if err := r.InternalValidate(nil, true); err == nil {
  1472  		t.Fatal("StateUpgrader must have type")
  1473  	}
  1474  	r.StateUpgraders[0].Type = cty.Object(map[string]cty.Type{
  1475  		"id": cty.String,
  1476  	})
  1477  
  1478  	// check for missing Upgrade func
  1479  	r.StateUpgraders[0].Upgrade = nil
  1480  	if err := r.InternalValidate(nil, true); err == nil {
  1481  		t.Fatal("StateUpgrader must have an Upgrade func")
  1482  	}
  1483  	r.StateUpgraders[0].Upgrade = func(m map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
  1484  		return m, nil
  1485  	}
  1486  
  1487  	// check for skipped version
  1488  	r.StateUpgraders[0].Version = 0
  1489  	r.StateUpgraders = append(r.StateUpgraders, StateUpgrader{
  1490  		Version: 2,
  1491  		Type: cty.Object(map[string]cty.Type{
  1492  			"id": cty.String,
  1493  		}),
  1494  		Upgrade: func(m map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
  1495  			return m, nil
  1496  		},
  1497  	})
  1498  	if err := r.InternalValidate(nil, true); err == nil {
  1499  		t.Fatal("StateUpgraders cannot skip versions")
  1500  	}
  1501  
  1502  	// add the missing version, but fail because it's still out of order
  1503  	r.StateUpgraders = append(r.StateUpgraders, StateUpgrader{
  1504  		Version: 1,
  1505  		Type: cty.Object(map[string]cty.Type{
  1506  			"id": cty.String,
  1507  		}),
  1508  		Upgrade: func(m map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
  1509  			return m, nil
  1510  		},
  1511  	})
  1512  	if err := r.InternalValidate(nil, true); err == nil {
  1513  		t.Fatal("upgraders must be defined in order")
  1514  	}
  1515  
  1516  	r.StateUpgraders[1], r.StateUpgraders[2] = r.StateUpgraders[2], r.StateUpgraders[1]
  1517  	if err := r.InternalValidate(nil, true); err != nil {
  1518  		t.Fatal(err)
  1519  	}
  1520  
  1521  	// can't add an upgrader for a schema >= the current version
  1522  	r.StateUpgraders = append(r.StateUpgraders, StateUpgrader{
  1523  		Version: 3,
  1524  		Type: cty.Object(map[string]cty.Type{
  1525  			"id": cty.String,
  1526  		}),
  1527  		Upgrade: func(m map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
  1528  			return m, nil
  1529  		},
  1530  	})
  1531  	if err := r.InternalValidate(nil, true); err == nil {
  1532  		t.Fatal("StateUpgraders cannot have a version >= current SchemaVersion")
  1533  	}
  1534  }
  1535  
  1536  // The legacy provider will need to be able to handle both types of schema
  1537  // transformations, which has been retrofitted into the Refresh method.
  1538  func TestResource_migrateAndUpgrade(t *testing.T) {
  1539  	r := &Resource{
  1540  		SchemaVersion: 4,
  1541  		Schema: map[string]*Schema{
  1542  			"four": {
  1543  				Type:     TypeInt,
  1544  				Required: true,
  1545  			},
  1546  		},
  1547  		// this MigrateState will take the state to version 2
  1548  		MigrateState: func(v int, is *terraform.InstanceState, _ interface{}) (*terraform.InstanceState, error) {
  1549  			switch v {
  1550  			case 0:
  1551  				_, ok := is.Attributes["zero"]
  1552  				if !ok {
  1553  					return nil, fmt.Errorf("zero not found in %#v", is.Attributes)
  1554  				}
  1555  				is.Attributes["one"] = "1"
  1556  				delete(is.Attributes, "zero")
  1557  				fallthrough
  1558  			case 1:
  1559  				_, ok := is.Attributes["one"]
  1560  				if !ok {
  1561  					return nil, fmt.Errorf("one not found in %#v", is.Attributes)
  1562  				}
  1563  				is.Attributes["two"] = "2"
  1564  				delete(is.Attributes, "one")
  1565  			default:
  1566  				return nil, fmt.Errorf("invalid schema version %d", v)
  1567  			}
  1568  			return is, nil
  1569  		},
  1570  	}
  1571  
  1572  	r.Read = func(d *ResourceData, m interface{}) error {
  1573  		return d.Set("four", 4)
  1574  	}
  1575  
  1576  	r.StateUpgraders = []StateUpgrader{
  1577  		{
  1578  			Version: 2,
  1579  			Type: cty.Object(map[string]cty.Type{
  1580  				"id":  cty.String,
  1581  				"two": cty.Number,
  1582  			}),
  1583  			Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
  1584  				_, ok := m["two"].(float64)
  1585  				if !ok {
  1586  					return nil, fmt.Errorf("two not found in %#v", m)
  1587  				}
  1588  				m["three"] = float64(3)
  1589  				delete(m, "two")
  1590  				return m, nil
  1591  			},
  1592  		},
  1593  		{
  1594  			Version: 3,
  1595  			Type: cty.Object(map[string]cty.Type{
  1596  				"id":    cty.String,
  1597  				"three": cty.Number,
  1598  			}),
  1599  			Upgrade: func(m map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
  1600  				_, ok := m["three"].(float64)
  1601  				if !ok {
  1602  					return nil, fmt.Errorf("three not found in %#v", m)
  1603  				}
  1604  				m["four"] = float64(4)
  1605  				delete(m, "three")
  1606  				return m, nil
  1607  			},
  1608  		},
  1609  	}
  1610  
  1611  	testStates := []*terraform.InstanceState{
  1612  		{
  1613  			ID: "bar",
  1614  			Attributes: map[string]string{
  1615  				"id":   "bar",
  1616  				"zero": "0",
  1617  			},
  1618  			Meta: map[string]interface{}{
  1619  				"schema_version": "0",
  1620  			},
  1621  		},
  1622  		{
  1623  			ID: "bar",
  1624  			Attributes: map[string]string{
  1625  				"id":  "bar",
  1626  				"one": "1",
  1627  			},
  1628  			Meta: map[string]interface{}{
  1629  				"schema_version": "1",
  1630  			},
  1631  		},
  1632  		{
  1633  			ID: "bar",
  1634  			Attributes: map[string]string{
  1635  				"id":  "bar",
  1636  				"two": "2",
  1637  			},
  1638  			Meta: map[string]interface{}{
  1639  				"schema_version": "2",
  1640  			},
  1641  		},
  1642  		{
  1643  			ID: "bar",
  1644  			Attributes: map[string]string{
  1645  				"id":    "bar",
  1646  				"three": "3",
  1647  			},
  1648  			Meta: map[string]interface{}{
  1649  				"schema_version": "3",
  1650  			},
  1651  		},
  1652  		{
  1653  			ID: "bar",
  1654  			Attributes: map[string]string{
  1655  				"id":   "bar",
  1656  				"four": "4",
  1657  			},
  1658  			Meta: map[string]interface{}{
  1659  				"schema_version": "4",
  1660  			},
  1661  		},
  1662  	}
  1663  
  1664  	for i, s := range testStates {
  1665  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
  1666  			newState, err := r.Refresh(s, nil)
  1667  			if err != nil {
  1668  				t.Fatal(err)
  1669  			}
  1670  
  1671  			expected := &terraform.InstanceState{
  1672  				ID: "bar",
  1673  				Attributes: map[string]string{
  1674  					"id":   "bar",
  1675  					"four": "4",
  1676  				},
  1677  				Meta: map[string]interface{}{
  1678  					"schema_version": "4",
  1679  				},
  1680  			}
  1681  
  1682  			if !cmp.Equal(expected, newState, equateEmpty) {
  1683  				t.Fatal(cmp.Diff(expected, newState, equateEmpty))
  1684  			}
  1685  		})
  1686  	}
  1687  }