github.com/gavinw2006/hashicorp-terraform@v0.11.12-beta1/command/format/plan_test.go (about)

     1  package format
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/davecgh/go-spew/spew"
     9  	"github.com/hashicorp/terraform/terraform"
    10  	"github.com/mitchellh/colorstring"
    11  )
    12  
    13  var disabledColorize = &colorstring.Colorize{
    14  	Colors:  colorstring.DefaultColors,
    15  	Disable: true,
    16  }
    17  
    18  func TestNewPlan(t *testing.T) {
    19  	tests := map[string]struct {
    20  		Input *terraform.Plan
    21  		Want  *Plan
    22  	}{
    23  		"nil input": {
    24  			Input: nil,
    25  			Want: &Plan{
    26  				Resources: nil,
    27  			},
    28  		},
    29  		"nil diff": {
    30  			Input: &terraform.Plan{},
    31  			Want: &Plan{
    32  				Resources: nil,
    33  			},
    34  		},
    35  		"empty diff": {
    36  			Input: &terraform.Plan{
    37  				Diff: &terraform.Diff{
    38  					Modules: []*terraform.ModuleDiff{
    39  						{
    40  							Path:      []string{"root"},
    41  							Resources: map[string]*terraform.InstanceDiff{},
    42  						},
    43  					},
    44  				},
    45  			},
    46  			Want: &Plan{
    47  				Resources: nil,
    48  			},
    49  		},
    50  		"create managed resource": {
    51  			Input: &terraform.Plan{
    52  				Diff: &terraform.Diff{
    53  					Modules: []*terraform.ModuleDiff{
    54  						{
    55  							Path: []string{"root"},
    56  							Resources: map[string]*terraform.InstanceDiff{
    57  								"test_resource.foo": {
    58  									Attributes: map[string]*terraform.ResourceAttrDiff{
    59  										"id": {
    60  											NewComputed: true,
    61  											RequiresNew: true,
    62  										},
    63  									},
    64  								},
    65  							},
    66  						},
    67  					},
    68  				},
    69  			},
    70  			Want: &Plan{
    71  				Resources: []*InstanceDiff{
    72  					{
    73  						Addr:   mustParseResourceAddress("test_resource.foo"),
    74  						Action: terraform.DiffCreate,
    75  						Attributes: []*AttributeDiff{
    76  							{
    77  								Path:        "id",
    78  								Action:      terraform.DiffCreate,
    79  								NewComputed: true,
    80  								ForcesNew:   true,
    81  							},
    82  						},
    83  					},
    84  				},
    85  			},
    86  		},
    87  		"create managed resource in child module": {
    88  			Input: &terraform.Plan{
    89  				Diff: &terraform.Diff{
    90  					Modules: []*terraform.ModuleDiff{
    91  						{
    92  							Path: []string{"root"},
    93  							Resources: map[string]*terraform.InstanceDiff{
    94  								"test_resource.foo": {
    95  									Attributes: map[string]*terraform.ResourceAttrDiff{
    96  										"id": {
    97  											NewComputed: true,
    98  											RequiresNew: true,
    99  										},
   100  									},
   101  								},
   102  							},
   103  						},
   104  						{
   105  							Path: []string{"root", "foo"},
   106  							Resources: map[string]*terraform.InstanceDiff{
   107  								"test_resource.foo": {
   108  									Attributes: map[string]*terraform.ResourceAttrDiff{
   109  										"id": {
   110  											NewComputed: true,
   111  											RequiresNew: true,
   112  										},
   113  									},
   114  								},
   115  							},
   116  						},
   117  					},
   118  				},
   119  			},
   120  			Want: &Plan{
   121  				Resources: []*InstanceDiff{
   122  					{
   123  						Addr:   mustParseResourceAddress("test_resource.foo"),
   124  						Action: terraform.DiffCreate,
   125  						Attributes: []*AttributeDiff{
   126  							{
   127  								Path:        "id",
   128  								Action:      terraform.DiffCreate,
   129  								NewComputed: true,
   130  								ForcesNew:   true,
   131  							},
   132  						},
   133  					},
   134  					{
   135  						Addr:   mustParseResourceAddress("module.foo.test_resource.foo"),
   136  						Action: terraform.DiffCreate,
   137  						Attributes: []*AttributeDiff{
   138  							{
   139  								Path:        "id",
   140  								Action:      terraform.DiffCreate,
   141  								NewComputed: true,
   142  								ForcesNew:   true,
   143  							},
   144  						},
   145  					},
   146  				},
   147  			},
   148  		},
   149  		"create data resource": {
   150  			Input: &terraform.Plan{
   151  				Diff: &terraform.Diff{
   152  					Modules: []*terraform.ModuleDiff{
   153  						{
   154  							Path: []string{"root"},
   155  							Resources: map[string]*terraform.InstanceDiff{
   156  								"data.test_data_source.foo": {
   157  									Attributes: map[string]*terraform.ResourceAttrDiff{
   158  										"id": {
   159  											NewComputed: true,
   160  											RequiresNew: true,
   161  										},
   162  									},
   163  								},
   164  							},
   165  						},
   166  					},
   167  				},
   168  			},
   169  			Want: &Plan{
   170  				Resources: []*InstanceDiff{
   171  					{
   172  						Addr:   mustParseResourceAddress("data.test_data_source.foo"),
   173  						Action: terraform.DiffRefresh,
   174  						Attributes: []*AttributeDiff{
   175  							{
   176  								Path:        "id",
   177  								Action:      terraform.DiffUpdate,
   178  								NewComputed: true,
   179  								ForcesNew:   true,
   180  							},
   181  						},
   182  					},
   183  				},
   184  			},
   185  		},
   186  		"destroy managed resource": {
   187  			Input: &terraform.Plan{
   188  				Diff: &terraform.Diff{
   189  					Modules: []*terraform.ModuleDiff{
   190  						{
   191  							Path: []string{"root"},
   192  							Resources: map[string]*terraform.InstanceDiff{
   193  								"test_resource.foo": {
   194  									Destroy: true,
   195  								},
   196  							},
   197  						},
   198  					},
   199  				},
   200  			},
   201  			Want: &Plan{
   202  				Resources: []*InstanceDiff{
   203  					{
   204  						Addr:   mustParseResourceAddress("test_resource.foo"),
   205  						Action: terraform.DiffDestroy,
   206  					},
   207  				},
   208  			},
   209  		},
   210  		"destroy data resource": {
   211  			Input: &terraform.Plan{
   212  				Diff: &terraform.Diff{
   213  					Modules: []*terraform.ModuleDiff{
   214  						{
   215  							Path: []string{"root"},
   216  							Resources: map[string]*terraform.InstanceDiff{
   217  								"data.test_data_source.foo": {
   218  									Destroy: true,
   219  								},
   220  							},
   221  						},
   222  					},
   223  				},
   224  			},
   225  			Want: &Plan{
   226  				// Data source destroys are not shown
   227  				Resources: nil,
   228  			},
   229  		},
   230  		"destroy many instances of a resource": {
   231  			Input: &terraform.Plan{
   232  				Diff: &terraform.Diff{
   233  					Modules: []*terraform.ModuleDiff{
   234  						{
   235  							Path: []string{"root"},
   236  							Resources: map[string]*terraform.InstanceDiff{
   237  								"test_resource.foo.0": {
   238  									Destroy: true,
   239  								},
   240  								"test_resource.foo.1": {
   241  									Destroy: true,
   242  								},
   243  								"test_resource.foo.10": {
   244  									Destroy: true,
   245  								},
   246  								"test_resource.foo.2": {
   247  									Destroy: true,
   248  								},
   249  								"test_resource.foo.3": {
   250  									Destroy: true,
   251  								},
   252  								"test_resource.foo.4": {
   253  									Destroy: true,
   254  								},
   255  								"test_resource.foo.5": {
   256  									Destroy: true,
   257  								},
   258  								"test_resource.foo.6": {
   259  									Destroy: true,
   260  								},
   261  								"test_resource.foo.7": {
   262  									Destroy: true,
   263  								},
   264  								"test_resource.foo.8": {
   265  									Destroy: true,
   266  								},
   267  								"test_resource.foo.9": {
   268  									Destroy: true,
   269  								},
   270  							},
   271  						},
   272  					},
   273  				},
   274  			},
   275  			Want: &Plan{
   276  				Resources: []*InstanceDiff{
   277  					{
   278  						Addr:   mustParseResourceAddress("test_resource.foo[0]"),
   279  						Action: terraform.DiffDestroy,
   280  					},
   281  					{
   282  						Addr:   mustParseResourceAddress("test_resource.foo[1]"),
   283  						Action: terraform.DiffDestroy,
   284  					},
   285  					{
   286  						Addr:   mustParseResourceAddress("test_resource.foo[2]"),
   287  						Action: terraform.DiffDestroy,
   288  					},
   289  					{
   290  						Addr:   mustParseResourceAddress("test_resource.foo[3]"),
   291  						Action: terraform.DiffDestroy,
   292  					},
   293  					{
   294  						Addr:   mustParseResourceAddress("test_resource.foo[4]"),
   295  						Action: terraform.DiffDestroy,
   296  					},
   297  					{
   298  						Addr:   mustParseResourceAddress("test_resource.foo[5]"),
   299  						Action: terraform.DiffDestroy,
   300  					},
   301  					{
   302  						Addr:   mustParseResourceAddress("test_resource.foo[6]"),
   303  						Action: terraform.DiffDestroy,
   304  					},
   305  					{
   306  						Addr:   mustParseResourceAddress("test_resource.foo[7]"),
   307  						Action: terraform.DiffDestroy,
   308  					},
   309  					{
   310  						Addr:   mustParseResourceAddress("test_resource.foo[8]"),
   311  						Action: terraform.DiffDestroy,
   312  					},
   313  					{
   314  						Addr:   mustParseResourceAddress("test_resource.foo[9]"),
   315  						Action: terraform.DiffDestroy,
   316  					},
   317  					{
   318  						Addr:   mustParseResourceAddress("test_resource.foo[10]"),
   319  						Action: terraform.DiffDestroy,
   320  					},
   321  				},
   322  			},
   323  		},
   324  	}
   325  
   326  	for name, test := range tests {
   327  		t.Run(name, func(t *testing.T) {
   328  			got := NewPlan(test.Input)
   329  			if !reflect.DeepEqual(got, test.Want) {
   330  				t.Errorf(
   331  					"wrong result\ninput: %sgot: %swant:%s",
   332  					spew.Sdump(test.Input),
   333  					spew.Sdump(got),
   334  					spew.Sdump(test.Want),
   335  				)
   336  			}
   337  		})
   338  	}
   339  }
   340  
   341  func TestPlanStats(t *testing.T) {
   342  	tests := map[string]struct {
   343  		Input *Plan
   344  		Want  PlanStats
   345  	}{
   346  		"empty": {
   347  			&Plan{},
   348  			PlanStats{},
   349  		},
   350  		"destroy": {
   351  			&Plan{
   352  				Resources: []*InstanceDiff{
   353  					{
   354  						Addr:   mustParseResourceAddress("test_resource.foo"),
   355  						Action: terraform.DiffDestroy,
   356  					},
   357  					{
   358  						Addr:   mustParseResourceAddress("test_resource.bar"),
   359  						Action: terraform.DiffDestroy,
   360  					},
   361  				},
   362  			},
   363  			PlanStats{
   364  				ToDestroy: 2,
   365  			},
   366  		},
   367  		"create": {
   368  			&Plan{
   369  				Resources: []*InstanceDiff{
   370  					{
   371  						Addr:   mustParseResourceAddress("test_resource.foo"),
   372  						Action: terraform.DiffCreate,
   373  					},
   374  					{
   375  						Addr:   mustParseResourceAddress("test_resource.bar"),
   376  						Action: terraform.DiffCreate,
   377  					},
   378  				},
   379  			},
   380  			PlanStats{
   381  				ToAdd: 2,
   382  			},
   383  		},
   384  		"update": {
   385  			&Plan{
   386  				Resources: []*InstanceDiff{
   387  					{
   388  						Addr:   mustParseResourceAddress("test_resource.foo"),
   389  						Action: terraform.DiffUpdate,
   390  					},
   391  					{
   392  						Addr:   mustParseResourceAddress("test_resource.bar"),
   393  						Action: terraform.DiffUpdate,
   394  					},
   395  				},
   396  			},
   397  			PlanStats{
   398  				ToChange: 2,
   399  			},
   400  		},
   401  		"data source refresh": {
   402  			&Plan{
   403  				Resources: []*InstanceDiff{
   404  					{
   405  						Addr:   mustParseResourceAddress("data.test.foo"),
   406  						Action: terraform.DiffRefresh,
   407  					},
   408  				},
   409  			},
   410  			PlanStats{
   411  				// data resource refreshes are not counted in our stats
   412  			},
   413  		},
   414  		"replace": {
   415  			&Plan{
   416  				Resources: []*InstanceDiff{
   417  					{
   418  						Addr:   mustParseResourceAddress("test_resource.foo"),
   419  						Action: terraform.DiffDestroyCreate,
   420  					},
   421  					{
   422  						Addr:   mustParseResourceAddress("test_resource.bar"),
   423  						Action: terraform.DiffDestroyCreate,
   424  					},
   425  				},
   426  			},
   427  			PlanStats{
   428  				ToDestroy: 2,
   429  				ToAdd:     2,
   430  			},
   431  		},
   432  	}
   433  
   434  	for name, test := range tests {
   435  		t.Run(name, func(t *testing.T) {
   436  			got := test.Input.Stats()
   437  			if !reflect.DeepEqual(got, test.Want) {
   438  				t.Errorf(
   439  					"wrong result\ninput: %sgot: %swant:%s",
   440  					spew.Sdump(test.Input),
   441  					spew.Sdump(got),
   442  					spew.Sdump(test.Want),
   443  				)
   444  			}
   445  		})
   446  	}
   447  }
   448  
   449  // Test that deposed instances are marked as such
   450  func TestPlan_destroyDeposed(t *testing.T) {
   451  	plan := &terraform.Plan{
   452  		Diff: &terraform.Diff{
   453  			Modules: []*terraform.ModuleDiff{
   454  				&terraform.ModuleDiff{
   455  					Path: []string{"root"},
   456  					Resources: map[string]*terraform.InstanceDiff{
   457  						"aws_instance.foo": &terraform.InstanceDiff{
   458  							DestroyDeposed: true,
   459  						},
   460  					},
   461  				},
   462  			},
   463  		},
   464  	}
   465  	dispPlan := NewPlan(plan)
   466  	actual := dispPlan.Format(disabledColorize)
   467  
   468  	expected := strings.TrimSpace(`
   469  - aws_instance.foo (deposed)
   470  	`)
   471  	if actual != expected {
   472  		t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
   473  	}
   474  }
   475  
   476  // Test that computed fields with an interpolation string get displayed
   477  func TestPlan_displayInterpolations(t *testing.T) {
   478  	plan := &terraform.Plan{
   479  		Diff: &terraform.Diff{
   480  			Modules: []*terraform.ModuleDiff{
   481  				&terraform.ModuleDiff{
   482  					Path: []string{"root"},
   483  					Resources: map[string]*terraform.InstanceDiff{
   484  						"aws_instance.foo": &terraform.InstanceDiff{
   485  							Attributes: map[string]*terraform.ResourceAttrDiff{
   486  								"computed_field": &terraform.ResourceAttrDiff{
   487  									New:         "${aws_instance.other.id}",
   488  									NewComputed: true,
   489  								},
   490  							},
   491  						},
   492  					},
   493  				},
   494  			},
   495  		},
   496  	}
   497  	dispPlan := NewPlan(plan)
   498  	out := dispPlan.Format(disabledColorize)
   499  	lines := strings.Split(out, "\n")
   500  	if len(lines) != 2 {
   501  		t.Fatal("expected 2 lines of output, got:\n", out)
   502  	}
   503  
   504  	actual := strings.TrimSpace(lines[1])
   505  	expected := `computed_field: "" => "${aws_instance.other.id}"`
   506  
   507  	if actual != expected {
   508  		t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
   509  	}
   510  }
   511  
   512  // Ensure that (forces new resource) text is included
   513  // https://github.com/hashicorp/terraform/issues/16035
   514  func TestPlan_forcesNewResource(t *testing.T) {
   515  	plan := &terraform.Plan{
   516  		Diff: &terraform.Diff{
   517  			Modules: []*terraform.ModuleDiff{
   518  				&terraform.ModuleDiff{
   519  					Path: []string{"root"},
   520  					Resources: map[string]*terraform.InstanceDiff{
   521  						"test_resource.foo": &terraform.InstanceDiff{
   522  							Destroy: true,
   523  							Attributes: map[string]*terraform.ResourceAttrDiff{
   524  								"A": &terraform.ResourceAttrDiff{
   525  									New:         "B",
   526  									RequiresNew: true,
   527  								},
   528  							},
   529  						},
   530  					},
   531  				},
   532  			},
   533  		},
   534  	}
   535  	dispPlan := NewPlan(plan)
   536  	actual := dispPlan.Format(disabledColorize)
   537  
   538  	expected := strings.TrimSpace(`
   539  -/+ test_resource.foo (new resource required)
   540        A: "" => "B" (forces new resource)
   541  	`)
   542  	if actual != expected {
   543  		t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
   544  	}
   545  }
   546  
   547  // Test that a root level data source gets a special plan output on create
   548  func TestPlan_rootDataSource(t *testing.T) {
   549  	plan := &terraform.Plan{
   550  		Diff: &terraform.Diff{
   551  			Modules: []*terraform.ModuleDiff{
   552  				&terraform.ModuleDiff{
   553  					Path: []string{"root"},
   554  					Resources: map[string]*terraform.InstanceDiff{
   555  						"data.type.name": &terraform.InstanceDiff{
   556  							Attributes: map[string]*terraform.ResourceAttrDiff{
   557  								"A": &terraform.ResourceAttrDiff{
   558  									New:         "B",
   559  									RequiresNew: true,
   560  								},
   561  							},
   562  						},
   563  					},
   564  				},
   565  			},
   566  		},
   567  	}
   568  	dispPlan := NewPlan(plan)
   569  	actual := dispPlan.Format(disabledColorize)
   570  
   571  	expected := strings.TrimSpace(`
   572   <= data.type.name
   573        A: "B"
   574  	`)
   575  	if actual != expected {
   576  		t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
   577  	}
   578  }
   579  
   580  // Test that data sources nested in modules get the same plan output
   581  func TestPlan_nestedDataSource(t *testing.T) {
   582  	plan := &terraform.Plan{
   583  		Diff: &terraform.Diff{
   584  			Modules: []*terraform.ModuleDiff{
   585  				&terraform.ModuleDiff{
   586  					Path: []string{"root", "nested"},
   587  					Resources: map[string]*terraform.InstanceDiff{
   588  						"data.type.name": &terraform.InstanceDiff{
   589  							Attributes: map[string]*terraform.ResourceAttrDiff{
   590  								"A": &terraform.ResourceAttrDiff{
   591  									New:         "B",
   592  									RequiresNew: true,
   593  								},
   594  							},
   595  						},
   596  					},
   597  				},
   598  			},
   599  		},
   600  	}
   601  	dispPlan := NewPlan(plan)
   602  	actual := dispPlan.Format(disabledColorize)
   603  
   604  	expected := strings.TrimSpace(`
   605   <= module.nested.data.type.name
   606        A: "B"
   607  	`)
   608  	if actual != expected {
   609  		t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
   610  	}
   611  }
   612  
   613  func mustParseResourceAddress(s string) *terraform.ResourceAddress {
   614  	addr, err := terraform.ParseResourceAddress(s)
   615  	if err != nil {
   616  		panic(err)
   617  	}
   618  	return addr
   619  }