diff --git a/modules/terraform/errors.go b/modules/terraform/errors.go index d80a8514b..ae0e9a484 100644 --- a/modules/terraform/errors.go +++ b/modules/terraform/errors.go @@ -87,3 +87,17 @@ type PanicWhileParsingVarFile struct { func (err PanicWhileParsingVarFile) Error() string { return fmt.Sprintf("Recovering panic while parsing '%s'. Got error of type '%v': %v", err.ConfigFile, reflect.TypeOf(err.RecoveredValue), err.RecoveredValue) } + +// UnsupportedDefaultWorkspaceDeletion is returned when user tries to delete the workspace "default" +type UnsupportedDefaultWorkspaceDeletion struct{} + +func (err *UnsupportedDefaultWorkspaceDeletion) Error() string { + return "Deleting the workspace 'default' is not supported" +} + +// WorkspaceDoesNotExist is returned when user tries to delete a workspace which does not exist +type WorkspaceDoesNotExist string + +func (err WorkspaceDoesNotExist) Error() string { + return fmt.Sprintf("The workspace %q does not exist.", string(err)) +} diff --git a/modules/terraform/workspace.go b/modules/terraform/workspace.go index cd6998daa..1778d33a0 100644 --- a/modules/terraform/workspace.go +++ b/modules/terraform/workspace.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/gruntwork-io/terratest/modules/testing" + "github.com/stretchr/testify/require" ) // WorkspaceSelectOrNew runs terraform workspace with the given options and the workspace name @@ -60,3 +61,49 @@ func nameMatchesWorkspace(name string, workspace string) bool { match, _ := regexp.MatchString(fmt.Sprintf("^\\*?\\s*%s$", name), workspace) return match } + +// WorkspaceDelete removes the specified terraform workspace with the given options. +// It returns the name of the current workspace AFTER deletion, and the returned error (that can be nil). +// If the workspace to delete is the current one, then it tries to switch to the "default" workspace. +// Deleting the workspace "default" is not supported. +func WorkspaceDeleteE(t testing.TestingT, options *Options, name string) (string, error) { + currentWorkspace, err := RunTerraformCommandE(t, options, "workspace", "show") + if err != nil { + return currentWorkspace, err + } + + if name == "default" { + return currentWorkspace, &UnsupportedDefaultWorkspaceDeletion{} + } + + out, err := RunTerraformCommandE(t, options, "workspace", "list") + if err != nil { + return currentWorkspace, err + } + if !isExistingWorkspace(out, name) { + return currentWorkspace, WorkspaceDoesNotExist(name) + } + + // Switch workspace before deleting if it is the current + if currentWorkspace == name { + currentWorkspace, err = WorkspaceSelectOrNewE(t, options, "default") + if err != nil { + return currentWorkspace, err + } + } + + // delete workspace + _, err = RunTerraformCommandE(t, options, "workspace", "delete", name) + + return currentWorkspace, err +} + +// WorkspaceDelete removes the specified terraform workspace with the given options. +// It returns the name of the current workspace AFTER deletion. +// If the workspace to delete is the current one, then it tries to switch to the "default" workspace. +// Deleting the workspace "default" is not supported and only return an empty string (to avoid a fatal error). +func WorkspaceDelete(t testing.TestingT, options *Options, name string) string { + out, err := WorkspaceDeleteE(t, options, name) + require.NoError(t, err) + return out +} diff --git a/modules/terraform/workspace_test.go b/modules/terraform/workspace_test.go index 4d3d05916..0ccb3e752 100644 --- a/modules/terraform/workspace_test.go +++ b/modules/terraform/workspace_test.go @@ -1,10 +1,12 @@ package terraform import ( + "errors" "testing" "github.com/gruntwork-io/terratest/modules/files" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestWorkspaceNew(t *testing.T) { @@ -130,3 +132,101 @@ func TestNameMatchesWorkspace(t *testing.T) { assert.Equal(t, testCase.expected, actual, "Name: %q, Workspace: %q", testCase.name, testCase.workspace) } } + +func TestWorkspaceDeleteE(t *testing.T) { + t.Parallel() + + // state describes an expected status when a given testCase begins + type state struct { + workspaces []string + current string + } + + // testCase describes a named test case with a state, args and expcted results + type testCase struct { + name string + initialState state + toDeleteWorkspace string + expectedCurrent string + expectedError error + } + + testCases := []testCase{ + { + name: "delete another existing workspace and stay on current", + initialState: state{ + workspaces: []string{"staging", "production"}, + current: "staging", + }, + toDeleteWorkspace: "production", + expectedCurrent: "staging", + expectedError: nil, + }, + { + name: "delete current workspace and switch to a specified", + initialState: state{ + workspaces: []string{"staging", "production"}, + current: "production", + }, + toDeleteWorkspace: "production", + expectedCurrent: "default", + expectedError: nil, + }, + { + name: "delete a non existing workspace should trigger an error", + initialState: state{ + workspaces: []string{"staging", "production"}, + current: "staging", + }, + toDeleteWorkspace: "hellothere", + expectedCurrent: "staging", + expectedError: WorkspaceDoesNotExist("hellothere"), + }, + { + name: "delete the default workspace triggers an error", + initialState: state{ + workspaces: []string{"staging", "production"}, + current: "staging", + }, + toDeleteWorkspace: "default", + expectedCurrent: "staging", + expectedError: &UnsupportedDefaultWorkspaceDeletion{}, + }, + } + + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + testFolder, err := files.CopyTerraformFolderToTemp("../../test/fixtures/terraform-workspace", testCase.name) + require.NoError(t, err) + + options := &Options{ + TerraformDir: testFolder, + } + + // Set up pre-existing environment based on test case description + for _, existingWorkspace := range testCase.initialState.workspaces { + _, err = RunTerraformCommandE(t, options, "workspace", "new", existingWorkspace) + require.NoError(t, err) + } + // Switch to the specified workspace + _, err = RunTerraformCommandE(t, options, "workspace", "select", testCase.initialState.current) + require.NoError(t, err) + + // Testing time, wooohoooo + gotResult, gotErr := WorkspaceDeleteE(t, options, testCase.toDeleteWorkspace) + + // Check for errors + if testCase.expectedError != nil { + assert.True(t, errors.As(gotErr, &testCase.expectedError)) + } else { + assert.NoError(t, gotErr) + // Check for results + assert.Equal(t, testCase.expectedCurrent, gotResult) + assert.False(t, isExistingWorkspace(RunTerraformCommand(t, options, "workspace", "list"), testCase.toDeleteWorkspace)) + } + }) + + } +}