opentofu/command/state_list_test.go
Kristin Laemmert 8bab3dd374
command/state list: list resources in nested and expanded modules (#27268)
* command/state list: list resources in nested and expaneded modules

A few distinct bugs fixed in here:

There was a bug in the logic checking if a given module was the child of
the targetAddr, now fixed. That resolved the basic issue where resources
in nested submodules were not listed.

The logic around allowMissing needed some tweaking to allow for empty
modules, as long as those modules had submodules with resources. state
list is the only command using allowMissing with false so this felt safe
to do.

Finally I extended the logic so list would included expanded modules,
which is to say giving module.foo would result in resources from
module.foo[1], module.foo[0], etc.

* update state list docs to show that module filtering includes any nested
modules
2020-12-14 11:07:15 -05:00

277 lines
6.8 KiB
Go

package command
import (
"os"
"strings"
"testing"
"github.com/mitchellh/cli"
)
func TestStateList(t *testing.T) {
state := testState()
statePath := testStateFile(t, state)
p := testProvider()
ui := cli.NewMockUi()
c := &StateListCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{
"-state", statePath,
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Test that outputs were displayed
expected := strings.TrimSpace(testStateListOutput) + "\n"
actual := ui.OutputWriter.String()
if actual != expected {
t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected)
}
}
func TestStateListWithID(t *testing.T) {
state := testState()
statePath := testStateFile(t, state)
p := testProvider()
ui := cli.NewMockUi()
c := &StateListCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{
"-state", statePath,
"-id", "bar",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Test that outputs were displayed
expected := strings.TrimSpace(testStateListOutput) + "\n"
actual := ui.OutputWriter.String()
if actual != expected {
t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected)
}
}
func TestStateListWithNonExistentID(t *testing.T) {
state := testState()
statePath := testStateFile(t, state)
p := testProvider()
ui := cli.NewMockUi()
c := &StateListCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{
"-state", statePath,
"-id", "baz",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Test that output is empty
if ui.OutputWriter != nil {
actual := ui.OutputWriter.String()
if actual != "" {
t.Fatalf("Expected an empty output but got: %q", actual)
}
}
}
func TestStateList_backendDefaultState(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
testCopyDir(t, testFixturePath("state-list-backend-default"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
p := testProvider()
ui := cli.NewMockUi()
c := &StateListCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Test that outputs were displayed
expected := "null_resource.a\n"
actual := ui.OutputWriter.String()
if actual != expected {
t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected)
}
}
func TestStateList_backendCustomState(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
testCopyDir(t, testFixturePath("state-list-backend-custom"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
p := testProvider()
ui := cli.NewMockUi()
c := &StateListCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
// Test that outputs were displayed
expected := "null_resource.a\n"
actual := ui.OutputWriter.String()
if actual != expected {
t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected)
}
}
func TestStateList_backendOverrideState(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
testCopyDir(t, testFixturePath("state-list-backend-custom"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
p := testProvider()
ui := cli.NewMockUi()
c := &StateListCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
// This test is configured to use a local backend that has
// a custom path defined. So we test if we can still pass
// is a user defined state file that will then override the
// one configured in the backend. As this file does not exist
// it should exit with a no state found error.
args := []string{"-state=" + DefaultStateFilename}
if code := c.Run(args); code != 1 {
t.Fatalf("bad: %d", code)
}
if !strings.Contains(ui.ErrorWriter.String(), "No state file was found!") {
t.Fatalf("expected a no state file error, got: %s", ui.ErrorWriter.String())
}
}
func TestStateList_noState(t *testing.T) {
tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd)
p := testProvider()
ui := cli.NewMockUi()
c := &StateListCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{}
if code := c.Run(args); code != 1 {
t.Fatalf("bad: %d", code)
}
}
func TestStateList_modules(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
testCopyDir(t, testFixturePath("state-list-nested-modules"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
p := testProvider()
ui := cli.NewMockUi()
c := &StateListCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
t.Run("list resources in module and submodules", func(t *testing.T) {
args := []string{"module.nest"}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d", code)
}
// resources in the module and any submodules should be included in the outputs
expected := "module.nest.test_instance.nest\nmodule.nest.module.subnest.test_instance.subnest\n"
actual := ui.OutputWriter.String()
if actual != expected {
t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected)
}
})
t.Run("submodule has resources only", func(t *testing.T) {
// now get the state for a module that has no resources, only another nested module
ui.OutputWriter.Reset()
args := []string{"module.nonexist"}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d", code)
}
expected := "module.nonexist.module.child.test_instance.child\n"
actual := ui.OutputWriter.String()
if actual != expected {
t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected)
}
})
t.Run("expanded module", func(t *testing.T) {
// finally get the state for a module with an index
ui.OutputWriter.Reset()
args := []string{"module.count"}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d", code)
}
expected := "module.count[0].test_instance.count\nmodule.count[1].test_instance.count\n"
actual := ui.OutputWriter.String()
if actual != expected {
t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected)
}
})
t.Run("completely nonexistent module", func(t *testing.T) {
// finally get the state for a module with an index
ui.OutputWriter.Reset()
args := []string{"module.notevenalittlebit"}
if code := c.Run(args); code != 1 {
t.Fatalf("bad: %d", code)
}
})
}
const testStateListOutput = `
test_instance.foo
`