From 529ee04269c440e20b4cb4a02d8a773cf89d4563 Mon Sep 17 00:00:00 2001 From: Nick McClendon Date: Mon, 28 Sep 2020 12:09:37 -0500 Subject: [PATCH] Fix taint and untaint commands when in a workspace (#22467) * Fix taint and untaint commands when in a workspace Fixes #22157. Removes DefaultStateFilepath as the default for the -state flag, allowing workspaces to be used properly. * update test with modern state types Co-authored-by: Kristin Laemmert --- command/command_test.go | 26 +++++++++++++++++++++++ command/taint.go | 2 +- command/taint_test.go | 42 ++++++++++++++++++++++++++++++++++++ command/untaint.go | 2 +- command/untaint_test.go | 47 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 2 deletions(-) diff --git a/command/command_test.go b/command/command_test.go index f6f3b6f89..098447bd3 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -42,6 +42,7 @@ import ( "github.com/zclconf/go-cty/cty" backendInit "github.com/hashicorp/terraform/backend/init" + backendLocal "github.com/hashicorp/terraform/backend/local" ) // These are the directories for our test data and fixtures. @@ -388,6 +389,31 @@ func testStateFileDefault(t *testing.T, s *terraform.State) string { return DefaultStateFilename } +// testStateFileWorkspaceDefault writes the state out to the default statefile +// for the given workspace in the cwd. Use `testCwd` to change into a temp cwd. +func testStateFileWorkspaceDefault(t *testing.T, workspace string, s *states.State) string { + t.Helper() + + workspaceDir := filepath.Join(backendLocal.DefaultWorkspaceDir, workspace) + err := os.MkdirAll(workspaceDir, os.ModePerm) + if err != nil { + t.Fatalf("err: %s", err) + } + + path := filepath.Join(workspaceDir, DefaultStateFilename) + f, err := os.Create(path) + if err != nil { + t.Fatalf("err: %s", err) + } + defer f.Close() + + if err := writeStateForTesting(s, f); err != nil { + t.Fatalf("err: %s", err) + } + + return path +} + // testStateFileRemote writes the state out to the remote statefile // in the cwd. Use `testCwd` to change into a temp cwd. func testStateFileRemote(t *testing.T, s *terraform.State) string { diff --git a/command/taint.go b/command/taint.go index 4c8d0f93f..04a5bf1f1 100644 --- a/command/taint.go +++ b/command/taint.go @@ -29,7 +29,7 @@ func (c *TaintCommand) Run(args []string) int { cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") cmdFlags.StringVar(&module, "module", "", "module") - cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") + cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { diff --git a/command/taint_test.go b/command/taint_test.go index 30e60e014..5935d5e8e 100644 --- a/command/taint_test.go +++ b/command/taint_test.go @@ -240,6 +240,48 @@ func TestTaint_defaultState(t *testing.T) { testStateOutput(t, path, testTaintStr) } +func TestTaint_defaultWorkspaceState(t *testing.T) { + // Get a temp cwd + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + 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.NewLegacyProvider("test"), + Module: addrs.RootModule, + }, + ) + }) + testWorkspace := "development" + path := testStateFileWorkspaceDefault(t, testWorkspace, state) + + ui := new(cli.MockUi) + meta := Meta{Ui: ui} + 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( diff --git a/command/untaint.go b/command/untaint.go index b1a77c6ae..2f91d4482 100644 --- a/command/untaint.go +++ b/command/untaint.go @@ -27,7 +27,7 @@ func (c *UntaintCommand) Run(args []string) int { cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") cmdFlags.StringVar(&module, "module", "", "module") - cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") + cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { diff --git a/command/untaint_test.go b/command/untaint_test.go index aa94eebf0..e85a42cdf 100644 --- a/command/untaint_test.go +++ b/command/untaint_test.go @@ -265,6 +265,53 @@ test_instance.foo: `)) } +func TestUntaint_defaultWorkspaceState(t *testing.T) { + // Get a temp cwd + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + // 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.ObjectTainted, + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewLegacyProvider("test"), + Module: addrs.RootModule, + }, + ) + }) + testWorkspace := "development" + path := testStateFileWorkspaceDefault(t, testWorkspace, state) + + ui := new(cli.MockUi) + meta := Meta{Ui: ui} + meta.SetWorkspace(testWorkspace) + c := &UntaintCommand{ + 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, strings.TrimSpace(` +test_instance.foo: + ID = bar + provider = provider["registry.terraform.io/-/test"] + `)) +} + func TestUntaint_missing(t *testing.T) { state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent(