@@ -43,6 +43,7 @@ import (
4343
4444const githubAPI = "https://api.github.com"
4545
46+ // API is the interface to the codespace service.
4647type 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.
5658func 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.
6467type User struct {
6568 Login string `json:"login"`
6669}
6770
71+ // GetUser returns the user associated with the given token.
6872func (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.
98103func 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.
109115type Repository struct {
110116 ID int `json:"id"`
111117}
112118
119+ // GetRepository returns the repository associated with the given owner and name.
113120func (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.
143151type 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
170177const (
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.
182191func (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.
215225type 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.
225235var ErrNotProvisioned = errors .New ("codespace not provisioned" )
226236
237+ // GetCodespaceToken returns a codespace token for the user.
227238func (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.
270282func (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.
351366func (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.
385401func (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.
523540func (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.
619638func (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.
627647func (a * API ) setHeaders (req * http.Request ) {
628648 if a .token != "" {
629649 req .Header .Set ("Authorization" , "Bearer " + a .token )
0 commit comments