PSS: Add tests showing `output` and `show` commands being used with PSS (#37894)

* test: Add E2E test demonstrating `output` command used with PSS

* test: Add E2E test demonstrating `show` command used with PSS

* docs: Fix code comment

* test: Add integration test for using pluggable state storage with the `output` command

* test: Add integration test for using pluggable state storage with the `show` command
pull/37959/head^2
Sarah French 4 months ago committed by GitHub
parent 940fbfb7ed
commit 578766bdc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -198,3 +198,155 @@ 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 <name>`
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 <name>
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)
}
}
// Tests using the `terraform show` command in combination with pluggable state storage
// > `terraform show`
// > `terraform show <path-to-state-file>`
// > `terraform show <path-to-plan-file>` // 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 to state file>
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(SarahFrench/radeksimko): Show plan file: terraform show <path to plan file>
}

@ -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()
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(

@ -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()
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".

Loading…
Cancel
Save