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

     1  package schema
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strconv"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/hashicorp/terraform/config"
    11  	"github.com/hashicorp/terraform/terraform"
    12  )
    13  
    14  func TestResourceApply_create(t *testing.T) {
    15  	r := &Resource{
    16  		SchemaVersion: 2,
    17  		Schema: map[string]*Schema{
    18  			"foo": &Schema{
    19  				Type:     TypeInt,
    20  				Optional: true,
    21  			},
    22  		},
    23  	}
    24  
    25  	called := false
    26  	r.Create = func(d *ResourceData, m interface{}) error {
    27  		called = true
    28  		d.SetId("foo")
    29  		return nil
    30  	}
    31  
    32  	var s *terraform.InstanceState = nil
    33  
    34  	d := &terraform.InstanceDiff{
    35  		Attributes: map[string]*terraform.ResourceAttrDiff{
    36  			"foo": &terraform.ResourceAttrDiff{
    37  				New: "42",
    38  			},
    39  		},
    40  	}
    41  
    42  	actual, err := r.Apply(s, d, nil)
    43  	if err != nil {
    44  		t.Fatalf("err: %s", err)
    45  	}
    46  
    47  	if !called {
    48  		t.Fatal("not called")
    49  	}
    50  
    51  	expected := &terraform.InstanceState{
    52  		ID: "foo",
    53  		Attributes: map[string]string{
    54  			"id":  "foo",
    55  			"foo": "42",
    56  		},
    57  		Meta: map[string]interface{}{
    58  			"schema_version": "2",
    59  		},
    60  	}
    61  
    62  	if !reflect.DeepEqual(actual, expected) {
    63  		t.Fatalf("bad: %#v", actual)
    64  	}
    65  }
    66  
    67  func TestResourceApply_Timeout_state(t *testing.T) {
    68  	r := &Resource{
    69  		SchemaVersion: 2,
    70  		Schema: map[string]*Schema{
    71  			"foo": &Schema{
    72  				Type:     TypeInt,
    73  				Optional: true,
    74  			},
    75  		},
    76  		Timeouts: &ResourceTimeout{
    77  			Create: DefaultTimeout(40 * time.Minute),
    78  			Update: DefaultTimeout(80 * time.Minute),
    79  			Delete: DefaultTimeout(40 * time.Minute),
    80  		},
    81  	}
    82  
    83  	called := false
    84  	r.Create = func(d *ResourceData, m interface{}) error {
    85  		called = true
    86  		d.SetId("foo")
    87  		return nil
    88  	}
    89  
    90  	var s *terraform.InstanceState = nil
    91  
    92  	d := &terraform.InstanceDiff{
    93  		Attributes: map[string]*terraform.ResourceAttrDiff{
    94  			"foo": &terraform.ResourceAttrDiff{
    95  				New: "42",
    96  			},
    97  		},
    98  	}
    99  
   100  	diffTimeout := &ResourceTimeout{
   101  		Create: DefaultTimeout(40 * time.Minute),
   102  		Update: DefaultTimeout(80 * time.Minute),
   103  		Delete: DefaultTimeout(40 * time.Minute),
   104  	}
   105  
   106  	if err := diffTimeout.DiffEncode(d); err != nil {
   107  		t.Fatalf("Error encoding timeout to diff: %s", err)
   108  	}
   109  
   110  	actual, err := r.Apply(s, d, nil)
   111  	if err != nil {
   112  		t.Fatalf("err: %s", err)
   113  	}
   114  
   115  	if !called {
   116  		t.Fatal("not called")
   117  	}
   118  
   119  	expected := &terraform.InstanceState{
   120  		ID: "foo",
   121  		Attributes: map[string]string{
   122  			"id":  "foo",
   123  			"foo": "42",
   124  		},
   125  		Meta: map[string]interface{}{
   126  			"schema_version": "2",
   127  			TimeoutKey:       expectedForValues(40, 0, 80, 40, 0),
   128  		},
   129  	}
   130  
   131  	if !reflect.DeepEqual(actual, expected) {
   132  		t.Fatalf("Not equal in Timeout State:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
   133  	}
   134  }
   135  
   136  // Regression test to ensure that the meta data is read from state, if a
   137  // resource is destroyed and the timeout meta is no longer available from the
   138  // config
   139  func TestResourceApply_Timeout_destroy(t *testing.T) {
   140  	timeouts := &ResourceTimeout{
   141  		Create: DefaultTimeout(40 * time.Minute),
   142  		Update: DefaultTimeout(80 * time.Minute),
   143  		Delete: DefaultTimeout(40 * time.Minute),
   144  	}
   145  
   146  	r := &Resource{
   147  		Schema: map[string]*Schema{
   148  			"foo": &Schema{
   149  				Type:     TypeInt,
   150  				Optional: true,
   151  			},
   152  		},
   153  		Timeouts: timeouts,
   154  	}
   155  
   156  	called := false
   157  	var delTimeout time.Duration
   158  	r.Delete = func(d *ResourceData, m interface{}) error {
   159  		delTimeout = d.Timeout(TimeoutDelete)
   160  		called = true
   161  		return nil
   162  	}
   163  
   164  	s := &terraform.InstanceState{
   165  		ID: "bar",
   166  	}
   167  
   168  	if err := timeouts.StateEncode(s); err != nil {
   169  		t.Fatalf("Error encoding to state: %s", err)
   170  	}
   171  
   172  	d := &terraform.InstanceDiff{
   173  		Destroy: true,
   174  	}
   175  
   176  	actual, err := r.Apply(s, d, nil)
   177  	if err != nil {
   178  		t.Fatalf("err: %s", err)
   179  	}
   180  
   181  	if !called {
   182  		t.Fatal("delete not called")
   183  	}
   184  
   185  	if *timeouts.Delete != delTimeout {
   186  		t.Fatalf("timeouts don't match, expected (%#v), got (%#v)", timeouts.Delete, delTimeout)
   187  	}
   188  
   189  	if actual != nil {
   190  		t.Fatalf("bad: %#v", actual)
   191  	}
   192  }
   193  
   194  func TestResourceDiff_Timeout_diff(t *testing.T) {
   195  	r := &Resource{
   196  		Schema: map[string]*Schema{
   197  			"foo": &Schema{
   198  				Type:     TypeInt,
   199  				Optional: true,
   200  			},
   201  		},
   202  		Timeouts: &ResourceTimeout{
   203  			Create: DefaultTimeout(40 * time.Minute),
   204  			Update: DefaultTimeout(80 * time.Minute),
   205  			Delete: DefaultTimeout(40 * time.Minute),
   206  		},
   207  	}
   208  
   209  	r.Create = func(d *ResourceData, m interface{}) error {
   210  		d.SetId("foo")
   211  		return nil
   212  	}
   213  
   214  	raw, err := config.NewRawConfig(
   215  		map[string]interface{}{
   216  			"foo": 42,
   217  			"timeouts": []map[string]interface{}{
   218  				map[string]interface{}{
   219  					"create": "2h",
   220  				}},
   221  		})
   222  	if err != nil {
   223  		t.Fatalf("err: %s", err)
   224  	}
   225  
   226  	var s *terraform.InstanceState = nil
   227  	conf := terraform.NewResourceConfig(raw)
   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": &terraform.ResourceAttrDiff{
   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 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": &Schema{
   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  	raw, err := config.NewRawConfig(
   275  		map[string]interface{}{
   276  			"foo": 42,
   277  		})
   278  	if err != nil {
   279  		t.Fatalf("err: %s", err)
   280  	}
   281  
   282  	var s *terraform.InstanceState
   283  	conf := terraform.NewResourceConfig(raw)
   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  }