Skip to content

Commit ec3281b

Browse files
authored
feat(delete-workspace): Add command to delete workspace (#38)
* Allow deleting workspace * Asks for confirmation, either via input or --yes Signed-off-by: Manuel Dewald <manuel@codesphere.com>
1 parent 0343ab7 commit ec3281b

21 files changed

+386
-40
lines changed

api/workspace.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,16 @@ func (c *Client) ListWorkspaces(teamId int) ([]Workspace, error) {
1919

2020
func (c *Client) GetWorkspace(workspaceId int) (Workspace, error) {
2121
workspace, _, err := c.api.WorkspacesAPI.WorkspacesGetWorkspace(c.ctx, float32(workspaceId)).Execute()
22-
return *workspace, err
22+
23+
if workspace != nil {
24+
return *workspace, err
25+
}
26+
return Workspace{}, err
27+
}
28+
29+
func (c *Client) DeleteWorkspace(workspaceId int) error {
30+
_, err := c.api.WorkspacesAPI.WorkspacesDeleteWorkspace(c.ctx, float32(workspaceId)).Execute()
31+
return err
2332
}
2433

2534
func (c *Client) WorkspaceStatus(workspaceId int) (*WorkspaceStatus, error) {

cli/cmd/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type Client interface {
2121
ExecCommand(workspaceId int, command string, workdir string, env map[string]string) (string, string, error)
2222
ListWorkspacePlans() ([]api.WorkspacePlan, error)
2323
DeployWorkspace(args api.DeployWorkspaceArgs) (*api.Workspace, error)
24+
DeleteWorkspace(wsId int) error
2425
}
2526

2627
func NewClient(opts GlobalOptions) (Client, error) {

cli/cmd/create-workspace.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ import (
1111

1212
"github.com/codesphere-cloud/cs-go/api"
1313
"github.com/codesphere-cloud/cs-go/pkg/cs"
14-
"github.com/codesphere-cloud/cs-go/pkg/out"
14+
"github.com/codesphere-cloud/cs-go/pkg/io"
1515
"github.com/spf13/cobra"
1616
)
1717

18-
// CreateWorkspaceCmd represents the workspace command
1918
type CreateWorkspaceCmd struct {
2019
cmd *cobra.Command
2120
Opts CreateWorkspaceOpts
@@ -78,15 +77,15 @@ func AddCreateWorkspaceCmd(create *cobra.Command, opts GlobalOptions) {
7877
Use: "workspace",
7978
Short: "Create a workspace",
8079
Args: cobra.RangeArgs(1, 1),
81-
Long: out.Long(`Create a workspace in Codesphere.
80+
Long: io.Long(`Create a workspace in Codesphere.
8281
8382
Specify a (private) git repository or start an empty workspace.
8483
Environment variables can be set to initialize the workspace with a specific environment.
8584
The command will wait for the workspace to become running or a timeout is reached.
8685
8786
To decide which plan suits your needs, run 'cs list plans'
8887
`),
89-
Example: out.FormatExampleCommands("create workspace my-workspace", []out.Example{
88+
Example: io.FormatExampleCommands("create workspace my-workspace", []io.Example{
9089
{Cmd: "-p 20", Desc: "Create an empty workspace, using plan 20"},
9190
{Cmd: "-r https://github.com/codesphere-cloud/landingpage-temp.git", Desc: "Create a workspace from a git repository"},
9291
{Cmd: "-r https://github.com/codesphere-cloud/landingpage-temp.git -e DEPLOYMENT=prod -e A=B", Desc: "Create a workspace and set environment variables"},

cli/cmd/create.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/spf13/cobra"
88
)
99

10-
// CreateCmd represents the create command
1110
type CreateCmd struct {
1211
cmd *cobra.Command
1312
}

cli/cmd/delete-workspace.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/codesphere-cloud/cs-go/pkg/io"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
type Prompt interface {
12+
InputPrompt(prompt string) string
13+
}
14+
15+
type DeleteWorkspaceCmd struct {
16+
cmd *cobra.Command
17+
Opts DeleteWorkspaceOpts
18+
Prompt Prompt
19+
}
20+
21+
type DeleteWorkspaceOpts struct {
22+
GlobalOptions
23+
Confirmed *bool
24+
}
25+
26+
func (c *DeleteWorkspaceCmd) RunE(_ *cobra.Command, args []string) error {
27+
wsId, err := c.Opts.GetWorkspaceId()
28+
if err != nil {
29+
return fmt.Errorf("failed to get workspace ID: %w", err)
30+
}
31+
32+
client, err := NewClient(c.Opts.GlobalOptions)
33+
if err != nil {
34+
return fmt.Errorf("failed to create Codesphere client: %w", err)
35+
}
36+
37+
return c.DeleteWorkspace(client, wsId)
38+
}
39+
40+
func AddDeleteWorkspaceCmd(delete *cobra.Command, opts GlobalOptions) {
41+
workspace := DeleteWorkspaceCmd{
42+
cmd: &cobra.Command{
43+
Use: "workspace",
44+
Short: "Delete workspace",
45+
Long: io.Long(`Delete workspace after confirmation.
46+
47+
Confirmation can be given interactively or with the --yes flag`),
48+
},
49+
Opts: DeleteWorkspaceOpts{GlobalOptions: opts},
50+
Prompt: &io.Prompt{},
51+
}
52+
workspace.Opts.Confirmed = workspace.cmd.Flags().Bool("yes", false, "Confirm deletion of workspace")
53+
delete.AddCommand(workspace.cmd)
54+
workspace.cmd.RunE = workspace.RunE
55+
}
56+
57+
func (c *DeleteWorkspaceCmd) DeleteWorkspace(client Client, wsId int) error {
58+
59+
workspace, err := client.GetWorkspace(wsId)
60+
if err != nil {
61+
return fmt.Errorf("failed to get workspace %d: %w", wsId, err)
62+
}
63+
64+
if !*c.Opts.Confirmed {
65+
fmt.Printf("Please confirm deletion of workspace '%s', ID %d, in team %d by entering its name:\n", workspace.Name, workspace.Id, workspace.TeamId)
66+
confirmation := c.Prompt.InputPrompt("Confirmation delete")
67+
68+
if confirmation != workspace.Name {
69+
return errors.New("confirmation failed")
70+
}
71+
}
72+
73+
return client.DeleteWorkspace(wsId)
74+
}

cli/cmd/delete.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
type DeleteCmd struct {
8+
cmd *cobra.Command
9+
}
10+
11+
func AddDeleteCmd(rootCmd *cobra.Command, opt GlobalOptions) {
12+
delete := DeleteCmd{
13+
cmd: &cobra.Command{
14+
Use: "delete",
15+
Short: "Delete Codesphere resources",
16+
Long: `Delete Codesphere resources, e.g. workspaces.`,
17+
},
18+
}
19+
rootCmd.AddCommand(delete.cmd)
20+
21+
// Add child commands here
22+
AddDeleteWorkspaceCmd(delete.cmd, opt)
23+
}

cli/cmd/delete_workspace_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package cmd_test
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
9+
10+
"github.com/codesphere-cloud/cs-go/api"
11+
"github.com/codesphere-cloud/cs-go/cli/cmd"
12+
)
13+
14+
var _ = Describe("CreateWorkspace", func() {
15+
var (
16+
mockEnv *cmd.MockEnv
17+
mockClient *cmd.MockClient
18+
mockPrompt *cmd.MockPrompt
19+
c *cmd.DeleteWorkspaceCmd
20+
wsId int
21+
wsName string
22+
confirmed bool
23+
ws api.Workspace
24+
)
25+
26+
JustBeforeEach(func() {
27+
mockClient = cmd.NewMockClient(GinkgoT())
28+
mockEnv = cmd.NewMockEnv(GinkgoT())
29+
mockPrompt = cmd.NewMockPrompt(GinkgoT())
30+
c = &cmd.DeleteWorkspaceCmd{
31+
Opts: cmd.DeleteWorkspaceOpts{
32+
GlobalOptions: cmd.GlobalOptions{
33+
Env: mockEnv,
34+
WorkspaceId: &wsId,
35+
},
36+
Confirmed: &confirmed,
37+
},
38+
Prompt: mockPrompt,
39+
}
40+
})
41+
BeforeEach(func() {
42+
ws = api.Workspace{
43+
Id: wsId,
44+
Name: wsName,
45+
}
46+
})
47+
Context("Unconfirmed", func() {
48+
BeforeEach(func() {
49+
confirmed = false
50+
wsName = "fake-ws"
51+
})
52+
Context("Workspace exists", func() {
53+
Context("Workspace name entered in confirmation prompt", func() {
54+
It("deletes the workspace", func() {
55+
mockClient.EXPECT().GetWorkspace(wsId).Return(ws, nil)
56+
mockPrompt.EXPECT().InputPrompt("Confirmation delete").Return(ws.Name)
57+
mockClient.EXPECT().DeleteWorkspace(wsId).Return(nil)
58+
err := c.DeleteWorkspace(mockClient, wsId)
59+
Expect(err).ToNot(HaveOccurred())
60+
})
61+
})
62+
Context("Wrong input entered in confirmation prompt", func() {
63+
It("Returns an error", func() {
64+
mockClient.EXPECT().GetWorkspace(wsId).Return(ws, nil)
65+
mockPrompt.EXPECT().InputPrompt("Confirmation delete").Return("other-workspace")
66+
err := c.DeleteWorkspace(mockClient, wsId)
67+
Expect(err).To(MatchError("confirmation failed"))
68+
})
69+
})
70+
})
71+
72+
})
73+
74+
Context("Confirmed via CLI flag", func() {
75+
var (
76+
getWsErr error
77+
)
78+
BeforeEach(func() {
79+
wsId = 42
80+
ws = api.Workspace{}
81+
confirmed = true
82+
getWsErr = nil
83+
})
84+
JustBeforeEach(func() {
85+
fmt.Println(ws)
86+
mockClient.EXPECT().GetWorkspace(wsId).Return(ws, getWsErr)
87+
})
88+
Context("Workspace exists", func() {
89+
BeforeEach(func() {
90+
ws = api.Workspace{
91+
Id: wsId,
92+
}
93+
})
94+
It("Deletes the workspace", func() {
95+
mockClient.EXPECT().DeleteWorkspace(wsId).Return(nil)
96+
err := c.DeleteWorkspace(mockClient, wsId)
97+
Expect(err).ToNot(HaveOccurred())
98+
})
99+
})
100+
101+
Context("Workspace doesn't exist", func() {
102+
BeforeEach(func() {
103+
ws = api.Workspace{}
104+
getWsErr = errors.New("404")
105+
})
106+
107+
It("Returns an error", func() {
108+
err := c.DeleteWorkspace(mockClient, wsId)
109+
Expect(err).To(MatchError("failed to get workspace 42: 404"))
110+
})
111+
})
112+
})
113+
114+
})

cli/cmd/exec.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ import (
99
"strings"
1010

1111
"github.com/codesphere-cloud/cs-go/pkg/cs"
12-
"github.com/codesphere-cloud/cs-go/pkg/out"
12+
"github.com/codesphere-cloud/cs-go/pkg/io"
1313

1414
"github.com/spf13/cobra"
1515
)
1616

17-
// ExecCmd represents the exec command
1817
type ExecCmd struct {
1918
cmd *cobra.Command
2019
Opts ExecOptions
@@ -44,9 +43,9 @@ func AddExecCmd(rootCmd *cobra.Command, opts GlobalOptions) {
4443
Use: "exec",
4544
Args: cobra.MinimumNArgs(1),
4645
Short: "Run a command in Codesphere workspace",
47-
Long: out.Long(`Run a command in a Codesphere workspace.
46+
Long: io.Long(`Run a command in a Codesphere workspace.
4847
Output will be printed to STDOUT, errors to STDERR.`),
49-
Example: out.FormatExampleCommands("exec", []out.Example{
48+
Example: io.FormatExampleCommands("exec", []io.Example{
5049
{Cmd: "-- echo hello world", Desc: "Print `hello world`"},
5150
{Cmd: "-- find .", Desc: "List all files in workspace"},
5251
{Cmd: "-d user -- find .", Desc: "List all files in the user directory"},

cli/cmd/licenses.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/spf13/cobra"
1212
)
1313

14-
// LicensesCmd represents the licenses command
1514
type LicensesCmd struct {
1615
cmd *cobra.Command
1716
}

cli/cmd/list-plans.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ package cmd
66
import (
77
"fmt"
88

9-
"github.com/codesphere-cloud/cs-go/pkg/out"
9+
"github.com/codesphere-cloud/cs-go/pkg/io"
1010

1111
"github.com/jedib0t/go-pretty/v6/table"
1212
"github.com/jedib0t/go-pretty/v6/text"
1313
"github.com/spf13/cobra"
1414
)
1515

16-
// ListPlansCmd represents the plans command
1716
type ListPlansCmd struct {
1817
cmd *cobra.Command
1918
Opts GlobalOptions
@@ -31,7 +30,7 @@ func (c *ListPlansCmd) RunE(_ *cobra.Command, args []string) error {
3130
return fmt.Errorf("failed to list plans: %s", err)
3231
}
3332

34-
t := out.GetTableWriter()
33+
t := io.GetTableWriter()
3534
t.AppendHeader(table.Row{"ID", "Name", "On Demand", "CPU", "RAM(GiB)", "SSD(GiB)", "Price(USD)", "Max Replicas"})
3635
t.SetColumnConfigs([]table.ColumnConfig{
3736
{Name: "Price(USD)", Align: text.AlignRight},
@@ -71,7 +70,7 @@ func AddListPlansCmd(list *cobra.Command, opts GlobalOptions) {
7170
cmd: &cobra.Command{
7271
Use: "plans",
7372
Short: "List available plans",
74-
Long: out.Long(`List available workpace plans.
73+
Long: io.Long(`List available workpace plans.
7574
7675
When creating new workspaces you need to select a specific plan.`),
7776
},

0 commit comments

Comments
 (0)