mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-24 08:00:17 -06:00
Issue 248: left-over resources after tofu test should be written to a file (#1243)
Signed-off-by: kazzhar <karthik.nayak@harness.io>
This commit is contained in:
parent
670f2515c3
commit
311b5c37b0
@ -980,6 +980,9 @@ func (runner *TestFileRunner) Cleanup(file *moduletest.File) {
|
||||
}
|
||||
runner.Suite.View.DestroySummary(diags, state.Run, file, updated)
|
||||
|
||||
if updated.HasManagedResourceInstanceObjects() {
|
||||
views.SaveErroredTestStateFile(updated, state.Run, file, runner.Suite.View)
|
||||
}
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,12 @@ import (
|
||||
"github.com/opentofu/opentofu/internal/command/jsonstate"
|
||||
"github.com/opentofu/opentofu/internal/command/views/json"
|
||||
"github.com/opentofu/opentofu/internal/configs"
|
||||
"github.com/opentofu/opentofu/internal/encryption"
|
||||
"github.com/opentofu/opentofu/internal/moduletest"
|
||||
"github.com/opentofu/opentofu/internal/plans"
|
||||
"github.com/opentofu/opentofu/internal/states"
|
||||
"github.com/opentofu/opentofu/internal/states/statefile"
|
||||
"github.com/opentofu/opentofu/internal/states/statemgr"
|
||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||
"github.com/opentofu/opentofu/internal/tofu"
|
||||
)
|
||||
@ -223,7 +225,7 @@ func (t *TestHuman) DestroySummary(diags tfdiags.Diagnostics, run *moduletest.Ru
|
||||
t.Diagnostics(run, file, diags)
|
||||
|
||||
if state.HasManagedResourceInstanceObjects() {
|
||||
t.view.streams.Eprint(format.WordWrap(fmt.Sprintf("\nOpenTofu left the following resources in state after executing %s, and they need to be cleaned up manually:\n", identifier), t.view.errorColumns()))
|
||||
t.view.streams.Eprint(format.WordWrap(fmt.Sprintf("\nOpenTofu left the following resources in state after executing %s, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:\n", identifier), t.view.errorColumns()))
|
||||
for _, resource := range state.AllResourceInstanceObjectAddrs() {
|
||||
if resource.DeposedKey != states.NotDeposed {
|
||||
t.view.streams.Eprintf(" - %s (%s)\n", resource.Instance, resource.DeposedKey)
|
||||
@ -460,21 +462,19 @@ func (t *TestJSON) DestroySummary(diags tfdiags.Diagnostics, run *moduletest.Run
|
||||
|
||||
if run != nil {
|
||||
t.view.log.Error(
|
||||
fmt.Sprintf("OpenTofu left some resources in state after executing %s/%s, they need to be cleaned up manually.", file.Name, run.Name),
|
||||
fmt.Sprintf("OpenTofu left some resources in state after executing %s/%s, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:", file.Name, run.Name),
|
||||
"type", json.MessageTestCleanup,
|
||||
json.MessageTestCleanup, cleanup,
|
||||
"@testfile", file.Name,
|
||||
"@testrun", run.Name)
|
||||
} else {
|
||||
t.view.log.Error(
|
||||
fmt.Sprintf("OpenTofu left some resources in state after executing %s, they need to be cleaned up manually.", file.Name),
|
||||
fmt.Sprintf("OpenTofu left some resources in state after executing %s, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:", file.Name),
|
||||
"type", json.MessageTestCleanup,
|
||||
json.MessageTestCleanup, cleanup,
|
||||
"@testfile", file.Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
t.Diagnostics(run, file, diags)
|
||||
}
|
||||
|
||||
@ -570,3 +570,61 @@ func testStatus(status moduletest.Status) string {
|
||||
panic("unrecognized status: " + status.String())
|
||||
}
|
||||
}
|
||||
|
||||
// SaveErroredTestStateFile is a helper function to invoked in DestorySummary
|
||||
// to store the state to errored_test.tfstate and handle associated diagnostics and errors with this operation
|
||||
func SaveErroredTestStateFile(state *states.State, run *moduletest.Run, file *moduletest.File, view Test) {
|
||||
var diags tfdiags.Diagnostics
|
||||
localFileSystem := statemgr.NewFilesystem("errored_test.tfstate", encryption.StateEncryptionDisabled())
|
||||
stateFile := statemgr.NewStateFile()
|
||||
stateFile.State = state
|
||||
|
||||
//creating an operation to invoke EmergencyDumpState()
|
||||
var op Operation
|
||||
switch v := view.(type) {
|
||||
case *TestHuman:
|
||||
op = NewOperation(arguments.ViewHuman, false, v.view)
|
||||
v.view.streams.Eprint(format.WordWrap("\nWriting state to file: errored_test.tfstate\n", v.view.errorColumns()))
|
||||
case *TestJSON:
|
||||
op = &OperationJSON{
|
||||
view: v.view,
|
||||
}
|
||||
v.view.log.Info("Writing state to file: errored_test.tfstate")
|
||||
default:
|
||||
}
|
||||
|
||||
writeErr := localFileSystem.WriteStateForMigration(stateFile, true)
|
||||
if writeErr != nil {
|
||||
// if the write operation to errored_test.tfstate executed by WriteStateForMigration fails, as a final attempt to
|
||||
// prevent leaving the user with no state file at all, the JSON state is printed onto the terminal by EmergencyDumpState()
|
||||
|
||||
if dumpErr := op.EmergencyDumpState(stateFile, encryption.StateEncryptionDisabled()); dumpErr != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to serialize state",
|
||||
fmt.Sprintf(stateWriteFatalErrorFmt, dumpErr),
|
||||
))
|
||||
}
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to persist state",
|
||||
stateWriteConsoleFallbackError,
|
||||
))
|
||||
}
|
||||
view.Diagnostics(run, file, diags)
|
||||
}
|
||||
|
||||
const stateWriteFatalErrorFmt = `Failed to save state after an errored test run.
|
||||
|
||||
Error serializing state: %s
|
||||
|
||||
A catastrophic error has prevented OpenTofu from persisting the state during an errored test run.
|
||||
|
||||
This is a serious bug in OpenTofu and should be reported.
|
||||
`
|
||||
|
||||
const stateWriteConsoleFallbackError = `The errors shown above prevented OpenTofu from writing the state to
|
||||
the errored_test.tfstate. As a fallback, the raw state data is printed above as a JSON object.
|
||||
|
||||
To retry writing this state, copy the state data (from the first { to the last } inclusive) and save it into a local file named "errored_test.tfstate".
|
||||
`
|
||||
|
@ -6,6 +6,8 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -849,7 +851,9 @@ some thing not very bad happened again
|
||||
`,
|
||||
stderr: `
|
||||
OpenTofu left the following resources in state after executing
|
||||
main.tftest.hcl, and they need to be cleaned up manually:
|
||||
main.tftest.hcl, these left-over resources can be viewed by reading the
|
||||
statefile written to disk(errored_test.tfstate) and they need to be cleaned
|
||||
up manually:
|
||||
- test.bar
|
||||
- test.bar (0fcb640a)
|
||||
- test.foo
|
||||
@ -921,10 +925,82 @@ Error: first error
|
||||
this time it is very bad
|
||||
|
||||
OpenTofu left the following resources in state after executing
|
||||
main.tftest.hcl, and they need to be cleaned up manually:
|
||||
main.tftest.hcl, these left-over resources can be viewed by reading the
|
||||
statefile written to disk(errored_test.tfstate) and they need to be cleaned
|
||||
up manually:
|
||||
- test.bar
|
||||
- test.bar (0fcb640a)
|
||||
- test.foo
|
||||
`,
|
||||
},
|
||||
"state_null_resource_with_errors": {
|
||||
diags: tfdiags.Diagnostics{
|
||||
tfdiags.Sourceless(tfdiags.Warning, "first warning", "some thing not very bad happened"),
|
||||
tfdiags.Sourceless(tfdiags.Warning, "second warning", "some thing not very bad happened again"),
|
||||
tfdiags.Sourceless(tfdiags.Error, "first error", "this time it is very bad"),
|
||||
},
|
||||
file: &moduletest.File{Name: "main.tftest.hcl"},
|
||||
state: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "null_resource",
|
||||
Name: "failing_will_depend_on_me",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("null"),
|
||||
})
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "null_resource",
|
||||
Name: "failing",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
Dependencies: []addrs.ConfigResource{
|
||||
{
|
||||
Module: []string{},
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "null_resource",
|
||||
Name: "failing_will_depend_on_me",
|
||||
},
|
||||
},
|
||||
},
|
||||
CreateBeforeDestroy: false,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("null"),
|
||||
})
|
||||
}),
|
||||
stdout: `
|
||||
Warning: first warning
|
||||
|
||||
some thing not very bad happened
|
||||
|
||||
Warning: second warning
|
||||
|
||||
some thing not very bad happened again
|
||||
`,
|
||||
stderr: `OpenTofu encountered an error destroying resources created while executing
|
||||
main.tftest.hcl.
|
||||
|
||||
Error: first error
|
||||
|
||||
this time it is very bad
|
||||
|
||||
OpenTofu left the following resources in state after executing
|
||||
main.tftest.hcl, these left-over resources can be viewed by reading the
|
||||
statefile written to disk(errored_test.tfstate) and they need to be cleaned
|
||||
up manually:
|
||||
- null_resource.failing
|
||||
- null_resource.failing_will_depend_on_me
|
||||
`,
|
||||
},
|
||||
}
|
||||
@ -1949,7 +2025,7 @@ func TestTestJSON_DestroySummary(t *testing.T) {
|
||||
want: []map[string]interface{}{
|
||||
{
|
||||
"@level": "error",
|
||||
"@message": "OpenTofu left some resources in state after executing main.tftest.hcl/run_block, they need to be cleaned up manually.",
|
||||
"@message": "OpenTofu left some resources in state after executing main.tftest.hcl/run_block, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:",
|
||||
"@module": "tofu.ui",
|
||||
"@testfile": "main.tftest.hcl",
|
||||
"@testrun": "run_block",
|
||||
@ -2015,7 +2091,7 @@ func TestTestJSON_DestroySummary(t *testing.T) {
|
||||
want: []map[string]interface{}{
|
||||
{
|
||||
"@level": "error",
|
||||
"@message": "OpenTofu left some resources in state after executing main.tftest.hcl, they need to be cleaned up manually.",
|
||||
"@message": "OpenTofu left some resources in state after executing main.tftest.hcl, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:",
|
||||
"@module": "tofu.ui",
|
||||
"@testfile": "main.tftest.hcl",
|
||||
"test_cleanup": map[string]interface{}{
|
||||
@ -2112,7 +2188,7 @@ func TestTestJSON_DestroySummary(t *testing.T) {
|
||||
want: []map[string]interface{}{
|
||||
{
|
||||
"@level": "error",
|
||||
"@message": "OpenTofu left some resources in state after executing main.tftest.hcl, they need to be cleaned up manually.",
|
||||
"@message": "OpenTofu left some resources in state after executing main.tftest.hcl, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:",
|
||||
"@module": "tofu.ui",
|
||||
"@testfile": "main.tftest.hcl",
|
||||
"test_cleanup": map[string]interface{}{
|
||||
@ -2169,6 +2245,107 @@ func TestTestJSON_DestroySummary(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"state_null_resource_with_errors": {
|
||||
diags: tfdiags.Diagnostics{
|
||||
tfdiags.Sourceless(tfdiags.Warning, "first warning", "something not very bad happened"),
|
||||
tfdiags.Sourceless(tfdiags.Warning, "second warning", "something not very bad happened again"),
|
||||
tfdiags.Sourceless(tfdiags.Error, "first error", "this time it is very bad"),
|
||||
},
|
||||
file: &moduletest.File{Name: "main.tftest.hcl"},
|
||||
state: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "null_resource",
|
||||
Name: "failing_will_depend_on_me",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("null"),
|
||||
})
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "null_resource",
|
||||
Name: "failing",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
Dependencies: []addrs.ConfigResource{
|
||||
{
|
||||
Module: []string{},
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "null_resource",
|
||||
Name: "failing_will_depend_on_me",
|
||||
},
|
||||
},
|
||||
},
|
||||
CreateBeforeDestroy: false,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("null"),
|
||||
})
|
||||
}), want: []map[string]interface{}{
|
||||
{
|
||||
"@level": "error",
|
||||
"@message": "OpenTofu left some resources in state after executing main.tftest.hcl, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:",
|
||||
"@module": "tofu.ui",
|
||||
"@testfile": "main.tftest.hcl",
|
||||
"test_cleanup": map[string]interface{}{
|
||||
"failed_resources": []interface{}{
|
||||
map[string]interface{}{
|
||||
"instance": "null_resource.failing",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"instance": "null_resource.failing_will_depend_on_me",
|
||||
},
|
||||
},
|
||||
},
|
||||
"type": "test_cleanup",
|
||||
},
|
||||
{
|
||||
"@level": "warn",
|
||||
"@message": "Warning: first warning",
|
||||
"@module": "tofu.ui",
|
||||
"@testfile": "main.tftest.hcl",
|
||||
"diagnostic": map[string]interface{}{
|
||||
"detail": "something not very bad happened",
|
||||
"severity": "warning",
|
||||
"summary": "first warning",
|
||||
},
|
||||
"type": "diagnostic",
|
||||
},
|
||||
{
|
||||
"@level": "warn",
|
||||
"@message": "Warning: second warning",
|
||||
"@module": "tofu.ui",
|
||||
"@testfile": "main.tftest.hcl",
|
||||
"diagnostic": map[string]interface{}{
|
||||
"detail": "something not very bad happened again",
|
||||
"severity": "warning",
|
||||
"summary": "second warning",
|
||||
},
|
||||
"type": "diagnostic",
|
||||
},
|
||||
{
|
||||
"@level": "error",
|
||||
"@message": "Error: first error",
|
||||
"@module": "tofu.ui",
|
||||
"@testfile": "main.tftest.hcl",
|
||||
"diagnostic": map[string]interface{}{
|
||||
"detail": "this time it is very bad",
|
||||
"severity": "error",
|
||||
"summary": "first error",
|
||||
},
|
||||
"type": "diagnostic",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
@ -3078,6 +3255,320 @@ func TestTestJSON_FatalInterruptSummary(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveErroredStateFile(t *testing.T) {
|
||||
tcsHuman := map[string]struct {
|
||||
state *states.State
|
||||
run *moduletest.Run
|
||||
file *moduletest.File
|
||||
stderr string
|
||||
want interface{}
|
||||
}{
|
||||
"state_foo_bar_human": {
|
||||
file: &moduletest.File{Name: "main.tftest.hcl"},
|
||||
state: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
})
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test",
|
||||
Name: "bar",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
})
|
||||
state.SetResourceInstanceDeposed(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test",
|
||||
Name: "bar",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
"0fcb640a",
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
})
|
||||
}),
|
||||
stderr: `
|
||||
Writing state to file: errored_test.tfstate
|
||||
`,
|
||||
want: nil,
|
||||
},
|
||||
"state_null_resource_human": {
|
||||
file: &moduletest.File{Name: "main.tftest.hcl"},
|
||||
state: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "null_resource",
|
||||
Name: "failing_will_depend_on_me",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("null"),
|
||||
})
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "null_resource",
|
||||
Name: "failing",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
Dependencies: []addrs.ConfigResource{
|
||||
{
|
||||
Module: []string{},
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "null_resource",
|
||||
Name: "failing_will_depend_on_me",
|
||||
},
|
||||
},
|
||||
},
|
||||
CreateBeforeDestroy: false,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("null"),
|
||||
})
|
||||
}),
|
||||
stderr: `
|
||||
Writing state to file: errored_test.tfstate
|
||||
`,
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
|
||||
tcsJson := map[string]struct {
|
||||
state *states.State
|
||||
run *moduletest.Run
|
||||
file *moduletest.File
|
||||
stderr string
|
||||
want interface{}
|
||||
}{
|
||||
"state_with_run_json": {
|
||||
file: &moduletest.File{Name: "main.tftest.hcl"},
|
||||
run: &moduletest.Run{Name: "run_block"},
|
||||
state: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
})
|
||||
}),
|
||||
stderr: "",
|
||||
want: []map[string]interface{}{
|
||||
{
|
||||
"@level": "info",
|
||||
"@message": "Writing state to file: errored_test.tfstate",
|
||||
"@module": string("tofu.ui"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"state_foo_bar_json": {
|
||||
file: &moduletest.File{Name: "main.tftest.hcl"},
|
||||
state: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test",
|
||||
Name: "foo",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
})
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test",
|
||||
Name: "bar",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
})
|
||||
state.SetResourceInstanceDeposed(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "test",
|
||||
Name: "bar",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
"0fcb640a",
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("test"),
|
||||
})
|
||||
}),
|
||||
stderr: "",
|
||||
want: []map[string]interface{}{
|
||||
{
|
||||
"@level": "info",
|
||||
"@message": "Writing state to file: errored_test.tfstate",
|
||||
"@module": "tofu.ui",
|
||||
},
|
||||
},
|
||||
},
|
||||
"state_null_resource_with_errors": {
|
||||
file: &moduletest.File{Name: "main.tftest.hcl"},
|
||||
state: states.BuildState(func(state *states.SyncState) {
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "null_resource",
|
||||
Name: "failing_will_depend_on_me",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("null"),
|
||||
})
|
||||
state.SetResourceInstanceCurrent(
|
||||
addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "null_resource",
|
||||
Name: "failing",
|
||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
Dependencies: []addrs.ConfigResource{
|
||||
{
|
||||
Module: []string{},
|
||||
Resource: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "null_resource",
|
||||
Name: "failing_will_depend_on_me",
|
||||
},
|
||||
},
|
||||
},
|
||||
CreateBeforeDestroy: false,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Module: addrs.RootModule,
|
||||
Provider: addrs.NewDefaultProvider("null"),
|
||||
})
|
||||
}),
|
||||
stderr: "",
|
||||
want: []map[string]interface{}{
|
||||
{
|
||||
"@level": "info",
|
||||
"@message": "Writing state to file: errored_test.tfstate",
|
||||
"@module": "tofu.ui",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
// Run tests for Human view
|
||||
runTestSaveErroredStateFile(t, tcsHuman, arguments.ViewHuman)
|
||||
|
||||
// Run tests for JSON view
|
||||
runTestSaveErroredStateFile(t, tcsJson, arguments.ViewJSON)
|
||||
}
|
||||
|
||||
func runTestSaveErroredStateFile(t *testing.T, tc map[string]struct {
|
||||
state *states.State
|
||||
run *moduletest.Run
|
||||
file *moduletest.File
|
||||
stderr string
|
||||
want interface{}
|
||||
}, viewType arguments.ViewType) {
|
||||
for name, data := range tc {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Create a temporary directory
|
||||
tempDir := t.TempDir()
|
||||
|
||||
// Modify the state file path to use the temporary directory
|
||||
tempStateFilePath := filepath.Clean(filepath.Join(tempDir, "errored_test.tfstate"))
|
||||
|
||||
// Get the current working directory
|
||||
originalDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting current working directory: %v", err)
|
||||
}
|
||||
|
||||
// Change the working directory to the temporary directory
|
||||
if err := os.Chdir(tempDir); err != nil {
|
||||
t.Fatalf("Error changing working directory: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
// Change the working directory back to the original directory after the test
|
||||
if err := os.Chdir(originalDir); err != nil {
|
||||
t.Fatalf("Error changing working directory back: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
|
||||
if viewType == arguments.ViewHuman {
|
||||
view := NewTest(arguments.ViewHuman, NewView(streams))
|
||||
SaveErroredTestStateFile(data.state, data.run, data.file, view)
|
||||
output := done(t)
|
||||
|
||||
actual, expected := output.Stderr(), data.stderr
|
||||
if diff := cmp.Diff(expected, actual); len(diff) > 0 {
|
||||
t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
|
||||
}
|
||||
} else if viewType == arguments.ViewJSON {
|
||||
view := NewTest(arguments.ViewJSON, NewView(streams))
|
||||
SaveErroredTestStateFile(data.state, data.run, data.file, view)
|
||||
want, ok := data.want.([]map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("Failed to assert want as []map[string]interface{}")
|
||||
}
|
||||
testJSONViewOutputEquals(t, done(t).All(), want)
|
||||
} else {
|
||||
t.Fatalf("Unsupported view type: %v", viewType)
|
||||
}
|
||||
|
||||
// Check if the state file exists
|
||||
if _, err := os.Stat(tempStateFilePath); os.IsNotExist(err) {
|
||||
// File does not exist
|
||||
t.Errorf("Expected state file 'errored_test.tfstate' to exist in: %s, but it does not.", tempDir)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func dynamicValue(t *testing.T, value cty.Value, typ cty.Type) plans.DynamicValue {
|
||||
d, err := plans.NewDynamicValue(value, typ)
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user