Merge branch 'trunk' of github.com:cli/cli into jg/choose-codespace-prompt

This commit is contained in:
Jose Garcia 2021-10-05 12:57:11 -04:00
commit a8d1718f21
14 changed files with 155 additions and 159 deletions

View file

@ -34,7 +34,6 @@ import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"time"
@ -44,6 +43,7 @@ import (
const githubAPI = "https://api.github.com"
// API is the interface to the codespace service.
type API struct {
token string
client httpClient
@ -54,6 +54,7 @@ type httpClient interface {
Do(req *http.Request) (*http.Response, error)
}
// New creates a new API client with the given token and HTTP client.
func New(token string, httpClient httpClient) *API {
return &API{
token: token,
@ -62,10 +63,12 @@ func New(token string, httpClient httpClient) *API {
}
}
// User represents a GitHub user.
type User struct {
Login string `json:"login"`
}
// GetUser returns the user associated with the given token.
func (a *API) GetUser(ctx context.Context) (*User, error) {
req, err := http.NewRequest(http.MethodGet, a.githubAPI+"/user", nil)
if err != nil {
@ -96,6 +99,7 @@ func (a *API) GetUser(ctx context.Context) (*User, error) {
return &response, nil
}
// jsonErrorResponse returns the error message from a JSON response.
func jsonErrorResponse(b []byte) error {
var response struct {
Message string `json:"message"`
@ -107,10 +111,12 @@ func jsonErrorResponse(b []byte) error {
return errors.New(response.Message)
}
// Repository represents a GitHub repository.
type Repository struct {
ID int `json:"id"`
}
// GetRepository returns the repository associated with the given owner and name.
func (a *API) GetRepository(ctx context.Context, nwo string) (*Repository, error) {
req, err := http.NewRequest(http.MethodGet, a.githubAPI+"/repos/"+strings.ToLower(nwo), nil)
if err != nil {
@ -174,6 +180,7 @@ func (a *API) ListCodespaces(ctx context.Context) ([]*codespace.Codespace, error
return response.Codespaces, nil
}
// getCodespaceTokenRequest is the request body for the get codespace token endpoint.
type getCodespaceTokenRequest struct {
MintRepositoryToken bool `json:"mint_repository_token"`
}
@ -186,6 +193,7 @@ type getCodespaceTokenResponse struct {
// creation of a codespace is not yet complete and that the caller should try again.
var ErrNotProvisioned = errors.New("codespace not provisioned")
// GetCodespaceToken returns a codespace token for the user.
func (a *API) GetCodespaceToken(ctx context.Context, ownerLogin, codespaceName string) (string, error) {
reqBody, err := json.Marshal(getCodespaceTokenRequest{true})
if err != nil {
@ -229,6 +237,7 @@ func (a *API) GetCodespaceToken(ctx context.Context, ownerLogin, codespaceName s
return response.RepositoryToken, nil
}
// GetCodespace returns a codespace for the user.
func (a *API) GetCodespace(ctx context.Context, token, owner, codespaceName string) (*codespace.Codespace, error) {
req, err := http.NewRequest(
http.MethodGet,
@ -264,19 +273,20 @@ func (a *API) GetCodespace(ctx context.Context, token, owner, codespaceName stri
return &response, nil
}
func (a *API) StartCodespace(ctx context.Context, token string, codespace *codespace.Codespace) error {
// StartCodespace starts a codespace for the user.
// If the codespace is already running, the returned error from the API is ignored.
func (a *API) StartCodespace(ctx context.Context, codespaceName string) error {
req, err := http.NewRequest(
http.MethodPost,
a.githubAPI+"/vscs_internal/proxy/environments/"+codespace.GUID+"/start",
a.githubAPI+"/user/codespaces/"+codespaceName+"/start",
nil,
)
if err != nil {
return fmt.Errorf("error creating request: %w", err)
}
// TODO: use a.setHeaders()
req.Header.Set("Authorization", "Bearer "+token)
resp, err := a.do(ctx, req, "/vscs_internal/proxy/environments/*/start")
a.setHeaders(req)
resp, err := a.do(ctx, req, "/user/codespaces/*/start")
if err != nil {
return fmt.Errorf("error making request: %w", err)
}
@ -288,19 +298,20 @@ func (a *API) StartCodespace(ctx context.Context, token string, codespace *codes
}
if resp.StatusCode != http.StatusOK {
if resp.StatusCode == http.StatusConflict {
// 409 means the codespace is already running which we can safely ignore
return nil
}
// Error response may be a numeric code or a JSON {"message": "..."}.
if bytes.HasPrefix(b, []byte("{")) {
return jsonErrorResponse(b) // probably JSON
}
if len(b) > 100 {
b = append(b[:97], "..."...)
}
if strings.TrimSpace(string(b)) == "7" {
// Non-HTTP 200 with error code 7 (EnvironmentNotShutdown) is benign.
// Ignore it.
} else {
return fmt.Errorf("failed to start codespace: %s", b)
}
return fmt.Errorf("failed to start codespace: %s", b)
}
return nil
@ -310,6 +321,7 @@ type getCodespaceRegionLocationResponse struct {
Current string `json:"current"`
}
// GetCodespaceRegionLocation returns the closest codespace location for the user.
func (a *API) GetCodespaceRegionLocation(ctx context.Context) (string, error) {
req, err := http.NewRequest(http.MethodGet, "https://online.visualstudio.com/api/v1/locations", nil)
if err != nil {
@ -339,13 +351,15 @@ func (a *API) GetCodespaceRegionLocation(ctx context.Context) (string, error) {
return response.Current, nil
}
type SKU struct {
type Machine struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
}
func (a *API) GetCodespacesSKUs(ctx context.Context, user *User, repository *Repository, branch, location string) ([]*SKU, error) {
req, err := http.NewRequest(http.MethodGet, a.githubAPI+"/vscs_internal/user/"+user.Login+"/skus", nil)
// GetCodespacesMachines returns the codespaces machines for the given repo, branch and location.
func (a *API) GetCodespacesMachines(ctx context.Context, repoID int, branch, location string) ([]*Machine, error) {
reqURL := fmt.Sprintf("%s/repositories/%d/codespaces/machines", a.githubAPI, repoID)
req, err := http.NewRequest(http.MethodGet, reqURL, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}
@ -353,11 +367,10 @@ func (a *API) GetCodespacesSKUs(ctx context.Context, user *User, repository *Rep
q := req.URL.Query()
q.Add("location", location)
q.Add("ref", branch)
q.Add("repository_id", strconv.Itoa(repository.ID))
req.URL.RawQuery = q.Encode()
a.setHeaders(req)
resp, err := a.do(ctx, req, "/vscs_internal/user/*/skus")
resp, err := a.do(ctx, req, "/repositories/*/codespaces/machines")
if err != nil {
return nil, fmt.Errorf("error making request: %w", err)
}
@ -373,13 +386,13 @@ func (a *API) GetCodespacesSKUs(ctx context.Context, user *User, repository *Rep
}
var response struct {
SKUs []*SKU `json:"skus"`
Machines []*Machine `json:"machines"`
}
if err := json.Unmarshal(b, &response); err != nil {
return nil, fmt.Errorf("error unmarshaling response: %w", err)
}
return response.SKUs, nil
return response.Machines, nil
}
// CreateCodespaceParams are the required parameters for provisioning a Codespace.
@ -482,20 +495,15 @@ func (a *API) startCreate(ctx context.Context, repoID int, machine, branch, loca
return &response, nil
}
func (a *API) DeleteCodespace(ctx context.Context, user string, codespaceName string) error {
token, err := a.GetCodespaceToken(ctx, user, codespaceName)
if err != nil {
return fmt.Errorf("error getting codespace token: %w", err)
}
req, err := http.NewRequest(http.MethodDelete, a.githubAPI+"/vscs_internal/user/"+user+"/codespaces/"+codespaceName, nil)
// DeleteCodespace deletes the given codespace.
func (a *API) DeleteCodespace(ctx context.Context, codespaceName string) error {
req, err := http.NewRequest(http.MethodDelete, a.githubAPI+"/user/codespaces/"+codespaceName, nil)
if err != nil {
return fmt.Errorf("error creating request: %w", err)
}
// TODO: use a.setHeaders()
req.Header.Set("Authorization", "Bearer "+token)
resp, err := a.do(ctx, req, "/vscs_internal/user/*/codespaces/*")
a.setHeaders(req)
resp, err := a.do(ctx, req, "/user/codespaces/*")
if err != nil {
return fmt.Errorf("error making request: %w", err)
}
@ -584,6 +592,8 @@ func (a *API) AuthorizedKeys(ctx context.Context, user string) ([]byte, error) {
return b, nil
}
// do executes the given request and returns the response. It creates an
// opentracing span to track the length of the request.
func (a *API) do(ctx context.Context, req *http.Request, spanName string) (*http.Response, error) {
// TODO(adonovan): use NewRequestWithContext(ctx) and drop ctx parameter.
span, ctx := opentracing.StartSpanFromContext(ctx, spanName)
@ -592,6 +602,7 @@ func (a *API) do(ctx context.Context, req *http.Request, spanName string) (*http
return a.client.Do(req)
}
// setHeaders sets the required headers for the API.
func (a *API) setHeaders(req *http.Request) {
if a.token != "" {
req.Header.Set("Authorization", "Bearer "+a.token)

View file

@ -26,7 +26,7 @@ func connectionReady(cs *codespace.Codespace) bool {
type apiClient interface {
GetCodespace(ctx context.Context, token, user, name string) (*codespace.Codespace, error)
GetCodespaceToken(ctx context.Context, user, codespace string) (string, error)
StartCodespace(ctx context.Context, token string, codespace *codespace.Codespace) error
StartCodespace(ctx context.Context, name string) error
}
// ConnectToLiveshare waits for a Codespace to become running,
@ -36,7 +36,7 @@ func ConnectToLiveshare(ctx context.Context, log logger, apiClient apiClient, us
if cs.Environment.State != codespace.EnvironmentStateAvailable {
startedCodespace = true
log.Print("Starting your codespace...")
if err := apiClient.StartCodespace(ctx, token, cs); err != nil {
if err := apiClient.StartCodespace(ctx, cs.Name); err != nil {
return nil, fmt.Errorf("error starting codespace: %w", err)
}
}

View file

@ -1,4 +1,4 @@
package ghcs
package codespace
import (
"context"

View file

@ -1,6 +1,6 @@
package ghcs
package codespace
// This file defines functions common to the entire ghcs command set.
// This file defines functions common to the entire codespace command set.
import (
"context"
@ -38,13 +38,13 @@ type apiClient interface {
GetCodespaceToken(ctx context.Context, user, name string) (string, error)
GetCodespace(ctx context.Context, token, user, name string) (*codespace.Codespace, error)
ListCodespaces(ctx context.Context) ([]*codespace.Codespace, error)
DeleteCodespace(ctx context.Context, user, name string) error
StartCodespace(ctx context.Context, token string, codespace *codespace.Codespace) error
DeleteCodespace(ctx context.Context, name string) error
StartCodespace(ctx context.Context, name string) error
CreateCodespace(ctx context.Context, params *api.CreateCodespaceParams) (*codespace.Codespace, error)
GetRepository(ctx context.Context, nwo string) (*api.Repository, error)
AuthorizedKeys(ctx context.Context, user string) ([]byte, error)
GetCodespaceRegionLocation(ctx context.Context) (string, error)
GetCodespacesSKUs(ctx context.Context, user *api.User, repository *api.Repository, branch, location string) ([]*api.SKU, error)
GetCodespacesMachines(ctx context.Context, repoID int, branch, location string) ([]*api.Machine, error)
GetCodespaceRepositoryContents(ctx context.Context, codespace *codespace.Codespace, path string) ([]byte, error)
}

View file

@ -1,4 +1,4 @@
package ghcs
package codespace
import (
"context"
@ -72,7 +72,7 @@ func (a *App) Create(ctx context.Context, opts createOptions) error {
return fmt.Errorf("error getting codespace user: %w", userResult.Err)
}
machine, err := getMachineName(ctx, opts.machine, userResult.User, repository, branch, locationResult.Location, a.apiClient)
machine, err := getMachineName(ctx, a.apiClient, repository.ID, opts.machine, branch, locationResult.Location)
if err != nil {
return fmt.Errorf("error getting machine type: %w", err)
}
@ -235,8 +235,8 @@ func getBranchName(branch string) (string, error) {
}
// getMachineName prompts the user to select the machine type, or validates the machine if non-empty.
func getMachineName(ctx context.Context, machine string, user *api.User, repo *api.Repository, branch, location string, apiClient apiClient) (string, error) {
skus, err := apiClient.GetCodespacesSKUs(ctx, user, repo, branch, location)
func getMachineName(ctx context.Context, apiClient apiClient, repoID int, machine, branch, location string) (string, error) {
machines, err := apiClient.GetCodespacesMachines(ctx, repoID, branch, location)
if err != nil {
return "", fmt.Errorf("error requesting machine instance types: %w", err)
}
@ -244,55 +244,55 @@ func getMachineName(ctx context.Context, machine string, user *api.User, repo *a
// if user supplied a machine type, it must be valid
// if no machine type was supplied, we don't error if there are no machine types for the current repo
if machine != "" {
for _, sku := range skus {
if machine == sku.Name {
for _, m := range machines {
if machine == m.Name {
return machine, nil
}
}
availableSKUs := make([]string, len(skus))
for i := 0; i < len(skus); i++ {
availableSKUs[i] = skus[i].Name
availableMachines := make([]string, len(machines))
for i := 0; i < len(machines); i++ {
availableMachines[i] = machines[i].Name
}
return "", fmt.Errorf("there is no such machine for the repository: %s\nAvailable machines: %v", machine, availableSKUs)
} else if len(skus) == 0 {
return "", fmt.Errorf("there is no such machine for the repository: %s\nAvailable machines: %v", machine, availableMachines)
} else if len(machines) == 0 {
return "", nil
}
if len(skus) == 1 {
return skus[0].Name, nil // VS Code does not prompt for SKU if there is only one, this makes us consistent with that behavior
if len(machines) == 1 {
// VS Code does not prompt for machine if there is only one, this makes us consistent with that behavior
return machines[0].Name, nil
}
skuNames := make([]string, 0, len(skus))
skuByName := make(map[string]*api.SKU)
for _, sku := range skus {
nameParts := camelcase.Split(sku.Name)
machineNames := make([]string, 0, len(machines))
machineByName := make(map[string]*api.Machine)
for _, m := range machines {
nameParts := camelcase.Split(m.Name)
machineName := strings.Title(strings.ToLower(nameParts[0]))
skuName := fmt.Sprintf("%s - %s", machineName, sku.DisplayName)
skuNames = append(skuNames, skuName)
skuByName[skuName] = sku
machineName = fmt.Sprintf("%s - %s", machineName, m.DisplayName)
machineNames = append(machineNames, machineName)
machineByName[machineName] = m
}
skuSurvey := []*survey.Question{
machineSurvey := []*survey.Question{
{
Name: "sku",
Name: "machine",
Prompt: &survey.Select{
Message: "Choose Machine Type:",
Options: skuNames,
Default: skuNames[0],
Options: machineNames,
Default: machineNames[0],
},
Validate: survey.Required,
},
}
var skuAnswers struct{ SKU string }
if err := ask(skuSurvey, &skuAnswers); err != nil {
return "", fmt.Errorf("error getting SKU: %w", err)
var machineAnswers struct{ Machine string }
if err := ask(machineSurvey, &machineAnswers); err != nil {
return "", fmt.Errorf("error getting machine: %w", err)
}
sku := skuByName[skuAnswers.SKU]
machine = sku.Name
selectedMachine := machineByName[machineAnswers.Machine]
return machine, nil
return selectedMachine.Name, nil
}

View file

@ -1,4 +1,4 @@
package ghcs
package codespace
import (
"context"
@ -80,7 +80,6 @@ func (a *App) Delete(ctx context.Context, opts deleteOptions) error {
nameFilter = c.Name
}
} else {
// TODO: this token is discarded and then re-requested later in DeleteCodespace
token, err := a.apiClient.GetCodespaceToken(ctx, user.Login, nameFilter)
if err != nil {
return fmt.Errorf("error getting codespace token: %w", err)
@ -132,7 +131,7 @@ func (a *App) Delete(ctx context.Context, opts deleteOptions) error {
for _, c := range codespacesToDelete {
codespaceName := c.Name
g.Go(func() error {
if err := a.apiClient.DeleteCodespace(ctx, user.Login, codespaceName); err != nil {
if err := a.apiClient.DeleteCodespace(ctx, codespaceName); err != nil {
_, _ = a.logger.Errorf("error deleting codespace %q: %v\n", codespaceName, err)
return err
}
@ -143,6 +142,13 @@ func (a *App) Delete(ctx context.Context, opts deleteOptions) error {
if err := g.Wait(); err != nil {
return errors.New("some codespaces failed to delete")
}
noun := "Codespace"
if len(codespacesToDelete) > 1 {
noun = noun + "s"
}
a.logger.Println(noun + " deleted.")
return nil
}

View file

@ -1,4 +1,4 @@
package ghcs
package codespace
import (
"bytes"
@ -157,10 +157,7 @@ func TestDelete(t *testing.T) {
GetUserFunc: func(_ context.Context) (*api.User, error) {
return user, nil
},
DeleteCodespaceFunc: func(_ context.Context, userLogin, name string) error {
if userLogin != user.Login {
return fmt.Errorf("unexpected user %q", userLogin)
}
DeleteCodespaceFunc: func(_ context.Context, name string) error {
if tt.deleteErr != nil {
return tt.deleteErr
}

View file

@ -1,4 +1,4 @@
package ghcs
package codespace
import (
"context"

View file

@ -1,4 +1,4 @@
package ghcs
package codespace
import (
"context"

View file

@ -1,7 +1,7 @@
// Code generated by moq; DO NOT EDIT.
// github.com/matryer/moq
package ghcs
package codespace
import (
"context"
@ -23,7 +23,7 @@ import (
// CreateCodespaceFunc: func(ctx context.Context, params *api.CreateCodespaceParams) (*codespace.Codespace, error) {
// panic("mock out the CreateCodespace method")
// },
// DeleteCodespaceFunc: func(ctx context.Context, user string, name string) error {
// DeleteCodespaceFunc: func(ctx context.Context, name string) error {
// panic("mock out the DeleteCodespace method")
// },
// GetCodespaceFunc: func(ctx context.Context, token string, user string, name string) (*codespace.Codespace, error) {
@ -38,8 +38,8 @@ import (
// GetCodespaceTokenFunc: func(ctx context.Context, user string, name string) (string, error) {
// panic("mock out the GetCodespaceToken method")
// },
// GetCodespacesSKUsFunc: func(ctx context.Context, user *api.User, repository *api.Repository, branch string, location string) ([]*api.SKU, error) {
// panic("mock out the GetCodespacesSKUs method")
// GetCodespacesMachinesFunc: func(ctx context.Context, repoID int, branch string, location string) ([]*api.Machine, error) {
// panic("mock out the GetCodespacesMachines method")
// },
// GetRepositoryFunc: func(ctx context.Context, nwo string) (*api.Repository, error) {
// panic("mock out the GetRepository method")
@ -50,7 +50,7 @@ import (
// ListCodespacesFunc: func(ctx context.Context) ([]*codespace.Codespace, error) {
// panic("mock out the ListCodespaces method")
// },
// StartCodespaceFunc: func(ctx context.Context, token string, codespaceMoqParam *codespace.Codespace) error {
// StartCodespaceFunc: func(ctx context.Context, name string) error {
// panic("mock out the StartCodespace method")
// },
// }
@ -67,7 +67,7 @@ type apiClientMock struct {
CreateCodespaceFunc func(ctx context.Context, params *api.CreateCodespaceParams) (*codespace.Codespace, error)
// DeleteCodespaceFunc mocks the DeleteCodespace method.
DeleteCodespaceFunc func(ctx context.Context, user string, name string) error
DeleteCodespaceFunc func(ctx context.Context, name string) error
// GetCodespaceFunc mocks the GetCodespace method.
GetCodespaceFunc func(ctx context.Context, token string, user string, name string) (*codespace.Codespace, error)
@ -81,8 +81,8 @@ type apiClientMock struct {
// GetCodespaceTokenFunc mocks the GetCodespaceToken method.
GetCodespaceTokenFunc func(ctx context.Context, user string, name string) (string, error)
// GetCodespacesSKUsFunc mocks the GetCodespacesSKUs method.
GetCodespacesSKUsFunc func(ctx context.Context, user *api.User, repository *api.Repository, branch string, location string) ([]*api.SKU, error)
// GetCodespacesMachinesFunc mocks the GetCodespacesMachines method.
GetCodespacesMachinesFunc func(ctx context.Context, repoID int, branch string, location string) ([]*api.Machine, error)
// GetRepositoryFunc mocks the GetRepository method.
GetRepositoryFunc func(ctx context.Context, nwo string) (*api.Repository, error)
@ -94,7 +94,7 @@ type apiClientMock struct {
ListCodespacesFunc func(ctx context.Context) ([]*codespace.Codespace, error)
// StartCodespaceFunc mocks the StartCodespace method.
StartCodespaceFunc func(ctx context.Context, token string, codespaceMoqParam *codespace.Codespace) error
StartCodespaceFunc func(ctx context.Context, name string) error
// calls tracks calls to the methods.
calls struct {
@ -116,8 +116,6 @@ type apiClientMock struct {
DeleteCodespace []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// User is the user argument value.
User string
// Name is the name argument value.
Name string
}
@ -155,14 +153,12 @@ type apiClientMock struct {
// Name is the name argument value.
Name string
}
// GetCodespacesSKUs holds details about calls to the GetCodespacesSKUs method.
GetCodespacesSKUs []struct {
// GetCodespacesMachines holds details about calls to the GetCodespacesMachines method.
GetCodespacesMachines []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// User is the user argument value.
User *api.User
// Repository is the repository argument value.
Repository *api.Repository
// RepoID is the repoID argument value.
RepoID int
// Branch is the branch argument value.
Branch string
// Location is the location argument value.
@ -189,10 +185,8 @@ type apiClientMock struct {
StartCodespace []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// Token is the token argument value.
Token string
// CodespaceMoqParam is the codespaceMoqParam argument value.
CodespaceMoqParam *codespace.Codespace
// Name is the name argument value.
Name string
}
}
lockAuthorizedKeys sync.RWMutex
@ -202,7 +196,7 @@ type apiClientMock struct {
lockGetCodespaceRegionLocation sync.RWMutex
lockGetCodespaceRepositoryContents sync.RWMutex
lockGetCodespaceToken sync.RWMutex
lockGetCodespacesSKUs sync.RWMutex
lockGetCodespacesMachines sync.RWMutex
lockGetRepository sync.RWMutex
lockGetUser sync.RWMutex
lockListCodespaces sync.RWMutex
@ -280,23 +274,21 @@ func (mock *apiClientMock) CreateCodespaceCalls() []struct {
}
// DeleteCodespace calls DeleteCodespaceFunc.
func (mock *apiClientMock) DeleteCodespace(ctx context.Context, user string, name string) error {
func (mock *apiClientMock) DeleteCodespace(ctx context.Context, name string) error {
if mock.DeleteCodespaceFunc == nil {
panic("apiClientMock.DeleteCodespaceFunc: method is nil but apiClient.DeleteCodespace was just called")
}
callInfo := struct {
Ctx context.Context
User string
Name string
}{
Ctx: ctx,
User: user,
Name: name,
}
mock.lockDeleteCodespace.Lock()
mock.calls.DeleteCodespace = append(mock.calls.DeleteCodespace, callInfo)
mock.lockDeleteCodespace.Unlock()
return mock.DeleteCodespaceFunc(ctx, user, name)
return mock.DeleteCodespaceFunc(ctx, name)
}
// DeleteCodespaceCalls gets all the calls that were made to DeleteCodespace.
@ -304,12 +296,10 @@ func (mock *apiClientMock) DeleteCodespace(ctx context.Context, user string, nam
// len(mockedapiClient.DeleteCodespaceCalls())
func (mock *apiClientMock) DeleteCodespaceCalls() []struct {
Ctx context.Context
User string
Name string
} {
var calls []struct {
Ctx context.Context
User string
Name string
}
mock.lockDeleteCodespace.RLock()
@ -470,50 +460,46 @@ func (mock *apiClientMock) GetCodespaceTokenCalls() []struct {
return calls
}
// GetCodespacesSKUs calls GetCodespacesSKUsFunc.
func (mock *apiClientMock) GetCodespacesSKUs(ctx context.Context, user *api.User, repository *api.Repository, branch string, location string) ([]*api.SKU, error) {
if mock.GetCodespacesSKUsFunc == nil {
panic("apiClientMock.GetCodespacesSKUsFunc: method is nil but apiClient.GetCodespacesSKUs was just called")
// GetCodespacesMachines calls GetCodespacesMachinesFunc.
func (mock *apiClientMock) GetCodespacesMachines(ctx context.Context, repoID int, branch string, location string) ([]*api.Machine, error) {
if mock.GetCodespacesMachinesFunc == nil {
panic("apiClientMock.GetCodespacesMachinesFunc: method is nil but apiClient.GetCodespacesMachines was just called")
}
callInfo := struct {
Ctx context.Context
User *api.User
Repository *api.Repository
Branch string
Location string
Ctx context.Context
RepoID int
Branch string
Location string
}{
Ctx: ctx,
User: user,
Repository: repository,
Branch: branch,
Location: location,
Ctx: ctx,
RepoID: repoID,
Branch: branch,
Location: location,
}
mock.lockGetCodespacesSKUs.Lock()
mock.calls.GetCodespacesSKUs = append(mock.calls.GetCodespacesSKUs, callInfo)
mock.lockGetCodespacesSKUs.Unlock()
return mock.GetCodespacesSKUsFunc(ctx, user, repository, branch, location)
mock.lockGetCodespacesMachines.Lock()
mock.calls.GetCodespacesMachines = append(mock.calls.GetCodespacesMachines, callInfo)
mock.lockGetCodespacesMachines.Unlock()
return mock.GetCodespacesMachinesFunc(ctx, repoID, branch, location)
}
// GetCodespacesSKUsCalls gets all the calls that were made to GetCodespacesSKUs.
// GetCodespacesMachinesCalls gets all the calls that were made to GetCodespacesMachines.
// Check the length with:
// len(mockedapiClient.GetCodespacesSKUsCalls())
func (mock *apiClientMock) GetCodespacesSKUsCalls() []struct {
Ctx context.Context
User *api.User
Repository *api.Repository
Branch string
Location string
// len(mockedapiClient.GetCodespacesMachinesCalls())
func (mock *apiClientMock) GetCodespacesMachinesCalls() []struct {
Ctx context.Context
RepoID int
Branch string
Location string
} {
var calls []struct {
Ctx context.Context
User *api.User
Repository *api.Repository
Branch string
Location string
Ctx context.Context
RepoID int
Branch string
Location string
}
mock.lockGetCodespacesSKUs.RLock()
calls = mock.calls.GetCodespacesSKUs
mock.lockGetCodespacesSKUs.RUnlock()
mock.lockGetCodespacesMachines.RLock()
calls = mock.calls.GetCodespacesMachines
mock.lockGetCodespacesMachines.RUnlock()
return calls
}
@ -615,37 +601,33 @@ func (mock *apiClientMock) ListCodespacesCalls() []struct {
}
// StartCodespace calls StartCodespaceFunc.
func (mock *apiClientMock) StartCodespace(ctx context.Context, token string, codespaceMoqParam *codespace.Codespace) error {
func (mock *apiClientMock) StartCodespace(ctx context.Context, name string) error {
if mock.StartCodespaceFunc == nil {
panic("apiClientMock.StartCodespaceFunc: method is nil but apiClient.StartCodespace was just called")
}
callInfo := struct {
Ctx context.Context
Token string
CodespaceMoqParam *codespace.Codespace
Ctx context.Context
Name string
}{
Ctx: ctx,
Token: token,
CodespaceMoqParam: codespaceMoqParam,
Ctx: ctx,
Name: name,
}
mock.lockStartCodespace.Lock()
mock.calls.StartCodespace = append(mock.calls.StartCodespace, callInfo)
mock.lockStartCodespace.Unlock()
return mock.StartCodespaceFunc(ctx, token, codespaceMoqParam)
return mock.StartCodespaceFunc(ctx, name)
}
// StartCodespaceCalls gets all the calls that were made to StartCodespace.
// Check the length with:
// len(mockedapiClient.StartCodespaceCalls())
func (mock *apiClientMock) StartCodespaceCalls() []struct {
Ctx context.Context
Token string
CodespaceMoqParam *codespace.Codespace
Ctx context.Context
Name string
} {
var calls []struct {
Ctx context.Context
Token string
CodespaceMoqParam *codespace.Codespace
Ctx context.Context
Name string
}
mock.lockStartCodespace.RLock()
calls = mock.calls.StartCodespace

View file

@ -1,7 +1,7 @@
// Code generated by moq; DO NOT EDIT.
// github.com/matryer/moq
package ghcs
package codespace
import (
"sync"

View file

@ -1,4 +1,4 @@
package ghcs
package codespace
import (
"bytes"
@ -79,7 +79,7 @@ func (a *App) ListPorts(ctx context.Context, codespaceName string, asJSON bool)
devContainerResult := <-devContainerCh
if devContainerResult.err != nil {
// Warn about failure to read the devcontainer file. Not a ghcs command error.
// Warn about failure to read the devcontainer file. Not a codespace command error.
_, _ = a.logger.Errorf("Failed to get port names: %v\n", devContainerResult.err.Error())
}

View file

@ -1,4 +1,4 @@
package ghcs
package codespace
import (
"github.com/spf13/cobra"
@ -8,7 +8,7 @@ var version = "DEV" // Replaced in the release build process (by GoReleaser or H
func NewRootCmd(app *App) *cobra.Command {
root := &cobra.Command{
Use: "ghcs",
Use: "codespace",
SilenceUsage: true, // don't print usage message after each error (see #80)
SilenceErrors: false, // print errors automatically so that main need not
Long: `Unofficial CLI tool to manage GitHub Codespaces.

View file

@ -1,4 +1,4 @@
package ghcs
package codespace
import (
"context"