mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-16 03:32:54 -06:00
e865faf318
I frequently see people attempting to ask questions about Terraform's error and warning messages but either only copying part of the message or accidentally copying a surrounding paragraph that isn't part of the message. While I'm sure some of these are just "careless" mistakes, I've also noticed that this has sometimes overlapped with someone asking a question whose answer is written directly in the part of the message they didn't include when copying, and so I have a theory that our current output doesn't create a good enough visual hierarchy for sighted users to understand where the diagnostic messages start and end when we show them in close proximity to other content, or to other diagnostic messages. As a result, some folks fail to notice the relevant message that might've answered their question. I tried a few different experiments for different approaches here, such as adding more horizontal rules to the output and coloring the detail text differently, but the approach that felt like the nicest compromise to me was what's implemented here, which is to add a vertical line along the left edge of each diagnostic message, colored to match with the typical color we use for each diagnostic severity. This means that the diagnostics end up slightly indented from what's around them, and the vertical line seems to help subtly signal how we intended the content to be grouped together.
517 lines
12 KiB
Go
517 lines
12 KiB
Go
package command
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/states"
|
|
"github.com/mitchellh/cli"
|
|
)
|
|
|
|
func TestUntaint(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.ObjectTainted,
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
statePath := testStateFile(t, state)
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &UntaintCommand{
|
|
Meta: Meta{
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
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())
|
|
}
|
|
|
|
expected := strings.TrimSpace(`
|
|
test_instance.foo:
|
|
ID = bar
|
|
provider = provider["registry.terraform.io/hashicorp/test"]
|
|
`)
|
|
testStateOutput(t, statePath, expected)
|
|
}
|
|
|
|
func TestUntaint_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.ObjectTainted,
|
|
},
|
|
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)
|
|
c := &UntaintCommand{
|
|
Meta: Meta{
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
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 TestUntaint_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.ObjectTainted,
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
testStateFileDefault(t, state)
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &UntaintCommand{
|
|
Meta: Meta{
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
args := []string{
|
|
"test_instance.foo",
|
|
}
|
|
if code := c.Run(args); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
}
|
|
|
|
// Backup is still tainted
|
|
testStateOutput(t, DefaultStateFilename+".backup", strings.TrimSpace(`
|
|
test_instance.foo: (tainted)
|
|
ID = bar
|
|
provider = provider["registry.terraform.io/hashicorp/test"]
|
|
`))
|
|
|
|
// State is untainted
|
|
testStateOutput(t, DefaultStateFilename, strings.TrimSpace(`
|
|
test_instance.foo:
|
|
ID = bar
|
|
provider = provider["registry.terraform.io/hashicorp/test"]
|
|
`))
|
|
}
|
|
|
|
func TestUntaint_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.ObjectTainted,
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
testStateFileDefault(t, state)
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &UntaintCommand{
|
|
Meta: Meta{
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
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, strings.TrimSpace(`
|
|
test_instance.foo:
|
|
ID = bar
|
|
provider = provider["registry.terraform.io/hashicorp/test"]
|
|
`))
|
|
}
|
|
|
|
func TestUntaint_badState(t *testing.T) {
|
|
ui := new(cli.MockUi)
|
|
c := &UntaintCommand{
|
|
Meta: Meta{
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
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 TestUntaint_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.ObjectTainted,
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
testStateFileDefault(t, state)
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &UntaintCommand{
|
|
Meta: Meta{
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
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, strings.TrimSpace(`
|
|
test_instance.foo:
|
|
ID = bar
|
|
provider = provider["registry.terraform.io/hashicorp/test"]
|
|
`))
|
|
}
|
|
|
|
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.NewDefaultProvider("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/hashicorp/test"]
|
|
`))
|
|
}
|
|
|
|
func TestUntaint_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.ObjectTainted,
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
statePath := testStateFile(t, state)
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &UntaintCommand{
|
|
Meta: Meta{
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
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 TestUntaint_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.ObjectTainted,
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
statePath := testStateFile(t, state)
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &UntaintCommand{
|
|
Meta: Meta{
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
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 TestUntaint_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.ObjectTainted,
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
testStateFileDefault(t, state)
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &UntaintCommand{
|
|
Meta: Meta{
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
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, strings.TrimSpace(`
|
|
test_instance.foo: (tainted)
|
|
ID = bar
|
|
provider = provider["registry.terraform.io/hashicorp/test"]
|
|
`))
|
|
testStateOutput(t, "foo", strings.TrimSpace(`
|
|
test_instance.foo:
|
|
ID = bar
|
|
provider = provider["registry.terraform.io/hashicorp/test"]
|
|
`))
|
|
}
|
|
|
|
func TestUntaint_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.ObjectTainted,
|
|
},
|
|
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":"bar"}`),
|
|
Status: states.ObjectTainted,
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
statePath := testStateFile(t, state)
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &UntaintCommand{
|
|
Meta: Meta{
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
args := []string{
|
|
"-state", statePath,
|
|
"module.child.test_instance.blah",
|
|
}
|
|
if code := c.Run(args); code != 0 {
|
|
t.Fatalf("command exited with status code %d; want 0\n\n%s", code, ui.ErrorWriter.String())
|
|
}
|
|
|
|
testStateOutput(t, statePath, strings.TrimSpace(`
|
|
test_instance.foo: (tainted)
|
|
ID = bar
|
|
provider = provider["registry.terraform.io/hashicorp/test"]
|
|
|
|
module.child:
|
|
test_instance.blah:
|
|
ID = bar
|
|
provider = provider["registry.terraform.io/hashicorp/test"]
|
|
`))
|
|
}
|