// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package command import ( "os" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/mitchellh/cli" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/states" ) func TestTaint(t *testing.T) { state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"bar"}`), Status: states.ObjectReady, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) }) statePath := testStateFile(t, state) ui := new(cli.MockUi) view, _ := testView(t) c := &TaintCommand{ Meta: Meta{ Ui: ui, View: view, }, } args := []string{ "-state", statePath, "test_instance.foo", } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } testStateOutput(t, statePath, testTaintStr) } func TestTaint_lockedState(t *testing.T) { state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"bar"}`), Status: states.ObjectReady, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) }) statePath := testStateFile(t, state) unlock, err := testLockState(t, testDataDir, statePath) if err != nil { t.Fatal(err) } defer unlock() ui := new(cli.MockUi) view, _ := testView(t) c := &TaintCommand{ Meta: Meta{ Ui: ui, View: view, }, } args := []string{ "-state", statePath, "test_instance.foo", } if code := c.Run(args); code == 0 { t.Fatal("expected error") } output := ui.ErrorWriter.String() if !strings.Contains(output, "lock") { t.Fatal("command output does not look like a lock error:", output) } } func TestTaint_backup(t *testing.T) { // Get a temp cwd testCwd(t) // Write the temp state state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"bar"}`), Status: states.ObjectReady, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) }) testStateFileDefault(t, state) ui := new(cli.MockUi) view, _ := testView(t) c := &TaintCommand{ Meta: Meta{ Ui: ui, View: view, }, } args := []string{ "test_instance.foo", } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } testStateOutput(t, DefaultStateFilename+".backup", testTaintDefaultStr) testStateOutput(t, DefaultStateFilename, testTaintStr) } func TestTaint_backupDisable(t *testing.T) { // Get a temp cwd testCwd(t) // Write the temp state state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"bar"}`), Status: states.ObjectReady, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) }) testStateFileDefault(t, state) ui := new(cli.MockUi) view, _ := testView(t) c := &TaintCommand{ Meta: Meta{ Ui: ui, View: view, }, } args := []string{ "-backup", "-", "test_instance.foo", } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } if _, err := os.Stat(DefaultStateFilename + ".backup"); err == nil { t.Fatal("backup path should not exist") } testStateOutput(t, DefaultStateFilename, testTaintStr) } func TestTaint_badState(t *testing.T) { ui := new(cli.MockUi) view, _ := testView(t) c := &TaintCommand{ Meta: Meta{ Ui: ui, View: view, }, } args := []string{ "-state", "i-should-not-exist-ever", "foo", } if code := c.Run(args); code != 1 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } } func TestTaint_defaultState(t *testing.T) { // Get a temp cwd testCwd(t) // Write the temp state state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"bar"}`), Status: states.ObjectReady, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) }) testStateFileDefault(t, state) ui := new(cli.MockUi) view, _ := testView(t) c := &TaintCommand{ Meta: Meta{ Ui: ui, View: view, }, } args := []string{ "test_instance.foo", } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } testStateOutput(t, DefaultStateFilename, testTaintStr) } func TestTaint_defaultWorkspaceState(t *testing.T) { // Get a temp cwd testCwd(t) state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"bar"}`), Status: states.ObjectReady, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) }) testWorkspace := "development" path := testStateFileWorkspaceDefault(t, testWorkspace, state) ui := new(cli.MockUi) view, _ := testView(t) meta := Meta{Ui: ui, View: view} meta.SetWorkspace(testWorkspace) c := &TaintCommand{ Meta: meta, } args := []string{ "test_instance.foo", } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } testStateOutput(t, path, testTaintStr) } func TestTaint_missing(t *testing.T) { state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"bar"}`), Status: states.ObjectReady, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) }) statePath := testStateFile(t, state) ui := new(cli.MockUi) view, _ := testView(t) c := &TaintCommand{ Meta: Meta{ Ui: ui, View: view, }, } args := []string{ "-state", statePath, "test_instance.bar", } if code := c.Run(args); code == 0 { t.Fatalf("bad: %d\n\n%s", code, ui.OutputWriter.String()) } } func TestTaint_missingAllow(t *testing.T) { state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"bar"}`), Status: states.ObjectReady, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) }) statePath := testStateFile(t, state) ui := new(cli.MockUi) view, _ := testView(t) c := &TaintCommand{ Meta: Meta{ Ui: ui, View: view, }, } args := []string{ "-allow-missing", "-state", statePath, "test_instance.bar", } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } // Check for the warning actual := strings.TrimSpace(ui.ErrorWriter.String()) expected := strings.TrimSpace(` Warning: No such resource instance Resource instance test_instance.bar was not found, but this is not an error because -allow-missing was set. `) if diff := cmp.Diff(expected, actual); diff != "" { t.Fatalf("wrong output\n%s", diff) } } func TestTaint_stateOut(t *testing.T) { // Get a temp cwd testCwd(t) // Write the temp state state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"bar"}`), Status: states.ObjectReady, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) }) testStateFileDefault(t, state) ui := new(cli.MockUi) view, _ := testView(t) c := &TaintCommand{ Meta: Meta{ Ui: ui, View: view, }, } args := []string{ "-state-out", "foo", "test_instance.foo", } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } testStateOutput(t, DefaultStateFilename, testTaintDefaultStr) testStateOutput(t, "foo", testTaintStr) } func TestTaint_module(t *testing.T) { state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"bar"}`), Status: states.ObjectReady, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "blah", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"blah"}`), Status: states.ObjectReady, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) }) statePath := testStateFile(t, state) ui := new(cli.MockUi) view, _ := testView(t) c := &TaintCommand{ Meta: Meta{ Ui: ui, View: view, }, } args := []string{ "-state", statePath, "module.child.test_instance.blah", } if code := c.Run(args); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } testStateOutput(t, statePath, testTaintModuleStr) } func TestTaint_checkRequiredVersion(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() testCopyDir(t, testFixturePath("command-check-required-version"), td) defer testChdir(t, td)() // Write the temp state state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"bar"}`), Status: states.ObjectReady, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) }) path := testStateFile(t, state) ui := cli.NewMockUi() view, _ := testView(t) c := &TaintCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, View: view, }, } args := []string{"test_instance.foo"} if code := c.Run(args); code != 1 { t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String()) } // State is unchanged testStateOutput(t, path, testTaintDefaultStr) // Required version diags are correct errStr := ui.ErrorWriter.String() if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) { t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr) } if strings.Contains(errStr, `required_version = ">= 0.13.0"`) { t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr) } } const testTaintStr = ` test_instance.foo: (tainted) ID = bar provider = provider["registry.terraform.io/hashicorp/test"] ` const testTaintDefaultStr = ` test_instance.foo: ID = bar provider = provider["registry.terraform.io/hashicorp/test"] ` const testTaintModuleStr = ` test_instance.foo: ID = bar provider = provider["registry.terraform.io/hashicorp/test"] module.child: test_instance.blah: (tainted) ID = blah provider = provider["registry.terraform.io/hashicorp/test"] `