opentofu/internal/command/taint_test.go
Martin Atkins f40800b3a4 Move states/ to internal/states/
This is part of a general effort to move all of Terraform's non-library
package surface under internal in order to reinforce that these are for
internal use within Terraform only.

If you were previously importing packages under this prefix into an
external codebase, you could pin to an earlier release tag as an interim
solution until you've make a plan to achieve the same functionality some
other way.
2021-05-17 14:09:07 -07:00

571 lines
13 KiB
Go

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(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
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.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
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.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
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.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
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.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
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.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 := tempDir(t)
testCopyDir(t, testFixturePath("taint-check-required-version"), td)
defer os.RemoveAll(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"]
`