github.com/atsaki/terraform@v0.4.3-0.20150919165407-25bba5967654/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  
   340  	for i, tc := range cases {
   341  		err := tc.In.InternalValidate(schemaMap{})
   342  		if (err != nil) != tc.Err {
   343  			t.Fatalf("%d: bad: %s", i, err)
   344  		}
   345  	}
   346  }
   347  
   348  func TestResourceRefresh(t *testing.T) {
   349  	r := &Resource{
   350  		SchemaVersion: 2,
   351  		Schema: map[string]*Schema{
   352  			"foo": &Schema{
   353  				Type:     TypeInt,
   354  				Optional: true,
   355  			},
   356  		},
   357  	}
   358  
   359  	r.Read = func(d *ResourceData, m interface{}) error {
   360  		if m != 42 {
   361  			return fmt.Errorf("meta not passed")
   362  		}
   363  
   364  		return d.Set("foo", d.Get("foo").(int)+1)
   365  	}
   366  
   367  	s := &terraform.InstanceState{
   368  		ID: "bar",
   369  		Attributes: map[string]string{
   370  			"foo": "12",
   371  		},
   372  	}
   373  
   374  	expected := &terraform.InstanceState{
   375  		ID: "bar",
   376  		Attributes: map[string]string{
   377  			"id":  "bar",
   378  			"foo": "13",
   379  		},
   380  		Meta: map[string]string{
   381  			"schema_version": "2",
   382  		},
   383  	}
   384  
   385  	actual, err := r.Refresh(s, 42)
   386  	if err != nil {
   387  		t.Fatalf("err: %s", err)
   388  	}
   389  
   390  	if !reflect.DeepEqual(actual, expected) {
   391  		t.Fatalf("bad: %#v", actual)
   392  	}
   393  }
   394  
   395  func TestResourceRefresh_blankId(t *testing.T) {
   396  	r := &Resource{
   397  		Schema: map[string]*Schema{
   398  			"foo": &Schema{
   399  				Type:     TypeInt,
   400  				Optional: true,
   401  			},
   402  		},
   403  	}
   404  
   405  	r.Read = func(d *ResourceData, m interface{}) error {
   406  		d.SetId("foo")
   407  		return nil
   408  	}
   409  
   410  	s := &terraform.InstanceState{
   411  		ID:         "",
   412  		Attributes: map[string]string{},
   413  	}
   414  
   415  	actual, err := r.Refresh(s, 42)
   416  	if err != nil {
   417  		t.Fatalf("err: %s", err)
   418  	}
   419  	if actual != nil {
   420  		t.Fatalf("bad: %#v", actual)
   421  	}
   422  }
   423  
   424  func TestResourceRefresh_delete(t *testing.T) {
   425  	r := &Resource{
   426  		Schema: map[string]*Schema{
   427  			"foo": &Schema{
   428  				Type:     TypeInt,
   429  				Optional: true,
   430  			},
   431  		},
   432  	}
   433  
   434  	r.Read = func(d *ResourceData, m interface{}) error {
   435  		d.SetId("")
   436  		return nil
   437  	}
   438  
   439  	s := &terraform.InstanceState{
   440  		ID: "bar",
   441  		Attributes: map[string]string{
   442  			"foo": "12",
   443  		},
   444  	}
   445  
   446  	actual, err := r.Refresh(s, 42)
   447  	if err != nil {
   448  		t.Fatalf("err: %s", err)
   449  	}
   450  
   451  	if actual != nil {
   452  		t.Fatalf("bad: %#v", actual)
   453  	}
   454  }
   455  
   456  func TestResourceRefresh_existsError(t *testing.T) {
   457  	r := &Resource{
   458  		Schema: map[string]*Schema{
   459  			"foo": &Schema{
   460  				Type:     TypeInt,
   461  				Optional: true,
   462  			},
   463  		},
   464  	}
   465  
   466  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
   467  		return false, fmt.Errorf("error")
   468  	}
   469  
   470  	r.Read = func(d *ResourceData, m interface{}) error {
   471  		panic("shouldn't be called")
   472  	}
   473  
   474  	s := &terraform.InstanceState{
   475  		ID: "bar",
   476  		Attributes: map[string]string{
   477  			"foo": "12",
   478  		},
   479  	}
   480  
   481  	actual, err := r.Refresh(s, 42)
   482  	if err == nil {
   483  		t.Fatalf("should error")
   484  	}
   485  	if !reflect.DeepEqual(actual, s) {
   486  		t.Fatalf("bad: %#v", actual)
   487  	}
   488  }
   489  
   490  func TestResourceRefresh_noExists(t *testing.T) {
   491  	r := &Resource{
   492  		Schema: map[string]*Schema{
   493  			"foo": &Schema{
   494  				Type:     TypeInt,
   495  				Optional: true,
   496  			},
   497  		},
   498  	}
   499  
   500  	r.Exists = func(*ResourceData, interface{}) (bool, error) {
   501  		return false, nil
   502  	}
   503  
   504  	r.Read = func(d *ResourceData, m interface{}) error {
   505  		panic("shouldn't be called")
   506  	}
   507  
   508  	s := &terraform.InstanceState{
   509  		ID: "bar",
   510  		Attributes: map[string]string{
   511  			"foo": "12",
   512  		},
   513  	}
   514  
   515  	actual, err := r.Refresh(s, 42)
   516  	if err != nil {
   517  		t.Fatalf("err: %s", err)
   518  	}
   519  	if actual != nil {
   520  		t.Fatalf("should have no state")
   521  	}
   522  }
   523  
   524  func TestResourceRefresh_needsMigration(t *testing.T) {
   525  	// Schema v2 it deals only in newfoo, which tracks foo as an int
   526  	r := &Resource{
   527  		SchemaVersion: 2,
   528  		Schema: map[string]*Schema{
   529  			"newfoo": &Schema{
   530  				Type:     TypeInt,
   531  				Optional: true,
   532  			},
   533  		},
   534  	}
   535  
   536  	r.Read = func(d *ResourceData, m interface{}) error {
   537  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
   538  	}
   539  
   540  	r.MigrateState = func(
   541  		v int,
   542  		s *terraform.InstanceState,
   543  		meta interface{}) (*terraform.InstanceState, error) {
   544  		// Real state migration functions will probably switch on this value,
   545  		// but we'll just assert on it for now.
   546  		if v != 1 {
   547  			t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v)
   548  		}
   549  
   550  		if meta != 42 {
   551  			t.Fatal("Expected meta to be passed through to the migration function")
   552  		}
   553  
   554  		oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64)
   555  		if err != nil {
   556  			t.Fatalf("err: %#v", err)
   557  		}
   558  		s.Attributes["newfoo"] = strconv.Itoa((int(oldfoo * 10)))
   559  		delete(s.Attributes, "oldfoo")
   560  
   561  		return s, nil
   562  	}
   563  
   564  	// State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th
   565  	// the scale of newfoo
   566  	s := &terraform.InstanceState{
   567  		ID: "bar",
   568  		Attributes: map[string]string{
   569  			"oldfoo": "1.2",
   570  		},
   571  		Meta: map[string]string{
   572  			"schema_version": "1",
   573  		},
   574  	}
   575  
   576  	actual, err := r.Refresh(s, 42)
   577  	if err != nil {
   578  		t.Fatalf("err: %s", err)
   579  	}
   580  
   581  	expected := &terraform.InstanceState{
   582  		ID: "bar",
   583  		Attributes: map[string]string{
   584  			"id":     "bar",
   585  			"newfoo": "13",
   586  		},
   587  		Meta: map[string]string{
   588  			"schema_version": "2",
   589  		},
   590  	}
   591  
   592  	if !reflect.DeepEqual(actual, expected) {
   593  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
   594  	}
   595  }
   596  
   597  func TestResourceRefresh_noMigrationNeeded(t *testing.T) {
   598  	r := &Resource{
   599  		SchemaVersion: 2,
   600  		Schema: map[string]*Schema{
   601  			"newfoo": &Schema{
   602  				Type:     TypeInt,
   603  				Optional: true,
   604  			},
   605  		},
   606  	}
   607  
   608  	r.Read = func(d *ResourceData, m interface{}) error {
   609  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
   610  	}
   611  
   612  	r.MigrateState = func(
   613  		v int,
   614  		s *terraform.InstanceState,
   615  		meta interface{}) (*terraform.InstanceState, error) {
   616  		t.Fatal("Migrate function shouldn't be called!")
   617  		return nil, nil
   618  	}
   619  
   620  	s := &terraform.InstanceState{
   621  		ID: "bar",
   622  		Attributes: map[string]string{
   623  			"newfoo": "12",
   624  		},
   625  		Meta: map[string]string{
   626  			"schema_version": "2",
   627  		},
   628  	}
   629  
   630  	actual, err := r.Refresh(s, nil)
   631  	if err != nil {
   632  		t.Fatalf("err: %s", err)
   633  	}
   634  
   635  	expected := &terraform.InstanceState{
   636  		ID: "bar",
   637  		Attributes: map[string]string{
   638  			"id":     "bar",
   639  			"newfoo": "13",
   640  		},
   641  		Meta: map[string]string{
   642  			"schema_version": "2",
   643  		},
   644  	}
   645  
   646  	if !reflect.DeepEqual(actual, expected) {
   647  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
   648  	}
   649  }
   650  
   651  func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) {
   652  	r := &Resource{
   653  		// Version 1 > Version 0
   654  		SchemaVersion: 1,
   655  		Schema: map[string]*Schema{
   656  			"newfoo": &Schema{
   657  				Type:     TypeInt,
   658  				Optional: true,
   659  			},
   660  		},
   661  	}
   662  
   663  	r.Read = func(d *ResourceData, m interface{}) error {
   664  		return d.Set("newfoo", d.Get("newfoo").(int)+1)
   665  	}
   666  
   667  	r.MigrateState = func(
   668  		v int,
   669  		s *terraform.InstanceState,
   670  		meta interface{}) (*terraform.InstanceState, error) {
   671  		s.Attributes["newfoo"] = s.Attributes["oldfoo"]
   672  		return s, nil
   673  	}
   674  
   675  	s := &terraform.InstanceState{
   676  		ID: "bar",
   677  		Attributes: map[string]string{
   678  			"oldfoo": "12",
   679  		},
   680  	}
   681  
   682  	actual, err := r.Refresh(s, nil)
   683  	if err != nil {
   684  		t.Fatalf("err: %s", err)
   685  	}
   686  
   687  	expected := &terraform.InstanceState{
   688  		ID: "bar",
   689  		Attributes: map[string]string{
   690  			"id":     "bar",
   691  			"newfoo": "13",
   692  		},
   693  		Meta: map[string]string{
   694  			"schema_version": "1",
   695  		},
   696  	}
   697  
   698  	if !reflect.DeepEqual(actual, expected) {
   699  		t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual)
   700  	}
   701  }
   702  
   703  func TestResourceRefresh_migrateStateErr(t *testing.T) {
   704  	r := &Resource{
   705  		SchemaVersion: 2,
   706  		Schema: map[string]*Schema{
   707  			"newfoo": &Schema{
   708  				Type:     TypeInt,
   709  				Optional: true,
   710  			},
   711  		},
   712  	}
   713  
   714  	r.Read = func(d *ResourceData, m interface{}) error {
   715  		t.Fatal("Read should never be called!")
   716  		return nil
   717  	}
   718  
   719  	r.MigrateState = func(
   720  		v int,
   721  		s *terraform.InstanceState,
   722  		meta interface{}) (*terraform.InstanceState, error) {
   723  		return s, fmt.Errorf("triggering an error")
   724  	}
   725  
   726  	s := &terraform.InstanceState{
   727  		ID: "bar",
   728  		Attributes: map[string]string{
   729  			"oldfoo": "12",
   730  		},
   731  	}
   732  
   733  	_, err := r.Refresh(s, nil)
   734  	if err == nil {
   735  		t.Fatal("expected error, but got none!")
   736  	}
   737  }