Merge branch 'trunk' of https://github.com/meiji163/cli into pin-ext
This commit is contained in:
commit
95fa7f97cd
10 changed files with 353 additions and 7 deletions
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
|
|
|
|||
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
go-version: 1.16
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v2
|
||||
|
|
|
|||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
go-version: 1.16
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Verify dependencies
|
||||
run: |
|
||||
|
|
|
|||
8
.github/workflows/releases.yml
vendored
8
.github/workflows/releases.yml
vendored
|
|
@ -10,7 +10,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Go 1.16
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
|
|
@ -44,7 +44,7 @@ jobs:
|
|||
GORELEASER_CURRENT_TAG: ${{steps.changelog.outputs.tag-name}}
|
||||
CERT_PASSWORD: ${{secrets.WINDOWS_CERT_PASSWORD}}
|
||||
- name: Checkout documentation site
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: github/cli.github.com
|
||||
path: site
|
||||
|
|
@ -128,7 +128,7 @@ jobs:
|
|||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Download gh.exe
|
||||
id: download_exe
|
||||
shell: bash
|
||||
|
|
@ -188,7 +188,7 @@ jobs:
|
|||
env:
|
||||
COMMITTER_TOKEN: ${{ secrets.UPLOAD_GITHUB_TOKEN }}
|
||||
- name: Checkout scoop bucket
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: cli/scoop-gh
|
||||
path: scoop-gh
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ type Codespace struct {
|
|||
State string `json:"state"`
|
||||
GitStatus CodespaceGitStatus `json:"git_status"`
|
||||
Connection CodespaceConnection `json:"connection"`
|
||||
Machine CodespaceMachine `json:"machine"`
|
||||
}
|
||||
|
||||
type CodespaceGitStatus struct {
|
||||
|
|
@ -180,6 +181,15 @@ type CodespaceGitStatus struct {
|
|||
HasUncommitedChanges bool `json:"has_uncommited_changes"`
|
||||
}
|
||||
|
||||
type CodespaceMachine struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
OperatingSystem string `json:"operating_system"`
|
||||
StorageInBytes int `json:"storage_in_bytes"`
|
||||
MemoryInBytes int `json:"memory_in_bytes"`
|
||||
CPUCount int `json:"cpus"`
|
||||
}
|
||||
|
||||
const (
|
||||
// CodespaceStateAvailable is the state for a running codespace environment.
|
||||
CodespaceStateAvailable = "Available"
|
||||
|
|
@ -207,6 +217,7 @@ var CodespaceFields = []string{
|
|||
"gitStatus",
|
||||
"createdAt",
|
||||
"lastUsedAt",
|
||||
"machineName",
|
||||
}
|
||||
|
||||
func (c *Codespace) ExportData(fields []string) map[string]interface{} {
|
||||
|
|
@ -219,6 +230,8 @@ func (c *Codespace) ExportData(fields []string) map[string]interface{} {
|
|||
data[f] = c.Owner.Login
|
||||
case "repository":
|
||||
data[f] = c.Repository.FullName
|
||||
case "machineName":
|
||||
data[f] = c.Machine.Name
|
||||
case "gitStatus":
|
||||
data[f] = map[string]interface{}{
|
||||
"ref": c.GitStatus.Ref,
|
||||
|
|
@ -265,6 +278,7 @@ func (a *API) ListCodespaces(ctx context.Context, limit int) (codespaces []*Code
|
|||
var response struct {
|
||||
Codespaces []*Codespace `json:"codespaces"`
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
if err := dec.Decode(&response); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling response: %w", err)
|
||||
|
|
@ -721,6 +735,48 @@ func (a *API) DeleteCodespace(ctx context.Context, codespaceName string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type EditCodespaceParams struct {
|
||||
DisplayName string `json:"display_name,omitempty"`
|
||||
IdleTimeoutMinutes int `json:"idle_timeout_minutes,omitempty"`
|
||||
Machine string `json:"machine,omitempty"`
|
||||
}
|
||||
|
||||
func (a *API) EditCodespace(ctx context.Context, codespaceName string, params *EditCodespaceParams) (*Codespace, error) {
|
||||
requestBody, err := json.Marshal(params)
|
||||
|
||||
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.StatusOK {
|
||||
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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
|
@ -265,3 +266,171 @@ func TestRetries(t *testing.T) {
|
|||
t.Fatalf("expected codespace name to be %q but got %q", csName, cs.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodespace_ExportData(t *testing.T) {
|
||||
type fields struct {
|
||||
Name string
|
||||
CreatedAt string
|
||||
DisplayName string
|
||||
LastUsedAt string
|
||||
Owner User
|
||||
Repository Repository
|
||||
State string
|
||||
GitStatus CodespaceGitStatus
|
||||
Connection CodespaceConnection
|
||||
Machine CodespaceMachine
|
||||
}
|
||||
type args struct {
|
||||
fields []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "just name",
|
||||
fields: fields{
|
||||
Name: "test",
|
||||
},
|
||||
args: args{
|
||||
fields: []string{"name"},
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"name": "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "just owner",
|
||||
fields: fields{
|
||||
Owner: User{
|
||||
Login: "test",
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
fields: []string{"owner"},
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"owner": "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "just machine",
|
||||
fields: fields{
|
||||
Machine: CodespaceMachine{
|
||||
Name: "test",
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
fields: []string{"machineName"},
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"machineName": "test",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Codespace{
|
||||
Name: tt.fields.Name,
|
||||
CreatedAt: tt.fields.CreatedAt,
|
||||
DisplayName: tt.fields.DisplayName,
|
||||
LastUsedAt: tt.fields.LastUsedAt,
|
||||
Owner: tt.fields.Owner,
|
||||
Repository: tt.fields.Repository,
|
||||
State: tt.fields.State,
|
||||
GitStatus: tt.fields.GitStatus,
|
||||
Connection: tt.fields.Connection,
|
||||
Machine: tt.fields.Machine,
|
||||
}
|
||||
if got := c.ExportData(tt.args.fields); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Codespace.ExportData() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createFakeEditServer(t *testing.T, codespaceName string) *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
checkPath := "/user/codespaces/" + codespaceName
|
||||
|
||||
if r.URL.Path != checkPath {
|
||||
t.Fatal("Incorrect path")
|
||||
}
|
||||
|
||||
if r.Method != http.MethodPatch {
|
||||
t.Fatal("Incorrect method")
|
||||
}
|
||||
|
||||
body := r.Body
|
||||
if body == nil {
|
||||
t.Fatal("No body")
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
var data map[string]interface{}
|
||||
err := json.NewDecoder(body).Decode(&data)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if data["display_name"] != "changeTo" {
|
||||
t.Fatal("Incorrect display name")
|
||||
}
|
||||
|
||||
response := Codespace{
|
||||
DisplayName: "changeTo",
|
||||
}
|
||||
|
||||
responseData, _ := json.Marshal(response)
|
||||
fmt.Fprint(w, string(responseData))
|
||||
}))
|
||||
}
|
||||
func TestAPI_EditCodespace(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
codespaceName string
|
||||
params *EditCodespaceParams
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *Codespace
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
codespaceName: "test",
|
||||
params: &EditCodespaceParams{
|
||||
DisplayName: "changeTo",
|
||||
},
|
||||
},
|
||||
want: &Codespace{
|
||||
DisplayName: "changeTo",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
svr := createFakeEditServer(t, tt.args.codespaceName)
|
||||
defer svr.Close()
|
||||
|
||||
a := &API{
|
||||
client: &http.Client{},
|
||||
githubAPI: svr.URL,
|
||||
}
|
||||
got, err := a.EditCodespace(tt.args.ctx, tt.args.codespaceName, tt.args.params)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("API.EditCodespace() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("API.EditCodespace() = %v, want %v", got.DisplayName, tt.want.DisplayName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,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)
|
||||
|
|
|
|||
64
pkg/cmd/codespace/edit.go
Normal file
64
pkg/cmd/codespace/edit.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
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")
|
||||
_, 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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue