Merge remote-tracking branch 'origin' into raffo/delete-codespaces
This commit is contained in:
commit
1232dba684
4 changed files with 100 additions and 26 deletions
|
|
@ -81,9 +81,15 @@ func create(opts *createOptions) error {
|
|||
return errors.New("there are no available machine types for this repository")
|
||||
}
|
||||
|
||||
log.Println("Creating your codespace...")
|
||||
|
||||
codespace, err := apiClient.CreateCodespace(ctx, userResult.User, repository, machine, branch, locationResult.Location)
|
||||
log.Print("Creating your codespace...")
|
||||
codespace, err := apiClient.CreateCodespace(ctx, log, &api.CreateCodespaceParams{
|
||||
User: userResult.User.Login,
|
||||
RepositoryID: repository.ID,
|
||||
Branch: branch,
|
||||
Machine: machine,
|
||||
Location: locationResult.Location,
|
||||
})
|
||||
log.Print("\n")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating codespace: %w", err)
|
||||
}
|
||||
|
|
@ -172,7 +178,7 @@ type getUserResult struct {
|
|||
|
||||
// getUser fetches the user record associated with the GITHUB_TOKEN
|
||||
func getUser(ctx context.Context, apiClient *api.API) <-chan getUserResult {
|
||||
ch := make(chan getUserResult)
|
||||
ch := make(chan getUserResult, 1)
|
||||
go func() {
|
||||
user, err := apiClient.GetUser(ctx)
|
||||
ch <- getUserResult{user, err}
|
||||
|
|
@ -187,7 +193,7 @@ type locationResult struct {
|
|||
|
||||
// getLocation fetches the closest Codespace datacenter region/location to the user.
|
||||
func getLocation(ctx context.Context, apiClient *api.API) <-chan locationResult {
|
||||
ch := make(chan locationResult)
|
||||
ch := make(chan locationResult, 1)
|
||||
go func() {
|
||||
location, err := apiClient.GetCodespaceRegionLocation(ctx)
|
||||
ch <- locationResult{location, err}
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ type portAttribute struct {
|
|||
}
|
||||
|
||||
func getDevContainer(ctx context.Context, apiClient *api.API, codespace *api.Codespace) <-chan devContainerResult {
|
||||
ch := make(chan devContainerResult)
|
||||
ch := make(chan devContainerResult, 1)
|
||||
go func() {
|
||||
contents, err := apiClient.GetCodespaceRepositoryContents(ctx, codespace, ".devcontainer/devcontainer.json")
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
|
@ -214,6 +215,10 @@ type getCodespaceTokenResponse struct {
|
|||
RepositoryToken string `json:"repository_token"`
|
||||
}
|
||||
|
||||
// ErrNotProvisioned is returned by GetCodespacesToken to indicate that the
|
||||
// creation of a codespace is not yet complete and that the caller should try again.
|
||||
var ErrNotProvisioned = errors.New("codespace not provisioned")
|
||||
|
||||
func (a *API) GetCodespaceToken(ctx context.Context, ownerLogin, codespaceName string) (string, error) {
|
||||
reqBody, err := json.Marshal(getCodespaceTokenRequest{true})
|
||||
if err != nil {
|
||||
|
|
@ -242,6 +247,10 @@ func (a *API) GetCodespaceToken(ctx context.Context, ownerLogin, codespaceName s
|
|||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.StatusCode == http.StatusUnprocessableEntity {
|
||||
return "", ErrNotProvisioned
|
||||
}
|
||||
|
||||
return "", jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
|
|
@ -401,20 +410,83 @@ func (a *API) GetCodespacesSKUs(ctx context.Context, user *User, repository *Rep
|
|||
return response.SKUs, nil
|
||||
}
|
||||
|
||||
type createCodespaceRequest struct {
|
||||
// CreateCodespaceParams are the required parameters for provisioning a Codespace.
|
||||
type CreateCodespaceParams struct {
|
||||
User string
|
||||
RepositoryID int
|
||||
Branch, Machine, Location string
|
||||
}
|
||||
|
||||
type logger interface {
|
||||
Print(v ...interface{}) (int, error)
|
||||
Println(v ...interface{}) (int, error)
|
||||
}
|
||||
|
||||
// CreateCodespace creates a codespace with the given parameters and returns a non-nil error if it
|
||||
// fails to create.
|
||||
func (a *API) CreateCodespace(ctx context.Context, log logger, params *CreateCodespaceParams) (*Codespace, error) {
|
||||
codespace, err := a.startCreate(
|
||||
ctx, params.User, params.RepositoryID, params.Machine, params.Branch, params.Location,
|
||||
)
|
||||
if err != errProvisioningInProgress {
|
||||
return codespace, err
|
||||
}
|
||||
|
||||
// errProvisioningInProgress indicates that codespace creation did not complete
|
||||
// within the GitHub API RPC time limit (10s), so it continues asynchronously.
|
||||
// We must poll the server to discover the outcome.
|
||||
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-ticker.C:
|
||||
log.Print(".")
|
||||
token, err := a.GetCodespaceToken(ctx, params.User, codespace.Name)
|
||||
if err != nil {
|
||||
if err == ErrNotProvisioned {
|
||||
// Do nothing. We expect this to fail until the codespace is provisioned
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to get codespace token: %w", err)
|
||||
}
|
||||
|
||||
codespace, err = a.GetCodespace(ctx, token, params.User, codespace.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get codespace: %w", err)
|
||||
}
|
||||
|
||||
return codespace, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type startCreateRequest struct {
|
||||
RepositoryID int `json:"repository_id"`
|
||||
Ref string `json:"ref"`
|
||||
Location string `json:"location"`
|
||||
SkuName string `json:"sku_name"`
|
||||
}
|
||||
|
||||
func (a *API) CreateCodespace(ctx context.Context, user *User, repository *Repository, sku, branch, location string) (*Codespace, error) {
|
||||
requestBody, err := json.Marshal(createCodespaceRequest{repository.ID, branch, location, sku})
|
||||
var errProvisioningInProgress = errors.New("provisioning in progress")
|
||||
|
||||
// startCreate starts the creation of a codespace.
|
||||
// It may return success or an error, or errProvisioningInProgress indicating that the operation
|
||||
// did not complete before the GitHub API's time limit for RPCs (10s), in which case the caller
|
||||
// must poll the server to learn the outcome.
|
||||
func (a *API) startCreate(ctx context.Context, user string, repository int, sku, branch, location string) (*Codespace, error) {
|
||||
requestBody, err := json.Marshal(startCreateRequest{repository, branch, location, sku})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, a.githubAPI+"/vscs_internal/user/"+user.Login+"/codespaces", bytes.NewBuffer(requestBody))
|
||||
req, err := http.NewRequest(http.MethodPost, a.githubAPI+"/vscs_internal/user/"+user+"/codespaces", bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating request: %w", err)
|
||||
}
|
||||
|
|
@ -431,8 +503,11 @@ func (a *API) CreateCodespace(ctx context.Context, user *User, repository *Repos
|
|||
return nil, fmt.Errorf("error reading response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode > http.StatusAccepted {
|
||||
switch {
|
||||
case resp.StatusCode > http.StatusAccepted:
|
||||
return nil, jsonErrorResponse(b)
|
||||
case resp.StatusCode == http.StatusAccepted:
|
||||
return nil, errProvisioningInProgress // RPC finished before result of creation known
|
||||
}
|
||||
|
||||
var response Codespace
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ func connectionReady(codespace *api.Codespace) bool {
|
|||
codespace.Environment.State == api.CodespaceEnvironmentStateAvailable
|
||||
}
|
||||
|
||||
// ConnectToLiveshare creates a Live Share client and joins the Live Share session.
|
||||
// It will start the Codespace if it is not already running, it will time out after 60 seconds if fails to start.
|
||||
// ConnectToLiveshare waits for a Codespace to become running,
|
||||
// and connects to it using a Live Share session.
|
||||
func ConnectToLiveshare(ctx context.Context, log logger, apiClient *api.API, userLogin, token string, codespace *api.Codespace) (*liveshare.Session, error) {
|
||||
var startedCodespace bool
|
||||
if codespace.Environment.State != api.CodespaceEnvironmentStateAvailable {
|
||||
|
|
@ -61,17 +61,10 @@ func ConnectToLiveshare(ctx context.Context, log logger, apiClient *api.API, use
|
|||
|
||||
log.Println("Connecting to your codespace...")
|
||||
|
||||
lsclient, err := liveshare.NewClient(
|
||||
liveshare.WithConnection(liveshare.Connection{
|
||||
SessionID: codespace.Environment.Connection.SessionID,
|
||||
SessionToken: codespace.Environment.Connection.SessionToken,
|
||||
RelaySAS: codespace.Environment.Connection.RelaySAS,
|
||||
RelayEndpoint: codespace.Environment.Connection.RelayEndpoint,
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating Live Share client: %w", err)
|
||||
}
|
||||
|
||||
return lsclient.JoinWorkspace(ctx)
|
||||
return liveshare.Connect(ctx, liveshare.Options{
|
||||
SessionID: codespace.Environment.Connection.SessionID,
|
||||
SessionToken: codespace.Environment.Connection.SessionToken,
|
||||
RelaySAS: codespace.Environment.Connection.RelaySAS,
|
||||
RelayEndpoint: codespace.Environment.Connection.RelayEndpoint,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue