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
122 changes: 122 additions & 0 deletions internal/command/e2etest/pluggable_state_store_test.go
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagined that future E2E tests related to PSS would all go in this file.

Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package e2etest

import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"testing"

"github.com/hashicorp/terraform/internal/e2e"
"github.com/hashicorp/terraform/internal/getproviders"
)

func TestPrimary_stateStore_workspaceCmd(t *testing.T) {
if v := os.Getenv("TF_TEST_EXPERIMENTS"); v == "" {
t.Skip("can't run without enabling experiments in the executable terraform binary, enable with TF_TEST_EXPERIMENTS=1")
}

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.Parallel()

tf := e2e.NewBinary(t, terraformBin, "testdata/full-workflow-with-state-store-fs")
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 a directory that we will point terraform
// to using the -plugin-dir cli flag.
platform := getproviders.CurrentPlatform.String()
hashiDir := "cache/registry.terraform.io/hashicorp/"
if err := os.MkdirAll(tf.Path(hashiDir, "simple6/0.0.1/", platform), os.ModePerm); err != nil {
t.Fatal(err)
}
if err := os.Rename(simple6ProviderExe, tf.Path(hashiDir, "simple6/0.0.1/", platform, "terraform-provider-simple6")); err != nil {
t.Fatal(err)
}

//// Init
_, stderr, err := tf.Run("init", "-enable-pluggable-state-storage-experiment=true", "-plugin-dir=cache", "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
fi, err := os.Stat(path.Join(tf.WorkDir(), workspaceDirName, "default", "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")
}

//// Create Workspace: terraform workspace new
newWorkspace := "foobar"
stdout, stderr, err := tf.Run("workspace", "new", newWorkspace, "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg := fmt.Sprintf("Created and switched to workspace %q!", newWorkspace)
if !strings.Contains(stdout, expectedMsg) {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout)
}
fi, err = os.Stat(path.Join(tf.WorkDir(), workspaceDirName, newWorkspace, "terraform.tfstate"))
if err != nil {
t.Fatalf("failed to open %s workspace's state file: %s", newWorkspace, err)
}
if fi.Size() == 0 {
t.Fatalf("%s workspace's state file should not have size 0 bytes", newWorkspace)
}

//// List Workspaces: : terraform workspace list
stdout, stderr, err = tf.Run("workspace", "list", "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
if !strings.Contains(stdout, newWorkspace) {
t.Errorf("unexpected output, expected the new %q workspace to be listed present, but it's missing. Got:\n%s", newWorkspace, stdout)
}

//// Select Workspace: terraform workspace select
selectedWorkspace := "default"
stdout, stderr, err = tf.Run("workspace", "select", selectedWorkspace, "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg = fmt.Sprintf("Switched to workspace %q.", selectedWorkspace)
if !strings.Contains(stdout, expectedMsg) {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout)
}

//// Show Workspace: terraform workspace show
stdout, stderr, err = tf.Run("workspace", "show", "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg = fmt.Sprintf("%s\n", selectedWorkspace)
if stdout != expectedMsg {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout)
}

//// Delete Workspace: terraform workspace delete
stdout, stderr, err = tf.Run("workspace", "delete", newWorkspace, "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg = fmt.Sprintf("Deleted workspace %q!\n", newWorkspace)
if stdout != expectedMsg {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout)
}
}
4 changes: 3 additions & 1 deletion internal/command/e2etest/primary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package e2etest

import (
"fmt"
"os"
"path/filepath"
"reflect"
Expand Down Expand Up @@ -251,6 +252,7 @@ func TestPrimary_stateStore(t *testing.T) {
t.Parallel()

tf := e2e.NewBinary(t, terraformBin, "testdata/full-workflow-with-state-store-fs")
workspaceDirName := "states" // See workspace_dir value in the configuration

// 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.
Expand Down Expand Up @@ -292,7 +294,7 @@ func TestPrimary_stateStore(t *testing.T) {
}

// Check the statefile saved by the fs state store.
path := "terraform.tfstate.d/default/terraform.tfstate"
path := fmt.Sprintf("%s/default/terraform.tfstate", workspaceDirName)
f, err := tf.OpenFile(path)
if err != nil {
t.Fatalf("unexpected error opening state file %s: %s\nstderr:\n%s", path, err, stderr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ terraform {

state_store "simple6_fs" {
provider "simple6" {}

workspace_dir = "states"
}
}

Expand Down
156 changes: 152 additions & 4 deletions internal/command/workspace_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
package command

import (
"io/ioutil"
"fmt"
"os"
"path/filepath"
"strings"
Expand All @@ -16,11 +16,159 @@ import (
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/backend/local"
"github.com/hashicorp/terraform/internal/backend/remote-state/inmem"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/states/statefile"
"github.com/hashicorp/terraform/internal/states/statemgr"
)

func TestWorkspace_allCommands_pluggableStateStore(t *testing.T) {
// Create a temporary working directory with pluggable state storage in the config
td := t.TempDir()
testCopyDir(t, testFixturePath("state-store-new"), td)
t.Chdir(td)

mock := testStateStoreMockWithChunkNegotiation(t, 1000)
newMeta := func(provider providers.Interface) (meta Meta, ui *cli.MockUi, close func()) {
// Assumes the mocked provider is hashicorp/test
providerSource, close := newMockProviderSource(t, map[string][]string{
"hashicorp/test": {"1.2.3"},
})

ui = new(cli.MockUi)
view, _ := testView(t)
meta = Meta{
AllowExperimentalFeatures: true,
Ui: ui,
View: view,
testingOverrides: &testingOverrides{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): providers.FactoryFixed(mock),
},
},
ProviderSource: providerSource,
}
return meta, ui, close
}

//// Init
meta, ui, close := newMeta(mock)
defer close()
intCmd := &InitCommand{
Meta: meta,
}
args := []string{"-enable-pluggable-state-storage-experiment"} // Needed to test init changes for PSS project
code := intCmd.Run(args)
if code != 0 {
t.Fatalf("bad: %d\n\n%s\n%s", code, ui.ErrorWriter, ui.OutputWriter)
}
// We expect a state to have been created for the default workspace
if _, ok := mock.MockStates["default"]; !ok {
t.Fatal("expected the default workspace to exist, but it didn't")
}

//// Create Workspace
newWorkspace := "foobar"

meta, ui, close = newMeta(mock)
defer close()
newCmd := &WorkspaceNewCommand{
Meta: meta,
}

current, _ := newCmd.Workspace()
if current != backend.DefaultStateName {
t.Fatal("before creating any custom workspaces, the current workspace should be 'default'")
}

args = []string{newWorkspace}
code = newCmd.Run(args)
if code != 0 {
t.Fatalf("bad: %d\n\n%s\n%s", code, ui.ErrorWriter, ui.OutputWriter)
}
expectedMsg := fmt.Sprintf("Created and switched to workspace %q!", newWorkspace)
if !strings.Contains(ui.OutputWriter.String(), expectedMsg) {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, ui.OutputWriter)
}
// We expect a state to have been created for the new custom workspace
if _, ok := mock.MockStates[newWorkspace]; !ok {
t.Fatalf("expected the %s workspace to exist, but it didn't", newWorkspace)
}
current, _ = newCmd.Workspace()
if current != newWorkspace {
t.Fatalf("current workspace should be %q, got %q", newWorkspace, current)
}

//// List Workspaces
meta, ui, close = newMeta(mock)
defer close()
listCmd := &WorkspaceListCommand{
Meta: meta,
}
args = []string{}
code = listCmd.Run(args)
if code != 0 {
t.Fatalf("bad: %d\n\n%s\n%s", code, ui.ErrorWriter, ui.OutputWriter)
}
if !strings.Contains(ui.OutputWriter.String(), newWorkspace) {
t.Errorf("unexpected output, expected the new %q workspace to be listed present, but it's missing. Got:\n%s", newWorkspace, ui.OutputWriter)
}

//// Select Workspace
meta, ui, close = newMeta(mock)
defer close()
selCmd := &WorkspaceSelectCommand{
Meta: meta,
}
selectedWorkspace := backend.DefaultStateName
args = []string{selectedWorkspace}
code = selCmd.Run(args)
if code != 0 {
t.Fatalf("bad: %d\n\n%s\n%s", code, ui.ErrorWriter, ui.OutputWriter)
}
expectedMsg = fmt.Sprintf("Switched to workspace %q.", selectedWorkspace)
if !strings.Contains(ui.OutputWriter.String(), expectedMsg) {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, ui.OutputWriter)
}

//// Show Workspace
meta, ui, close = newMeta(mock)
defer close()
showCmd := &WorkspaceShowCommand{
Meta: meta,
}
args = []string{}
code = showCmd.Run(args)
if code != 0 {
t.Fatalf("bad: %d\n\n%s\n%s", code, ui.ErrorWriter, ui.OutputWriter)
}
expectedMsg = fmt.Sprintf("%s\n", selectedWorkspace)
if !strings.Contains(ui.OutputWriter.String(), expectedMsg) {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, ui.OutputWriter)
}

current, _ = newCmd.Workspace()
if current != backend.DefaultStateName {
t.Fatal("current workspace should be 'default'")
}

//// Delete Workspace
meta, ui, close = newMeta(mock)
defer close()
deleteCmd := &WorkspaceDeleteCommand{
Meta: meta,
}
args = []string{newWorkspace}
code = deleteCmd.Run(args)
if code != 0 {
t.Fatalf("bad: %d\n\n%s\n%s", code, ui.ErrorWriter, ui.OutputWriter)
}
expectedMsg = fmt.Sprintf("Deleted workspace %q!\n", newWorkspace)
if !strings.Contains(ui.OutputWriter.String(), expectedMsg) {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, ui.OutputWriter)
}
}

func TestWorkspace_createAndChange(t *testing.T) {
// Create a temporary working directory that is empty
td := t.TempDir()
Expand Down Expand Up @@ -114,7 +262,7 @@ func TestWorkspace_createAndList(t *testing.T) {
t.Chdir(td)

// make sure a vars file doesn't interfere
err := ioutil.WriteFile(
err := os.WriteFile(
DefaultVarsFilename,
[]byte(`foo = "bar"`),
0644,
Expand Down Expand Up @@ -162,7 +310,7 @@ func TestWorkspace_createAndShow(t *testing.T) {
t.Chdir(td)

// make sure a vars file doesn't interfere
err := ioutil.WriteFile(
err := os.WriteFile(
DefaultVarsFilename,
[]byte(`foo = "bar"`),
0644,
Expand Down Expand Up @@ -345,7 +493,7 @@ func TestWorkspace_delete(t *testing.T) {
if err := os.MkdirAll(DefaultDataDir, 0755); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(DefaultDataDir, local.DefaultWorkspaceFile), []byte("test"), 0644); err != nil {
if err := os.WriteFile(filepath.Join(DefaultDataDir, local.DefaultWorkspaceFile), []byte("test"), 0644); err != nil {
t.Fatal(err)
}

Expand Down
Loading