github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/untaint_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package command
     5  
     6  import (
     7  	"os"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/google/go-cmp/cmp"
    12  	"github.com/mitchellh/cli"
    13  	"github.com/terramate-io/tf/addrs"
    14  	"github.com/terramate-io/tf/states"
    15  )
    16  
    17  func TestUntaint(t *testing.T) {
    18  	state := states.BuildState(func(s *states.SyncState) {
    19  		s.SetResourceInstanceCurrent(
    20  			addrs.Resource{
    21  				Mode: addrs.ManagedResourceMode,
    22  				Type: "test_instance",
    23  				Name: "foo",
    24  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
    25  			&states.ResourceInstanceObjectSrc{
    26  				AttrsJSON: []byte(`{"id":"bar"}`),
    27  				Status:    states.ObjectTainted,
    28  			},
    29  			addrs.AbsProviderConfig{
    30  				Provider: addrs.NewDefaultProvider("test"),
    31  				Module:   addrs.RootModule,
    32  			},
    33  		)
    34  	})
    35  	statePath := testStateFile(t, state)
    36  
    37  	ui := new(cli.MockUi)
    38  	view, _ := testView(t)
    39  	c := &UntaintCommand{
    40  		Meta: Meta{
    41  			Ui:   ui,
    42  			View: view,
    43  		},
    44  	}
    45  
    46  	args := []string{
    47  		"-state", statePath,
    48  		"test_instance.foo",
    49  	}
    50  	if code := c.Run(args); code != 0 {
    51  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
    52  	}
    53  
    54  	expected := strings.TrimSpace(`
    55  test_instance.foo:
    56    ID = bar
    57    provider = provider["registry.terraform.io/hashicorp/test"]
    58  	`)
    59  	testStateOutput(t, statePath, expected)
    60  }
    61  
    62  func TestUntaint_lockedState(t *testing.T) {
    63  	state := states.BuildState(func(s *states.SyncState) {
    64  		s.SetResourceInstanceCurrent(
    65  			addrs.Resource{
    66  				Mode: addrs.ManagedResourceMode,
    67  				Type: "test_instance",
    68  				Name: "foo",
    69  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
    70  			&states.ResourceInstanceObjectSrc{
    71  				AttrsJSON: []byte(`{"id":"bar"}`),
    72  				Status:    states.ObjectTainted,
    73  			},
    74  			addrs.AbsProviderConfig{
    75  				Provider: addrs.NewDefaultProvider("test"),
    76  				Module:   addrs.RootModule,
    77  			},
    78  		)
    79  	})
    80  	statePath := testStateFile(t, state)
    81  	unlock, err := testLockState(t, testDataDir, statePath)
    82  	if err != nil {
    83  		t.Fatal(err)
    84  	}
    85  	defer unlock()
    86  
    87  	ui := new(cli.MockUi)
    88  	view, _ := testView(t)
    89  	c := &UntaintCommand{
    90  		Meta: Meta{
    91  			Ui:   ui,
    92  			View: view,
    93  		},
    94  	}
    95  
    96  	args := []string{
    97  		"-state", statePath,
    98  		"test_instance.foo",
    99  	}
   100  	if code := c.Run(args); code == 0 {
   101  		t.Fatal("expected error")
   102  	}
   103  
   104  	output := ui.ErrorWriter.String()
   105  	if !strings.Contains(output, "lock") {
   106  		t.Fatal("command output does not look like a lock error:", output)
   107  	}
   108  }
   109  
   110  func TestUntaint_backup(t *testing.T) {
   111  	// Get a temp cwd
   112  	testCwd(t)
   113  
   114  	// Write the temp state
   115  	state := states.BuildState(func(s *states.SyncState) {
   116  		s.SetResourceInstanceCurrent(
   117  			addrs.Resource{
   118  				Mode: addrs.ManagedResourceMode,
   119  				Type: "test_instance",
   120  				Name: "foo",
   121  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   122  			&states.ResourceInstanceObjectSrc{
   123  				AttrsJSON: []byte(`{"id":"bar"}`),
   124  				Status:    states.ObjectTainted,
   125  			},
   126  			addrs.AbsProviderConfig{
   127  				Provider: addrs.NewDefaultProvider("test"),
   128  				Module:   addrs.RootModule,
   129  			},
   130  		)
   131  	})
   132  	testStateFileDefault(t, state)
   133  
   134  	ui := new(cli.MockUi)
   135  	view, _ := testView(t)
   136  	c := &UntaintCommand{
   137  		Meta: Meta{
   138  			Ui:   ui,
   139  			View: view,
   140  		},
   141  	}
   142  
   143  	args := []string{
   144  		"test_instance.foo",
   145  	}
   146  	if code := c.Run(args); code != 0 {
   147  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   148  	}
   149  
   150  	// Backup is still tainted
   151  	testStateOutput(t, DefaultStateFilename+".backup", strings.TrimSpace(`
   152  test_instance.foo: (tainted)
   153    ID = bar
   154    provider = provider["registry.terraform.io/hashicorp/test"]
   155  	`))
   156  
   157  	// State is untainted
   158  	testStateOutput(t, DefaultStateFilename, strings.TrimSpace(`
   159  test_instance.foo:
   160    ID = bar
   161    provider = provider["registry.terraform.io/hashicorp/test"]
   162  	`))
   163  }
   164  
   165  func TestUntaint_backupDisable(t *testing.T) {
   166  	// Get a temp cwd
   167  	testCwd(t)
   168  
   169  	// Write the temp state
   170  	state := states.BuildState(func(s *states.SyncState) {
   171  		s.SetResourceInstanceCurrent(
   172  			addrs.Resource{
   173  				Mode: addrs.ManagedResourceMode,
   174  				Type: "test_instance",
   175  				Name: "foo",
   176  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   177  			&states.ResourceInstanceObjectSrc{
   178  				AttrsJSON: []byte(`{"id":"bar"}`),
   179  				Status:    states.ObjectTainted,
   180  			},
   181  			addrs.AbsProviderConfig{
   182  				Provider: addrs.NewDefaultProvider("test"),
   183  				Module:   addrs.RootModule,
   184  			},
   185  		)
   186  	})
   187  	testStateFileDefault(t, state)
   188  
   189  	ui := new(cli.MockUi)
   190  	view, _ := testView(t)
   191  	c := &UntaintCommand{
   192  		Meta: Meta{
   193  			Ui:   ui,
   194  			View: view,
   195  		},
   196  	}
   197  
   198  	args := []string{
   199  		"-backup", "-",
   200  		"test_instance.foo",
   201  	}
   202  	if code := c.Run(args); code != 0 {
   203  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   204  	}
   205  
   206  	if _, err := os.Stat(DefaultStateFilename + ".backup"); err == nil {
   207  		t.Fatal("backup path should not exist")
   208  	}
   209  
   210  	testStateOutput(t, DefaultStateFilename, strings.TrimSpace(`
   211  test_instance.foo:
   212    ID = bar
   213    provider = provider["registry.terraform.io/hashicorp/test"]
   214  	`))
   215  }
   216  
   217  func TestUntaint_badState(t *testing.T) {
   218  	ui := new(cli.MockUi)
   219  	view, _ := testView(t)
   220  	c := &UntaintCommand{
   221  		Meta: Meta{
   222  			Ui:   ui,
   223  			View: view,
   224  		},
   225  	}
   226  
   227  	args := []string{
   228  		"-state", "i-should-not-exist-ever",
   229  		"foo",
   230  	}
   231  	if code := c.Run(args); code != 1 {
   232  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   233  	}
   234  }
   235  
   236  func TestUntaint_defaultState(t *testing.T) {
   237  	// Get a temp cwd
   238  	testCwd(t)
   239  
   240  	// Write the temp state
   241  	state := states.BuildState(func(s *states.SyncState) {
   242  		s.SetResourceInstanceCurrent(
   243  			addrs.Resource{
   244  				Mode: addrs.ManagedResourceMode,
   245  				Type: "test_instance",
   246  				Name: "foo",
   247  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   248  			&states.ResourceInstanceObjectSrc{
   249  				AttrsJSON: []byte(`{"id":"bar"}`),
   250  				Status:    states.ObjectTainted,
   251  			},
   252  			addrs.AbsProviderConfig{
   253  				Provider: addrs.NewDefaultProvider("test"),
   254  				Module:   addrs.RootModule,
   255  			},
   256  		)
   257  	})
   258  	testStateFileDefault(t, state)
   259  
   260  	ui := new(cli.MockUi)
   261  	view, _ := testView(t)
   262  	c := &UntaintCommand{
   263  		Meta: Meta{
   264  			Ui:   ui,
   265  			View: view,
   266  		},
   267  	}
   268  
   269  	args := []string{
   270  		"test_instance.foo",
   271  	}
   272  	if code := c.Run(args); code != 0 {
   273  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   274  	}
   275  
   276  	testStateOutput(t, DefaultStateFilename, strings.TrimSpace(`
   277  test_instance.foo:
   278    ID = bar
   279    provider = provider["registry.terraform.io/hashicorp/test"]
   280  	`))
   281  }
   282  
   283  func TestUntaint_defaultWorkspaceState(t *testing.T) {
   284  	// Get a temp cwd
   285  	testCwd(t)
   286  
   287  	// Write the temp state
   288  	state := states.BuildState(func(s *states.SyncState) {
   289  		s.SetResourceInstanceCurrent(
   290  			addrs.Resource{
   291  				Mode: addrs.ManagedResourceMode,
   292  				Type: "test_instance",
   293  				Name: "foo",
   294  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   295  			&states.ResourceInstanceObjectSrc{
   296  				AttrsJSON: []byte(`{"id":"bar"}`),
   297  				Status:    states.ObjectTainted,
   298  			},
   299  			addrs.AbsProviderConfig{
   300  				Provider: addrs.NewDefaultProvider("test"),
   301  				Module:   addrs.RootModule,
   302  			},
   303  		)
   304  	})
   305  	testWorkspace := "development"
   306  	path := testStateFileWorkspaceDefault(t, testWorkspace, state)
   307  
   308  	ui := new(cli.MockUi)
   309  	view, _ := testView(t)
   310  	meta := Meta{Ui: ui, View: view}
   311  	meta.SetWorkspace(testWorkspace)
   312  	c := &UntaintCommand{
   313  		Meta: meta,
   314  	}
   315  
   316  	args := []string{
   317  		"test_instance.foo",
   318  	}
   319  	if code := c.Run(args); code != 0 {
   320  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   321  	}
   322  
   323  	testStateOutput(t, path, strings.TrimSpace(`
   324  test_instance.foo:
   325    ID = bar
   326    provider = provider["registry.terraform.io/hashicorp/test"]
   327  	`))
   328  }
   329  
   330  func TestUntaint_missing(t *testing.T) {
   331  	state := states.BuildState(func(s *states.SyncState) {
   332  		s.SetResourceInstanceCurrent(
   333  			addrs.Resource{
   334  				Mode: addrs.ManagedResourceMode,
   335  				Type: "test_instance",
   336  				Name: "foo",
   337  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   338  			&states.ResourceInstanceObjectSrc{
   339  				AttrsJSON: []byte(`{"id":"bar"}`),
   340  				Status:    states.ObjectTainted,
   341  			},
   342  			addrs.AbsProviderConfig{
   343  				Provider: addrs.NewDefaultProvider("test"),
   344  				Module:   addrs.RootModule,
   345  			},
   346  		)
   347  	})
   348  	statePath := testStateFile(t, state)
   349  
   350  	ui := new(cli.MockUi)
   351  	view, _ := testView(t)
   352  	c := &UntaintCommand{
   353  		Meta: Meta{
   354  			Ui:   ui,
   355  			View: view,
   356  		},
   357  	}
   358  
   359  	args := []string{
   360  		"-state", statePath,
   361  		"test_instance.bar",
   362  	}
   363  	if code := c.Run(args); code == 0 {
   364  		t.Fatalf("bad: %d\n\n%s", code, ui.OutputWriter.String())
   365  	}
   366  }
   367  
   368  func TestUntaint_missingAllow(t *testing.T) {
   369  	state := states.BuildState(func(s *states.SyncState) {
   370  		s.SetResourceInstanceCurrent(
   371  			addrs.Resource{
   372  				Mode: addrs.ManagedResourceMode,
   373  				Type: "test_instance",
   374  				Name: "foo",
   375  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   376  			&states.ResourceInstanceObjectSrc{
   377  				AttrsJSON: []byte(`{"id":"bar"}`),
   378  				Status:    states.ObjectTainted,
   379  			},
   380  			addrs.AbsProviderConfig{
   381  				Provider: addrs.NewDefaultProvider("test"),
   382  				Module:   addrs.RootModule,
   383  			},
   384  		)
   385  	})
   386  	statePath := testStateFile(t, state)
   387  
   388  	ui := new(cli.MockUi)
   389  	view, _ := testView(t)
   390  	c := &UntaintCommand{
   391  		Meta: Meta{
   392  			Ui:   ui,
   393  			View: view,
   394  		},
   395  	}
   396  
   397  	args := []string{
   398  		"-allow-missing",
   399  		"-state", statePath,
   400  		"test_instance.bar",
   401  	}
   402  	if code := c.Run(args); code != 0 {
   403  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   404  	}
   405  
   406  	// Check for the warning
   407  	actual := strings.TrimSpace(ui.ErrorWriter.String())
   408  	expected := strings.TrimSpace(`
   409  Warning: No such resource instance
   410  
   411  Resource instance test_instance.bar was not found, but this is not an error
   412  because -allow-missing was set.
   413  
   414  `)
   415  	if diff := cmp.Diff(expected, actual); diff != "" {
   416  		t.Fatalf("wrong output\n%s", diff)
   417  	}
   418  }
   419  
   420  func TestUntaint_stateOut(t *testing.T) {
   421  	// Get a temp cwd
   422  	testCwd(t)
   423  
   424  	// Write the temp state
   425  	state := states.BuildState(func(s *states.SyncState) {
   426  		s.SetResourceInstanceCurrent(
   427  			addrs.Resource{
   428  				Mode: addrs.ManagedResourceMode,
   429  				Type: "test_instance",
   430  				Name: "foo",
   431  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   432  			&states.ResourceInstanceObjectSrc{
   433  				AttrsJSON: []byte(`{"id":"bar"}`),
   434  				Status:    states.ObjectTainted,
   435  			},
   436  			addrs.AbsProviderConfig{
   437  				Provider: addrs.NewDefaultProvider("test"),
   438  				Module:   addrs.RootModule,
   439  			},
   440  		)
   441  	})
   442  	testStateFileDefault(t, state)
   443  
   444  	ui := new(cli.MockUi)
   445  	view, _ := testView(t)
   446  	c := &UntaintCommand{
   447  		Meta: Meta{
   448  			Ui:   ui,
   449  			View: view,
   450  		},
   451  	}
   452  
   453  	args := []string{
   454  		"-state-out", "foo",
   455  		"test_instance.foo",
   456  	}
   457  	if code := c.Run(args); code != 0 {
   458  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   459  	}
   460  
   461  	testStateOutput(t, DefaultStateFilename, strings.TrimSpace(`
   462  test_instance.foo: (tainted)
   463    ID = bar
   464    provider = provider["registry.terraform.io/hashicorp/test"]
   465  	`))
   466  	testStateOutput(t, "foo", strings.TrimSpace(`
   467  test_instance.foo:
   468    ID = bar
   469    provider = provider["registry.terraform.io/hashicorp/test"]
   470  	`))
   471  }
   472  
   473  func TestUntaint_module(t *testing.T) {
   474  	state := states.BuildState(func(s *states.SyncState) {
   475  		s.SetResourceInstanceCurrent(
   476  			addrs.Resource{
   477  				Mode: addrs.ManagedResourceMode,
   478  				Type: "test_instance",
   479  				Name: "foo",
   480  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   481  			&states.ResourceInstanceObjectSrc{
   482  				AttrsJSON: []byte(`{"id":"bar"}`),
   483  				Status:    states.ObjectTainted,
   484  			},
   485  			addrs.AbsProviderConfig{
   486  				Provider: addrs.NewDefaultProvider("test"),
   487  				Module:   addrs.RootModule,
   488  			},
   489  		)
   490  		s.SetResourceInstanceCurrent(
   491  			addrs.Resource{
   492  				Mode: addrs.ManagedResourceMode,
   493  				Type: "test_instance",
   494  				Name: "blah",
   495  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)),
   496  			&states.ResourceInstanceObjectSrc{
   497  				AttrsJSON: []byte(`{"id":"bar"}`),
   498  				Status:    states.ObjectTainted,
   499  			},
   500  			addrs.AbsProviderConfig{
   501  				Provider: addrs.NewDefaultProvider("test"),
   502  				Module:   addrs.RootModule,
   503  			},
   504  		)
   505  	})
   506  	statePath := testStateFile(t, state)
   507  
   508  	ui := new(cli.MockUi)
   509  	view, _ := testView(t)
   510  	c := &UntaintCommand{
   511  		Meta: Meta{
   512  			Ui:   ui,
   513  			View: view,
   514  		},
   515  	}
   516  
   517  	args := []string{
   518  		"-state", statePath,
   519  		"module.child.test_instance.blah",
   520  	}
   521  	if code := c.Run(args); code != 0 {
   522  		t.Fatalf("command exited with status code %d; want 0\n\n%s", code, ui.ErrorWriter.String())
   523  	}
   524  
   525  	testStateOutput(t, statePath, strings.TrimSpace(`
   526  test_instance.foo: (tainted)
   527    ID = bar
   528    provider = provider["registry.terraform.io/hashicorp/test"]
   529  
   530  module.child:
   531    test_instance.blah:
   532      ID = bar
   533      provider = provider["registry.terraform.io/hashicorp/test"]
   534  	`))
   535  }