@@ -36,6 +36,7 @@ import (
3636 "net/http"
3737 "strconv"
3838 "strings"
39+ "time"
3940
4041 "github.com/opentracing/opentracing-go"
4142)
@@ -402,16 +403,91 @@ func (a *API) GetCodespacesSKUs(ctx context.Context, user *User, repository *Rep
402403 return response .SKUs , nil
403404}
404405
406+ // ProvisionCodespaceParams are the required parameters for provisioning a Codespace.
407+ type ProvisionCodespaceParams struct {
408+ User * User
409+ Repository * Repository
410+ Branch , Machine , Location string
411+ }
412+
413+ type logger interface {
414+ Print (v ... interface {}) (int , error )
415+ Println (v ... interface {}) (int , error )
416+ }
417+
418+ // ProvisionCodespace creates a codespace with the given parameters and handles polling in the case
419+ // of initial creation failures.
420+ func (a * API ) ProvisionCodespace (ctx context.Context , log logger , params * ProvisionCodespaceParams ) (* Codespace , error ) {
421+ codespace , err := a .createCodespace (
422+ ctx , params .User , params .Repository , params .Machine , params .Branch , params .Location ,
423+ )
424+ if err != nil {
425+ // This error is returned by the API when the initial creation fails with a retryable error.
426+ // A retryable error means that GitHub will retry to re-create Codespace and clients should poll
427+ // the API and attempt to fetch the Codespace for the next two minutes.
428+ if err == errProvisioningInProgress {
429+ pollTimeout := 2 * time .Minute
430+ pollInterval := 1 * time .Second
431+ log .Print ("." )
432+ codespace , err = pollForCodespace (ctx , a , log , pollTimeout , pollInterval , params .User .Login , codespace .Name )
433+ log .Print ("\n " )
434+
435+ if err != nil {
436+ return nil , fmt .Errorf ("error creating codespace with async provisioning: %s: %w" , codespace .Name , err )
437+ }
438+ }
439+
440+ return nil , err
441+ }
442+
443+ return codespace , nil
444+ }
445+
446+ type apiClient interface {
447+ GetCodespaceToken (ctx context.Context , userLogin , codespaceName string ) (string , error )
448+ GetCodespace (ctx context.Context , token , userLogin , codespaceName string ) (* Codespace , error )
449+ }
450+
451+ // pollForCodespace polls the Codespaces GET endpoint on a given interval for a specified duration.
452+ // If it succeeds at fetching the codespace, we consider the codespace provisioned.
453+ func pollForCodespace (ctx context.Context , client apiClient , log logger , duration , interval time.Duration , user , name string ) (* Codespace , error ) {
454+ ctx , cancel := context .WithTimeout (ctx , duration )
455+ defer cancel ()
456+
457+ ticker := time .NewTicker (interval )
458+ defer ticker .Stop ()
459+
460+ for {
461+ select {
462+ case <- ctx .Done ():
463+ return nil , ctx .Err ()
464+ case <- ticker .C :
465+ log .Print ("." )
466+ token , err := client .GetCodespaceToken (ctx , user , name )
467+ if err != nil {
468+ if err == ErrNotProvisioned {
469+ // Do nothing. We expect this to fail until the codespace is provisioned
470+ continue
471+ }
472+
473+ return nil , fmt .Errorf ("failed to get codespace token: %w" , err )
474+ }
475+
476+ return client .GetCodespace (ctx , token , user , name )
477+ }
478+ }
479+ }
480+
405481type createCodespaceRequest struct {
406482 RepositoryID int `json:"repository_id"`
407483 Ref string `json:"ref"`
408484 Location string `json:"location"`
409485 SkuName string `json:"sku_name"`
410486}
411487
412- var ErrProvisioningInProgress = errors .New ("provisioning in progress" )
488+ var errProvisioningInProgress = errors .New ("provisioning in progress" )
413489
414- func (a * API ) CreateCodespace (ctx context.Context , user * User , repository * Repository , sku , branch , location string ) (* Codespace , error ) {
490+ func (a * API ) createCodespace (ctx context.Context , user * User , repository * Repository , sku , branch , location string ) (* Codespace , error ) {
415491 requestBody , err := json .Marshal (createCodespaceRequest {repository .ID , branch , location , sku })
416492 if err != nil {
417493 return nil , fmt .Errorf ("error marshaling request: %w" , err )
@@ -442,7 +518,7 @@ func (a *API) CreateCodespace(ctx context.Context, user *User, repository *Repos
442518 // being retried. For clients this means that they must implement a polling strategy
443519 // to check for the codespace existence for the next two minutes. We return an error
444520 // here so callers can detect and handle this condition.
445- return nil , ErrProvisioningInProgress
521+ return nil , errProvisioningInProgress
446522 }
447523
448524 var response Codespace
0 commit comments