github.com/bigkraig/terraform@v0.6.4-0.20151219155159-c90d1b074e31/helper/schema/resource_test.go (about)

     1  package schema
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strconv"
     7  	"testing"
     8  
     9  	"github.com/hashicorp/terraform/terraform"
    10  )
    11  
    12  func TestResourceApply_create(t *testing.T) {
    13  	r := &Resource{
    14  		SchemaVersion: 2,
    15  		Schema: map[string]*Schema{
    16  			"foo": &Schema{
    17  				Type:     TypeInt,
    18  				Optional: true,
    19  			},
    20  		},
    21  	}
    22  
    23  	called := false
    24  	r.Create = func(d *ResourceData, m interface{}) error {
    25  		called = true
    26  		d.SetId("foo")
    27  		return nil
    28  	}
    29  
    30  	var s *terraform.InstanceState = nil
    31  
    32  	d := &terraform.InstanceDiff{
    33  		Attributes: map[string]*terraform.ResourceAttrDiff{
    34  			"foo": &terraform.ResourceAttrDiff{
    35  				New: "42",
    36  			},
    37  		},
    38  	}
    39  
    40  	actual, err := r.Apply(s, d, nil)
    41  	if err != nil {
    42  		t.Fatalf("err: %s", err)
    43  	}
    44  
    45  	if !called {
    46  		t.Fatal("not called")
    47  	}
    48  
    49  	expected := &terraform.InstanceState{
    50  		ID: "foo",
    51  		Attributes: map[string]string{
    52  			"id":  "foo",
    53  			"foo": "42",
    54  		},
    55  		Meta: map[string]string{
    56  			"schema_version": "2",
    57  		},
    58  	}
    59  
    60  	if !reflect.DeepEqual(actual, expected) {
    61  		t.Fatalf("bad: %#v", actual)
    62  	}
    63  }
    64  
    65  func TestResourceApply_destroy(t *testing.T) {
    66  	r := &Resource{
    67  		Schema: map[string]*Schema{
    68  			"foo": &Schema{
    69  				Type:     TypeInt,
    70  				Optional: true,
    71  			},
    72  		},
    73  	}
    74  
    75  	called := false
    76  	r.Delete = func(d *ResourceData, m interface{}) error {
    77  		called = true
    78  		return nil
    79  	}
    80  
    81  	s := &terraform.InstanceState{
    82  		ID: "bar",
    83  	}
    84  
    85  	d := &terraform.InstanceDiff{
    86  		Destroy: true,
    87  	}
    88  
    89  	actual, err := r.Apply(s, d, nil)
    90  	if err != nil {
    91  		t.Fatalf("err: %s", err)
    92  	}
    93  
    94  	if !called {
    95  		t.Fatal("delete not called")
    96  	}
    97  
    98  	if actual != nil {
    99  		t.Fatalf("bad: %#v", actual)
   100  	}
   101  }
   102  
   103  func TestResourceApply_destroyCreate(t *testing.T) {
   104  	r := &Resource{
   105  		Schema: map[string]*Schema{
   106  			"foo": &Schema{
   107  				Type:     TypeInt,
   108  				Optional: true,
   109  			},
   110  
   111  			"tags": &Schema{
   112  				Type:     TypeMap,
   113  				Optional: true,
   114  				Computed: true,
   115  			},
   116  		},
   117  	}
   118  
   119  	change := false
   120  	r.Create = func(d *ResourceData, m interface{}) error {
   121  		change = d.HasChange("tags")
   122  		d.SetId("foo")
   123  		return nil
   124  	}
   125  	r.Delete = func(d *ResourceData, m interface{}) error {
   126  		return nil
   127  	}
   128  
   129  	var s *terraform.InstanceState = &terraform.InstanceState{
   130  		ID: "bar",
   131  		Attributes: map[string]string{
   132  			"foo":       "bar",
   133  			"tags.Name": "foo",
   134  		},
   135  	}
   136  
   137  	d := &terraform.InstanceDiff{
   138  		Attributes: map[string]*terraform.ResourceAttrDiff{
   139  			"foo": &terraform.ResourceAttrDiff{
   140  				New:         "42",
   141  				RequiresNew: true,
   142  			},
   143  			"tags.Name": &terraform.ResourceAttrDiff{
   144  				Old:         "foo",
   145  				New:         "foo",
   146  				RequiresNew: true,
   147  			},
   148  		},
   149  	}
   150  
   151  	actual, err := r.Apply(s, d, nil)
   152  	if err != nil {
   153  		t.Fatalf("err: %s", err)
   154  	}
   155  
   156  	if !change {
   157  		t.Fatal("should have change")
   158  	}
   159  
   160  	expected := &terraform.InstanceState{
   161  		ID: "foo",
   162  		Attributes: map[string]string{
   163  			"id":        "foo",
   164  			"foo":       "42",
   165  			"tags.#":    "1",
   166  			"tags.Name": "foo",
   167  		},
   168  	}
   169  
   170  	if !reflect.DeepEqual(actual, expected) {
   171  		t.Fatalf("bad: %#v", actual)
   172  	}
   173  }
   174  
   175  func TestResourceApply_destroyPartial(t *testing.T) {
   176  	r := &Resource{
   177  		Schema: map[string]*Schema{
   178  			"foo": &Schema{
   179  				Type:     TypeInt,
   180  				Optional: true,
   181  			},
   182  		},
   183  		SchemaVersion: 3,
   184  	}
   185  
   186  	r.Delete = func(d *ResourceData, m interface{}) error {
   187  		d.Set("foo", 42)
   188  		return fmt.Errorf("some error")
   189  	}
   190  
   191  	s := &terraform.InstanceState{
   192  		ID: "bar",
   193  		Attributes: map[string]string{
   194  			"foo": "12",
   195  		},
   196  	}
   197  
   198  	d := &terraform.InstanceDiff{
   199  		Destroy: true,
   200  	}
   201  
   202  	actual, err := r.Apply(s, d, nil)
   203  	if err == nil {
   204  		t.Fatal("should error")
   205  	}
   206  
   207  	expected := &terraform.InstanceState{
   208  		ID: "bar",
   209  		Attributes: map[string]string{
   210  			"id":  "bar",
   211  			"foo": "42",
   212  		},
   213  		Meta: map[string]string{
   214  			"schema_version": "3",
   215  		},
   216  	}
   217  
   218  	if !reflect.DeepEqual(actual, expected) {
   219  		t.Fatalf("expected:\n%#v\n\ngot:\n%#v", expected, actual)
   220  	}
   221  }
   222  
   223  func TestResourceApply_update(t *testing.T) {
   224  	r := &Resource{
   225  		Schema: map[string]*Schema{
   226  			"foo": &Schema{
   227  				Type:     TypeInt,
   228  				Optional: true,
   229  			},
   230  		},
   231  	}
   232  
   233  	r.Update = func(d *ResourceData, m interface{}) error {
   234  		d.Set("foo", 42)
   235  		return nil
   236  	}
   237  
   238  	s := &terraform.InstanceState{
   239  		ID: "foo",
   240  		Attributes: map[string]string{
   241  			"foo": "12",
   242  		},
   243  	}
   244  
   245  	d := &terraform.InstanceDiff{
   246  		Attributes: map[string]*terraform.ResourceAttrDiff{
   247  			"foo": &terraform.ResourceAttrDiff{
   248  				New: "13",
   249  			},
   250  		},
   251  	}
   252  
   253  	actual, err := r.Apply(s, d, nil)
   254  	if err != nil {
   255  		t.Fatalf("err: %s", err)
   256  	}
   257  
   258  	expected := &terraform.InstanceState{
   259  		ID: "foo",
   260  		Attributes: map[string]string{
   261  			"id":  "foo",
   262  			"foo": "42",
   263  		},
   264  	}
   265  
   266  	if !reflect.DeepEqual(actual, expected) {
   267  		t.Fatalf("bad: %#v", actual)
   268  	}
   269  }
   270  
   271  func TestResourceApply_updateNoCallback(t *testing.T) {
   272  	r := &Resource{
   273  		Schema: map[string]*Schema{
   274  			"foo": &Schema{
   275  				Type:     TypeInt,
   276  				Optional: true,
   277  			},
   278  		},
   279  	}
   280  
   281  	r.Update = nil
   282  
   283  	s := &terraform.InstanceState{
   284  		ID: "foo",
   285  		Attributes: map[string]string{
   286  			"foo": "12",
   287  		},
   288  	}
   289  
   290  	d := &terraform.InstanceDiff{
   291  		Attributes: map[string]*terraform.ResourceAttrDiff{
   292  			"foo": &terraform.ResourceAttrDiff{
   293  				New: "13",
   294  			},
   295  		},
   296  	}
   297  
   298  	actual, err := r.Apply(s, d, nil)
   299  	if err == nil {
   300  		t.Fatal("should error")
   301  	}
   302  
   303  	expected := &terraform.InstanceState{
   304  		ID: "foo",
   305  		Attributes: map[string]string{
   306  			"foo": "12",
   307  		},
   308  	}
   309  
   310  	if !reflect.DeepEqual(actual, expected) {
   311  		t.Fatalf("bad: %#v", actual)
   312  	}
   313  }
   314  
   315  func TestResourceInternalValidate(t *testing.T) {
   316  	cases := []struct {
   317  		In  *Resource
   318  		Err bool
   319  	}{
   320  		{
   321  			nil,
   322  			true,
   323  		},
   324  
   325  		// No optional and no required
   326  		{
   327  			&Resource{
   328  				Schema: map[string]*Schema{
   329  					"foo": &Schema{
   330  						Type:     TypeInt,
   331  						Optional: true,
   332  						Required: true,
   333  					},
   334  				},
   335  			},
   336  			true,
   337  		},
   338  
   339  		// Update undefined for non-ForceNew field
   340  		{
   341  			&Resource{
   342  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   343  				Schema: map[string]*Schema{
   344  					"boo": &Schema{
   345  						Type:     TypeInt,
   346  						Optional: true,
   347  					},
   348  				},
   349  			},
   350  			true,
   351  		},
   352  
   353  		// Update defined for ForceNew field
   354  		{
   355  			&Resource{
   356  				Create: func(d *ResourceData, meta interface{}) error { return nil },
   357  				Update: func(d *ResourceData, meta interface{}) error { return nil },
   358  				Schema: map[string]*Schema{
   359  					"goo": &Schema{
   360  						Type:     TypeInt,
   361  						Optional: true,
   362  						ForceNew: true,
   363  					},
   364  				},
   365  			},
   366  			true,
   367  		},
   368  	}
   369  
   370  	for i, tc := range cases {
   371  		err := tc.In.InternalValidate(schemaMap{})
   372  		if err != nil != tc.Err {
   373  			t.Fatalf("%d: bad: %s", i, err)
   374  		}
   375  	}
   376  }
   377  
   378  func TestResourceRefresh(t *testing.T) {
   379  	r := &Resource{
   380  		SchemaVersion: 2,
   381  		Schema: map[string]*Schema{
   382  			"foo": &Schema{
   383  				Type:     TypeInt,
   384  				Optional: true,
   385  			},
   386  		},
   387  	}
   388  
   389  	r.Read = func(d *ResourceData, m interface{}) error {
   390  		if m != 42 {
   391  			return fmt.Errorf("meta not passed")
   392  		}
   393  
   394  		return d.Set("foo", d.Get("foo").(int)+1)
   395  	}
   396  
   397  	s := &terraform.InstanceState{
   398  		ID: "bar",
   399  		Attributes: map[string]string{
   400  			"foo": "12",
   401  		},
   402  	}
   403  
   404  	expected := &terraform.InstanceState{
   405  		ID: "bar",
   406  		Attributes: map[string]string{
   407  			"id":  "bar",
   408  			"foo": "13",
   409  		},
   410  		Meta: map[string]string{
   411  			"schema_version": "2",
   412  		},
   413  	}
   414  
   415  	actual, err := r.Refresh(s, 42)
   416  	if err != nil {
   417  		t.Fatalf("err: %s", err)
   418  	}
   419  
   420  	if !reflect.DeepEqual(actual, expected) {
   421  		t.Fatalf("bad: %#v", actual)
   422  	}
   423  }
   424  
   425  func TestResourceRefresh_blankId(t *testing.T) {
   426  	r := &Resource{
   427  		Schema: map[string]*Schema{
   428  			"foo": &Schema{
   429  				Type:     TypeInt,
   430  				Optional: true,
   431  			},
   432  		},
   433  	}
   434  
   435  	r.Read = func(d *ResourceData, m interface{}) error {
   436  		d.SetId("foo")
   437  		return nil
   438  	}
   439  
   440  	s := &terraform.InstanceState{
   441  		ID:         "",
   442  		Attributes: map[string]string{},
   443  	}
   444  
   445  	actual, err := r.Refresh(s, 42)
   446  	if err != nil {
   447  		t.Fatalf("err: %s", err)
   448  	}
   449  	if actual != nil {
   450  		t.Fatalf("bad: %#v", actual)
   451  	}
   452  }
   453  
   454  func TestResourceRefresh_delete(t *testing.T) {
   455  	r := &Resource{
   456  		Schema: map[string]*Schema{
   457  			"foo": &Schema{
   458  				Type:     TypeInt,
   459  				Optional: true,
   460  			},
   461  		},
   462  	}
   463  
   464  	r.Read = func(d *ResourceData, m interface{}) error {
   465  		d.SetId("")
   466  		return nil
   467  	}
   468  
   469  	s := &terraform.InstanceState{
   470  		ID: "bar",
   471  		Attributes: map[string]string{
   472  			"foo": "12",
   473  		},
   474  	}
   475  
   476  	actual, err := r.Refresh(s, 42)
   477  	if err != nil {
   478  		t.Fatalf("err: %s", err)
   479  	}
   480  
   481  	if actual != nil {
   482  		t.Fatalf("bad: %#v", actual)
   483  	}
   484  }
   485  
   486  func TestResourceRefresh_existsError(t *testing.T) {
   487  	r := &Resource{
   488  		Schema: map[string]*Schema{
   489  			"foo": &Schema{
   490  				Type:     TypeInt,
   491  				Optional: true,
   492  			},
   493  		},
   494  	}
   495  
   496  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
   497  		return false, fmt.Errorf("error")
   498  	}
   499  
   500  	r.Read = func(d *ResourceData, m interface{}) error {
   501  		panic("shouldn't be called")
   502  	}
   503  
   504  	s := &terraform.InstanceState{
   505  		ID: "bar",
   506  		Attributes: map[string]string{
   507  			"foo": "12",
   508  		},
   509  	}
   510  
   511  	actual, err := r.Refresh(s, 42)
   512  	if err == nil {
   513  		t.Fatalf("should error")
   514  	}
   515  	if !reflect.DeepEqual(actual, s) {
   516  		t.Fatalf("bad: %#v", actual)
   517  	}
   518  }
   519  
   520  func TestResourceRefresh_noExists(t *testing.T) {
   521  	r := &Resource{
   522  		Schema: map[string]*Schema{
   523  			"foo": &Schema{
   524  				Type:     TypeInt,
   525  				Optional: true,
   526  			},
   527  		},
   528  	}
   529  
   530  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
   531  		return false, nil
   532  	}
   533  
   534  	r.Read = func(d *ResourceData, m interface{}) error {
   535  		panic("shouldn't be called")
   536  	}
   537  
   538  	s := &terraform.InstanceState{
   539  		ID: "bar",
   540  		Attributes: map[string]string{
   541  			"foo": "12",
   542  		},
   543  	}
   544  
   545  	actual, err := r.Refresh(s, 42)
   546  	if err != nil {
   547  		t.Fatalf("err: %s", err)
   548  	}
   549  	if actual != nil {
   550  		t.Fatalf("should have no state")
   551  	}
   552  }
   553  
   554  func TestResourceRefresh_needsMigration(t *testing.T) {
   555  	// Schema v2 it deals only in newfoo, which tracks foo as an int
   556  	r := &Resource{
   557  		SchemaVersion: 2,
   558  		Schema: map[string]*Schema{
   559  			"newfoo": &Schema{
   560  				Type:     TypeInt,
   561  				Optional: true,
   562  			},
   563  		},
   564  	}
   565  
   566  	r.Read = func(d *ResourceData, m interface{}) error {
   567  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
   568  	}
   569  
   570  	r.MigrateState = func(
   571  		v int,
   572  		s *terraform.InstanceState,
   573  		meta interface{}) (*terraform.InstanceState, error) {
   574  		// Real state migration functions will probably switch on this value,
   575  		// but we'll just assert on it for now.
   576  		if v != 1 {
   577  			t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v)
   578  		}
   579  
   580  		if meta != 42 {
   581  			t.Fatal("Expected meta to be passed through to the migration function")
   582  		}
   583  
   584  		oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64)
   585  		if err != nil {
   586  			t.Fatalf("err: %#v", err)
   587  		}
   588  		s.Attributes["newfoo"] = strconv.Itoa(int(oldfoo * 10))
   589  		delete(s.Attributes, "oldfoo")
   590  
   591  		return s, nil
   592  	}
   593  
   594  	// State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th
   595  	// the scale of newfoo
   596  	s := &terraform.InstanceState{
   597  		ID: "bar",
   598  		Attributes: map[string]string{
   599  			"oldfoo": "1.2",
   600  		},
   601  		Meta: map[string]string{
   602  			"schema_version": "1",
   603  		},
   604  	}
   605  
   606  	actual, err := r.Refresh(s, 42)
   607  	if err != nil {
   608  		t.Fatalf("err: %s", err)
   609  	}
   610  
   611  	expected := &terraform.InstanceState{
   612  		ID: "bar",
   613  		Attributes: map[string]string{
   614  			"id":     "bar",
   615  			"newfoo": "13",
   616  		},
   617  		Meta: map[string]string{
   618  			"schema_version": "2",
   619  		},
   620  	}
   621  
   622  	if !reflect.DeepEqual(actual, expected) {
   623  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
   624  	}
   625  }
   626  
   627  func TestResourceRefresh_noMigrationNeeded(t *testing.T) {
   628  	r := &Resource{
   629  		SchemaVersion: 2,
   630  		Schema: map[string]*Schema{
   631  			"newfoo": &Schema{
   632  				Type:     TypeInt,
   633  				Optional: true,
   634  			},
   635  		},
   636  	}
   637  
   638  	r.Read = func(d *ResourceData, m interface{}) error {
   639  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
   640  	}
   641  
   642  	r.MigrateState = func(
   643  		v int,
   644  		s *terraform.InstanceState,
   645  		meta interface{}) (*terraform.InstanceState, error) {
   646  		t.Fatal("Migrate function shouldn't be called!")
   647  		return nil, nil
   648  	}
   649  
   650  	s := &terraform.InstanceState{
   651  		ID: "bar",
   652  		Attributes: map[string]string{
   653  			"newfoo": "12",
   654  		},
   655  		Meta: map[string]string{
   656  			"schema_version": "2",
   657  		},
   658  	}
   659  
   660  	actual, err := r.Refresh(s, nil)
   661  	if err != nil {
   662  		t.Fatalf("err: %s", err)
   663  	}
   664  
   665  	expected := &terraform.InstanceState{
   666  		ID: "bar",
   667  		Attributes: map[string]string{
   668  			"id":     "bar",
   669  			"newfoo": "13",
   670  		},
   671  		Meta: map[string]string{
   672  			"schema_version": "2",
   673  		},
   674  	}
   675  
   676  	if !reflect.DeepEqual(actual, expected) {
   677  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
   678  	}
   679  }
   680  
   681  func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) {
   682  	r := &Resource{
   683  		// Version 1 > Version 0
   684  		SchemaVersion: 1,
   685  		Schema: map[string]*Schema{
   686  			"newfoo": &Schema{
   687  				Type:     TypeInt,
   688  				Optional: true,
   689  			},
   690  		},
   691  	}
   692  
   693  	r.Read = func(d *ResourceData, m interface{}) error {
   694  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
   695  	}
   696  
   697  	r.MigrateState = func(
   698  		v int,
   699  		s *terraform.InstanceState,
   700  		meta interface{}) (*terraform.InstanceState, error) {
   701  		s.Attributes["newfoo"] = s.Attributes["oldfoo"]
   702  		return s, nil
   703  	}
   704  
   705  	s := &terraform.InstanceState{
   706  		ID: "bar",
   707  		Attributes: map[string]string{
   708  			"oldfoo": "12",
   709  		},
   710  	}
   711  
   712  	actual, err := r.Refresh(s, nil)
   713  	if err != nil {
   714  		t.Fatalf("err: %s", err)
   715  	}
   716  
   717  	expected := &terraform.InstanceState{
   718  		ID: "bar",
   719  		Attributes: map[string]string{
   720  			"id":     "bar",
   721  			"newfoo": "13",
   722  		},
   723  		Meta: map[string]string{
   724  			"schema_version": "1",
   725  		},
   726  	}
   727  
   728  	if !reflect.DeepEqual(actual, expected) {
   729  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
   730  	}
   731  }
   732  
   733  func TestResourceRefresh_migrateStateErr(t *testing.T) {
   734  	r := &Resource{
   735  		SchemaVersion: 2,
   736  		Schema: map[string]*Schema{
   737  			"newfoo": &Schema{
   738  				Type:     TypeInt,
   739  				Optional: true,
   740  			},
   741  		},
   742  	}
   743  
   744  	r.Read = func(d *ResourceData, m interface{}) error {
   745  		t.Fatal("Read should never be called!")
   746  		return nil
   747  	}
   748  
   749  	r.MigrateState = func(
   750  		v int,
   751  		s *terraform.InstanceState,
   752  		meta interface{}) (*terraform.InstanceState, error) {
   753  		return s, fmt.Errorf("triggering an error")
   754  	}
   755  
   756  	s := &terraform.InstanceState{
   757  		ID: "bar",
   758  		Attributes: map[string]string{
   759  			"oldfoo": "12",
   760  		},
   761  	}
   762  
   763  	_, err := r.Refresh(s, nil)
   764  	if err == nil {
   765  		t.Fatal("expected error, but got none!")
   766  	}
   767  }