// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package command import ( "bytes" "strings" "testing" "github.com/hashicorp/cli" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/statefile" ) 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 := t.TempDir() testCopyDir(t, testFixturePath("state-list-backend-default"), td) t.Chdir(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 := t.TempDir() testCopyDir(t, testFixturePath("state-list-backend-custom"), td) t.Chdir(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) } } // Tests using `terraform state list` subcommand in combination with pluggable state storage // // Note: Whereas other tests in this file use the local backend and require a state file in the test fixures, // with pluggable state storage we can define the state via the mocked provider. func TestStateList_stateStore(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() testCopyDir(t, testFixturePath("state-store-unchanged"), td) t.Chdir(td) // Get bytes describing a state containing a resource state := states.NewState() rootModule := state.RootModule() rootModule.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "foo", }.Instance(addrs.NoKey), &states.ResourceInstanceObjectSrc{ Status: states.ObjectReady, AttrsJSON: []byte(`{ "ami": "bar", "network_interface": [{ "device_index": 0, "description": "Main network interface" }] }`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) var stateBuf bytes.Buffer if err := statefile.Write(statefile.New(state, "", 1), &stateBuf); err != nil { t.Fatal(err) } // Create a mock that contains a persisted "default" state that uses the bytes from above. mockProvider := mockPluggableStateStorageProvider() mockProvider.MockStates = map[string]interface{}{ "default": stateBuf.Bytes(), } mockProviderAddress := addrs.NewDefaultProvider("test") ui := cli.NewMockUi() c := &StateListCommand{ Meta: Meta{ AllowExperimentalFeatures: true, testingOverrides: &testingOverrides{ Providers: map[addrs.Provider]providers.Factory{ mockProviderAddress: providers.FactoryFixed(mockProvider), }, }, 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 := "test_instance.foo\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 := t.TempDir() testCopyDir(t, testFixturePath("state-list-backend-custom"), td) t.Chdir(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 := t.TempDir() t.Chdir(tmp) 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 := t.TempDir() testCopyDir(t, testFixturePath("state-list-nested-modules"), td) t.Chdir(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 `