From 0188967ce2a61e43912c4b274a6febf1cdc00e36 Mon Sep 17 00:00:00 2001 From: Damien Duportal Date: Wed, 24 Feb 2021 08:39:50 +0100 Subject: [PATCH] Implement Terraform Workspace deletion. Fixes #583. Signed-off-by: Damien Duportal --- modules/terraform/workspace.go | 46 ++++++++ modules/terraform/workspace_test.go | 174 ++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) diff --git a/modules/terraform/workspace.go b/modules/terraform/workspace.go index cd6998daa8..3fc301eafd 100644 --- a/modules/terraform/workspace.go +++ b/modules/terraform/workspace.go @@ -60,3 +60,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 := RunTerraformCommand(t, options, "workspace", "show") + + if name == "default" { + return currentWorkspace, fmt.Errorf("Deleting the workspace 'default' is not supported") + } + + out, err := RunTerraformCommandE(t, options, "workspace", "list") + if err != nil { + return currentWorkspace, err + } + if !isExistingWorkspace(out, name) { + return currentWorkspace, fmt.Errorf("The workspace %q does not exist.", name) + } + + // Switch workspace before deleting if it is the current + if currentWorkspace == name { + currentWorkspace = WorkspaceSelectOrNew(t, options, "default") + } + + // 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 { + if name == "default" { + return name + } + + out, err := WorkspaceDeleteE(t, options, name) + if err != nil { + t.Fatal(err) + } + return out +} diff --git a/modules/terraform/workspace_test.go b/modules/terraform/workspace_test.go index 4d3d059169..cbca18d4b2 100644 --- a/modules/terraform/workspace_test.go +++ b/modules/terraform/workspace_test.go @@ -130,3 +130,177 @@ func TestNameMatchesWorkspace(t *testing.T) { assert.Equal(t, testCase.expected, actual, "Name: %q, Workspace: %q", testCase.name, testCase.workspace) } } + +// Please note that this test depends on other functions that should be mocked to be a unit test. +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 + expectedErrorMessage string + } + + testCases := []testCase{ + { + name: "delete another existing workspace and stay on current", + initialState: state{ + workspaces: []string{"staging", "production"}, + current: "staging", + }, + toDeleteWorkspace: "production", + expectedCurrent: "staging", + expectedErrorMessage: "", + }, + { + name: "delete current workspace and switch to a specified", + initialState: state{ + workspaces: []string{"staging", "production"}, + current: "production", + }, + toDeleteWorkspace: "production", + expectedCurrent: "default", + expectedErrorMessage: "", + }, + { + name: "delete a non existing workspace should trigger an error", + initialState: state{ + workspaces: []string{"staging", "production"}, + current: "staging", + }, + toDeleteWorkspace: "hellothere", + expectedCurrent: "staging", + expectedErrorMessage: "The workspace \"hellothere\" does not exist.", + }, + { + name: "delete the default workspace triggers an error", + initialState: state{ + workspaces: []string{"staging", "production"}, + current: "staging", + }, + toDeleteWorkspace: "default", + expectedCurrent: "staging", + expectedErrorMessage: "Deleting the workspace 'default' is not supported", + }, + } + + for _, tt := range testCases { + testFolder, err := files.CopyTerraformFolderToTemp("../../test/fixtures/terraform-workspace", tt.name) + if err != nil { + t.Fatal(err) + } + + options := &Options{ + TerraformDir: testFolder, + } + + // Set up pre-existing environment based on test case description + for _, existingWorkspace := range tt.initialState.workspaces { + _, err = RunTerraformCommandE(t, options, "workspace", "new", existingWorkspace) + if err != nil { + t.Fatal(err) + } + } + // Switch to the specified workspace + _, err = RunTerraformCommandE(t, options, "workspace", "select", tt.initialState.current) + if err != nil { + t.Fatal(err) + } + + // Testing time, wooohoooo + gotResult, gotErr := WorkspaceDeleteE(t, options, tt.toDeleteWorkspace) + + // Check for errors + if tt.expectedErrorMessage != "" { + assert.Error(t, gotErr) + assert.Equal(t, tt.expectedErrorMessage, gotErr.Error()) + } else { + assert.Nil(t, gotErr) + // Check for results + assert.Equal(t, tt.expectedCurrent, gotResult) + assert.False(t, isExistingWorkspace(RunTerraformCommand(t, options, "workspace", "list"), tt.toDeleteWorkspace)) + } + + } +} + +// Please note that this test depends on other functions that should be mocked to be a unit test. +func TestWorkspaceDelete(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 + } + + testCases := []testCase{ + { + name: "delete another existing workspace and stay on current", + initialState: state{ + workspaces: []string{"staging", "production"}, + current: "staging", + }, + toDeleteWorkspace: "production", + expectedCurrent: "staging", + }, + { + name: "delete current workspace and switch to a specified", + initialState: state{ + workspaces: []string{"staging", "production"}, + current: "production", + }, + toDeleteWorkspace: "production", + expectedCurrent: "default", + }, + } + + for _, tt := range testCases { + testFolder, err := files.CopyTerraformFolderToTemp("../../test/fixtures/terraform-workspace", tt.name) + if err != nil { + t.Fatal(err) + } + + options := &Options{ + TerraformDir: testFolder, + } + + // Set up pre-existing environment based on test case description + for _, existingWorkspace := range tt.initialState.workspaces { + _, err = RunTerraformCommandE(t, options, "workspace", "new", existingWorkspace) + if err != nil { + t.Fatal(err) + } + } + // Switch to the specified workspace + _, err = RunTerraformCommandE(t, options, "workspace", "select", tt.initialState.current) + if err != nil { + t.Fatal(err) + } + + // Testing time, wooohoooo + gotResult := WorkspaceDelete(t, options, tt.toDeleteWorkspace) + + // Check for results + assert.Equal(t, tt.expectedCurrent, gotResult) + assert.False(t, isExistingWorkspace(RunTerraformCommand(t, options, "workspace", "list"), tt.toDeleteWorkspace)) + + } +}