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