github.com/hooklift/terraform@v0.11.0-beta1.0.20171117000744-6786c1361ffe/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  	}
   854  
   855  	for i, tc := range cases {
   856  		t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
   857  			sm := schemaMap{}
   858  			if tc.In != nil {
   859  				sm = schemaMap(tc.In.Schema)
   860  			}
   861  
   862  			err := tc.In.InternalValidate(sm, tc.Writable)
   863  			if err != nil && !tc.Err {
   864  				t.Fatalf("%d: expected validation to pass: %s", i, err)
   865  			}
   866  			if err == nil && tc.Err {
   867  				t.Fatalf("%d: expected validation to fail", i)
   868  			}
   869  		})
   870  	}
   871  }
   872  
   873  func TestResourceRefresh(t *testing.T) {
   874  	r := &Resource{
   875  		SchemaVersion: 2,
   876  		Schema: map[string]*Schema{
   877  			"foo": &Schema{
   878  				Type:     TypeInt,
   879  				Optional: true,
   880  			},
   881  		},
   882  	}
   883  
   884  	r.Read = func(d *ResourceData, m interface{}) error {
   885  		if m != 42 {
   886  			return fmt.Errorf("meta not passed")
   887  		}
   888  
   889  		return d.Set("foo", d.Get("foo").(int)+1)
   890  	}
   891  
   892  	s := &terraform.InstanceState{
   893  		ID: "bar",
   894  		Attributes: map[string]string{
   895  			"foo": "12",
   896  		},
   897  	}
   898  
   899  	expected := &terraform.InstanceState{
   900  		ID: "bar",
   901  		Attributes: map[string]string{
   902  			"id":  "bar",
   903  			"foo": "13",
   904  		},
   905  		Meta: map[string]interface{}{
   906  			"schema_version": "2",
   907  		},
   908  	}
   909  
   910  	actual, err := r.Refresh(s, 42)
   911  	if err != nil {
   912  		t.Fatalf("err: %s", err)
   913  	}
   914  
   915  	if !reflect.DeepEqual(actual, expected) {
   916  		t.Fatalf("bad: %#v", actual)
   917  	}
   918  }
   919  
   920  func TestResourceRefresh_blankId(t *testing.T) {
   921  	r := &Resource{
   922  		Schema: map[string]*Schema{
   923  			"foo": &Schema{
   924  				Type:     TypeInt,
   925  				Optional: true,
   926  			},
   927  		},
   928  	}
   929  
   930  	r.Read = func(d *ResourceData, m interface{}) error {
   931  		d.SetId("foo")
   932  		return nil
   933  	}
   934  
   935  	s := &terraform.InstanceState{
   936  		ID:         "",
   937  		Attributes: map[string]string{},
   938  	}
   939  
   940  	actual, err := r.Refresh(s, 42)
   941  	if err != nil {
   942  		t.Fatalf("err: %s", err)
   943  	}
   944  	if actual != nil {
   945  		t.Fatalf("bad: %#v", actual)
   946  	}
   947  }
   948  
   949  func TestResourceRefresh_delete(t *testing.T) {
   950  	r := &Resource{
   951  		Schema: map[string]*Schema{
   952  			"foo": &Schema{
   953  				Type:     TypeInt,
   954  				Optional: true,
   955  			},
   956  		},
   957  	}
   958  
   959  	r.Read = func(d *ResourceData, m interface{}) error {
   960  		d.SetId("")
   961  		return nil
   962  	}
   963  
   964  	s := &terraform.InstanceState{
   965  		ID: "bar",
   966  		Attributes: map[string]string{
   967  			"foo": "12",
   968  		},
   969  	}
   970  
   971  	actual, err := r.Refresh(s, 42)
   972  	if err != nil {
   973  		t.Fatalf("err: %s", err)
   974  	}
   975  
   976  	if actual != nil {
   977  		t.Fatalf("bad: %#v", actual)
   978  	}
   979  }
   980  
   981  func TestResourceRefresh_existsError(t *testing.T) {
   982  	r := &Resource{
   983  		Schema: map[string]*Schema{
   984  			"foo": &Schema{
   985  				Type:     TypeInt,
   986  				Optional: true,
   987  			},
   988  		},
   989  	}
   990  
   991  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
   992  		return false, fmt.Errorf("error")
   993  	}
   994  
   995  	r.Read = func(d *ResourceData, m interface{}) error {
   996  		panic("shouldn't be called")
   997  	}
   998  
   999  	s := &terraform.InstanceState{
  1000  		ID: "bar",
  1001  		Attributes: map[string]string{
  1002  			"foo": "12",
  1003  		},
  1004  	}
  1005  
  1006  	actual, err := r.Refresh(s, 42)
  1007  	if err == nil {
  1008  		t.Fatalf("should error")
  1009  	}
  1010  	if !reflect.DeepEqual(actual, s) {
  1011  		t.Fatalf("bad: %#v", actual)
  1012  	}
  1013  }
  1014  
  1015  func TestResourceRefresh_noExists(t *testing.T) {
  1016  	r := &Resource{
  1017  		Schema: map[string]*Schema{
  1018  			"foo": &Schema{
  1019  				Type:     TypeInt,
  1020  				Optional: true,
  1021  			},
  1022  		},
  1023  	}
  1024  
  1025  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
  1026  		return false, nil
  1027  	}
  1028  
  1029  	r.Read = func(d *ResourceData, m interface{}) error {
  1030  		panic("shouldn't be called")
  1031  	}
  1032  
  1033  	s := &terraform.InstanceState{
  1034  		ID: "bar",
  1035  		Attributes: map[string]string{
  1036  			"foo": "12",
  1037  		},
  1038  	}
  1039  
  1040  	actual, err := r.Refresh(s, 42)
  1041  	if err != nil {
  1042  		t.Fatalf("err: %s", err)
  1043  	}
  1044  	if actual != nil {
  1045  		t.Fatalf("should have no state")
  1046  	}
  1047  }
  1048  
  1049  func TestResourceRefresh_needsMigration(t *testing.T) {
  1050  	// Schema v2 it deals only in newfoo, which tracks foo as an int
  1051  	r := &Resource{
  1052  		SchemaVersion: 2,
  1053  		Schema: map[string]*Schema{
  1054  			"newfoo": &Schema{
  1055  				Type:     TypeInt,
  1056  				Optional: true,
  1057  			},
  1058  		},
  1059  	}
  1060  
  1061  	r.Read = func(d *ResourceData, m interface{}) error {
  1062  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
  1063  	}
  1064  
  1065  	r.MigrateState = func(
  1066  		v int,
  1067  		s *terraform.InstanceState,
  1068  		meta interface{}) (*terraform.InstanceState, error) {
  1069  		// Real state migration functions will probably switch on this value,
  1070  		// but we'll just assert on it for now.
  1071  		if v != 1 {
  1072  			t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v)
  1073  		}
  1074  
  1075  		if meta != 42 {
  1076  			t.Fatal("Expected meta to be passed through to the migration function")
  1077  		}
  1078  
  1079  		oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64)
  1080  		if err != nil {
  1081  			t.Fatalf("err: %#v", err)
  1082  		}
  1083  		s.Attributes["newfoo"] = strconv.Itoa(int(oldfoo * 10))
  1084  		delete(s.Attributes, "oldfoo")
  1085  
  1086  		return s, nil
  1087  	}
  1088  
  1089  	// State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th
  1090  	// the scale of newfoo
  1091  	s := &terraform.InstanceState{
  1092  		ID: "bar",
  1093  		Attributes: map[string]string{
  1094  			"oldfoo": "1.2",
  1095  		},
  1096  		Meta: map[string]interface{}{
  1097  			"schema_version": "1",
  1098  		},
  1099  	}
  1100  
  1101  	actual, err := r.Refresh(s, 42)
  1102  	if err != nil {
  1103  		t.Fatalf("err: %s", err)
  1104  	}
  1105  
  1106  	expected := &terraform.InstanceState{
  1107  		ID: "bar",
  1108  		Attributes: map[string]string{
  1109  			"id":     "bar",
  1110  			"newfoo": "13",
  1111  		},
  1112  		Meta: map[string]interface{}{
  1113  			"schema_version": "2",
  1114  		},
  1115  	}
  1116  
  1117  	if !reflect.DeepEqual(actual, expected) {
  1118  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1119  	}
  1120  }
  1121  
  1122  func TestResourceRefresh_noMigrationNeeded(t *testing.T) {
  1123  	r := &Resource{
  1124  		SchemaVersion: 2,
  1125  		Schema: map[string]*Schema{
  1126  			"newfoo": &Schema{
  1127  				Type:     TypeInt,
  1128  				Optional: true,
  1129  			},
  1130  		},
  1131  	}
  1132  
  1133  	r.Read = func(d *ResourceData, m interface{}) error {
  1134  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
  1135  	}
  1136  
  1137  	r.MigrateState = func(
  1138  		v int,
  1139  		s *terraform.InstanceState,
  1140  		meta interface{}) (*terraform.InstanceState, error) {
  1141  		t.Fatal("Migrate function shouldn't be called!")
  1142  		return nil, nil
  1143  	}
  1144  
  1145  	s := &terraform.InstanceState{
  1146  		ID: "bar",
  1147  		Attributes: map[string]string{
  1148  			"newfoo": "12",
  1149  		},
  1150  		Meta: map[string]interface{}{
  1151  			"schema_version": "2",
  1152  		},
  1153  	}
  1154  
  1155  	actual, err := r.Refresh(s, nil)
  1156  	if err != nil {
  1157  		t.Fatalf("err: %s", err)
  1158  	}
  1159  
  1160  	expected := &terraform.InstanceState{
  1161  		ID: "bar",
  1162  		Attributes: map[string]string{
  1163  			"id":     "bar",
  1164  			"newfoo": "13",
  1165  		},
  1166  		Meta: map[string]interface{}{
  1167  			"schema_version": "2",
  1168  		},
  1169  	}
  1170  
  1171  	if !reflect.DeepEqual(actual, expected) {
  1172  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1173  	}
  1174  }
  1175  
  1176  func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) {
  1177  	r := &Resource{
  1178  		// Version 1 > Version 0
  1179  		SchemaVersion: 1,
  1180  		Schema: map[string]*Schema{
  1181  			"newfoo": &Schema{
  1182  				Type:     TypeInt,
  1183  				Optional: true,
  1184  			},
  1185  		},
  1186  	}
  1187  
  1188  	r.Read = func(d *ResourceData, m interface{}) error {
  1189  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
  1190  	}
  1191  
  1192  	r.MigrateState = func(
  1193  		v int,
  1194  		s *terraform.InstanceState,
  1195  		meta interface{}) (*terraform.InstanceState, error) {
  1196  		s.Attributes["newfoo"] = s.Attributes["oldfoo"]
  1197  		return s, nil
  1198  	}
  1199  
  1200  	s := &terraform.InstanceState{
  1201  		ID: "bar",
  1202  		Attributes: map[string]string{
  1203  			"oldfoo": "12",
  1204  		},
  1205  	}
  1206  
  1207  	actual, err := r.Refresh(s, nil)
  1208  	if err != nil {
  1209  		t.Fatalf("err: %s", err)
  1210  	}
  1211  
  1212  	expected := &terraform.InstanceState{
  1213  		ID: "bar",
  1214  		Attributes: map[string]string{
  1215  			"id":     "bar",
  1216  			"newfoo": "13",
  1217  		},
  1218  		Meta: map[string]interface{}{
  1219  			"schema_version": "1",
  1220  		},
  1221  	}
  1222  
  1223  	if !reflect.DeepEqual(actual, expected) {
  1224  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
  1225  	}
  1226  }
  1227  
  1228  func TestResourceRefresh_migrateStateErr(t *testing.T) {
  1229  	r := &Resource{
  1230  		SchemaVersion: 2,
  1231  		Schema: map[string]*Schema{
  1232  			"newfoo": &Schema{
  1233  				Type:     TypeInt,
  1234  				Optional: true,
  1235  			},
  1236  		},
  1237  	}
  1238  
  1239  	r.Read = func(d *ResourceData, m interface{}) error {
  1240  		t.Fatal("Read should never be called!")
  1241  		return nil
  1242  	}
  1243  
  1244  	r.MigrateState = func(
  1245  		v int,
  1246  		s *terraform.InstanceState,
  1247  		meta interface{}) (*terraform.InstanceState, error) {
  1248  		return s, fmt.Errorf("triggering an error")
  1249  	}
  1250  
  1251  	s := &terraform.InstanceState{
  1252  		ID: "bar",
  1253  		Attributes: map[string]string{
  1254  			"oldfoo": "12",
  1255  		},
  1256  	}
  1257  
  1258  	_, err := r.Refresh(s, nil)
  1259  	if err == nil {
  1260  		t.Fatal("expected error, but got none!")
  1261  	}
  1262  }
  1263  
  1264  func TestResourceData(t *testing.T) {
  1265  	r := &Resource{
  1266  		SchemaVersion: 2,
  1267  		Schema: map[string]*Schema{
  1268  			"foo": &Schema{
  1269  				Type:     TypeInt,
  1270  				Optional: true,
  1271  			},
  1272  		},
  1273  	}
  1274  
  1275  	state := &terraform.InstanceState{
  1276  		ID: "foo",
  1277  		Attributes: map[string]string{
  1278  			"id":  "foo",
  1279  			"foo": "42",
  1280  		},
  1281  	}
  1282  
  1283  	data := r.Data(state)
  1284  	if data.Id() != "foo" {
  1285  		t.Fatalf("err: %s", data.Id())
  1286  	}
  1287  	if v := data.Get("foo"); v != 42 {
  1288  		t.Fatalf("bad: %#v", v)
  1289  	}
  1290  
  1291  	// Set expectations
  1292  	state.Meta = map[string]interface{}{
  1293  		"schema_version": "2",
  1294  	}
  1295  
  1296  	result := data.State()
  1297  	if !reflect.DeepEqual(result, state) {
  1298  		t.Fatalf("bad: %#v", result)
  1299  	}
  1300  }
  1301  
  1302  func TestResourceData_blank(t *testing.T) {
  1303  	r := &Resource{
  1304  		SchemaVersion: 2,
  1305  		Schema: map[string]*Schema{
  1306  			"foo": &Schema{
  1307  				Type:     TypeInt,
  1308  				Optional: true,
  1309  			},
  1310  		},
  1311  	}
  1312  
  1313  	data := r.Data(nil)
  1314  	if data.Id() != "" {
  1315  		t.Fatalf("err: %s", data.Id())
  1316  	}
  1317  	if v := data.Get("foo"); v != 0 {
  1318  		t.Fatalf("bad: %#v", v)
  1319  	}
  1320  }