github.com/hobbeswalsh/terraform@v0.3.7-0.20150619183303-ad17cf55a0fa/terraform/state_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"reflect"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/hashicorp/terraform/config"
    11  )
    12  
    13  func TestStateAddModule(t *testing.T) {
    14  	cases := []struct {
    15  		In  [][]string
    16  		Out [][]string
    17  	}{
    18  		{
    19  			[][]string{
    20  				[]string{"root"},
    21  				[]string{"root", "child"},
    22  			},
    23  			[][]string{
    24  				[]string{"root"},
    25  				[]string{"root", "child"},
    26  			},
    27  		},
    28  
    29  		{
    30  			[][]string{
    31  				[]string{"root", "foo", "bar"},
    32  				[]string{"root", "foo"},
    33  				[]string{"root"},
    34  				[]string{"root", "bar"},
    35  			},
    36  			[][]string{
    37  				[]string{"root"},
    38  				[]string{"root", "bar"},
    39  				[]string{"root", "foo"},
    40  				[]string{"root", "foo", "bar"},
    41  			},
    42  		},
    43  	}
    44  
    45  	for _, tc := range cases {
    46  		s := new(State)
    47  		for _, p := range tc.In {
    48  			s.AddModule(p)
    49  		}
    50  
    51  		actual := make([][]string, 0, len(tc.In))
    52  		for _, m := range s.Modules {
    53  			actual = append(actual, m.Path)
    54  		}
    55  
    56  		if !reflect.DeepEqual(actual, tc.Out) {
    57  			t.Fatalf("In: %#v\n\nOut: %#v", tc.In, actual)
    58  		}
    59  	}
    60  }
    61  
    62  func TestStateModuleOrphans(t *testing.T) {
    63  	state := &State{
    64  		Modules: []*ModuleState{
    65  			&ModuleState{
    66  				Path: RootModulePath,
    67  			},
    68  			&ModuleState{
    69  				Path: []string{RootModuleName, "foo"},
    70  			},
    71  			&ModuleState{
    72  				Path: []string{RootModuleName, "bar"},
    73  			},
    74  		},
    75  	}
    76  
    77  	config := testModule(t, "state-module-orphans").Config()
    78  	actual := state.ModuleOrphans(RootModulePath, config)
    79  	expected := [][]string{
    80  		[]string{RootModuleName, "foo"},
    81  	}
    82  
    83  	if !reflect.DeepEqual(actual, expected) {
    84  		t.Fatalf("bad: %#v", actual)
    85  	}
    86  }
    87  
    88  func TestStateModuleOrphans_nilConfig(t *testing.T) {
    89  	state := &State{
    90  		Modules: []*ModuleState{
    91  			&ModuleState{
    92  				Path: RootModulePath,
    93  			},
    94  			&ModuleState{
    95  				Path: []string{RootModuleName, "foo"},
    96  			},
    97  			&ModuleState{
    98  				Path: []string{RootModuleName, "bar"},
    99  			},
   100  		},
   101  	}
   102  
   103  	actual := state.ModuleOrphans(RootModulePath, nil)
   104  	expected := [][]string{
   105  		[]string{RootModuleName, "foo"},
   106  		[]string{RootModuleName, "bar"},
   107  	}
   108  
   109  	if !reflect.DeepEqual(actual, expected) {
   110  		t.Fatalf("bad: %#v", actual)
   111  	}
   112  }
   113  
   114  func TestStateEqual(t *testing.T) {
   115  	cases := []struct {
   116  		Result   bool
   117  		One, Two *State
   118  	}{
   119  		// Nils
   120  		{
   121  			false,
   122  			nil,
   123  			&State{Version: 2},
   124  		},
   125  
   126  		{
   127  			true,
   128  			nil,
   129  			nil,
   130  		},
   131  
   132  		// Different versions
   133  		{
   134  			false,
   135  			&State{Version: 5},
   136  			&State{Version: 2},
   137  		},
   138  
   139  		// Different modules
   140  		{
   141  			false,
   142  			&State{
   143  				Modules: []*ModuleState{
   144  					&ModuleState{
   145  						Path: RootModulePath,
   146  					},
   147  				},
   148  			},
   149  			&State{},
   150  		},
   151  
   152  		{
   153  			true,
   154  			&State{
   155  				Modules: []*ModuleState{
   156  					&ModuleState{
   157  						Path: RootModulePath,
   158  					},
   159  				},
   160  			},
   161  			&State{
   162  				Modules: []*ModuleState{
   163  					&ModuleState{
   164  						Path: RootModulePath,
   165  					},
   166  				},
   167  			},
   168  		},
   169  	}
   170  
   171  	for i, tc := range cases {
   172  		if tc.One.Equal(tc.Two) != tc.Result {
   173  			t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
   174  		}
   175  		if tc.Two.Equal(tc.One) != tc.Result {
   176  			t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
   177  		}
   178  	}
   179  }
   180  
   181  func TestStateIncrementSerialMaybe(t *testing.T) {
   182  	cases := map[string]struct {
   183  		S1, S2 *State
   184  		Serial int64
   185  	}{
   186  		"S2 is nil": {
   187  			&State{},
   188  			nil,
   189  			0,
   190  		},
   191  		"S2 is identical": {
   192  			&State{},
   193  			&State{},
   194  			0,
   195  		},
   196  		"S2 is different": {
   197  			&State{},
   198  			&State{
   199  				Modules: []*ModuleState{
   200  					&ModuleState{Path: rootModulePath},
   201  				},
   202  			},
   203  			1,
   204  		},
   205  		"S1 serial is higher": {
   206  			&State{Serial: 5},
   207  			&State{
   208  				Serial: 3,
   209  				Modules: []*ModuleState{
   210  					&ModuleState{Path: rootModulePath},
   211  				},
   212  			},
   213  			5,
   214  		},
   215  	}
   216  
   217  	for name, tc := range cases {
   218  		tc.S1.IncrementSerialMaybe(tc.S2)
   219  		if tc.S1.Serial != tc.Serial {
   220  			t.Fatalf("Bad: %s\nGot: %d", name, tc.S1.Serial)
   221  		}
   222  	}
   223  }
   224  
   225  func TestResourceStateEqual(t *testing.T) {
   226  	cases := []struct {
   227  		Result   bool
   228  		One, Two *ResourceState
   229  	}{
   230  		// Different types
   231  		{
   232  			false,
   233  			&ResourceState{Type: "foo"},
   234  			&ResourceState{Type: "bar"},
   235  		},
   236  
   237  		// Different dependencies
   238  		{
   239  			false,
   240  			&ResourceState{Dependencies: []string{"foo"}},
   241  			&ResourceState{Dependencies: []string{"bar"}},
   242  		},
   243  
   244  		{
   245  			false,
   246  			&ResourceState{Dependencies: []string{"foo", "bar"}},
   247  			&ResourceState{Dependencies: []string{"foo"}},
   248  		},
   249  
   250  		{
   251  			true,
   252  			&ResourceState{Dependencies: []string{"bar", "foo"}},
   253  			&ResourceState{Dependencies: []string{"foo", "bar"}},
   254  		},
   255  
   256  		// Different primaries
   257  		{
   258  			false,
   259  			&ResourceState{Primary: nil},
   260  			&ResourceState{Primary: &InstanceState{ID: "foo"}},
   261  		},
   262  
   263  		{
   264  			true,
   265  			&ResourceState{Primary: &InstanceState{ID: "foo"}},
   266  			&ResourceState{Primary: &InstanceState{ID: "foo"}},
   267  		},
   268  
   269  		// Different tainted
   270  		{
   271  			false,
   272  			&ResourceState{
   273  				Tainted: nil,
   274  			},
   275  			&ResourceState{
   276  				Tainted: []*InstanceState{
   277  					&InstanceState{ID: "foo"},
   278  				},
   279  			},
   280  		},
   281  
   282  		{
   283  			true,
   284  			&ResourceState{
   285  				Tainted: []*InstanceState{
   286  					&InstanceState{ID: "foo"},
   287  				},
   288  			},
   289  			&ResourceState{
   290  				Tainted: []*InstanceState{
   291  					&InstanceState{ID: "foo"},
   292  				},
   293  			},
   294  		},
   295  
   296  		{
   297  			true,
   298  			&ResourceState{
   299  				Tainted: []*InstanceState{
   300  					&InstanceState{ID: "foo"},
   301  					nil,
   302  				},
   303  			},
   304  			&ResourceState{
   305  				Tainted: []*InstanceState{
   306  					&InstanceState{ID: "foo"},
   307  				},
   308  			},
   309  		},
   310  	}
   311  
   312  	for i, tc := range cases {
   313  		if tc.One.Equal(tc.Two) != tc.Result {
   314  			t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
   315  		}
   316  		if tc.Two.Equal(tc.One) != tc.Result {
   317  			t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
   318  		}
   319  	}
   320  }
   321  
   322  func TestResourceStateTaint(t *testing.T) {
   323  	cases := map[string]struct {
   324  		Input  *ResourceState
   325  		Output *ResourceState
   326  	}{
   327  		"no primary": {
   328  			&ResourceState{},
   329  			&ResourceState{},
   330  		},
   331  
   332  		"primary, no tainted": {
   333  			&ResourceState{
   334  				Primary: &InstanceState{ID: "foo"},
   335  			},
   336  			&ResourceState{
   337  				Tainted: []*InstanceState{
   338  					&InstanceState{ID: "foo"},
   339  				},
   340  			},
   341  		},
   342  
   343  		"primary, with tainted": {
   344  			&ResourceState{
   345  				Primary: &InstanceState{ID: "foo"},
   346  				Tainted: []*InstanceState{
   347  					&InstanceState{ID: "bar"},
   348  				},
   349  			},
   350  			&ResourceState{
   351  				Tainted: []*InstanceState{
   352  					&InstanceState{ID: "bar"},
   353  					&InstanceState{ID: "foo"},
   354  				},
   355  			},
   356  		},
   357  	}
   358  
   359  	for k, tc := range cases {
   360  		tc.Input.Taint()
   361  		if !reflect.DeepEqual(tc.Input, tc.Output) {
   362  			t.Fatalf(
   363  				"Failure: %s\n\nExpected: %#v\n\nGot: %#v",
   364  				k, tc.Output, tc.Input)
   365  		}
   366  	}
   367  }
   368  
   369  func TestInstanceStateEmpty(t *testing.T) {
   370  	cases := map[string]struct {
   371  		In     *InstanceState
   372  		Result bool
   373  	}{
   374  		"nil is empty": {
   375  			nil,
   376  			true,
   377  		},
   378  		"non-nil but without ID is empty": {
   379  			&InstanceState{},
   380  			true,
   381  		},
   382  		"with ID is not empty": {
   383  			&InstanceState{
   384  				ID: "i-abc123",
   385  			},
   386  			false,
   387  		},
   388  	}
   389  
   390  	for tn, tc := range cases {
   391  		if tc.In.Empty() != tc.Result {
   392  			t.Fatalf("%q expected %#v to be empty: %#v", tn, tc.In, tc.Result)
   393  		}
   394  	}
   395  }
   396  
   397  func TestInstanceStateEqual(t *testing.T) {
   398  	cases := []struct {
   399  		Result   bool
   400  		One, Two *InstanceState
   401  	}{
   402  		// Nils
   403  		{
   404  			false,
   405  			nil,
   406  			&InstanceState{},
   407  		},
   408  
   409  		{
   410  			false,
   411  			&InstanceState{},
   412  			nil,
   413  		},
   414  
   415  		// Different IDs
   416  		{
   417  			false,
   418  			&InstanceState{ID: "foo"},
   419  			&InstanceState{ID: "bar"},
   420  		},
   421  
   422  		// Different Attributes
   423  		{
   424  			false,
   425  			&InstanceState{Attributes: map[string]string{"foo": "bar"}},
   426  			&InstanceState{Attributes: map[string]string{"foo": "baz"}},
   427  		},
   428  
   429  		// Different Attribute keys
   430  		{
   431  			false,
   432  			&InstanceState{Attributes: map[string]string{"foo": "bar"}},
   433  			&InstanceState{Attributes: map[string]string{"bar": "baz"}},
   434  		},
   435  
   436  		{
   437  			false,
   438  			&InstanceState{Attributes: map[string]string{"bar": "baz"}},
   439  			&InstanceState{Attributes: map[string]string{"foo": "bar"}},
   440  		},
   441  	}
   442  
   443  	for i, tc := range cases {
   444  		if tc.One.Equal(tc.Two) != tc.Result {
   445  			t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
   446  		}
   447  	}
   448  }
   449  
   450  func TestStateEmpty(t *testing.T) {
   451  	cases := []struct {
   452  		In     *State
   453  		Result bool
   454  	}{
   455  		{
   456  			nil,
   457  			true,
   458  		},
   459  		{
   460  			&State{},
   461  			true,
   462  		},
   463  		{
   464  			&State{
   465  				Remote: &RemoteState{Type: "foo"},
   466  			},
   467  			true,
   468  		},
   469  		{
   470  			&State{
   471  				Modules: []*ModuleState{
   472  					&ModuleState{},
   473  				},
   474  			},
   475  			false,
   476  		},
   477  	}
   478  
   479  	for i, tc := range cases {
   480  		if tc.In.Empty() != tc.Result {
   481  			t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In)
   482  		}
   483  	}
   484  }
   485  
   486  func TestStateIsRemote(t *testing.T) {
   487  	cases := []struct {
   488  		In     *State
   489  		Result bool
   490  	}{
   491  		{
   492  			nil,
   493  			false,
   494  		},
   495  		{
   496  			&State{},
   497  			false,
   498  		},
   499  		{
   500  			&State{
   501  				Remote: &RemoteState{Type: "foo"},
   502  			},
   503  			true,
   504  		},
   505  	}
   506  
   507  	for i, tc := range cases {
   508  		if tc.In.IsRemote() != tc.Result {
   509  			t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In)
   510  		}
   511  	}
   512  }
   513  
   514  func TestInstanceState_MergeDiff(t *testing.T) {
   515  	is := InstanceState{
   516  		ID: "foo",
   517  		Attributes: map[string]string{
   518  			"foo":  "bar",
   519  			"port": "8000",
   520  		},
   521  	}
   522  
   523  	diff := &InstanceDiff{
   524  		Attributes: map[string]*ResourceAttrDiff{
   525  			"foo": &ResourceAttrDiff{
   526  				Old: "bar",
   527  				New: "baz",
   528  			},
   529  			"bar": &ResourceAttrDiff{
   530  				Old: "",
   531  				New: "foo",
   532  			},
   533  			"baz": &ResourceAttrDiff{
   534  				Old:         "",
   535  				New:         "foo",
   536  				NewComputed: true,
   537  			},
   538  			"port": &ResourceAttrDiff{
   539  				NewRemoved: true,
   540  			},
   541  		},
   542  	}
   543  
   544  	is2 := is.MergeDiff(diff)
   545  
   546  	expected := map[string]string{
   547  		"foo": "baz",
   548  		"bar": "foo",
   549  		"baz": config.UnknownVariableValue,
   550  	}
   551  
   552  	if !reflect.DeepEqual(expected, is2.Attributes) {
   553  		t.Fatalf("bad: %#v", is2.Attributes)
   554  	}
   555  }
   556  
   557  func TestInstanceState_MergeDiff_nil(t *testing.T) {
   558  	var is *InstanceState = nil
   559  
   560  	diff := &InstanceDiff{
   561  		Attributes: map[string]*ResourceAttrDiff{
   562  			"foo": &ResourceAttrDiff{
   563  				Old: "",
   564  				New: "baz",
   565  			},
   566  		},
   567  	}
   568  
   569  	is2 := is.MergeDiff(diff)
   570  
   571  	expected := map[string]string{
   572  		"foo": "baz",
   573  	}
   574  
   575  	if !reflect.DeepEqual(expected, is2.Attributes) {
   576  		t.Fatalf("bad: %#v", is2.Attributes)
   577  	}
   578  }
   579  
   580  func TestInstanceState_MergeDiff_nilDiff(t *testing.T) {
   581  	is := InstanceState{
   582  		ID: "foo",
   583  		Attributes: map[string]string{
   584  			"foo": "bar",
   585  		},
   586  	}
   587  
   588  	is2 := is.MergeDiff(nil)
   589  
   590  	expected := map[string]string{
   591  		"foo": "bar",
   592  	}
   593  
   594  	if !reflect.DeepEqual(expected, is2.Attributes) {
   595  		t.Fatalf("bad: %#v", is2.Attributes)
   596  	}
   597  }
   598  
   599  func TestReadUpgradeState(t *testing.T) {
   600  	state := &StateV1{
   601  		Resources: map[string]*ResourceStateV1{
   602  			"foo": &ResourceStateV1{
   603  				ID: "bar",
   604  			},
   605  		},
   606  	}
   607  	buf := new(bytes.Buffer)
   608  	if err := testWriteStateV1(state, buf); err != nil {
   609  		t.Fatalf("err: %s", err)
   610  	}
   611  
   612  	// ReadState should transparently detect the old
   613  	// version and upgrade up so the latest.
   614  	actual, err := ReadState(buf)
   615  	if err != nil {
   616  		t.Fatalf("err: %s", err)
   617  	}
   618  
   619  	upgraded, err := upgradeV1State(state)
   620  	if err != nil {
   621  		t.Fatalf("err: %s", err)
   622  	}
   623  
   624  	if !reflect.DeepEqual(actual, upgraded) {
   625  		t.Fatalf("bad: %#v", actual)
   626  	}
   627  }
   628  
   629  func TestReadWriteState(t *testing.T) {
   630  	state := &State{
   631  		Serial: 9,
   632  		Remote: &RemoteState{
   633  			Type: "http",
   634  			Config: map[string]string{
   635  				"url": "http://my-cool-server.com/",
   636  			},
   637  		},
   638  		Modules: []*ModuleState{
   639  			&ModuleState{
   640  				Path: rootModulePath,
   641  				Dependencies: []string{
   642  					"aws_instance.bar",
   643  				},
   644  				Resources: map[string]*ResourceState{
   645  					"foo": &ResourceState{
   646  						Primary: &InstanceState{
   647  							ID: "bar",
   648  							Ephemeral: EphemeralState{
   649  								ConnInfo: map[string]string{
   650  									"type":     "ssh",
   651  									"user":     "root",
   652  									"password": "supersecret",
   653  								},
   654  							},
   655  						},
   656  					},
   657  				},
   658  			},
   659  		},
   660  	}
   661  
   662  	// Checksum before the write
   663  	chksum := checksumStruct(t, state)
   664  
   665  	buf := new(bytes.Buffer)
   666  	if err := WriteState(state, buf); err != nil {
   667  		t.Fatalf("err: %s", err)
   668  	}
   669  
   670  	// Verify that the version and serial are set
   671  	if state.Version != StateVersion {
   672  		t.Fatalf("bad version number: %d", state.Version)
   673  	}
   674  
   675  	// Checksum after the write
   676  	chksumAfter := checksumStruct(t, state)
   677  	if chksumAfter != chksum {
   678  		t.Fatalf("structure changed during serialization!")
   679  	}
   680  
   681  	actual, err := ReadState(buf)
   682  	if err != nil {
   683  		t.Fatalf("err: %s", err)
   684  	}
   685  
   686  	// ReadState should not restore sensitive information!
   687  	mod := state.RootModule()
   688  	mod.Resources["foo"].Primary.Ephemeral = EphemeralState{}
   689  
   690  	if !reflect.DeepEqual(actual, state) {
   691  		t.Fatalf("bad: %#v", actual)
   692  	}
   693  }
   694  
   695  func TestReadStateNewVersion(t *testing.T) {
   696  	type out struct {
   697  		Version int
   698  	}
   699  
   700  	buf, err := json.Marshal(&out{StateVersion + 1})
   701  	if err != nil {
   702  		t.Fatalf("err: %v", err)
   703  	}
   704  
   705  	s, err := ReadState(bytes.NewReader(buf))
   706  	if s != nil {
   707  		t.Fatalf("unexpected: %#v", s)
   708  	}
   709  	if !strings.Contains(err.Error(), "not supported") {
   710  		t.Fatalf("err: %v", err)
   711  	}
   712  }
   713  
   714  func TestUpgradeV1State(t *testing.T) {
   715  	old := &StateV1{
   716  		Outputs: map[string]string{
   717  			"ip": "127.0.0.1",
   718  		},
   719  		Resources: map[string]*ResourceStateV1{
   720  			"foo": &ResourceStateV1{
   721  				Type: "test_resource",
   722  				ID:   "bar",
   723  				Attributes: map[string]string{
   724  					"key": "val",
   725  				},
   726  			},
   727  			"bar": &ResourceStateV1{
   728  				Type: "test_resource",
   729  				ID:   "1234",
   730  				Attributes: map[string]string{
   731  					"a": "b",
   732  				},
   733  			},
   734  		},
   735  		Tainted: map[string]struct{}{
   736  			"bar": struct{}{},
   737  		},
   738  	}
   739  	state, err := upgradeV1State(old)
   740  	if err != nil {
   741  		t.Fatalf("err: %v", err)
   742  	}
   743  
   744  	if len(state.Modules) != 1 {
   745  		t.Fatalf("should only have root module: %#v", state.Modules)
   746  	}
   747  	root := state.RootModule()
   748  
   749  	if len(root.Outputs) != 1 {
   750  		t.Fatalf("bad outputs: %v", root.Outputs)
   751  	}
   752  	if root.Outputs["ip"] != "127.0.0.1" {
   753  		t.Fatalf("bad outputs: %v", root.Outputs)
   754  	}
   755  
   756  	if len(root.Resources) != 2 {
   757  		t.Fatalf("bad resources: %v", root.Resources)
   758  	}
   759  
   760  	foo := root.Resources["foo"]
   761  	if foo.Type != "test_resource" {
   762  		t.Fatalf("bad: %#v", foo)
   763  	}
   764  	if foo.Primary == nil || foo.Primary.ID != "bar" ||
   765  		foo.Primary.Attributes["key"] != "val" {
   766  		t.Fatalf("bad: %#v", foo)
   767  	}
   768  	if len(foo.Tainted) > 0 {
   769  		t.Fatalf("bad: %#v", foo)
   770  	}
   771  
   772  	bar := root.Resources["bar"]
   773  	if bar.Type != "test_resource" {
   774  		t.Fatalf("bad: %#v", bar)
   775  	}
   776  	if bar.Primary != nil {
   777  		t.Fatalf("bad: %#v", bar)
   778  	}
   779  	if len(bar.Tainted) != 1 {
   780  		t.Fatalf("bad: %#v", bar)
   781  	}
   782  	bt := bar.Tainted[0]
   783  	if bt.ID != "1234" || bt.Attributes["a"] != "b" {
   784  		t.Fatalf("bad: %#v", bt)
   785  	}
   786  }