From 89a4c0dab0a0fff4da1fd11a5ee712087aa340c7 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Wed, 12 Nov 2025 14:10:29 +0000 Subject: [PATCH 1/5] test: Add E2E test demonstrating `output` command used with PSS --- .../e2etest/pluggable_state_store_test.go | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/internal/command/e2etest/pluggable_state_store_test.go b/internal/command/e2etest/pluggable_state_store_test.go index 92238bd0fd3b..41e4e657df2e 100644 --- a/internal/command/e2etest/pluggable_state_store_test.go +++ b/internal/command/e2etest/pluggable_state_store_test.go @@ -198,3 +198,72 @@ resource "terraform_data" "my-data" { t.Errorf("wrong result, diff:\n%s", diff) } } + +// Tests using the `terraform output` command in combination with pluggable state storage: +// > `terraform output` +// > `terraform output ` +func TestPrimary_stateStore_outputCmd(t *testing.T) { + + if !canRunGoBuild { + // We're running in a separate-build-then-run context, so we can't + // currently execute this test which depends on being able to build + // new executable at runtime. + // + // (See the comment on canRunGoBuild's declaration for more information.) + t.Skip("can't run without building a new provider executable") + } + + t.Setenv(e2e.TestExperimentFlag, "true") + tfBin := e2e.GoBuild("github.com/hashicorp/terraform", "terraform") + + fixturePath := filepath.Join("testdata", "initialized-directory-with-state-store-fs") + tf := e2e.NewBinary(t, tfBin, fixturePath) + + workspaceDirName := "states" // see test fixture value for workspace_dir + + // In order to test integration with PSS we need a provider plugin implementing a state store. + // Here will build the simple6 (built with protocol v6) provider, which implements PSS. + simple6Provider := filepath.Join(tf.WorkDir(), "terraform-provider-simple6") + simple6ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple-v6/main", simple6Provider) + + // Move the provider binaries into the correct .terraform/providers/ directory + // that will contain provider binaries in an initialized working directory. + platform := getproviders.CurrentPlatform.String() + if err := os.MkdirAll(tf.Path(".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/", platform), os.ModePerm); err != nil { + t.Fatal(err) + } + if err := os.Rename(simple6ProviderExe, tf.Path(".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/", platform, "terraform-provider-simple6")); err != nil { + t.Fatal(err) + } + + // Assert that the test starts with the default state present from test fixtures + defaultStateId := "default" + fi, err := os.Stat(path.Join(tf.WorkDir(), workspaceDirName, defaultStateId, "terraform.tfstate")) + if err != nil { + t.Fatalf("failed to open default workspace's state file: %s", err) + } + if fi.Size() == 0 { + t.Fatal("default workspace's state file should not have size 0 bytes") + } + + //// List all outputs: terraform output + stdout, stderr, err := tf.Run("output", "-no-color") + if err != nil { + t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr) + } + expectedMsg := "greeting = \"hello world\"\n" // See the test fixture files + if stdout != expectedMsg { + t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout) + } + + //// View a specific output: terraform output + outputName := "greeting" + stdout, stderr, err = tf.Run("output", outputName, "-no-color") + if err != nil { + t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr) + } + expectedMsg = "\"hello world\"\n" // Only the value is outputted, no name present + if stdout != expectedMsg { + t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout) + } +} From 477bb86a45260accae22324b75f534e2a56930fd Mon Sep 17 00:00:00 2001 From: Sarah French Date: Wed, 12 Nov 2025 14:11:01 +0000 Subject: [PATCH 2/5] test: Add E2E test demonstrating `show` command used with PSS --- .../e2etest/pluggable_state_store_test.go | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/internal/command/e2etest/pluggable_state_store_test.go b/internal/command/e2etest/pluggable_state_store_test.go index 41e4e657df2e..b5a3b85f2717 100644 --- a/internal/command/e2etest/pluggable_state_store_test.go +++ b/internal/command/e2etest/pluggable_state_store_test.go @@ -267,3 +267,86 @@ func TestPrimary_stateStore_outputCmd(t *testing.T) { t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout) } } + +// Tests using the `terraform show` command in combination with pluggable state storage +// > `terraform show` +// > `terraform show ` +// > `terraform show ` // TODO +func TestPrimary_stateStore_showCmd(t *testing.T) { + + if !canRunGoBuild { + // We're running in a separate-build-then-run context, so we can't + // currently execute this test which depends on being able to build + // new executable at runtime. + // + // (See the comment on canRunGoBuild's declaration for more information.) + t.Skip("can't run without building a new provider executable") + } + + t.Setenv(e2e.TestExperimentFlag, "true") + tfBin := e2e.GoBuild("github.com/hashicorp/terraform", "terraform") + + fixturePath := filepath.Join("testdata", "initialized-directory-with-state-store-fs") + tf := e2e.NewBinary(t, tfBin, fixturePath) + + workspaceDirName := "states" // see test fixture value for workspace_dir + + // In order to test integration with PSS we need a provider plugin implementing a state store. + // Here will build the simple6 (built with protocol v6) provider, which implements PSS. + simple6Provider := filepath.Join(tf.WorkDir(), "terraform-provider-simple6") + simple6ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple-v6/main", simple6Provider) + + // Move the provider binaries into the correct .terraform/providers/ directory + // that will contain provider binaries in an initialized working directory. + platform := getproviders.CurrentPlatform.String() + if err := os.MkdirAll(tf.Path(".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/", platform), os.ModePerm); err != nil { + t.Fatal(err) + } + if err := os.Rename(simple6ProviderExe, tf.Path(".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/", platform, "terraform-provider-simple6")); err != nil { + t.Fatal(err) + } + + // Assert that the test starts with the default state present from test fixtures + defaultStateId := "default" + fi, err := os.Stat(path.Join(tf.WorkDir(), workspaceDirName, defaultStateId, "terraform.tfstate")) + if err != nil { + t.Fatalf("failed to open default workspace's state file: %s", err) + } + if fi.Size() == 0 { + t.Fatal("default workspace's state file should not have size 0 bytes") + } + + //// Show state: terraform state + stdout, stderr, err := tf.Run("show", "-no-color") + if err != nil { + t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr) + } + expectedMsg := `# terraform_data.my-data: +resource "terraform_data" "my-data" { + id = "d71fb368-2ba1-fb4c-5bd9-6a2b7f05d60c" + input = "hello world" + output = "hello world" +} + + +Outputs: + +greeting = "hello world" +` // See the test fixture folder's state file + + if diff := cmp.Diff(stdout, expectedMsg); diff != "" { + t.Errorf("wrong result, diff:\n%s", diff) + } + + //// Show state: terraform show + path := fmt.Sprintf("./%s/%s/terraform.tfstate", workspaceDirName, defaultStateId) + stdout, stderr, err = tf.Run("show", path, "-no-color") + if err != nil { + t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr) + } + if diff := cmp.Diff(stdout, expectedMsg); diff != "" { + t.Errorf("wrong result, diff:\n%s", diff) + } + + // TODO: Show plan file: terraform show +} From 8827b638331e3988d1170d137d6a6ab7de2542e2 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Wed, 12 Nov 2025 14:11:18 +0000 Subject: [PATCH 3/5] docs: Fix code comment --- internal/command/e2etest/pluggable_state_store_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/e2etest/pluggable_state_store_test.go b/internal/command/e2etest/pluggable_state_store_test.go index b5a3b85f2717..358c4b26d9f4 100644 --- a/internal/command/e2etest/pluggable_state_store_test.go +++ b/internal/command/e2etest/pluggable_state_store_test.go @@ -348,5 +348,5 @@ greeting = "hello world" t.Errorf("wrong result, diff:\n%s", diff) } - // TODO: Show plan file: terraform show + // TODO(SarahFrench/radeksimko): Show plan file: terraform show } From 18f9ac2ce6ed0cdb5501f988c87ca3df60a75832 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 24 Nov 2025 17:11:03 +0000 Subject: [PATCH 4/5] test: Add integration test for using pluggable state storage with the `output` command --- internal/command/output_test.go | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/internal/command/output_test.go b/internal/command/output_test.go index 28bbc6d1f2ac..eeaa9e57f8a7 100644 --- a/internal/command/output_test.go +++ b/internal/command/output_test.go @@ -4,6 +4,7 @@ package command import ( + "bytes" "os" "path/filepath" "strings" @@ -12,7 +13,9 @@ import ( "github.com/zclconf/go-cty/cty" "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 TestOutput(t *testing.T) { @@ -50,6 +53,61 @@ func TestOutput(t *testing.T) { } } +func TestOutput_stateStore(t *testing.T) { + originalState := states.BuildState(func(s *states.SyncState) { + s.SetOutputValue( + addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance), + cty.StringVal("bar"), + false, + ) + }) + + // Create a temporary working directory that is empty + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-unchanged"), td) + t.Chdir(td) + + // Get bytes describing the state + var stateBuf bytes.Buffer + if err := statefile.Write(statefile.New(originalState, "", 1), &stateBuf); err != nil { + t.Fatalf("error during test setup: %s", err) + } + + // Create a mock that contains a persisted "default" state that uses the bytes from above. + mockProvider := mockPluggableStateStorageProvider(t) + mockProvider.MockStates = map[string]interface{}{ + "default": stateBuf.Bytes(), + } + mockProviderAddress := addrs.NewDefaultProvider("test") + + view, done := testView(t) + c := &OutputCommand{ + Meta: Meta{ + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + View: view, + }, + } + + args := []string{ + "foo", + } + code := c.Run(args) + output := done(t) + if code != 0 { + t.Fatalf("bad: \n%s", output.Stderr()) + } + + actual := strings.TrimSpace(output.Stdout()) + if actual != `"bar"` { + t.Fatalf("bad: %#v", actual) + } +} + func TestOutput_json(t *testing.T) { originalState := states.BuildState(func(s *states.SyncState) { s.SetOutputValue( From b897d0ffbd71d89cf193d0dd2c859488d3bcea6d Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 24 Nov 2025 18:03:06 +0000 Subject: [PATCH 5/5] test: Add integration test for using pluggable state storage with the `show` command --- internal/command/show_test.go | 78 +++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/internal/command/show_test.go b/internal/command/show_test.go index 71f76088b14f..d3fa3f5941bd 100644 --- a/internal/command/show_test.go +++ b/internal/command/show_test.go @@ -4,6 +4,7 @@ package command import ( + "bytes" "encoding/json" "io/ioutil" "os" @@ -21,6 +22,7 @@ import ( "github.com/hashicorp/terraform/internal/providers" testing_provider "github.com/hashicorp/terraform/internal/providers/testing" "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/states/statefile" "github.com/hashicorp/terraform/internal/states/statemgr" "github.com/hashicorp/terraform/version" ) @@ -1105,6 +1107,82 @@ func TestShow_corruptStatefile(t *testing.T) { } } +// TestShow_stateStore tests the `show` command with no arguments, which uses the state that +// matches the selected workspace. In this test the default workspace is in use. +func TestShow_stateStore(t *testing.T) { + originalState := testState() + originalState.SetOutputValue( + addrs.OutputValue{Name: "test"}.Absolute(addrs.RootModuleInstance), + cty.ObjectVal(map[string]cty.Value{ + "attr": cty.NullVal(cty.DynamicPseudoType), + "null": cty.NullVal(cty.String), + "list": cty.ListVal([]cty.Value{cty.NullVal(cty.Number)}), + }), + false, + ) + + // Create a temporary working directory that is empty + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-unchanged"), td) + t.Chdir(td) + + // Get bytes describing the state + var stateBuf bytes.Buffer + if err := statefile.Write(statefile.New(originalState, "", 1), &stateBuf); err != nil { + t.Fatalf("error during test setup: %s", err) + } + + // Create a mock that contains a persisted "default" state that uses the bytes from above. + mockProvider := mockPluggableStateStorageProvider(t) + mockProvider.MockStates = map[string]interface{}{ + "default": stateBuf.Bytes(), + } + mockProviderAddress := addrs.NewDefaultProvider("test") + + view, done := testView(t) + c := &ShowCommand{ + Meta: Meta{ + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + View: view, + }, + } + + args := []string{ + "-no-color", + } + code := c.Run(args) + output := done(t) + if code != 0 { + t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr()) + } + + expected := `# test_instance.foo: +resource "test_instance" "foo" { + id = "bar" +} + + +Outputs: + +test = { + list = [ + null, + ] +}` + actual := strings.TrimSpace(output.Stdout()) + if actual != expected { + t.Fatalf("expected = %s'\ngot = %s", + expected, + actual, + ) + } +} + // showFixtureSchema returns a schema suitable for processing the configuration // in testdata/show. This schema should be assigned to a mock provider // named "test".