Skip to content

Commit 017632d

Browse files
committed
Merge branch 'trunk' of github.com:cli/cli into jg/choose-codespace-prompt
2 parents 7fe8357 + 52e16df commit 017632d

File tree

12 files changed

+71
-244
lines changed

12 files changed

+71
-244
lines changed

internal/codespaces/api/api.go

Lines changed: 22 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -180,77 +180,28 @@ func (a *API) ListCodespaces(ctx context.Context) ([]*codespace.Codespace, error
180180
return response.Codespaces, nil
181181
}
182182

183-
// getCodespaceTokenRequest is the request body for the get codespace token endpoint.
184-
type getCodespaceTokenRequest struct {
185-
MintRepositoryToken bool `json:"mint_repository_token"`
186-
}
187-
188-
type getCodespaceTokenResponse struct {
189-
RepositoryToken string `json:"repository_token"`
190-
}
191-
192-
// ErrNotProvisioned is returned by GetCodespacesToken to indicate that the
193-
// creation of a codespace is not yet complete and that the caller should try again.
194-
var ErrNotProvisioned = errors.New("codespace not provisioned")
195-
196-
// GetCodespaceToken returns a codespace token for the user.
197-
func (a *API) GetCodespaceToken(ctx context.Context, ownerLogin, codespaceName string) (string, error) {
198-
reqBody, err := json.Marshal(getCodespaceTokenRequest{true})
199-
if err != nil {
200-
return "", fmt.Errorf("error preparing request body: %w", err)
201-
}
202-
203-
req, err := http.NewRequest(
204-
http.MethodPost,
205-
a.githubAPI+"/vscs_internal/user/"+ownerLogin+"/codespaces/"+codespaceName+"/token",
206-
bytes.NewBuffer(reqBody),
207-
)
208-
if err != nil {
209-
return "", fmt.Errorf("error creating request: %w", err)
210-
}
211-
212-
a.setHeaders(req)
213-
resp, err := a.do(ctx, req, "/vscs_internal/user/*/codespaces/*/token")
214-
if err != nil {
215-
return "", fmt.Errorf("error making request: %w", err)
216-
}
217-
defer resp.Body.Close()
218-
219-
b, err := ioutil.ReadAll(resp.Body)
220-
if err != nil {
221-
return "", fmt.Errorf("error reading response body: %w", err)
222-
}
223-
224-
if resp.StatusCode != http.StatusOK {
225-
if resp.StatusCode == http.StatusUnprocessableEntity {
226-
return "", ErrNotProvisioned
227-
}
228-
229-
return "", jsonErrorResponse(b)
230-
}
231-
232-
var response getCodespaceTokenResponse
233-
if err := json.Unmarshal(b, &response); err != nil {
234-
return "", fmt.Errorf("error unmarshaling response: %w", err)
235-
}
236-
237-
return response.RepositoryToken, nil
238-
}
239-
240-
// GetCodespace returns a codespace for the user.
241-
func (a *API) GetCodespace(ctx context.Context, token, owner, codespaceName string) (*codespace.Codespace, error) {
183+
// GetCodespace returns the user codespace based on the provided name.
184+
// If the codespace is not found, an error is returned.
185+
// If includeConnection is true, it will return the connection information for the codespace.
186+
func (a *API) GetCodespace(ctx context.Context, codespaceName string, includeConnection bool) (*codespace.Codespace, error) {
242187
req, err := http.NewRequest(
243188
http.MethodGet,
244-
a.githubAPI+"/vscs_internal/user/"+owner+"/codespaces/"+codespaceName,
189+
a.githubAPI+"/user/codespaces/"+codespaceName,
245190
nil,
246191
)
247192
if err != nil {
248193
return nil, fmt.Errorf("error creating request: %w", err)
249194
}
250195

251-
// TODO: use a.setHeaders()
252-
req.Header.Set("Authorization", "Bearer "+token)
253-
resp, err := a.do(ctx, req, "/vscs_internal/user/*/codespaces/*")
196+
if includeConnection {
197+
q := req.URL.Query()
198+
q.Add("internal", "true")
199+
q.Add("refresh", "true")
200+
req.URL.RawQuery = q.Encode()
201+
}
202+
203+
a.setHeaders(req)
204+
resp, err := a.do(ctx, req, "/user/codespaces/*")
254205
if err != nil {
255206
return nil, fmt.Errorf("error making request: %w", err)
256207
}
@@ -397,17 +348,16 @@ func (a *API) GetCodespacesMachines(ctx context.Context, repoID int, branch, loc
397348

398349
// CreateCodespaceParams are the required parameters for provisioning a Codespace.
399350
type CreateCodespaceParams struct {
400-
User string
401351
RepositoryID int
402352
Branch, Machine, Location string
403353
}
404354

405355
// CreateCodespace creates a codespace with the given parameters and returns a non-nil error if it
406356
// fails to create.
407357
func (a *API) CreateCodespace(ctx context.Context, params *CreateCodespaceParams) (*codespace.Codespace, error) {
408-
codespace, err := a.startCreate(ctx, params.RepositoryID, params.Machine, params.Branch, params.Location)
358+
cs, err := a.startCreate(ctx, params.RepositoryID, params.Machine, params.Branch, params.Location)
409359
if err != errProvisioningInProgress {
410-
return codespace, err
360+
return nil, err
411361
}
412362

413363
// errProvisioningInProgress indicates that codespace creation did not complete
@@ -424,22 +374,17 @@ func (a *API) CreateCodespace(ctx context.Context, params *CreateCodespaceParams
424374
case <-ctx.Done():
425375
return nil, ctx.Err()
426376
case <-ticker.C:
427-
token, err := a.GetCodespaceToken(ctx, params.User, codespace.Name)
377+
cs, err = a.GetCodespace(ctx, cs.Name, false)
428378
if err != nil {
429-
if err == ErrNotProvisioned {
430-
// Do nothing. We expect this to fail until the codespace is provisioned
431-
continue
432-
}
433-
434-
return nil, fmt.Errorf("failed to get codespace token: %w", err)
379+
return nil, fmt.Errorf("failed to get codespace: %w", err)
435380
}
436381

437-
codespace, err = a.GetCodespace(ctx, token, params.User, codespace.Name)
438-
if err != nil {
439-
return nil, fmt.Errorf("failed to get codespace: %w", err)
382+
// we continue to poll until the codespace shows as provisioned
383+
if cs.State != codespace.StateProvisioned {
384+
continue
440385
}
441386

442-
return codespace, nil
387+
return cs, nil
443388
}
444389
}
445390
}

internal/codespaces/codespace/codespace.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type Codespace struct {
66
Name string `json:"name"`
77
CreatedAt string `json:"created_at"`
88
LastUsedAt string `json:"last_used_at"`
9+
State string `json:"state"`
910
GUID string `json:"guid"`
1011
Branch string `json:"branch"`
1112
RepositoryName string `json:"repository_name"`
@@ -14,6 +15,8 @@ type Codespace struct {
1415
Environment Environment `json:"environment"`
1516
}
1617

18+
const StateProvisioned = "provisioned"
19+
1720
// DisplayName returns the repository nwo and branch.
1821
// If includeName is true, the name of the codespace is included.
1922
// If includeGitStatus is true, the branch will include a star if

internal/codespaces/codespaces.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,13 @@ func connectionReady(cs *codespace.Codespace) bool {
2424
}
2525

2626
type apiClient interface {
27-
GetCodespace(ctx context.Context, token, user, name string) (*codespace.Codespace, error)
28-
GetCodespaceToken(ctx context.Context, user, codespace string) (string, error)
27+
GetCodespace(ctx context.Context, name string, includeConnection bool) (*codespace.Codespace, error)
2928
StartCodespace(ctx context.Context, name string) error
3029
}
3130

3231
// ConnectToLiveshare waits for a Codespace to become running,
3332
// and connects to it using a Live Share session.
34-
func ConnectToLiveshare(ctx context.Context, log logger, apiClient apiClient, userLogin, token string, cs *codespace.Codespace) (*liveshare.Session, error) {
33+
func ConnectToLiveshare(ctx context.Context, log logger, apiClient apiClient, cs *codespace.Codespace) (*liveshare.Session, error) {
3534
var startedCodespace bool
3635
if cs.Environment.State != codespace.EnvironmentStateAvailable {
3736
startedCodespace = true
@@ -55,7 +54,7 @@ func ConnectToLiveshare(ctx context.Context, log logger, apiClient apiClient, us
5554
}
5655

5756
var err error
58-
cs, err = apiClient.GetCodespace(ctx, token, userLogin, cs.Name)
57+
cs, err = apiClient.GetCodespace(ctx, cs.Name, true)
5958
if err != nil {
6059
return nil, fmt.Errorf("error getting codespace: %w", err)
6160
}

internal/codespaces/states.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"strings"
1010
"time"
1111

12-
"github.com/cli/cli/v2/internal/codespaces/api"
1312
"github.com/cli/cli/v2/internal/codespaces/codespace"
1413
"github.com/cli/cli/v2/pkg/liveshare"
1514
)
@@ -37,13 +36,8 @@ type PostCreateState struct {
3736
// PollPostCreateStates watches for state changes in a codespace,
3837
// and calls the supplied poller for each batch of state changes.
3938
// It runs until it encounters an error, including cancellation of the context.
40-
func PollPostCreateStates(ctx context.Context, log logger, apiClient apiClient, user *api.User, cs *codespace.Codespace, poller func([]PostCreateState)) (err error) {
41-
token, err := apiClient.GetCodespaceToken(ctx, user.Login, cs.Name)
42-
if err != nil {
43-
return fmt.Errorf("getting codespace token: %w", err)
44-
}
45-
46-
session, err := ConnectToLiveshare(ctx, log, apiClient, user.Login, token, cs)
39+
func PollPostCreateStates(ctx context.Context, log logger, apiClient apiClient, cs *codespace.Codespace, poller func([]PostCreateState)) (err error) {
40+
session, err := ConnectToLiveshare(ctx, log, apiClient, cs)
4741
if err != nil {
4842
return fmt.Errorf("connect to Live Share: %w", err)
4943
}

pkg/cmd/codespace/common.go

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ func NewApp(logger *output.Logger, apiClient apiClient) *App {
3535
//go:generate moq -fmt goimports -rm -skip-ensure -out mock_api.go . apiClient
3636
type apiClient interface {
3737
GetUser(ctx context.Context) (*api.User, error)
38-
GetCodespaceToken(ctx context.Context, user, name string) (string, error)
39-
GetCodespace(ctx context.Context, token, user, name string) (*codespace.Codespace, error)
38+
GetCodespace(ctx context.Context, name string, includeConnection bool) (*codespace.Codespace, error)
4039
ListCodespaces(ctx context.Context) ([]*codespace.Codespace, error)
4140
DeleteCodespace(ctx context.Context, name string) error
4241
StartCodespace(ctx context.Context, name string) error
@@ -136,35 +135,24 @@ func chooseCodespaceFromList(ctx context.Context, codespaces []*codespace.Codesp
136135
}
137136

138137
// getOrChooseCodespace prompts the user to choose a codespace if the codespaceName is empty.
139-
// It then fetches the codespace token and the codespace record.
140-
func getOrChooseCodespace(ctx context.Context, apiClient apiClient, user *api.User, codespaceName string) (codespace *codespace.Codespace, token string, err error) {
138+
// It then fetches the codespace record with full connection details.
139+
func getOrChooseCodespace(ctx context.Context, apiClient apiClient, codespaceName string) (cs *codespace.Codespace, err error) {
141140
if codespaceName == "" {
142-
codespace, err = chooseCodespace(ctx, apiClient)
141+
cs, err = chooseCodespace(ctx, apiClient)
143142
if err != nil {
144143
if err == errNoCodespaces {
145-
return nil, "", err
144+
return nil, err
146145
}
147-
return nil, "", fmt.Errorf("choosing codespace: %w", err)
148-
}
149-
codespaceName = codespace.Name
150-
151-
token, err = apiClient.GetCodespaceToken(ctx, user.Login, codespaceName)
152-
if err != nil {
153-
return nil, "", fmt.Errorf("getting codespace token: %w", err)
146+
return nil, fmt.Errorf("choosing codespace: %w", err)
154147
}
155148
} else {
156-
token, err = apiClient.GetCodespaceToken(ctx, user.Login, codespaceName)
157-
if err != nil {
158-
return nil, "", fmt.Errorf("getting codespace token for given codespace: %w", err)
159-
}
160-
161-
codespace, err = apiClient.GetCodespace(ctx, token, user.Login, codespaceName)
149+
cs, err = apiClient.GetCodespace(ctx, codespaceName, true)
162150
if err != nil {
163-
return nil, "", fmt.Errorf("getting full codespace details: %w", err)
151+
return nil, fmt.Errorf("getting full codespace details: %w", err)
164152
}
165153
}
166154

167-
return codespace, token, nil
155+
return cs, nil
168156
}
169157

170158
func safeClose(closer io.Closer, err *error) {

pkg/cmd/codespace/create.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ func (a *App) Create(ctx context.Context, opts createOptions) error {
8282

8383
a.logger.Print("Creating your codespace...")
8484
codespace, err := a.apiClient.CreateCodespace(ctx, &api.CreateCodespaceParams{
85-
User: userResult.User.Login,
8685
RepositoryID: repository.ID,
8786
Branch: branch,
8887
Machine: machine,
@@ -158,7 +157,7 @@ func showStatus(ctx context.Context, log *output.Logger, apiClient apiClient, us
158157
}
159158
}
160159

161-
err := codespaces.PollPostCreateStates(ctx, log, apiClient, user, cs, poller)
160+
err := codespaces.PollPostCreateStates(ctx, log, apiClient, cs, poller)
162161
if err != nil {
163162
if errors.Is(err, context.Canceled) && breakNextState {
164163
return nil // we cancelled the context to stop polling, we can ignore the error

pkg/cmd/codespace/delete.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,7 @@ func newDeleteCmd(app *App) *cobra.Command {
5858
return deleteCmd
5959
}
6060

61-
func (a *App) Delete(ctx context.Context, opts deleteOptions) error {
62-
user, err := a.apiClient.GetUser(ctx)
63-
if err != nil {
64-
return fmt.Errorf("error getting user: %w", err)
65-
}
66-
61+
func (a *App) Delete(ctx context.Context, opts deleteOptions) (err error) {
6762
var codespaces []*codespace.Codespace
6863
nameFilter := opts.codespaceName
6964
if nameFilter == "" {
@@ -80,12 +75,7 @@ func (a *App) Delete(ctx context.Context, opts deleteOptions) error {
8075
nameFilter = c.Name
8176
}
8277
} else {
83-
token, err := a.apiClient.GetCodespaceToken(ctx, user.Login, nameFilter)
84-
if err != nil {
85-
return fmt.Errorf("error getting codespace token: %w", err)
86-
}
87-
88-
cs, err := a.apiClient.GetCodespace(ctx, token, user.Login, nameFilter)
78+
cs, err := a.apiClient.GetCodespace(ctx, nameFilter, false)
8979
if err != nil {
9080
return fmt.Errorf("error fetching codespace information: %w", err)
9181
}

pkg/cmd/codespace/delete_test.go

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -169,19 +169,7 @@ func TestDelete(t *testing.T) {
169169
return tt.codespaces, nil
170170
}
171171
} else {
172-
apiMock.GetCodespaceTokenFunc = func(_ context.Context, userLogin, name string) (string, error) {
173-
if userLogin != user.Login {
174-
return "", fmt.Errorf("unexpected user %q", userLogin)
175-
}
176-
return "CS_TOKEN", nil
177-
}
178-
apiMock.GetCodespaceFunc = func(_ context.Context, token, userLogin, name string) (*codespace.Codespace, error) {
179-
if userLogin != user.Login {
180-
return nil, fmt.Errorf("unexpected user %q", userLogin)
181-
}
182-
if token != "CS_TOKEN" {
183-
return nil, fmt.Errorf("unexpected token %q", token)
184-
}
172+
apiMock.GetCodespaceFunc = func(_ context.Context, name string, includeConnection bool) (*codespace.Codespace, error) {
185173
return tt.codespaces[0], nil
186174
}
187175
}
@@ -207,9 +195,6 @@ func TestDelete(t *testing.T) {
207195
if (err != nil) != tt.wantErr {
208196
t.Errorf("delete() error = %v, wantErr %v", err, tt.wantErr)
209197
}
210-
if n := len(apiMock.GetUserCalls()); n != 1 {
211-
t.Errorf("GetUser invoked %d times, expected %d", n, 1)
212-
}
213198
var gotDeleted []string
214199
for _, delArgs := range apiMock.DeleteCodespaceCalls() {
215200
gotDeleted = append(gotDeleted, delArgs.Name)

pkg/cmd/codespace/logs.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ func (a *App) Logs(ctx context.Context, codespaceName string, follow bool) (err
4646
authkeys <- checkAuthorizedKeys(ctx, a.apiClient, user.Login)
4747
}()
4848

49-
codespace, token, err := getOrChooseCodespace(ctx, a.apiClient, user, codespaceName)
49+
codespace, err := getOrChooseCodespace(ctx, a.apiClient, codespaceName)
5050
if err != nil {
5151
return fmt.Errorf("get or choose codespace: %w", err)
5252
}
5353

54-
session, err := codespaces.ConnectToLiveshare(ctx, a.logger, a.apiClient, user.Login, token, codespace)
54+
session, err := codespaces.ConnectToLiveshare(ctx, a.logger, a.apiClient, codespace)
5555
if err != nil {
5656
return fmt.Errorf("connecting to Live Share: %w", err)
5757
}

0 commit comments

Comments
 (0)