mirror of https://github.com/hashicorp/terraform
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
817 lines
22 KiB
817 lines
22 KiB
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package command
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/cli"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"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)
|
|
|
|
// Assumes the mocked provider is hashicorp/test
|
|
providerSource, close := newMockProviderSource(t, map[string][]string{
|
|
"hashicorp/test": {"1.2.3"},
|
|
})
|
|
defer close()
|
|
|
|
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,
|
|
}
|
|
|
|
//// Init
|
|
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"
|
|
ui = new(cli.MockUi)
|
|
meta.Ui = ui
|
|
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
|
|
ui = new(cli.MockUi)
|
|
meta.Ui = ui
|
|
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
|
|
ui = new(cli.MockUi)
|
|
meta.Ui = ui
|
|
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
|
|
ui = new(cli.MockUi)
|
|
meta.Ui = ui
|
|
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
|
|
ui = new(cli.MockUi)
|
|
meta.Ui = ui
|
|
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()
|
|
os.MkdirAll(td, 0755)
|
|
t.Chdir(td)
|
|
|
|
newCmd := &WorkspaceNewCommand{}
|
|
|
|
current, _ := newCmd.Workspace()
|
|
if current != backend.DefaultStateName {
|
|
t.Fatal("current workspace should be 'default'")
|
|
}
|
|
|
|
args := []string{"test"}
|
|
ui := new(cli.MockUi)
|
|
view, _ := testView(t)
|
|
newCmd.Meta = Meta{Ui: ui, View: view}
|
|
if code := newCmd.Run(args); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
|
}
|
|
|
|
current, _ = newCmd.Workspace()
|
|
if current != "test" {
|
|
t.Fatalf("current workspace should be 'test', got %q", current)
|
|
}
|
|
|
|
selCmd := &WorkspaceSelectCommand{}
|
|
args = []string{backend.DefaultStateName}
|
|
ui = new(cli.MockUi)
|
|
selCmd.Meta = Meta{Ui: ui, View: view}
|
|
if code := selCmd.Run(args); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
|
}
|
|
|
|
current, _ = newCmd.Workspace()
|
|
if current != backend.DefaultStateName {
|
|
t.Fatal("current workspace should be 'default'")
|
|
}
|
|
|
|
}
|
|
|
|
func TestWorkspace_cannotCreateOrSelectEmptyStringWorkspace(t *testing.T) {
|
|
// Create a temporary working directory that is empty
|
|
td := t.TempDir()
|
|
os.MkdirAll(td, 0755)
|
|
t.Chdir(td)
|
|
|
|
newCmd := &WorkspaceNewCommand{}
|
|
|
|
current, _ := newCmd.Workspace()
|
|
if current != backend.DefaultStateName {
|
|
t.Fatal("current workspace should be 'default'")
|
|
}
|
|
|
|
args := []string{""}
|
|
ui := cli.NewMockUi()
|
|
view, _ := testView(t)
|
|
newCmd.Meta = Meta{Ui: ui, View: view}
|
|
if code := newCmd.Run(args); code != 1 {
|
|
t.Fatalf("expected failure when trying to create the \"\" workspace.\noutput: %s", ui.OutputWriter)
|
|
}
|
|
|
|
gotStderr := ui.ErrorWriter.String()
|
|
if want, got := `The workspace name "" is not allowed`, gotStderr; !strings.Contains(got, want) {
|
|
t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, got)
|
|
}
|
|
|
|
ui = cli.NewMockUi()
|
|
selectCmd := &WorkspaceSelectCommand{
|
|
Meta: Meta{
|
|
Ui: ui,
|
|
View: view,
|
|
},
|
|
}
|
|
if code := selectCmd.Run(args); code != 1 {
|
|
t.Fatalf("expected failure when trying to select the the \"\" workspace.\noutput: %s", ui.OutputWriter)
|
|
}
|
|
|
|
gotStderr = ui.ErrorWriter.String()
|
|
if want, got := `The workspace name "" is not allowed`, gotStderr; !strings.Contains(got, want) {
|
|
t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, got)
|
|
}
|
|
}
|
|
|
|
// Create some workspaces and test the list output.
|
|
// This also ensures we switch to the correct env after each call
|
|
func TestWorkspace_createAndList(t *testing.T) {
|
|
// Create a temporary working directory that is empty
|
|
td := t.TempDir()
|
|
os.MkdirAll(td, 0755)
|
|
t.Chdir(td)
|
|
|
|
// make sure a vars file doesn't interfere
|
|
err := os.WriteFile(
|
|
DefaultVarsFilename,
|
|
[]byte(`foo = "bar"`),
|
|
0644,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
envs := []string{"test_a", "test_b", "test_c"}
|
|
|
|
// create multiple workspaces
|
|
for _, env := range envs {
|
|
ui := new(cli.MockUi)
|
|
view, _ := testView(t)
|
|
newCmd := &WorkspaceNewCommand{
|
|
Meta: Meta{Ui: ui, View: view},
|
|
}
|
|
if code := newCmd.Run([]string{env}); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
|
}
|
|
}
|
|
|
|
listCmd := &WorkspaceListCommand{}
|
|
ui := new(cli.MockUi)
|
|
view, _ := testView(t)
|
|
listCmd.Meta = Meta{Ui: ui, View: view}
|
|
|
|
if code := listCmd.Run(nil); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
|
}
|
|
|
|
actual := strings.TrimSpace(ui.OutputWriter.String())
|
|
expected := "default\n test_a\n test_b\n* test_c"
|
|
|
|
if actual != expected {
|
|
t.Fatalf("\nexpected: %q\nactual: %q", expected, actual)
|
|
}
|
|
}
|
|
|
|
// Create some workspaces and test the show output.
|
|
func TestWorkspace_createAndShow(t *testing.T) {
|
|
// Create a temporary working directory that is empty
|
|
td := t.TempDir()
|
|
os.MkdirAll(td, 0755)
|
|
t.Chdir(td)
|
|
|
|
// make sure a vars file doesn't interfere
|
|
err := os.WriteFile(
|
|
DefaultVarsFilename,
|
|
[]byte(`foo = "bar"`),
|
|
0644,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// make sure current workspace show outputs "default"
|
|
showCmd := &WorkspaceShowCommand{}
|
|
ui := new(cli.MockUi)
|
|
view, _ := testView(t)
|
|
showCmd.Meta = Meta{Ui: ui, View: view}
|
|
|
|
if code := showCmd.Run(nil); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
|
}
|
|
|
|
actual := strings.TrimSpace(ui.OutputWriter.String())
|
|
expected := "default"
|
|
|
|
if actual != expected {
|
|
t.Fatalf("\nexpected: %q\nactual: %q", expected, actual)
|
|
}
|
|
|
|
newCmd := &WorkspaceNewCommand{}
|
|
|
|
env := []string{"test_a"}
|
|
|
|
// create test_a workspace
|
|
ui = new(cli.MockUi)
|
|
newCmd.Meta = Meta{Ui: ui, View: view}
|
|
if code := newCmd.Run(env); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
|
}
|
|
|
|
selCmd := &WorkspaceSelectCommand{}
|
|
ui = new(cli.MockUi)
|
|
selCmd.Meta = Meta{Ui: ui, View: view}
|
|
if code := selCmd.Run(env); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
|
}
|
|
|
|
showCmd = &WorkspaceShowCommand{}
|
|
ui = new(cli.MockUi)
|
|
showCmd.Meta = Meta{Ui: ui, View: view}
|
|
|
|
if code := showCmd.Run(nil); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
|
}
|
|
|
|
actual = strings.TrimSpace(ui.OutputWriter.String())
|
|
expected = "test_a"
|
|
|
|
if actual != expected {
|
|
t.Fatalf("\nexpected: %q\nactual: %q", expected, actual)
|
|
}
|
|
}
|
|
|
|
// Don't allow names that aren't URL safe
|
|
func TestWorkspace_createInvalid(t *testing.T) {
|
|
// Create a temporary working directory that is empty
|
|
td := t.TempDir()
|
|
os.MkdirAll(td, 0755)
|
|
t.Chdir(td)
|
|
|
|
envs := []string{"test_a*", "test_b/foo", "../../../test_c", "好_d"}
|
|
|
|
// create multiple workspaces
|
|
for _, env := range envs {
|
|
ui := new(cli.MockUi)
|
|
view, _ := testView(t)
|
|
newCmd := &WorkspaceNewCommand{
|
|
Meta: Meta{Ui: ui, View: view},
|
|
}
|
|
if code := newCmd.Run([]string{env}); code == 0 {
|
|
t.Fatalf("expected failure: \n%s", ui.OutputWriter)
|
|
}
|
|
}
|
|
|
|
// list workspaces to make sure none were created
|
|
listCmd := &WorkspaceListCommand{}
|
|
ui := new(cli.MockUi)
|
|
view, _ := testView(t)
|
|
listCmd.Meta = Meta{Ui: ui, View: view}
|
|
|
|
if code := listCmd.Run(nil); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
|
}
|
|
|
|
actual := strings.TrimSpace(ui.OutputWriter.String())
|
|
expected := "* default"
|
|
|
|
if actual != expected {
|
|
t.Fatalf("\nexpected: %q\nactual: %q", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestWorkspace_createWithState(t *testing.T) {
|
|
td := t.TempDir()
|
|
testCopyDir(t, testFixturePath("inmem-backend"), td)
|
|
t.Chdir(td)
|
|
defer inmem.Reset()
|
|
|
|
// init the backend
|
|
ui := new(cli.MockUi)
|
|
view, _ := testView(t)
|
|
initCmd := &InitCommand{
|
|
Meta: Meta{Ui: ui, View: view},
|
|
}
|
|
if code := initCmd.Run([]string{}); code != 0 {
|
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
|
}
|
|
|
|
originalState := states.BuildState(func(s *states.SyncState) {
|
|
s.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
&states.ResourceInstanceObjectSrc{
|
|
AttrsJSON: []byte(`{"id":"bar"}`),
|
|
Status: states.ObjectReady,
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
)
|
|
})
|
|
|
|
err := statemgr.NewFilesystem("test.tfstate").WriteState(originalState)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
workspace := "test_workspace"
|
|
|
|
args := []string{"-state", "test.tfstate", workspace}
|
|
ui = new(cli.MockUi)
|
|
newCmd := &WorkspaceNewCommand{
|
|
Meta: Meta{Ui: ui, View: view},
|
|
}
|
|
if code := newCmd.Run(args); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
|
}
|
|
|
|
newPath := filepath.Join(local.DefaultWorkspaceDir, "test", DefaultStateFilename)
|
|
envState := statemgr.NewFilesystem(newPath)
|
|
err = envState.RefreshState()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
b := backend.TestBackendConfig(t, inmem.New(), nil)
|
|
sMgr, sDiags := b.StateMgr(workspace)
|
|
if sDiags.HasErrors() {
|
|
t.Fatal(sDiags)
|
|
}
|
|
|
|
newState := sMgr.State()
|
|
|
|
if got, want := newState.String(), originalState.String(); got != want {
|
|
t.Fatalf("states not equal\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestWorkspace_delete(t *testing.T) {
|
|
td := t.TempDir()
|
|
os.MkdirAll(td, 0755)
|
|
t.Chdir(td)
|
|
|
|
// create the workspace directories
|
|
if err := os.MkdirAll(filepath.Join(local.DefaultWorkspaceDir, "test"), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// create the workspace file
|
|
if err := os.MkdirAll(DefaultDataDir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(DefaultDataDir, local.DefaultWorkspaceFile), []byte("test"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ui := new(cli.MockUi)
|
|
view, _ := testView(t)
|
|
delCmd := &WorkspaceDeleteCommand{
|
|
Meta: Meta{Ui: ui, View: view},
|
|
}
|
|
|
|
current, _ := delCmd.Workspace()
|
|
if current != "test" {
|
|
t.Fatal("wrong workspace:", current)
|
|
}
|
|
|
|
// we can't delete our current workspace
|
|
args := []string{"test"}
|
|
if code := delCmd.Run(args); code == 0 {
|
|
t.Fatal("expected error deleting current workspace")
|
|
}
|
|
|
|
// change back to default
|
|
if err := delCmd.SetWorkspace(backend.DefaultStateName); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// try the delete again
|
|
ui = new(cli.MockUi)
|
|
delCmd.Meta.Ui = ui
|
|
if code := delCmd.Run(args); code != 0 {
|
|
t.Fatalf("error deleting workspace: %s", ui.ErrorWriter)
|
|
}
|
|
|
|
current, _ = delCmd.Workspace()
|
|
if current != backend.DefaultStateName {
|
|
t.Fatalf("wrong workspace: %q", current)
|
|
}
|
|
}
|
|
|
|
// TestWorkspace_deleteInvalid shows that if a workspace with an invalid name
|
|
// has been created, Terraform allows users to delete it.
|
|
func TestWorkspace_deleteInvalid(t *testing.T) {
|
|
td := t.TempDir()
|
|
os.MkdirAll(td, 0755)
|
|
t.Chdir(td)
|
|
|
|
// choose an invalid workspace name
|
|
workspace := "test workspace"
|
|
path := filepath.Join(local.DefaultWorkspaceDir, workspace)
|
|
|
|
// create the workspace directories
|
|
if err := os.MkdirAll(path, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ui := new(cli.MockUi)
|
|
view, _ := testView(t)
|
|
delCmd := &WorkspaceDeleteCommand{
|
|
Meta: Meta{Ui: ui, View: view},
|
|
}
|
|
|
|
// delete the workspace
|
|
if code := delCmd.Run([]string{workspace}); code != 0 {
|
|
t.Fatalf("error deleting workspace: %s", ui.ErrorWriter)
|
|
}
|
|
|
|
if _, err := os.Stat(path); err == nil {
|
|
t.Fatalf("should have deleted workspace, but %s still exists", path)
|
|
} else if !os.IsNotExist(err) {
|
|
t.Fatalf("unexpected error for workspace path: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestWorkspace_deleteRejectsEmptyString(t *testing.T) {
|
|
td := t.TempDir()
|
|
os.MkdirAll(td, 0755)
|
|
t.Chdir(td)
|
|
|
|
// Empty string identifier for workspace
|
|
workspace := ""
|
|
path := filepath.Join(local.DefaultWorkspaceDir, workspace)
|
|
|
|
// create the workspace directories
|
|
if err := os.MkdirAll(path, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ui := cli.NewMockUi()
|
|
view, _ := testView(t)
|
|
delCmd := &WorkspaceDeleteCommand{
|
|
Meta: Meta{Ui: ui, View: view},
|
|
}
|
|
|
|
// delete the workspace
|
|
if code := delCmd.Run([]string{workspace}); code != cli.RunResultHelp {
|
|
t.Fatalf("expected code %d but got %d. Output: %s", cli.RunResultHelp, code, ui.OutputWriter)
|
|
}
|
|
if !strings.Contains(string(ui.ErrorWriter.Bytes()), "got an empty string") {
|
|
t.Fatalf("expected error to include \"got an empty string\" but was missing, got: %s", ui.ErrorWriter)
|
|
}
|
|
}
|
|
|
|
func TestWorkspace_deleteWithState(t *testing.T) {
|
|
td := t.TempDir()
|
|
os.MkdirAll(td, 0755)
|
|
t.Chdir(td)
|
|
|
|
// create the workspace directories
|
|
if err := os.MkdirAll(filepath.Join(local.DefaultWorkspaceDir, "test"), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// create a non-empty state
|
|
originalState := states.BuildState(func(ss *states.SyncState) {
|
|
ss.SetResourceInstanceCurrent(
|
|
addrs.AbsResourceInstance{
|
|
Resource: addrs.ResourceInstance{
|
|
Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_instance",
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
&states.ResourceInstanceObjectSrc{
|
|
AttrsJSON: []byte("{}"),
|
|
Status: states.ObjectReady,
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewBuiltInProvider("test"),
|
|
},
|
|
)
|
|
})
|
|
originalStateFile := &statefile.File{
|
|
Serial: 1,
|
|
Lineage: "whatever",
|
|
State: originalState,
|
|
}
|
|
|
|
f, err := os.Create(filepath.Join(local.DefaultWorkspaceDir, "test", "terraform.tfstate"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
if err := statefile.Write(originalStateFile, f); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ui := cli.NewMockUi()
|
|
view, _ := testView(t)
|
|
delCmd := &WorkspaceDeleteCommand{
|
|
Meta: Meta{Ui: ui, View: view},
|
|
}
|
|
args := []string{"test"}
|
|
if code := delCmd.Run(args); code == 0 {
|
|
t.Fatalf("expected failure without -force.\noutput: %s", ui.OutputWriter)
|
|
}
|
|
gotStderr := ui.ErrorWriter.String()
|
|
if want, got := `Workspace "test" is currently tracking the following resource instances`, gotStderr; !strings.Contains(got, want) {
|
|
t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, got)
|
|
}
|
|
if want, got := `- test_instance.foo`, gotStderr; !strings.Contains(got, want) {
|
|
t.Errorf("error message doesn't mention the remaining instance\nwant substring: %s\ngot:\n%s", want, got)
|
|
}
|
|
|
|
ui = new(cli.MockUi)
|
|
delCmd.Meta.Ui = ui
|
|
|
|
args = []string{"-force", "test"}
|
|
if code := delCmd.Run(args); code != 0 {
|
|
t.Fatalf("failure: %s", ui.ErrorWriter)
|
|
}
|
|
|
|
if _, err := os.Stat(filepath.Join(local.DefaultWorkspaceDir, "test")); !os.IsNotExist(err) {
|
|
t.Fatal("env 'test' still exists!")
|
|
}
|
|
}
|
|
|
|
func TestWorkspace_cannotDeleteDefaultWorkspace(t *testing.T) {
|
|
td := t.TempDir()
|
|
os.MkdirAll(td, 0755)
|
|
t.Chdir(td)
|
|
|
|
// Create an empty default state, i.e. create default workspace.
|
|
originalStateFile := &statefile.File{
|
|
Serial: 1,
|
|
Lineage: "whatever",
|
|
State: states.NewState(),
|
|
}
|
|
|
|
f, err := os.Create(filepath.Join(local.DefaultStateFilename))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
if err := statefile.Write(originalStateFile, f); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a non-default workspace
|
|
if err := os.MkdirAll(filepath.Join(local.DefaultWorkspaceDir, "test"), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Select the non-default "test" workspace
|
|
selectCmd := &WorkspaceSelectCommand{}
|
|
args := []string{"test"}
|
|
ui := cli.NewMockUi()
|
|
view, _ := testView(t)
|
|
selectCmd.Meta = Meta{Ui: ui, View: view}
|
|
if code := selectCmd.Run(args); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
|
}
|
|
|
|
// Assert there is a default and "test" workspace, and "test" is selected
|
|
listCmd := &WorkspaceListCommand{}
|
|
ui = cli.NewMockUi()
|
|
listCmd.Meta = Meta{Ui: ui, View: view}
|
|
|
|
if code := listCmd.Run(nil); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
|
}
|
|
|
|
actual := strings.TrimSpace(ui.OutputWriter.String())
|
|
expected := "default\n* test"
|
|
|
|
if actual != expected {
|
|
t.Fatalf("\nexpected: %q\nactual: %q", expected, actual)
|
|
}
|
|
|
|
// Attempt to delete the default workspace (not forced)
|
|
ui = cli.NewMockUi()
|
|
delCmd := &WorkspaceDeleteCommand{
|
|
Meta: Meta{Ui: ui, View: view},
|
|
}
|
|
args = []string{"default"}
|
|
if code := delCmd.Run(args); code != 1 {
|
|
t.Fatalf("expected failure when trying to delete the default workspace.\noutput: %s", ui.OutputWriter)
|
|
}
|
|
|
|
// User should be prevented from deleting the default workspace despite:
|
|
// * the state being empty
|
|
// * default not being the selected workspace
|
|
gotStderr := ui.ErrorWriter.String()
|
|
if want, got := `Cannot delete the default workspace`, gotStderr; !strings.Contains(got, want) {
|
|
t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, got)
|
|
}
|
|
|
|
// Attempt to force delete the default workspace
|
|
ui = cli.NewMockUi()
|
|
delCmd = &WorkspaceDeleteCommand{
|
|
Meta: Meta{Ui: ui, View: view},
|
|
}
|
|
args = []string{"-force", "default"}
|
|
if code := delCmd.Run(args); code != 1 {
|
|
t.Fatalf("expected failure when trying to delete the default workspace.\noutput: %s", ui.OutputWriter)
|
|
}
|
|
|
|
// Outcome should be the same even when forcing
|
|
gotStderr = ui.ErrorWriter.String()
|
|
if want, got := `Cannot delete the default workspace`, gotStderr; !strings.Contains(got, want) {
|
|
t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, got)
|
|
}
|
|
}
|
|
|
|
func TestWorkspace_selectWithOrCreate(t *testing.T) {
|
|
// Create a temporary working directory that is empty
|
|
td := t.TempDir()
|
|
os.MkdirAll(td, 0755)
|
|
t.Chdir(td)
|
|
|
|
selectCmd := &WorkspaceSelectCommand{}
|
|
|
|
current, _ := selectCmd.Workspace()
|
|
if current != backend.DefaultStateName {
|
|
t.Fatal("current workspace should be 'default'")
|
|
}
|
|
|
|
args := []string{"-or-create", "test"}
|
|
ui := new(cli.MockUi)
|
|
view, _ := testView(t)
|
|
selectCmd.Meta = Meta{Ui: ui, View: view}
|
|
if code := selectCmd.Run(args); code != 0 {
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
|
}
|
|
|
|
current, _ = selectCmd.Workspace()
|
|
if current != "test" {
|
|
t.Fatalf("current workspace should be 'test', got %q", current)
|
|
}
|
|
|
|
}
|
|
|
|
func TestValidWorkspaceName(t *testing.T) {
|
|
cases := map[string]struct {
|
|
input string
|
|
valid bool
|
|
}{
|
|
"foobar": {
|
|
input: "foobar",
|
|
valid: true,
|
|
},
|
|
"valid symbols": {
|
|
input: "-._~@:",
|
|
valid: true,
|
|
},
|
|
"includes space": {
|
|
input: "two words",
|
|
valid: false,
|
|
},
|
|
"empty string": {
|
|
input: "",
|
|
valid: false,
|
|
},
|
|
}
|
|
|
|
for tn, tc := range cases {
|
|
t.Run(tn, func(t *testing.T) {
|
|
valid := validWorkspaceName(tc.input)
|
|
if valid != tc.valid {
|
|
t.Fatalf("unexpected output when processing input %q. Wanted %v got %v", tc.input, tc.valid, valid)
|
|
}
|
|
})
|
|
}
|
|
}
|