github.com/acm1/terraform@v0.6.2-0.20150729164239-1f314444f45c/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  	}
   184  
   185  	r.Delete = func(d *ResourceData, m interface{}) error {
   186  		d.Set("foo", 42)
   187  		return fmt.Errorf("some error")
   188  	}
   189  
   190  	s := &terraform.InstanceState{
   191  		ID: "bar",
   192  		Attributes: map[string]string{
   193  			"foo": "12",
   194  		},
   195  	}
   196  
   197  	d := &terraform.InstanceDiff{
   198  		Destroy: true,
   199  	}
   200  
   201  	actual, err := r.Apply(s, d, nil)
   202  	if err == nil {
   203  		t.Fatal("should error")
   204  	}
   205  
   206  	expected := &terraform.InstanceState{
   207  		ID: "bar",
   208  		Attributes: map[string]string{
   209  			"id":  "bar",
   210  			"foo": "42",
   211  		},
   212  	}
   213  
   214  	if !reflect.DeepEqual(actual, expected) {
   215  		t.Fatalf("bad: %#v", actual)
   216  	}
   217  }
   218  
   219  func TestResourceApply_update(t *testing.T) {
   220  	r := &Resource{
   221  		Schema: map[string]*Schema{
   222  			"foo": &Schema{
   223  				Type:     TypeInt,
   224  				Optional: true,
   225  			},
   226  		},
   227  	}
   228  
   229  	r.Update = func(d *ResourceData, m interface{}) error {
   230  		d.Set("foo", 42)
   231  		return nil
   232  	}
   233  
   234  	s := &terraform.InstanceState{
   235  		ID: "foo",
   236  		Attributes: map[string]string{
   237  			"foo": "12",
   238  		},
   239  	}
   240  
   241  	d := &terraform.InstanceDiff{
   242  		Attributes: map[string]*terraform.ResourceAttrDiff{
   243  			"foo": &terraform.ResourceAttrDiff{
   244  				New: "13",
   245  			},
   246  		},
   247  	}
   248  
   249  	actual, err := r.Apply(s, d, nil)
   250  	if err != nil {
   251  		t.Fatalf("err: %s", err)
   252  	}
   253  
   254  	expected := &terraform.InstanceState{
   255  		ID: "foo",
   256  		Attributes: map[string]string{
   257  			"id":  "foo",
   258  			"foo": "42",
   259  		},
   260  	}
   261  
   262  	if !reflect.DeepEqual(actual, expected) {
   263  		t.Fatalf("bad: %#v", actual)
   264  	}
   265  }
   266  
   267  func TestResourceApply_updateNoCallback(t *testing.T) {
   268  	r := &Resource{
   269  		Schema: map[string]*Schema{
   270  			"foo": &Schema{
   271  				Type:     TypeInt,
   272  				Optional: true,
   273  			},
   274  		},
   275  	}
   276  
   277  	r.Update = nil
   278  
   279  	s := &terraform.InstanceState{
   280  		ID: "foo",
   281  		Attributes: map[string]string{
   282  			"foo": "12",
   283  		},
   284  	}
   285  
   286  	d := &terraform.InstanceDiff{
   287  		Attributes: map[string]*terraform.ResourceAttrDiff{
   288  			"foo": &terraform.ResourceAttrDiff{
   289  				New: "13",
   290  			},
   291  		},
   292  	}
   293  
   294  	actual, err := r.Apply(s, d, nil)
   295  	if err == nil {
   296  		t.Fatal("should error")
   297  	}
   298  
   299  	expected := &terraform.InstanceState{
   300  		ID: "foo",
   301  		Attributes: map[string]string{
   302  			"foo": "12",
   303  		},
   304  	}
   305  
   306  	if !reflect.DeepEqual(actual, expected) {
   307  		t.Fatalf("bad: %#v", actual)
   308  	}
   309  }
   310  
   311  func TestResourceInternalValidate(t *testing.T) {
   312  	cases := []struct {
   313  		In  *Resource
   314  		Err bool
   315  	}{
   316  		{
   317  			nil,
   318  			true,
   319  		},
   320  
   321  		// No optional and no required
   322  		{
   323  			&Resource{
   324  				Schema: map[string]*Schema{
   325  					"foo": &Schema{
   326  						Type:     TypeInt,
   327  						Optional: true,
   328  						Required: true,
   329  					},
   330  				},
   331  			},
   332  			true,
   333  		},
   334  	}
   335  
   336  	for i, tc := range cases {
   337  		err := tc.In.InternalValidate(schemaMap{})
   338  		if (err != nil) != tc.Err {
   339  			t.Fatalf("%d: bad: %s", i, err)
   340  		}
   341  	}
   342  }
   343  
   344  func TestResourceRefresh(t *testing.T) {
   345  	r := &Resource{
   346  		SchemaVersion: 2,
   347  		Schema: map[string]*Schema{
   348  			"foo": &Schema{
   349  				Type:     TypeInt,
   350  				Optional: true,
   351  			},
   352  		},
   353  	}
   354  
   355  	r.Read = func(d *ResourceData, m interface{}) error {
   356  		if m != 42 {
   357  			return fmt.Errorf("meta not passed")
   358  		}
   359  
   360  		return d.Set("foo", d.Get("foo").(int)+1)
   361  	}
   362  
   363  	s := &terraform.InstanceState{
   364  		ID: "bar",
   365  		Attributes: map[string]string{
   366  			"foo": "12",
   367  		},
   368  	}
   369  
   370  	expected := &terraform.InstanceState{
   371  		ID: "bar",
   372  		Attributes: map[string]string{
   373  			"id":  "bar",
   374  			"foo": "13",
   375  		},
   376  		Meta: map[string]string{
   377  			"schema_version": "2",
   378  		},
   379  	}
   380  
   381  	actual, err := r.Refresh(s, 42)
   382  	if err != nil {
   383  		t.Fatalf("err: %s", err)
   384  	}
   385  
   386  	if !reflect.DeepEqual(actual, expected) {
   387  		t.Fatalf("bad: %#v", actual)
   388  	}
   389  }
   390  
   391  func TestResourceRefresh_blankId(t *testing.T) {
   392  	r := &Resource{
   393  		Schema: map[string]*Schema{
   394  			"foo": &Schema{
   395  				Type:     TypeInt,
   396  				Optional: true,
   397  			},
   398  		},
   399  	}
   400  
   401  	r.Read = func(d *ResourceData, m interface{}) error {
   402  		d.SetId("foo")
   403  		return nil
   404  	}
   405  
   406  	s := &terraform.InstanceState{
   407  		ID:         "",
   408  		Attributes: map[string]string{},
   409  	}
   410  
   411  	actual, err := r.Refresh(s, 42)
   412  	if err != nil {
   413  		t.Fatalf("err: %s", err)
   414  	}
   415  	if actual != nil {
   416  		t.Fatalf("bad: %#v", actual)
   417  	}
   418  }
   419  
   420  func TestResourceRefresh_delete(t *testing.T) {
   421  	r := &Resource{
   422  		Schema: map[string]*Schema{
   423  			"foo": &Schema{
   424  				Type:     TypeInt,
   425  				Optional: true,
   426  			},
   427  		},
   428  	}
   429  
   430  	r.Read = func(d *ResourceData, m interface{}) error {
   431  		d.SetId("")
   432  		return nil
   433  	}
   434  
   435  	s := &terraform.InstanceState{
   436  		ID: "bar",
   437  		Attributes: map[string]string{
   438  			"foo": "12",
   439  		},
   440  	}
   441  
   442  	actual, err := r.Refresh(s, 42)
   443  	if err != nil {
   444  		t.Fatalf("err: %s", err)
   445  	}
   446  
   447  	if actual != nil {
   448  		t.Fatalf("bad: %#v", actual)
   449  	}
   450  }
   451  
   452  func TestResourceRefresh_existsError(t *testing.T) {
   453  	r := &Resource{
   454  		Schema: map[string]*Schema{
   455  			"foo": &Schema{
   456  				Type:     TypeInt,
   457  				Optional: true,
   458  			},
   459  		},
   460  	}
   461  
   462  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
   463  		return false, fmt.Errorf("error")
   464  	}
   465  
   466  	r.Read = func(d *ResourceData, m interface{}) error {
   467  		panic("shouldn't be called")
   468  	}
   469  
   470  	s := &terraform.InstanceState{
   471  		ID: "bar",
   472  		Attributes: map[string]string{
   473  			"foo": "12",
   474  		},
   475  	}
   476  
   477  	actual, err := r.Refresh(s, 42)
   478  	if err == nil {
   479  		t.Fatalf("should error")
   480  	}
   481  	if !reflect.DeepEqual(actual, s) {
   482  		t.Fatalf("bad: %#v", actual)
   483  	}
   484  }
   485  
   486  func TestResourceRefresh_noExists(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, nil
   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("err: %s", err)
   514  	}
   515  	if actual != nil {
   516  		t.Fatalf("should have no state")
   517  	}
   518  }
   519  
   520  func TestResourceRefresh_needsMigration(t *testing.T) {
   521  	// Schema v2 it deals only in newfoo, which tracks foo as an int
   522  	r := &Resource{
   523  		SchemaVersion: 2,
   524  		Schema: map[string]*Schema{
   525  			"newfoo": &Schema{
   526  				Type:     TypeInt,
   527  				Optional: true,
   528  			},
   529  		},
   530  	}
   531  
   532  	r.Read = func(d *ResourceData, m interface{}) error {
   533  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
   534  	}
   535  
   536  	r.MigrateState = func(
   537  		v int,
   538  		s *terraform.InstanceState,
   539  		meta interface{}) (*terraform.InstanceState, error) {
   540  		// Real state migration functions will probably switch on this value,
   541  		// but we'll just assert on it for now.
   542  		if v != 1 {
   543  			t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v)
   544  		}
   545  
   546  		if meta != 42 {
   547  			t.Fatal("Expected meta to be passed through to the migration function")
   548  		}
   549  
   550  		oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64)
   551  		if err != nil {
   552  			t.Fatalf("err: %#v", err)
   553  		}
   554  		s.Attributes["newfoo"] = strconv.Itoa((int(oldfoo * 10)))
   555  		delete(s.Attributes, "oldfoo")
   556  
   557  		return s, nil
   558  	}
   559  
   560  	// State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th
   561  	// the scale of newfoo
   562  	s := &terraform.InstanceState{
   563  		ID: "bar",
   564  		Attributes: map[string]string{
   565  			"oldfoo": "1.2",
   566  		},
   567  		Meta: map[string]string{
   568  			"schema_version": "1",
   569  		},
   570  	}
   571  
   572  	actual, err := r.Refresh(s, 42)
   573  	if err != nil {
   574  		t.Fatalf("err: %s", err)
   575  	}
   576  
   577  	expected := &terraform.InstanceState{
   578  		ID: "bar",
   579  		Attributes: map[string]string{
   580  			"id":     "bar",
   581  			"newfoo": "13",
   582  		},
   583  		Meta: map[string]string{
   584  			"schema_version": "2",
   585  		},
   586  	}
   587  
   588  	if !reflect.DeepEqual(actual, expected) {
   589  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
   590  	}
   591  }
   592  
   593  func TestResourceRefresh_noMigrationNeeded(t *testing.T) {
   594  	r := &Resource{
   595  		SchemaVersion: 2,
   596  		Schema: map[string]*Schema{
   597  			"newfoo": &Schema{
   598  				Type:     TypeInt,
   599  				Optional: true,
   600  			},
   601  		},
   602  	}
   603  
   604  	r.Read = func(d *ResourceData, m interface{}) error {
   605  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
   606  	}
   607  
   608  	r.MigrateState = func(
   609  		v int,
   610  		s *terraform.InstanceState,
   611  		meta interface{}) (*terraform.InstanceState, error) {
   612  		t.Fatal("Migrate function shouldn't be called!")
   613  		return nil, nil
   614  	}
   615  
   616  	s := &terraform.InstanceState{
   617  		ID: "bar",
   618  		Attributes: map[string]string{
   619  			"newfoo": "12",
   620  		},
   621  		Meta: map[string]string{
   622  			"schema_version": "2",
   623  		},
   624  	}
   625  
   626  	actual, err := r.Refresh(s, nil)
   627  	if err != nil {
   628  		t.Fatalf("err: %s", err)
   629  	}
   630  
   631  	expected := &terraform.InstanceState{
   632  		ID: "bar",
   633  		Attributes: map[string]string{
   634  			"id":     "bar",
   635  			"newfoo": "13",
   636  		},
   637  		Meta: map[string]string{
   638  			"schema_version": "2",
   639  		},
   640  	}
   641  
   642  	if !reflect.DeepEqual(actual, expected) {
   643  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
   644  	}
   645  }
   646  
   647  func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) {
   648  	r := &Resource{
   649  		// Version 1 > Version 0
   650  		SchemaVersion: 1,
   651  		Schema: map[string]*Schema{
   652  			"newfoo": &Schema{
   653  				Type:     TypeInt,
   654  				Optional: true,
   655  			},
   656  		},
   657  	}
   658  
   659  	r.Read = func(d *ResourceData, m interface{}) error {
   660  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
   661  	}
   662  
   663  	r.MigrateState = func(
   664  		v int,
   665  		s *terraform.InstanceState,
   666  		meta interface{}) (*terraform.InstanceState, error) {
   667  		s.Attributes["newfoo"] = s.Attributes["oldfoo"]
   668  		return s, nil
   669  	}
   670  
   671  	s := &terraform.InstanceState{
   672  		ID: "bar",
   673  		Attributes: map[string]string{
   674  			"oldfoo": "12",
   675  		},
   676  	}
   677  
   678  	actual, err := r.Refresh(s, nil)
   679  	if err != nil {
   680  		t.Fatalf("err: %s", err)
   681  	}
   682  
   683  	expected := &terraform.InstanceState{
   684  		ID: "bar",
   685  		Attributes: map[string]string{
   686  			"id":     "bar",
   687  			"newfoo": "13",
   688  		},
   689  		Meta: map[string]string{
   690  			"schema_version": "1",
   691  		},
   692  	}
   693  
   694  	if !reflect.DeepEqual(actual, expected) {
   695  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
   696  	}
   697  }
   698  
   699  func TestResourceRefresh_migrateStateErr(t *testing.T) {
   700  	r := &Resource{
   701  		SchemaVersion: 2,
   702  		Schema: map[string]*Schema{
   703  			"newfoo": &Schema{
   704  				Type:     TypeInt,
   705  				Optional: true,
   706  			},
   707  		},
   708  	}
   709  
   710  	r.Read = func(d *ResourceData, m interface{}) error {
   711  		t.Fatal("Read should never be called!")
   712  		return nil
   713  	}
   714  
   715  	r.MigrateState = func(
   716  		v int,
   717  		s *terraform.InstanceState,
   718  		meta interface{}) (*terraform.InstanceState, error) {
   719  		return s, fmt.Errorf("triggering an error")
   720  	}
   721  
   722  	s := &terraform.InstanceState{
   723  		ID: "bar",
   724  		Attributes: map[string]string{
   725  			"oldfoo": "12",
   726  		},
   727  	}
   728  
   729  	_, err := r.Refresh(s, nil)
   730  	if err == nil {
   731  		t.Fatal("expected error, but got none!")
   732  	}
   733  }