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