From 0635514ddfab4d2fc4ca2dfb729b0b59a0fe97da Mon Sep 17 00:00:00 2001 From: Patrick Veverka Date: Tue, 15 Feb 2022 02:06:01 +0000 Subject: [PATCH] first stab wip move to method param flushing out --- internal/codespaces/api/api.go | 54 ++++++++++++++++++++++++++++ pkg/cmd/codespace/common.go | 1 + pkg/cmd/codespace/edit.go | 65 ++++++++++++++++++++++++++++++++++ pkg/cmd/codespace/mock_api.go | 55 ++++++++++++++++++++++++++++ pkg/cmd/codespace/root.go | 1 + 5 files changed, 176 insertions(+) create mode 100644 pkg/cmd/codespace/edit.go diff --git a/internal/codespaces/api/api.go b/internal/codespaces/api/api.go index a207f562c..9bd41db5d 100644 --- a/internal/codespaces/api/api.go +++ b/internal/codespaces/api/api.go @@ -686,6 +686,60 @@ func (a *API) DeleteCodespace(ctx context.Context, codespaceName string) error { return nil } +type EditCodespaceParams struct { + DisplayName string + IdleTimeoutMinutes int + Machine string +} + +type editRequest struct { + DisplayName string `json:"display_name,omitempty"` + IdleTimeoutMinutes int `json:"idle_timeout_minutes,omitempty"` + Machine string `json:"machine"` +} + +func (a *API) EditCodespace(ctx context.Context, codespaceName string, params *EditCodespaceParams) (*Codespace, error) { + requestBody, err := json.Marshal(editRequest{ + DisplayName: params.DisplayName, + IdleTimeoutMinutes: params.IdleTimeoutMinutes, + Machine: params.Machine, + }) + fmt.Printf("requestBody: %s\n", requestBody) + if err != nil { + return nil, fmt.Errorf("error marshaling request: %w", err) + } + + req, err := http.NewRequest(http.MethodPatch, a.githubAPI+"/user/codespaces/"+codespaceName, bytes.NewBuffer(requestBody)) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + a.setHeaders(req) + resp, err := a.do(ctx, req, "/user/codespaces") + if err != nil { + return nil, fmt.Errorf("error making request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusAccepted { + return nil, errProvisioningInProgress // RPC finished before result of creation known + } else if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + return nil, api.HandleHTTPError(resp) + } + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + + var response Codespace + if err := json.Unmarshal(b, &response); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + return &response, nil +} + type getCodespaceRepositoryContentsResponse struct { Content string `json:"content"` } diff --git a/pkg/cmd/codespace/common.go b/pkg/cmd/codespace/common.go index d7273edb8..bdc4d6ef4 100644 --- a/pkg/cmd/codespace/common.go +++ b/pkg/cmd/codespace/common.go @@ -62,6 +62,7 @@ type apiClient interface { StartCodespace(ctx context.Context, name string) error StopCodespace(ctx context.Context, name string) error CreateCodespace(ctx context.Context, params *api.CreateCodespaceParams) (*api.Codespace, error) + EditCodespace(ctx context.Context, codespaceName string, params *api.EditCodespaceParams) (*api.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) diff --git a/pkg/cmd/codespace/edit.go b/pkg/cmd/codespace/edit.go new file mode 100644 index 000000000..16b4d3ad2 --- /dev/null +++ b/pkg/cmd/codespace/edit.go @@ -0,0 +1,65 @@ +package codespace + +import ( + "context" + "fmt" + "time" + + "github.com/cli/cli/v2/internal/codespaces/api" + "github.com/spf13/cobra" +) + +type editOptions struct { + codespaceName string + displayName string + idleTimeout time.Duration + machine string +} + +func newEditCmd(app *App) *cobra.Command { + opts := editOptions{} + + editCmd := &cobra.Command{ + Use: "edit", + Short: "Edit a codespace", + Args: noArgsConstraint, + RunE: func(cmd *cobra.Command, args []string) error { + return app.Edit(cmd.Context(), opts) + }, + } + + editCmd.Flags().StringVarP(&opts.codespaceName, "codespace", "c", "", "Name of the codespace") + editCmd.Flags().StringVarP(&opts.displayName, "displayName", "d", "", "display name") + editCmd.Flags().DurationVar(&opts.idleTimeout, "idle-timeout", 0, "allowed inactivity before codespace is stopped, e.g. \"10m\", \"1h\"") + editCmd.Flags().StringVarP(&opts.machine, "machine", "m", "", "hardware specifications for the VM") + + return editCmd +} + +// Edits a codespace +func (a *App) Edit(ctx context.Context, opts editOptions) error { + userInputs := struct { + CodespaceName string + DisplayName string + IdleTimeout time.Duration + SKU string + }{ + CodespaceName: opts.codespaceName, + DisplayName: opts.displayName, + IdleTimeout: opts.idleTimeout, + SKU: opts.machine, + } + a.StartProgressIndicatorWithLabel("Editing codespace") + codespace, err := a.apiClient.EditCodespace(ctx, userInputs.CodespaceName, &api.EditCodespaceParams{ + DisplayName: userInputs.DisplayName, + IdleTimeoutMinutes: int(userInputs.IdleTimeout.Minutes()), + Machine: userInputs.SKU, + }) + a.StopProgressIndicator() + if err != nil { + return fmt.Errorf("error editing codespace: %w", err) + } + + fmt.Fprintln(a.io.Out, codespace.Name) + return nil +} diff --git a/pkg/cmd/codespace/mock_api.go b/pkg/cmd/codespace/mock_api.go index fd275e802..220750afe 100644 --- a/pkg/cmd/codespace/mock_api.go +++ b/pkg/cmd/codespace/mock_api.go @@ -25,6 +25,9 @@ import ( // DeleteCodespaceFunc: func(ctx context.Context, name string) error { // panic("mock out the DeleteCodespace method") // }, +// EditCodespaceFunc: func(ctx context.Context, codespaceName string, params *api.EditCodespaceParams) (*api.Codespace, error) { +// panic("mock out the EditCodespace method") +// }, // GetCodespaceFunc: func(ctx context.Context, name string, includeConnection bool) (*api.Codespace, error) { // panic("mock out the GetCodespace method") // }, @@ -71,6 +74,9 @@ type apiClientMock struct { // DeleteCodespaceFunc mocks the DeleteCodespace method. DeleteCodespaceFunc func(ctx context.Context, name string) error + // EditCodespaceFunc mocks the EditCodespace method. + EditCodespaceFunc func(ctx context.Context, codespaceName string, params *api.EditCodespaceParams) (*api.Codespace, error) + // GetCodespaceFunc mocks the GetCodespace method. GetCodespaceFunc func(ctx context.Context, name string, includeConnection bool) (*api.Codespace, error) @@ -124,6 +130,15 @@ type apiClientMock struct { // Name is the name argument value. Name string } + // EditCodespace holds details about calls to the EditCodespace method. + EditCodespace []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // CodespaceName is the codespaceName argument value. + CodespaceName string + // Params is the params argument value. + Params *api.EditCodespaceParams + } // GetCodespace holds details about calls to the GetCodespace method. GetCodespace []struct { // Ctx is the ctx argument value. @@ -204,6 +219,7 @@ type apiClientMock struct { lockAuthorizedKeys sync.RWMutex lockCreateCodespace sync.RWMutex lockDeleteCodespace sync.RWMutex + lockEditCodespace sync.RWMutex lockGetCodespace sync.RWMutex lockGetCodespaceRegionLocation sync.RWMutex lockGetCodespaceRepoSuggestions sync.RWMutex @@ -321,6 +337,45 @@ func (mock *apiClientMock) DeleteCodespaceCalls() []struct { return calls } +// EditCodespace calls EditCodespaceFunc. +func (mock *apiClientMock) EditCodespace(ctx context.Context, codespaceName string, params *api.EditCodespaceParams) (*api.Codespace, error) { + if mock.EditCodespaceFunc == nil { + panic("apiClientMock.EditCodespaceFunc: method is nil but apiClient.EditCodespace was just called") + } + callInfo := struct { + Ctx context.Context + CodespaceName string + Params *api.EditCodespaceParams + }{ + Ctx: ctx, + CodespaceName: codespaceName, + Params: params, + } + mock.lockEditCodespace.Lock() + mock.calls.EditCodespace = append(mock.calls.EditCodespace, callInfo) + mock.lockEditCodespace.Unlock() + return mock.EditCodespaceFunc(ctx, codespaceName, params) +} + +// EditCodespaceCalls gets all the calls that were made to EditCodespace. +// Check the length with: +// len(mockedapiClient.EditCodespaceCalls()) +func (mock *apiClientMock) EditCodespaceCalls() []struct { + Ctx context.Context + CodespaceName string + Params *api.EditCodespaceParams +} { + var calls []struct { + Ctx context.Context + CodespaceName string + Params *api.EditCodespaceParams + } + mock.lockEditCodespace.RLock() + calls = mock.calls.EditCodespace + mock.lockEditCodespace.RUnlock() + return calls +} + // GetCodespace calls GetCodespaceFunc. func (mock *apiClientMock) GetCodespace(ctx context.Context, name string, includeConnection bool) (*api.Codespace, error) { if mock.GetCodespaceFunc == nil { diff --git a/pkg/cmd/codespace/root.go b/pkg/cmd/codespace/root.go index 5b2c0d8fc..0a04d2fdd 100644 --- a/pkg/cmd/codespace/root.go +++ b/pkg/cmd/codespace/root.go @@ -12,6 +12,7 @@ func NewRootCmd(app *App) *cobra.Command { root.AddCommand(newCodeCmd(app)) root.AddCommand(newCreateCmd(app)) + root.AddCommand(newEditCmd(app)) root.AddCommand(newDeleteCmd(app)) root.AddCommand(newListCmd(app)) root.AddCommand(newLogsCmd(app))