Skip to content

Commit a3efb53

Browse files
committed
Update API.StartCodespace to use new API endpoint
- Switch to using name instead of GUID - Remove GUID from the code since it is not used anywhere else - Add docs to the api client methods - Re-gen mocked client
1 parent 6b18761 commit a3efb53

File tree

4 files changed

+49
-35
lines changed

4 files changed

+49
-35
lines changed

internal/codespaces/api/api.go

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343

4444
const githubAPI = "https://api.github.com"
4545

46+
// API is the interface to the codespace service.
4647
type API struct {
4748
token string
4849
client httpClient
@@ -53,6 +54,7 @@ type httpClient interface {
5354
Do(req *http.Request) (*http.Response, error)
5455
}
5556

57+
// New creates a new API client with the given token and HTTP client.
5658
func New(token string, httpClient httpClient) *API {
5759
return &API{
5860
token: token,
@@ -61,10 +63,12 @@ func New(token string, httpClient httpClient) *API {
6163
}
6264
}
6365

66+
// User represents a GitHub user.
6467
type User struct {
6568
Login string `json:"login"`
6669
}
6770

71+
// GetUser returns the user associated with the given token.
6872
func (a *API) GetUser(ctx context.Context) (*User, error) {
6973
req, err := http.NewRequest(http.MethodGet, a.githubAPI+"/user", nil)
7074
if err != nil {
@@ -95,6 +99,7 @@ func (a *API) GetUser(ctx context.Context) (*User, error) {
9599
return &response, nil
96100
}
97101

102+
// jsonErrorResponse returns the error message from a JSON response.
98103
func jsonErrorResponse(b []byte) error {
99104
var response struct {
100105
Message string `json:"message"`
@@ -106,10 +111,12 @@ func jsonErrorResponse(b []byte) error {
106111
return errors.New(response.Message)
107112
}
108113

114+
// Repository represents a GitHub repository.
109115
type Repository struct {
110116
ID int `json:"id"`
111117
}
112118

119+
// GetRepository returns the repository associated with the given owner and name.
113120
func (a *API) GetRepository(ctx context.Context, nwo string) (*Repository, error) {
114121
req, err := http.NewRequest(http.MethodGet, a.githubAPI+"/repos/"+strings.ToLower(nwo), nil)
115122
if err != nil {
@@ -140,9 +147,9 @@ func (a *API) GetRepository(ctx context.Context, nwo string) (*Repository, error
140147
return &response, nil
141148
}
142149

150+
// Codespace represents a codespace.
143151
type Codespace struct {
144152
Name string `json:"name"`
145-
GUID string `json:"guid"`
146153
CreatedAt string `json:"created_at"`
147154
LastUsedAt string `json:"last_used_at"`
148155
Branch string `json:"branch"`
@@ -168,6 +175,7 @@ type CodespaceEnvironmentGitStatus struct {
168175
}
169176

170177
const (
178+
// CodespaceEnvironmentStateAvailable is the state for a running codespace environment.
171179
CodespaceEnvironmentStateAvailable = "Available"
172180
)
173181

@@ -179,6 +187,7 @@ type CodespaceEnvironmentConnection struct {
179187
HostPublicKeys []string `json:"hostPublicKeys"`
180188
}
181189

190+
// ListCodespaces returns a list of codespaces for the user.
182191
func (a *API) ListCodespaces(ctx context.Context) ([]*Codespace, error) {
183192
req, err := http.NewRequest(
184193
http.MethodGet, a.githubAPI+"/user/codespaces", nil,
@@ -212,6 +221,7 @@ func (a *API) ListCodespaces(ctx context.Context) ([]*Codespace, error) {
212221
return response.Codespaces, nil
213222
}
214223

224+
// getCodespaceTokenRequest is the request body for the get codespace token endpoint.
215225
type getCodespaceTokenRequest struct {
216226
MintRepositoryToken bool `json:"mint_repository_token"`
217227
}
@@ -224,6 +234,7 @@ type getCodespaceTokenResponse struct {
224234
// creation of a codespace is not yet complete and that the caller should try again.
225235
var ErrNotProvisioned = errors.New("codespace not provisioned")
226236

237+
// GetCodespaceToken returns a codespace token for the user.
227238
func (a *API) GetCodespaceToken(ctx context.Context, ownerLogin, codespaceName string) (string, error) {
228239
reqBody, err := json.Marshal(getCodespaceTokenRequest{true})
229240
if err != nil {
@@ -267,6 +278,7 @@ func (a *API) GetCodespaceToken(ctx context.Context, ownerLogin, codespaceName s
267278
return response.RepositoryToken, nil
268279
}
269280

281+
// GetCodespace returns a codespace for the user.
270282
func (a *API) GetCodespace(ctx context.Context, token, owner, codespace string) (*Codespace, error) {
271283
req, err := http.NewRequest(
272284
http.MethodGet,
@@ -302,19 +314,20 @@ func (a *API) GetCodespace(ctx context.Context, token, owner, codespace string)
302314
return &response, nil
303315
}
304316

305-
func (a *API) StartCodespace(ctx context.Context, token string, codespace *Codespace) error {
317+
// StartCodespace starts a codespace for the user.
318+
// If the codespace is already running, the returned error from the API is ignored.
319+
func (a *API) StartCodespace(ctx context.Context, codespaceName string) error {
306320
req, err := http.NewRequest(
307321
http.MethodPost,
308-
a.githubAPI+"/vscs_internal/proxy/environments/"+codespace.GUID+"/start",
322+
a.githubAPI+"/user/codespaces/"+codespaceName+"/start",
309323
nil,
310324
)
311325
if err != nil {
312326
return fmt.Errorf("error creating request: %w", err)
313327
}
314328

315-
// TODO: use a.setHeaders()
316-
req.Header.Set("Authorization", "Bearer "+token)
317-
resp, err := a.do(ctx, req, "/vscs_internal/proxy/environments/*/start")
329+
a.setHeaders(req)
330+
resp, err := a.do(ctx, req, "/user/codespaces/*/start")
318331
if err != nil {
319332
return fmt.Errorf("error making request: %w", err)
320333
}
@@ -326,19 +339,20 @@ func (a *API) StartCodespace(ctx context.Context, token string, codespace *Codes
326339
}
327340

328341
if resp.StatusCode != http.StatusOK {
342+
if resp.StatusCode == http.StatusConflict {
343+
// 409 means the codespace is already running which we can safely ignore
344+
return nil
345+
}
346+
329347
// Error response may be a numeric code or a JSON {"message": "..."}.
330348
if bytes.HasPrefix(b, []byte("{")) {
331349
return jsonErrorResponse(b) // probably JSON
332350
}
351+
333352
if len(b) > 100 {
334353
b = append(b[:97], "..."...)
335354
}
336-
if strings.TrimSpace(string(b)) == "7" {
337-
// Non-HTTP 200 with error code 7 (EnvironmentNotShutdown) is benign.
338-
// Ignore it.
339-
} else {
340-
return fmt.Errorf("failed to start codespace: %s", b)
341-
}
355+
return fmt.Errorf("failed to start codespace: %s", b)
342356
}
343357

344358
return nil
@@ -348,6 +362,7 @@ type getCodespaceRegionLocationResponse struct {
348362
Current string `json:"current"`
349363
}
350364

365+
// GetCodespaceRegionLocation returns the closest codespace location for the user.
351366
func (a *API) GetCodespaceRegionLocation(ctx context.Context) (string, error) {
352367
req, err := http.NewRequest(http.MethodGet, "https://online.visualstudio.com/api/v1/locations", nil)
353368
if err != nil {
@@ -382,6 +397,7 @@ type SKU struct {
382397
DisplayName string `json:"display_name"`
383398
}
384399

400+
// GetCodespacesSKUs returns the available SKUs for the user for a given repo, branch and location.
385401
func (a *API) GetCodespacesSKUs(ctx context.Context, user *User, repository *Repository, branch, location string) ([]*SKU, error) {
386402
req, err := http.NewRequest(http.MethodGet, a.githubAPI+"/vscs_internal/user/"+user.Login+"/skus", nil)
387403
if err != nil {
@@ -520,6 +536,7 @@ func (a *API) startCreate(ctx context.Context, repoID int, machine, branch, loca
520536
return &response, nil
521537
}
522538

539+
// DeleteCodespace deletes the given codespace.
523540
func (a *API) DeleteCodespace(ctx context.Context, codespaceName string) error {
524541
req, err := http.NewRequest(http.MethodDelete, a.githubAPI+"/user/codespaces/"+codespaceName, nil)
525542
if err != nil {
@@ -616,6 +633,8 @@ func (a *API) AuthorizedKeys(ctx context.Context, user string) ([]byte, error) {
616633
return b, nil
617634
}
618635

636+
// do executes the given request and returns the response. It creates an
637+
// opentracing span to track the length of the request.
619638
func (a *API) do(ctx context.Context, req *http.Request, spanName string) (*http.Response, error) {
620639
// TODO(adonovan): use NewRequestWithContext(ctx) and drop ctx parameter.
621640
span, ctx := opentracing.StartSpanFromContext(ctx, spanName)
@@ -624,6 +643,7 @@ func (a *API) do(ctx context.Context, req *http.Request, spanName string) (*http
624643
return a.client.Do(req)
625644
}
626645

646+
// setHeaders sets the required headers for the API.
627647
func (a *API) setHeaders(req *http.Request) {
628648
if a.token != "" {
629649
req.Header.Set("Authorization", "Bearer "+a.token)

internal/codespaces/codespaces.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func connectionReady(codespace *api.Codespace) bool {
2626
type apiClient interface {
2727
GetCodespace(ctx context.Context, token, user, name string) (*api.Codespace, error)
2828
GetCodespaceToken(ctx context.Context, user, codespace string) (string, error)
29-
StartCodespace(ctx context.Context, token string, codespace *api.Codespace) error
29+
StartCodespace(ctx context.Context, name string) error
3030
}
3131

3232
// ConnectToLiveshare waits for a Codespace to become running,
@@ -36,7 +36,7 @@ func ConnectToLiveshare(ctx context.Context, log logger, apiClient apiClient, us
3636
if codespace.Environment.State != api.CodespaceEnvironmentStateAvailable {
3737
startedCodespace = true
3838
log.Print("Starting your codespace...")
39-
if err := apiClient.StartCodespace(ctx, token, codespace); err != nil {
39+
if err := apiClient.StartCodespace(ctx, codespace.Name); err != nil {
4040
return nil, fmt.Errorf("error starting codespace: %w", err)
4141
}
4242
}

pkg/cmd/codespace/common.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type apiClient interface {
3737
GetCodespace(ctx context.Context, token, user, name string) (*api.Codespace, error)
3838
ListCodespaces(ctx context.Context) ([]*api.Codespace, error)
3939
DeleteCodespace(ctx context.Context, name string) error
40-
StartCodespace(ctx context.Context, token string, codespace *api.Codespace) error
40+
StartCodespace(ctx context.Context, name string) error
4141
CreateCodespace(ctx context.Context, params *api.CreateCodespaceParams) (*api.Codespace, error)
4242
GetRepository(ctx context.Context, nwo string) (*api.Repository, error)
4343
AuthorizedKeys(ctx context.Context, user string) ([]byte, error)

pkg/cmd/codespace/mock_api.go

Lines changed: 14 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)