X Tutup
Skip to content

Commit 678da44

Browse files
committed
Simplify delete further
1 parent ab86739 commit 678da44

File tree

6 files changed

+58
-122
lines changed

6 files changed

+58
-122
lines changed

cmd/ghcs/common.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
var errNoCodespaces = errors.New("you have no codespaces")
2020

2121
func chooseCodespace(ctx context.Context, apiClient *api.API, user *api.User) (*api.Codespace, error) {
22-
codespaces, err := apiClient.ListCodespaces(ctx, user)
22+
codespaces, err := apiClient.ListCodespaces(ctx, user.Login)
2323
if err != nil {
2424
return nil, fmt.Errorf("error getting codespaces: %w", err)
2525
}

cmd/ghcs/delete.go

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ package ghcs
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"os"
78
"strings"
89
"time"
910

1011
"github.com/AlecAivazis/survey/v2"
11-
"github.com/github/ghcs/cmd/ghcs/output"
1212
"github.com/github/ghcs/internal/api"
1313
"github.com/spf13/cobra"
1414
"golang.org/x/sync/errgroup"
@@ -17,35 +17,43 @@ import (
1717
type deleteOptions struct {
1818
deleteAll bool
1919
skipConfirm bool
20-
isInteractive bool
2120
codespaceName string
2221
repoFilter string
2322
keepDays uint16
23+
24+
isInteractive bool
2425
now func() time.Time
25-
apiClient *api.API
26+
apiClient apiClient
27+
prompter prompter
28+
}
29+
30+
type prompter interface {
31+
Confirm(message string) (bool, error)
32+
}
33+
34+
type apiClient interface {
35+
GetUser(ctx context.Context) (*api.User, error)
36+
ListCodespaces(ctx context.Context, user string) ([]*api.Codespace, error)
37+
DeleteCodespace(ctx context.Context, user, name string) error
2638
}
2739

2840
func newDeleteCmd() *cobra.Command {
2941
opts := deleteOptions{
30-
apiClient: api.New(os.Getenv("GITHUB_TOKEN")),
31-
now: time.Now,
3242
isInteractive: hasTTY,
43+
now: time.Now,
44+
apiClient: api.New(os.Getenv("GITHUB_TOKEN")),
45+
prompter: &surveyPrompter{},
3346
}
3447

3548
deleteCmd := &cobra.Command{
3649
Use: "delete",
3750
Short: "Delete a codespace",
3851
Args: cobra.NoArgs,
3952
RunE: func(cmd *cobra.Command, args []string) error {
40-
// switch {
41-
// case allCodespaces && repo != "":
42-
// return errors.New("both --all and --repo is not supported")
43-
// case allCodespaces:
44-
// return deleteAll(log, force, keepThresholdDays)
45-
// case repo != "":
46-
// return deleteByRepo(log, repo, force, keepThresholdDays)
47-
log := output.NewLogger(os.Stdout, os.Stderr, false)
48-
return delete(context.Background(), log, opts)
53+
if opts.deleteAll && opts.repoFilter != "" {
54+
return errors.New("both --all and --repo is not supported")
55+
}
56+
return delete(context.Background(), opts)
4957
},
5058
}
5159

@@ -58,13 +66,13 @@ func newDeleteCmd() *cobra.Command {
5866
return deleteCmd
5967
}
6068

61-
func delete(ctx context.Context, log *output.Logger, opts deleteOptions) error {
69+
func delete(ctx context.Context, opts deleteOptions) error {
6270
user, err := opts.apiClient.GetUser(ctx)
6371
if err != nil {
6472
return fmt.Errorf("error getting user: %w", err)
6573
}
6674

67-
codespaces, err := opts.apiClient.ListCodespaces(ctx, user)
75+
codespaces, err := opts.apiClient.ListCodespaces(ctx, user.Login)
6876
if err != nil {
6977
return fmt.Errorf("error getting codespaces: %w", err)
7078
}
@@ -78,7 +86,7 @@ func delete(ctx context.Context, log *output.Logger, opts deleteOptions) error {
7886
nameFilter = c.Name
7987
}
8088

81-
var codespacesToDelete []*api.Codespace
89+
codespacesToDelete := make([]*api.Codespace, 0, len(codespaces))
8290
lastUpdatedCutoffTime := opts.now().AddDate(0, 0, -int(opts.keepDays))
8391
for _, c := range codespaces {
8492
if nameFilter != "" && c.Name != nameFilter {
@@ -97,9 +105,9 @@ func delete(ctx context.Context, log *output.Logger, opts deleteOptions) error {
97105
}
98106
}
99107
if nameFilter == "" || !opts.skipConfirm {
100-
confirmed, err := confirmDeletion(c)
108+
confirmed, err := confirmDeletion(opts.prompter, c, opts.isInteractive)
101109
if err != nil {
102-
return fmt.Errorf("deletion could not be confirmed: %w", err)
110+
return fmt.Errorf("unable to confirm: %w", err)
103111
}
104112
if !confirmed {
105113
continue
@@ -112,11 +120,7 @@ func delete(ctx context.Context, log *output.Logger, opts deleteOptions) error {
112120
for _, c := range codespacesToDelete {
113121
codespaceName := c.Name
114122
g.Go(func() error {
115-
token, err := opts.apiClient.GetCodespaceToken(ctx, user.Login, codespaceName)
116-
if err != nil {
117-
return fmt.Errorf("error getting codespace token: %w", err)
118-
}
119-
if err := opts.apiClient.DeleteCodespace(ctx, user, token, codespaceName); err != nil {
123+
if err := opts.apiClient.DeleteCodespace(ctx, user.Login, codespaceName); err != nil {
120124
return fmt.Errorf("error deleting codespace: %w", err)
121125
}
122126
return nil
@@ -126,24 +130,29 @@ func delete(ctx context.Context, log *output.Logger, opts deleteOptions) error {
126130
return g.Wait()
127131
}
128132

129-
func confirmDeletion(codespace *api.Codespace) (bool, error) {
133+
func confirmDeletion(p prompter, codespace *api.Codespace, isInteractive bool) (bool, error) {
130134
gs := codespace.Environment.GitStatus
131135
hasUnsavedChanges := gs.HasUncommitedChanges || gs.HasUnpushedChanges
132136
if !hasUnsavedChanges {
133137
return true, nil
134138
}
135-
if !hasTTY {
139+
if !isInteractive {
136140
return false, fmt.Errorf("codespace %s has unsaved changes (use --force to override)", codespace.Name)
137141
}
142+
return p.Confirm(fmt.Sprintf("Codespace %s has unsaved changes. OK to delete?", codespace.Name))
143+
}
144+
145+
type surveyPrompter struct{}
138146

147+
func (p *surveyPrompter) Confirm(message string) (bool, error) {
139148
var confirmed struct {
140149
Confirmed bool
141150
}
142151
q := []*survey.Question{
143152
{
144153
Name: "confirmed",
145154
Prompt: &survey.Confirm{
146-
Message: fmt.Sprintf("Codespace %s has unsaved changes. OK to delete?", codespace.Name),
155+
Message: message,
147156
},
148157
},
149158
}

cmd/ghcs/delete_test.go

Lines changed: 8 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,28 @@
11
package ghcs
22

33
import (
4+
"context"
45
"testing"
5-
"time"
6-
7-
"github.com/github/ghcs/internal/api"
86
)
97

10-
func TestFilterCodespacesToDelete(t *testing.T) {
11-
type args struct {
12-
codespaces []*api.Codespace
13-
thresholdDays int
14-
}
8+
func TestDelete(t *testing.T) {
159
tests := []struct {
1610
name string
17-
now time.Time
18-
args args
11+
opts deleteOptions
1912
wantErr bool
20-
deleted []*api.Codespace
2113
}{
2214
{
23-
name: "no codespaces is to be deleted",
24-
25-
args: args{
26-
codespaces: []*api.Codespace{
27-
{
28-
Name: "testcodespace",
29-
CreatedAt: "2021-08-09T10:10:24+02:00",
30-
LastUsedAt: "2021-08-09T13:10:24+02:00",
31-
Environment: api.CodespaceEnvironment{
32-
State: "Shutdown",
33-
},
34-
},
35-
},
36-
thresholdDays: 1,
15+
name: "by name",
16+
opts: deleteOptions{
17+
codespaceName: "foo-bar-123",
3718
},
38-
now: time.Date(2021, 8, 9, 20, 10, 24, 0, time.UTC),
39-
deleted: []*api.Codespace{},
40-
},
41-
{
42-
name: "one codespace is to be deleted",
43-
44-
args: args{
45-
codespaces: []*api.Codespace{
46-
{
47-
Name: "testcodespace",
48-
CreatedAt: "2021-08-09T10:10:24+02:00",
49-
LastUsedAt: "2021-08-09T13:10:24+02:00",
50-
Environment: api.CodespaceEnvironment{
51-
State: "Shutdown",
52-
},
53-
},
54-
},
55-
thresholdDays: 1,
56-
},
57-
now: time.Date(2021, 8, 15, 20, 12, 24, 0, time.UTC),
58-
deleted: []*api.Codespace{
59-
{
60-
Name: "testcodespace",
61-
CreatedAt: "2021-08-09T10:10:24+02:00",
62-
LastUsedAt: "2021-08-09T13:10:24+02:00",
63-
},
64-
},
65-
},
66-
{
67-
name: "threshold is invalid",
68-
69-
args: args{
70-
codespaces: []*api.Codespace{
71-
{
72-
Name: "testcodespace",
73-
CreatedAt: "2021-08-09T10:10:24+02:00",
74-
LastUsedAt: "2021-08-09T13:10:24+02:00",
75-
Environment: api.CodespaceEnvironment{
76-
State: "Shutdown",
77-
},
78-
},
79-
},
80-
thresholdDays: -1,
81-
},
82-
now: time.Date(2021, 8, 15, 20, 12, 24, 0, time.UTC),
83-
wantErr: true,
84-
deleted: []*api.Codespace{},
8519
},
8620
}
8721
for _, tt := range tests {
8822
t.Run(tt.name, func(t *testing.T) {
89-
90-
now = func() time.Time {
91-
return tt.now
92-
}
93-
94-
codespaces, err := filterCodespacesToDelete(tt.args.codespaces, tt.args.thresholdDays)
23+
err := delete(context.Background(), tt.opts)
9524
if (err != nil) != tt.wantErr {
96-
t.Errorf("API.CleanupUnusedCodespaces() error = %v, wantErr %v", err, tt.wantErr)
97-
}
98-
99-
if len(codespaces) != len(tt.deleted) {
100-
t.Errorf("expected %d deleted codespaces, got %d", len(tt.deleted), len(codespaces))
25+
t.Errorf("delete() error = %v, wantErr %v", err, tt.wantErr)
10126
}
10227
})
10328
}

cmd/ghcs/list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func list(opts *listOptions) error {
4040
return fmt.Errorf("error getting user: %w", err)
4141
}
4242

43-
codespaces, err := apiClient.ListCodespaces(ctx, user)
43+
codespaces, err := apiClient.ListCodespaces(ctx, user.Login)
4444
if err != nil {
4545
return fmt.Errorf("error getting codespaces: %w", err)
4646
}

internal/api/api.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ import (
3232
"encoding/json"
3333
"errors"
3434
"fmt"
35-
"github.com/opentracing/opentracing-go"
3635
"io/ioutil"
3736
"net/http"
3837
"strconv"
3938
"strings"
39+
40+
"github.com/opentracing/opentracing-go"
4041
)
4142

4243
const githubAPI = "https://api.github.com"
@@ -172,9 +173,9 @@ type CodespaceEnvironmentConnection struct {
172173
RelaySAS string `json:"relaySas"`
173174
}
174175

175-
func (a *API) ListCodespaces(ctx context.Context, user *User) ([]*Codespace, error) {
176+
func (a *API) ListCodespaces(ctx context.Context, user string) ([]*Codespace, error) {
176177
req, err := http.NewRequest(
177-
http.MethodGet, a.githubAPI+"/vscs_internal/user/"+user.Login+"/codespaces", nil,
178+
http.MethodGet, a.githubAPI+"/vscs_internal/user/"+user+"/codespaces", nil,
178179
)
179180
if err != nil {
180181
return nil, fmt.Errorf("error creating request: %w", err)
@@ -442,8 +443,13 @@ func (a *API) CreateCodespace(ctx context.Context, user *User, repository *Repos
442443
return &response, nil
443444
}
444445

445-
func (a *API) DeleteCodespace(ctx context.Context, user *User, token, codespaceName string) error {
446-
req, err := http.NewRequest(http.MethodDelete, a.githubAPI+"/vscs_internal/user/"+user.Login+"/codespaces/"+codespaceName, nil)
446+
func (a *API) DeleteCodespace(ctx context.Context, user string, codespaceName string) error {
447+
token, err := a.GetCodespaceToken(ctx, user, codespaceName)
448+
if err != nil {
449+
return fmt.Errorf("error getting codespace token: %w", err)
450+
}
451+
452+
req, err := http.NewRequest(http.MethodDelete, a.githubAPI+"/vscs_internal/user/"+user+"/codespaces/"+codespaceName, nil)
447453
if err != nil {
448454
return fmt.Errorf("error creating request: %w", err)
449455
}

internal/api/api_test.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ import (
1010
)
1111

1212
func TestListCodespaces(t *testing.T) {
13-
user := &User{
14-
Login: "testuser",
15-
}
16-
1713
codespaces := []*Codespace{
1814
{
1915
Name: "testcodespace",
@@ -38,7 +34,7 @@ func TestListCodespaces(t *testing.T) {
3834
token: "faketoken",
3935
}
4036
ctx := context.TODO()
41-
codespaces, err := api.ListCodespaces(ctx, user)
37+
codespaces, err := api.ListCodespaces(ctx, "testuser")
4238
if err != nil {
4339
t.Fatal(err)
4440
}

0 commit comments

Comments
 (0)
X Tutup