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