github.com/tomaszheflik/terraform@v0.7.3-0.20160827060421-32f990b41594/terraform/diff_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  	"testing"
     7  )
     8  
     9  func TestDiffEmpty(t *testing.T) {
    10  	diff := new(Diff)
    11  	if !diff.Empty() {
    12  		t.Fatal("should be empty")
    13  	}
    14  
    15  	mod := diff.AddModule(rootModulePath)
    16  	mod.Resources["nodeA"] = &InstanceDiff{
    17  		Attributes: map[string]*ResourceAttrDiff{
    18  			"foo": &ResourceAttrDiff{
    19  				Old: "foo",
    20  				New: "bar",
    21  			},
    22  		},
    23  	}
    24  
    25  	if diff.Empty() {
    26  		t.Fatal("should not be empty")
    27  	}
    28  }
    29  
    30  func TestDiffEmpty_taintedIsNotEmpty(t *testing.T) {
    31  	diff := new(Diff)
    32  
    33  	mod := diff.AddModule(rootModulePath)
    34  	mod.Resources["nodeA"] = &InstanceDiff{
    35  		DestroyTainted: true,
    36  	}
    37  
    38  	if diff.Empty() {
    39  		t.Fatal("should not be empty, since DestroyTainted was set")
    40  	}
    41  }
    42  
    43  func TestModuleDiff_ChangeType(t *testing.T) {
    44  	cases := []struct {
    45  		Diff   *ModuleDiff
    46  		Result DiffChangeType
    47  	}{
    48  		{
    49  			&ModuleDiff{},
    50  			DiffNone,
    51  		},
    52  		{
    53  			&ModuleDiff{
    54  				Resources: map[string]*InstanceDiff{
    55  					"foo": &InstanceDiff{Destroy: true},
    56  				},
    57  			},
    58  			DiffDestroy,
    59  		},
    60  		{
    61  			&ModuleDiff{
    62  				Resources: map[string]*InstanceDiff{
    63  					"foo": &InstanceDiff{
    64  						Attributes: map[string]*ResourceAttrDiff{
    65  							"foo": &ResourceAttrDiff{
    66  								Old: "",
    67  								New: "bar",
    68  							},
    69  						},
    70  					},
    71  				},
    72  			},
    73  			DiffUpdate,
    74  		},
    75  		{
    76  			&ModuleDiff{
    77  				Resources: map[string]*InstanceDiff{
    78  					"foo": &InstanceDiff{
    79  						Attributes: map[string]*ResourceAttrDiff{
    80  							"foo": &ResourceAttrDiff{
    81  								Old:         "",
    82  								New:         "bar",
    83  								RequiresNew: true,
    84  							},
    85  						},
    86  					},
    87  				},
    88  			},
    89  			DiffCreate,
    90  		},
    91  		{
    92  			&ModuleDiff{
    93  				Resources: map[string]*InstanceDiff{
    94  					"foo": &InstanceDiff{
    95  						Destroy: true,
    96  						Attributes: map[string]*ResourceAttrDiff{
    97  							"foo": &ResourceAttrDiff{
    98  								Old:         "",
    99  								New:         "bar",
   100  								RequiresNew: true,
   101  							},
   102  						},
   103  					},
   104  				},
   105  			},
   106  			DiffUpdate,
   107  		},
   108  	}
   109  
   110  	for i, tc := range cases {
   111  		actual := tc.Diff.ChangeType()
   112  		if actual != tc.Result {
   113  			t.Fatalf("%d: %#v", i, actual)
   114  		}
   115  	}
   116  }
   117  
   118  func TestModuleDiff_Empty(t *testing.T) {
   119  	diff := new(ModuleDiff)
   120  	if !diff.Empty() {
   121  		t.Fatal("should be empty")
   122  	}
   123  
   124  	diff.Resources = map[string]*InstanceDiff{
   125  		"nodeA": &InstanceDiff{},
   126  	}
   127  
   128  	if !diff.Empty() {
   129  		t.Fatal("should be empty")
   130  	}
   131  
   132  	diff.Resources["nodeA"].Attributes = map[string]*ResourceAttrDiff{
   133  		"foo": &ResourceAttrDiff{
   134  			Old: "foo",
   135  			New: "bar",
   136  		},
   137  	}
   138  
   139  	if diff.Empty() {
   140  		t.Fatal("should not be empty")
   141  	}
   142  
   143  	diff.Resources["nodeA"].Attributes = nil
   144  	diff.Resources["nodeA"].Destroy = true
   145  
   146  	if diff.Empty() {
   147  		t.Fatal("should not be empty")
   148  	}
   149  }
   150  
   151  func TestModuleDiff_String(t *testing.T) {
   152  	diff := &ModuleDiff{
   153  		Resources: map[string]*InstanceDiff{
   154  			"nodeA": &InstanceDiff{
   155  				Attributes: map[string]*ResourceAttrDiff{
   156  					"foo": &ResourceAttrDiff{
   157  						Old: "foo",
   158  						New: "bar",
   159  					},
   160  					"bar": &ResourceAttrDiff{
   161  						Old:         "foo",
   162  						NewComputed: true,
   163  					},
   164  					"longfoo": &ResourceAttrDiff{
   165  						Old:         "foo",
   166  						New:         "bar",
   167  						RequiresNew: true,
   168  					},
   169  					"secretfoo": &ResourceAttrDiff{
   170  						Old:       "foo",
   171  						New:       "bar",
   172  						Sensitive: true,
   173  					},
   174  				},
   175  			},
   176  		},
   177  	}
   178  
   179  	actual := strings.TrimSpace(diff.String())
   180  	expected := strings.TrimSpace(moduleDiffStrBasic)
   181  	if actual != expected {
   182  		t.Fatalf("bad:\n%s", actual)
   183  	}
   184  }
   185  
   186  func TestInstanceDiff_ChangeType(t *testing.T) {
   187  	cases := []struct {
   188  		Diff   *InstanceDiff
   189  		Result DiffChangeType
   190  	}{
   191  		{
   192  			&InstanceDiff{},
   193  			DiffNone,
   194  		},
   195  		{
   196  			&InstanceDiff{Destroy: true},
   197  			DiffDestroy,
   198  		},
   199  		{
   200  			&InstanceDiff{
   201  				Attributes: map[string]*ResourceAttrDiff{
   202  					"foo": &ResourceAttrDiff{
   203  						Old: "",
   204  						New: "bar",
   205  					},
   206  				},
   207  			},
   208  			DiffUpdate,
   209  		},
   210  		{
   211  			&InstanceDiff{
   212  				Attributes: map[string]*ResourceAttrDiff{
   213  					"foo": &ResourceAttrDiff{
   214  						Old:         "",
   215  						New:         "bar",
   216  						RequiresNew: true,
   217  					},
   218  				},
   219  			},
   220  			DiffCreate,
   221  		},
   222  		{
   223  			&InstanceDiff{
   224  				Destroy: true,
   225  				Attributes: map[string]*ResourceAttrDiff{
   226  					"foo": &ResourceAttrDiff{
   227  						Old:         "",
   228  						New:         "bar",
   229  						RequiresNew: true,
   230  					},
   231  				},
   232  			},
   233  			DiffDestroyCreate,
   234  		},
   235  		{
   236  			&InstanceDiff{
   237  				DestroyTainted: true,
   238  				Attributes: map[string]*ResourceAttrDiff{
   239  					"foo": &ResourceAttrDiff{
   240  						Old:         "",
   241  						New:         "bar",
   242  						RequiresNew: true,
   243  					},
   244  				},
   245  			},
   246  			DiffDestroyCreate,
   247  		},
   248  	}
   249  
   250  	for i, tc := range cases {
   251  		actual := tc.Diff.ChangeType()
   252  		if actual != tc.Result {
   253  			t.Fatalf("%d: %#v", i, actual)
   254  		}
   255  	}
   256  }
   257  
   258  func TestInstanceDiff_Empty(t *testing.T) {
   259  	var rd *InstanceDiff
   260  
   261  	if !rd.Empty() {
   262  		t.Fatal("should be empty")
   263  	}
   264  
   265  	rd = new(InstanceDiff)
   266  
   267  	if !rd.Empty() {
   268  		t.Fatal("should be empty")
   269  	}
   270  
   271  	rd = &InstanceDiff{Destroy: true}
   272  
   273  	if rd.Empty() {
   274  		t.Fatal("should not be empty")
   275  	}
   276  
   277  	rd = &InstanceDiff{
   278  		Attributes: map[string]*ResourceAttrDiff{
   279  			"foo": &ResourceAttrDiff{
   280  				New: "bar",
   281  			},
   282  		},
   283  	}
   284  
   285  	if rd.Empty() {
   286  		t.Fatal("should not be empty")
   287  	}
   288  }
   289  
   290  func TestModuleDiff_Instances(t *testing.T) {
   291  	yesDiff := &InstanceDiff{Destroy: true}
   292  	noDiff := &InstanceDiff{Destroy: true, DestroyTainted: true}
   293  
   294  	cases := []struct {
   295  		Diff   *ModuleDiff
   296  		Id     string
   297  		Result []*InstanceDiff
   298  	}{
   299  		{
   300  			&ModuleDiff{
   301  				Resources: map[string]*InstanceDiff{
   302  					"foo": yesDiff,
   303  					"bar": noDiff,
   304  				},
   305  			},
   306  			"foo",
   307  			[]*InstanceDiff{
   308  				yesDiff,
   309  			},
   310  		},
   311  
   312  		{
   313  			&ModuleDiff{
   314  				Resources: map[string]*InstanceDiff{
   315  					"foo":   yesDiff,
   316  					"foo.0": yesDiff,
   317  					"bar":   noDiff,
   318  				},
   319  			},
   320  			"foo",
   321  			[]*InstanceDiff{
   322  				yesDiff,
   323  				yesDiff,
   324  			},
   325  		},
   326  
   327  		{
   328  			&ModuleDiff{
   329  				Resources: map[string]*InstanceDiff{
   330  					"foo":     yesDiff,
   331  					"foo.0":   yesDiff,
   332  					"foo_bar": noDiff,
   333  					"bar":     noDiff,
   334  				},
   335  			},
   336  			"foo",
   337  			[]*InstanceDiff{
   338  				yesDiff,
   339  				yesDiff,
   340  			},
   341  		},
   342  	}
   343  
   344  	for i, tc := range cases {
   345  		actual := tc.Diff.Instances(tc.Id)
   346  		if !reflect.DeepEqual(actual, tc.Result) {
   347  			t.Fatalf("%d: %#v", i, actual)
   348  		}
   349  	}
   350  }
   351  
   352  func TestInstanceDiff_RequiresNew(t *testing.T) {
   353  	rd := &InstanceDiff{
   354  		Attributes: map[string]*ResourceAttrDiff{
   355  			"foo": &ResourceAttrDiff{},
   356  		},
   357  	}
   358  
   359  	if rd.RequiresNew() {
   360  		t.Fatal("should not require new")
   361  	}
   362  
   363  	rd.Attributes["foo"].RequiresNew = true
   364  
   365  	if !rd.RequiresNew() {
   366  		t.Fatal("should require new")
   367  	}
   368  }
   369  
   370  func TestInstanceDiff_RequiresNew_nil(t *testing.T) {
   371  	var rd *InstanceDiff
   372  
   373  	if rd.RequiresNew() {
   374  		t.Fatal("should not require new")
   375  	}
   376  }
   377  
   378  func TestInstanceDiffSame(t *testing.T) {
   379  	cases := []struct {
   380  		One, Two *InstanceDiff
   381  		Same     bool
   382  		Reason   string
   383  	}{
   384  		{
   385  			&InstanceDiff{},
   386  			&InstanceDiff{},
   387  			true,
   388  			"",
   389  		},
   390  
   391  		{
   392  			nil,
   393  			nil,
   394  			true,
   395  			"",
   396  		},
   397  
   398  		{
   399  			&InstanceDiff{Destroy: false},
   400  			&InstanceDiff{Destroy: true},
   401  			false,
   402  			"diff: Destroy; old: false, new: true",
   403  		},
   404  
   405  		{
   406  			&InstanceDiff{Destroy: true},
   407  			&InstanceDiff{Destroy: true},
   408  			true,
   409  			"",
   410  		},
   411  
   412  		{
   413  			&InstanceDiff{
   414  				Attributes: map[string]*ResourceAttrDiff{
   415  					"foo": &ResourceAttrDiff{},
   416  				},
   417  			},
   418  			&InstanceDiff{
   419  				Attributes: map[string]*ResourceAttrDiff{
   420  					"foo": &ResourceAttrDiff{},
   421  				},
   422  			},
   423  			true,
   424  			"",
   425  		},
   426  
   427  		{
   428  			&InstanceDiff{
   429  				Attributes: map[string]*ResourceAttrDiff{
   430  					"bar": &ResourceAttrDiff{},
   431  				},
   432  			},
   433  			&InstanceDiff{
   434  				Attributes: map[string]*ResourceAttrDiff{
   435  					"foo": &ResourceAttrDiff{},
   436  				},
   437  			},
   438  			false,
   439  			"attribute mismatch: bar",
   440  		},
   441  
   442  		// Extra attributes
   443  		{
   444  			&InstanceDiff{
   445  				Attributes: map[string]*ResourceAttrDiff{
   446  					"foo": &ResourceAttrDiff{},
   447  				},
   448  			},
   449  			&InstanceDiff{
   450  				Attributes: map[string]*ResourceAttrDiff{
   451  					"foo": &ResourceAttrDiff{},
   452  					"bar": &ResourceAttrDiff{},
   453  				},
   454  			},
   455  			false,
   456  			"extra attributes: bar",
   457  		},
   458  
   459  		{
   460  			&InstanceDiff{
   461  				Attributes: map[string]*ResourceAttrDiff{
   462  					"foo": &ResourceAttrDiff{RequiresNew: true},
   463  				},
   464  			},
   465  			&InstanceDiff{
   466  				Attributes: map[string]*ResourceAttrDiff{
   467  					"foo": &ResourceAttrDiff{RequiresNew: false},
   468  				},
   469  			},
   470  			false,
   471  			"diff RequiresNew; old: true, new: false",
   472  		},
   473  
   474  		{
   475  			&InstanceDiff{
   476  				Attributes: map[string]*ResourceAttrDiff{
   477  					"foo.#": &ResourceAttrDiff{NewComputed: true},
   478  				},
   479  			},
   480  			&InstanceDiff{
   481  				Attributes: map[string]*ResourceAttrDiff{
   482  					"foo.#": &ResourceAttrDiff{
   483  						Old: "0",
   484  						New: "1",
   485  					},
   486  					"foo.0": &ResourceAttrDiff{
   487  						Old: "",
   488  						New: "12",
   489  					},
   490  				},
   491  			},
   492  			true,
   493  			"",
   494  		},
   495  
   496  		{
   497  			&InstanceDiff{
   498  				Attributes: map[string]*ResourceAttrDiff{
   499  					"foo.#": &ResourceAttrDiff{
   500  						Old: "0",
   501  						New: "1",
   502  					},
   503  					"foo.~35964334.bar": &ResourceAttrDiff{
   504  						Old: "",
   505  						New: "${var.foo}",
   506  					},
   507  				},
   508  			},
   509  			&InstanceDiff{
   510  				Attributes: map[string]*ResourceAttrDiff{
   511  					"foo.#": &ResourceAttrDiff{
   512  						Old: "0",
   513  						New: "1",
   514  					},
   515  					"foo.87654323.bar": &ResourceAttrDiff{
   516  						Old: "",
   517  						New: "12",
   518  					},
   519  				},
   520  			},
   521  			true,
   522  			"",
   523  		},
   524  
   525  		{
   526  			&InstanceDiff{
   527  				Attributes: map[string]*ResourceAttrDiff{
   528  					"foo.#": &ResourceAttrDiff{
   529  						Old:         "0",
   530  						NewComputed: true,
   531  					},
   532  				},
   533  			},
   534  			&InstanceDiff{
   535  				Attributes: map[string]*ResourceAttrDiff{},
   536  			},
   537  			true,
   538  			"",
   539  		},
   540  
   541  		// Computed sets may not contain all fields in the original diff, and
   542  		// because multiple entries for the same set can compute to the same
   543  		// hash before the values are computed or interpolated, the overall
   544  		// count can change as well.
   545  		{
   546  			&InstanceDiff{
   547  				Attributes: map[string]*ResourceAttrDiff{
   548  					"foo.#": &ResourceAttrDiff{
   549  						Old: "0",
   550  						New: "1",
   551  					},
   552  					"foo.~35964334.bar": &ResourceAttrDiff{
   553  						Old: "",
   554  						New: "${var.foo}",
   555  					},
   556  				},
   557  			},
   558  			&InstanceDiff{
   559  				Attributes: map[string]*ResourceAttrDiff{
   560  					"foo.#": &ResourceAttrDiff{
   561  						Old: "0",
   562  						New: "2",
   563  					},
   564  					"foo.87654323.bar": &ResourceAttrDiff{
   565  						Old: "",
   566  						New: "12",
   567  					},
   568  					"foo.87654325.bar": &ResourceAttrDiff{
   569  						Old: "",
   570  						New: "12",
   571  					},
   572  					"foo.87654325.baz": &ResourceAttrDiff{
   573  						Old: "",
   574  						New: "12",
   575  					},
   576  				},
   577  			},
   578  			true,
   579  			"",
   580  		},
   581  
   582  		// Computed values in maps will fail the "Same" check as well
   583  		{
   584  			&InstanceDiff{
   585  				Attributes: map[string]*ResourceAttrDiff{
   586  					"foo.%": &ResourceAttrDiff{
   587  						Old:         "",
   588  						New:         "",
   589  						NewComputed: true,
   590  					},
   591  				},
   592  			},
   593  			&InstanceDiff{
   594  				Attributes: map[string]*ResourceAttrDiff{
   595  					"foo.%": &ResourceAttrDiff{
   596  						Old:         "0",
   597  						New:         "1",
   598  						NewComputed: false,
   599  					},
   600  					"foo.val": &ResourceAttrDiff{
   601  						Old: "",
   602  						New: "something",
   603  					},
   604  				},
   605  			},
   606  			true,
   607  			"",
   608  		},
   609  
   610  		// In a DESTROY/CREATE scenario, the plan diff will be run against the
   611  		// state of the old instance, while the apply diff will be run against an
   612  		// empty state (because the state is cleared when the destroy runs.)
   613  		// For complex attributes, this can result in keys that seem to disappear
   614  		// between the two diffs, when in reality everything is working just fine.
   615  		//
   616  		// Same() needs to take into account this scenario by analyzing NewRemoved
   617  		// and treating as "Same" a diff that does indeed have that key removed.
   618  		{
   619  			&InstanceDiff{
   620  				Attributes: map[string]*ResourceAttrDiff{
   621  					"somemap.oldkey": &ResourceAttrDiff{
   622  						Old:        "long ago",
   623  						New:        "",
   624  						NewRemoved: true,
   625  					},
   626  					"somemap.newkey": &ResourceAttrDiff{
   627  						Old: "",
   628  						New: "brave new world",
   629  					},
   630  				},
   631  			},
   632  			&InstanceDiff{
   633  				Attributes: map[string]*ResourceAttrDiff{
   634  					"somemap.newkey": &ResourceAttrDiff{
   635  						Old: "",
   636  						New: "brave new world",
   637  					},
   638  				},
   639  			},
   640  			true,
   641  			"",
   642  		},
   643  
   644  		// Another thing that can occur in DESTROY/CREATE scenarios is that list
   645  		// values that are going to zero have diffs that show up at plan time but
   646  		// are gone at apply time. The NewRemoved handling catches the fields and
   647  		// treats them as OK, but it also needs to treat the .# field itself as
   648  		// okay to be present in the old diff but not in the new one.
   649  		{
   650  			&InstanceDiff{
   651  				Attributes: map[string]*ResourceAttrDiff{
   652  					"reqnew": &ResourceAttrDiff{
   653  						Old:         "old",
   654  						New:         "new",
   655  						RequiresNew: true,
   656  					},
   657  					"somemap.#": &ResourceAttrDiff{
   658  						Old: "1",
   659  						New: "0",
   660  					},
   661  					"somemap.oldkey": &ResourceAttrDiff{
   662  						Old:        "long ago",
   663  						New:        "",
   664  						NewRemoved: true,
   665  					},
   666  				},
   667  			},
   668  			&InstanceDiff{
   669  				Attributes: map[string]*ResourceAttrDiff{
   670  					"reqnew": &ResourceAttrDiff{
   671  						Old:         "",
   672  						New:         "new",
   673  						RequiresNew: true,
   674  					},
   675  				},
   676  			},
   677  			true,
   678  			"",
   679  		},
   680  
   681  		{
   682  			&InstanceDiff{
   683  				Attributes: map[string]*ResourceAttrDiff{
   684  					"reqnew": &ResourceAttrDiff{
   685  						Old:         "old",
   686  						New:         "new",
   687  						RequiresNew: true,
   688  					},
   689  					"somemap.%": &ResourceAttrDiff{
   690  						Old: "1",
   691  						New: "0",
   692  					},
   693  					"somemap.oldkey": &ResourceAttrDiff{
   694  						Old:        "long ago",
   695  						New:        "",
   696  						NewRemoved: true,
   697  					},
   698  				},
   699  			},
   700  			&InstanceDiff{
   701  				Attributes: map[string]*ResourceAttrDiff{
   702  					"reqnew": &ResourceAttrDiff{
   703  						Old:         "",
   704  						New:         "new",
   705  						RequiresNew: true,
   706  					},
   707  				},
   708  			},
   709  			true,
   710  			"",
   711  		},
   712  	}
   713  
   714  	for i, tc := range cases {
   715  		same, reason := tc.One.Same(tc.Two)
   716  		if same != tc.Same {
   717  			t.Fatalf("%d: expected same: %t, got %t (%s)\n\n one: %#v\n\ntwo: %#v",
   718  				i, tc.Same, same, reason, tc.One, tc.Two)
   719  		}
   720  		if reason != tc.Reason {
   721  			t.Fatalf(
   722  				"%d: bad reason\n\nexpected: %#v\n\ngot: %#v", i, tc.Reason, reason)
   723  		}
   724  	}
   725  }
   726  
   727  const moduleDiffStrBasic = `
   728  CREATE: nodeA
   729    bar:       "foo" => "<computed>"
   730    foo:       "foo" => "bar"
   731    longfoo:   "foo" => "bar" (forces new resource)
   732    secretfoo: "<sensitive>" => "<sensitive>" (attribute changed)
   733  `