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