Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 153 additions & 1 deletion internal/command/e2etest/pluggable_state_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func TestPrimary_stateStore_stateCmds(t *testing.T) {
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 directory .terraform/providers/ directory
// 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 {
Expand Down Expand Up @@ -197,3 +197,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>
}
36 changes: 32 additions & 4 deletions internal/command/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package command

import (
"bytes"
"os"
"path/filepath"
"strings"
Expand All @@ -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) {
Expand Down Expand Up @@ -59,18 +62,43 @@ func TestOutput_stateStore(t *testing.T) {
)
})

statePath := testStateFile(t, originalState)
// 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")
providerSource, close := newMockProviderSource(t, map[string][]string{
"hashicorp/test": {"1.0.0"},
})
defer close()

view, done := testView(t)
c := &OutputCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()),
View: view,
AllowExperimentalFeatures: true,
testingOverrides: &testingOverrides{
Providers: map[addrs.Provider]providers.Factory{
mockProviderAddress: providers.FactoryFixed(mockProvider),
},
},
ProviderSource: providerSource,
View: view,
},
}

args := []string{
"-state", statePath,
"foo",
}
code := c.Run(args)
Expand Down
83 changes: 83 additions & 0 deletions internal/command/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package command

import (
"bytes"
"encoding/json"
"io/ioutil"
"os"
Expand All @@ -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"
)
Expand Down Expand Up @@ -1105,6 +1107,87 @@ 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")
providerSource, close := newMockProviderSource(t, map[string][]string{
"hashicorp/test": {"1.0.0"},
})
defer close()

view, done := testView(t)
c := &ShowCommand{
Meta: Meta{
AllowExperimentalFeatures: true,
testingOverrides: &testingOverrides{
Providers: map[addrs.Provider]providers.Factory{
mockProviderAddress: providers.FactoryFixed(mockProvider),
},
},
ProviderSource: providerSource,
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".
Expand Down