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

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