From cbb82535448b11b53dfd51ea8a67c37e6a9ac1f4 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 9 Sep 2021 10:28:55 -0400 Subject: [PATCH] consolidate survey functions --- cmd/ghcs/code.go | 5 +- cmd/ghcs/common.go | 101 ++++++++++++++++++++++++++++++ cmd/ghcs/create.go | 5 -- cmd/ghcs/delete.go | 3 +- cmd/ghcs/logs.go | 2 +- cmd/ghcs/ports.go | 4 +- cmd/ghcs/ssh.go | 2 +- internal/codespaces/codespaces.go | 80 ----------------------- 8 files changed, 108 insertions(+), 94 deletions(-) create mode 100644 cmd/ghcs/common.go diff --git a/cmd/ghcs/code.go b/cmd/ghcs/code.go index 5bad53648..81c2cc9a2 100644 --- a/cmd/ghcs/code.go +++ b/cmd/ghcs/code.go @@ -7,7 +7,6 @@ import ( "os" "github.com/github/ghcs/api" - "github.com/github/ghcs/internal/codespaces" "github.com/skratchdot/open-golang/open" "github.com/spf13/cobra" ) @@ -47,9 +46,9 @@ func code(codespaceName string, useInsiders bool) error { } if codespaceName == "" { - codespace, err := codespaces.ChooseCodespace(ctx, apiClient, user) + codespace, err := chooseCodespace(ctx, apiClient, user) if err != nil { - if err == codespaces.ErrNoCodespaces { + if err == errNoCodespaces { return err } return fmt.Errorf("error choosing codespace: %v", err) diff --git a/cmd/ghcs/common.go b/cmd/ghcs/common.go new file mode 100644 index 000000000..712c04daa --- /dev/null +++ b/cmd/ghcs/common.go @@ -0,0 +1,101 @@ +package main + +// This file defines functions common to the entire ghcs command set. + +import ( + "context" + "errors" + "fmt" + "sort" + + "github.com/AlecAivazis/survey/v2" + "github.com/github/ghcs/api" + "golang.org/x/term" +) + +var errNoCodespaces = errors.New("You have no codespaces.") + +func chooseCodespace(ctx context.Context, apiClient *api.API, user *api.User) (*api.Codespace, error) { + codespaces, err := apiClient.ListCodespaces(ctx, user) + if err != nil { + return nil, fmt.Errorf("error getting codespaces: %v", err) + } + + if len(codespaces) == 0 { + return nil, errNoCodespaces + } + + sort.Slice(codespaces, func(i, j int) bool { + return codespaces[i].CreatedAt > codespaces[j].CreatedAt + }) + + codespacesByName := make(map[string]*api.Codespace) + codespacesNames := make([]string, 0, len(codespaces)) + for _, codespace := range codespaces { + codespacesByName[codespace.Name] = codespace + codespacesNames = append(codespacesNames, codespace.Name) + } + + sshSurvey := []*survey.Question{ + { + Name: "codespace", + Prompt: &survey.Select{ + Message: "Choose codespace:", + Options: codespacesNames, + Default: codespacesNames[0], + }, + Validate: survey.Required, + }, + } + + var answers struct { + Codespace string + } + if err := ask(sshSurvey, &answers); err != nil { + return nil, fmt.Errorf("error getting answers: %v", err) + } + + codespace := codespacesByName[answers.Codespace] + return codespace, nil +} + +func getOrChooseCodespace(ctx context.Context, apiClient *api.API, user *api.User, codespaceName string) (codespace *api.Codespace, token string, err error) { + if codespaceName == "" { + codespace, err = chooseCodespace(ctx, apiClient, user) + if err != nil { + if err == errNoCodespaces { + return nil, "", err + } + return nil, "", fmt.Errorf("choosing codespace: %v", err) + } + codespaceName = codespace.Name + + token, err = apiClient.GetCodespaceToken(ctx, user.Login, codespaceName) + if err != nil { + return nil, "", fmt.Errorf("getting codespace token: %v", err) + } + } else { + token, err = apiClient.GetCodespaceToken(ctx, user.Login, codespaceName) + if err != nil { + return nil, "", fmt.Errorf("getting codespace token for given codespace: %v", err) + } + + codespace, err = apiClient.GetCodespace(ctx, token, user.Login, codespaceName) + if err != nil { + return nil, "", fmt.Errorf("getting full codespace details: %v", err) + } + } + + return codespace, token, nil +} + +var hasTTY = term.IsTerminal(0) && term.IsTerminal(1) // is process connected to a terminal? + +// ask asks survey questions on the terminal, using standard options. +// It fails unless hasTTY, but ideally callers should avoid calling it in that case. +func ask(qs []*survey.Question, response interface{}) error { + if !hasTTY { + return fmt.Errorf("no terminal") + } + return survey.Ask(qs, response, survey.WithShowCursor(true)) +} diff --git a/cmd/ghcs/create.go b/cmd/ghcs/create.go index bd1d89e4e..093450e7d 100644 --- a/cmd/ghcs/create.go +++ b/cmd/ghcs/create.go @@ -286,8 +286,3 @@ func getMachineName(ctx context.Context, machine string, user *api.User, repo *a return machine, nil } - -// ask asks survery questions using standard options. -func ask(qs []*survey.Question, response interface{}) error { - return survey.Ask(qs, response, survey.WithShowCursor(true)) -} diff --git a/cmd/ghcs/delete.go b/cmd/ghcs/delete.go index 92c405766..75b9362bb 100644 --- a/cmd/ghcs/delete.go +++ b/cmd/ghcs/delete.go @@ -8,7 +8,6 @@ import ( "github.com/github/ghcs/api" "github.com/github/ghcs/cmd/ghcs/output" - "github.com/github/ghcs/internal/codespaces" "github.com/spf13/cobra" ) @@ -63,7 +62,7 @@ func delete_(codespaceName string) error { return fmt.Errorf("error getting user: %v", err) } - codespace, token, err := codespaces.GetOrChooseCodespace(ctx, apiClient, user, codespaceName) + codespace, token, err := getOrChooseCodespace(ctx, apiClient, user, codespaceName) if err != nil { return fmt.Errorf("get or choose codespace: %v", err) } diff --git a/cmd/ghcs/logs.go b/cmd/ghcs/logs.go index e35ecb728..9998b5f0f 100644 --- a/cmd/ghcs/logs.go +++ b/cmd/ghcs/logs.go @@ -51,7 +51,7 @@ func logs(ctx context.Context, tail bool, codespaceName string) error { return fmt.Errorf("getting user: %v", err) } - codespace, token, err := codespaces.GetOrChooseCodespace(ctx, apiClient, user, codespaceName) + codespace, token, err := getOrChooseCodespace(ctx, apiClient, user, codespaceName) if err != nil { return fmt.Errorf("get or choose codespace: %v", err) } diff --git a/cmd/ghcs/ports.go b/cmd/ghcs/ports.go index 4258991b6..c175549a0 100644 --- a/cmd/ghcs/ports.go +++ b/cmd/ghcs/ports.go @@ -67,9 +67,9 @@ func ports(opts *portsOptions) error { return fmt.Errorf("error getting user: %v", err) } - codespace, token, err := codespaces.GetOrChooseCodespace(ctx, apiClient, user, opts.codespaceName) + codespace, token, err := getOrChooseCodespace(ctx, apiClient, user, opts.codespaceName) if err != nil { - if err == codespaces.ErrNoCodespaces { + if err == errNoCodespaces { return err } return fmt.Errorf("error choosing codespace: %v", err) diff --git a/cmd/ghcs/ssh.go b/cmd/ghcs/ssh.go index e2003b347..aefb959f3 100644 --- a/cmd/ghcs/ssh.go +++ b/cmd/ghcs/ssh.go @@ -52,7 +52,7 @@ func ssh(ctx context.Context, sshProfile, codespaceName string, localSSHServerPo return fmt.Errorf("error getting user: %v", err) } - codespace, token, err := codespaces.GetOrChooseCodespace(ctx, apiClient, user, codespaceName) + codespace, token, err := getOrChooseCodespace(ctx, apiClient, user, codespaceName) if err != nil { return fmt.Errorf("get or choose codespace: %v", err) } diff --git a/internal/codespaces/codespaces.go b/internal/codespaces/codespaces.go index b5fa4a583..9aee3564c 100644 --- a/internal/codespaces/codespaces.go +++ b/internal/codespaces/codespaces.go @@ -4,62 +4,12 @@ import ( "context" "errors" "fmt" - "sort" "time" - "github.com/AlecAivazis/survey/v2" "github.com/github/ghcs/api" "github.com/github/go-liveshare" ) -var ( - ErrNoCodespaces = errors.New("You have no codespaces.") -) - -func ChooseCodespace(ctx context.Context, apiClient *api.API, user *api.User) (*api.Codespace, error) { - codespaces, err := apiClient.ListCodespaces(ctx, user) - if err != nil { - return nil, fmt.Errorf("error getting codespaces: %v", err) - } - - if len(codespaces) == 0 { - return nil, ErrNoCodespaces - } - - sort.Slice(codespaces, func(i, j int) bool { - return codespaces[i].CreatedAt > codespaces[j].CreatedAt - }) - - codespacesByName := make(map[string]*api.Codespace) - codespacesNames := make([]string, 0, len(codespaces)) - for _, codespace := range codespaces { - codespacesByName[codespace.Name] = codespace - codespacesNames = append(codespacesNames, codespace.Name) - } - - sshSurvey := []*survey.Question{ - { - Name: "codespace", - Prompt: &survey.Select{ - Message: "Choose codespace:", - Options: codespacesNames, - Default: codespacesNames[0], - }, - Validate: survey.Required, - }, - } - - answers := struct { - Codespace string - }{} - if err := survey.Ask(sshSurvey, &answers); err != nil { - return nil, fmt.Errorf("error getting answers: %v", err) - } - - codespace := codespacesByName[answers.Codespace] - return codespace, nil -} - type logger interface { Print(v ...interface{}) (int, error) Println(v ...interface{}) (int, error) @@ -123,33 +73,3 @@ func ConnectToLiveshare(ctx context.Context, log logger, apiClient *api.API, use return lsclient.JoinWorkspace(ctx) } - -func GetOrChooseCodespace(ctx context.Context, apiClient *api.API, user *api.User, codespaceName string) (codespace *api.Codespace, token string, err error) { - if codespaceName == "" { - codespace, err = ChooseCodespace(ctx, apiClient, user) - if err != nil { - if err == ErrNoCodespaces { - return nil, "", err - } - return nil, "", fmt.Errorf("choosing codespace: %v", err) - } - codespaceName = codespace.Name - - token, err = apiClient.GetCodespaceToken(ctx, user.Login, codespaceName) - if err != nil { - return nil, "", fmt.Errorf("getting codespace token: %v", err) - } - } else { - token, err = apiClient.GetCodespaceToken(ctx, user.Login, codespaceName) - if err != nil { - return nil, "", fmt.Errorf("getting codespace token for given codespace: %v", err) - } - - codespace, err = apiClient.GetCodespace(ctx, token, user.Login, codespaceName) - if err != nil { - return nil, "", fmt.Errorf("getting full codespace details: %v", err) - } - } - - return codespace, token, nil -}