Skip to content

Commit

Permalink
Implement Terraform Workspace deletion. Fixes gruntwork-io#583.
Browse files Browse the repository at this point in the history
Signed-off-by: Damien Duportal <[email protected]>
  • Loading branch information
dduportal committed Feb 24, 2021
1 parent 60ee8ed commit ea5112e
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 0 deletions.
46 changes: 46 additions & 0 deletions modules/terraform/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
174 changes: 174 additions & 0 deletions modules/terraform/workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))

}
}

0 comments on commit ea5112e

Please sign in to comment.