github.com/opentofu/opentofu@v1.7.1/internal/command/untaint_test.go (about)

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