merge main
This commit is contained in:
commit
634796e8a8
10 changed files with 143 additions and 113 deletions
98
api/api.go
98
api/api.go
|
|
@ -1,5 +1,12 @@
|
|||
// TODO(adonovan): rename to package codespaces, and codespaces.Client.
|
||||
package api
|
||||
|
||||
// For descriptions of service interfaces, see:
|
||||
// - https://online.visualstudio.com/api/swagger (for visualstudio.com)
|
||||
// - https://docs.github.com/en/rest/reference/repos (for api.github.com)
|
||||
// - https://github.com/github/github/blob/master/app/api/codespaces.rb (for vscs_internal)
|
||||
// TODO(adonovan): replace the last link with a public doc URL when available.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
|
@ -9,7 +16,6 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
|
@ -29,10 +35,6 @@ type User struct {
|
|||
Login string `json:"login"`
|
||||
}
|
||||
|
||||
type errResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (a *API) GetUser(ctx context.Context) (*User, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, githubAPI+"/user", nil)
|
||||
if err != nil {
|
||||
|
|
@ -44,6 +46,7 @@ func (a *API) GetUser(ctx context.Context) (*User, error) {
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
|
|
@ -51,7 +54,7 @@ func (a *API) GetUser(ctx context.Context) (*User, error) {
|
|||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, a.errorResponse(b)
|
||||
return nil, jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response User
|
||||
|
|
@ -62,8 +65,10 @@ func (a *API) GetUser(ctx context.Context) (*User, error) {
|
|||
return &response, nil
|
||||
}
|
||||
|
||||
func (a *API) errorResponse(b []byte) error {
|
||||
var response errResponse
|
||||
func jsonErrorResponse(b []byte) error {
|
||||
var response struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
return fmt.Errorf("error unmarshaling error response: %v", err)
|
||||
}
|
||||
|
|
@ -86,6 +91,7 @@ func (a *API) GetRepository(ctx context.Context, nwo string) (*Repository, error
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
|
|
@ -93,7 +99,7 @@ func (a *API) GetRepository(ctx context.Context, nwo string) (*Repository, error
|
|||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, a.errorResponse(b)
|
||||
return nil, jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response Repository
|
||||
|
|
@ -104,14 +110,6 @@ func (a *API) GetRepository(ctx context.Context, nwo string) (*Repository, error
|
|||
return &response, nil
|
||||
}
|
||||
|
||||
type Codespaces []*Codespace
|
||||
|
||||
func (c Codespaces) SortByCreatedAt() {
|
||||
sort.Slice(c, func(i, j int) bool {
|
||||
return c[i].CreatedAt > c[j].CreatedAt
|
||||
})
|
||||
}
|
||||
|
||||
type Codespace struct {
|
||||
Name string `json:"name"`
|
||||
GUID string `json:"guid"`
|
||||
|
|
@ -139,7 +137,7 @@ type CodespaceEnvironmentConnection struct {
|
|||
RelaySAS string `json:"relaySas"`
|
||||
}
|
||||
|
||||
func (a *API) ListCodespaces(ctx context.Context, user *User) (Codespaces, error) {
|
||||
func (a *API) ListCodespaces(ctx context.Context, user *User) ([]*Codespace, error) {
|
||||
req, err := http.NewRequest(
|
||||
http.MethodGet, githubAPI+"/vscs_internal/user/"+user.Login+"/codespaces", nil,
|
||||
)
|
||||
|
|
@ -152,6 +150,7 @@ func (a *API) ListCodespaces(ctx context.Context, user *User) (Codespaces, error
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
|
|
@ -159,12 +158,12 @@ func (a *API) ListCodespaces(ctx context.Context, user *User) (Codespaces, error
|
|||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, a.errorResponse(b)
|
||||
return nil, jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
response := struct {
|
||||
Codespaces Codespaces `json:"codespaces"`
|
||||
}{}
|
||||
var response struct {
|
||||
Codespaces []*Codespace `json:"codespaces"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling response: %v", err)
|
||||
}
|
||||
|
|
@ -199,6 +198,7 @@ func (a *API) GetCodespaceToken(ctx context.Context, ownerLogin, codespaceName s
|
|||
if err != nil {
|
||||
return "", fmt.Errorf("error making request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
|
|
@ -206,7 +206,7 @@ func (a *API) GetCodespaceToken(ctx context.Context, ownerLogin, codespaceName s
|
|||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", a.errorResponse(b)
|
||||
return "", jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response getCodespaceTokenResponse
|
||||
|
|
@ -232,6 +232,7 @@ func (a *API) GetCodespace(ctx context.Context, token, owner, codespace string)
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
|
|
@ -239,7 +240,7 @@ func (a *API) GetCodespace(ctx context.Context, token, owner, codespace string)
|
|||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, a.errorResponse(b)
|
||||
return nil, jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response Codespace
|
||||
|
|
@ -261,10 +262,24 @@ func (a *API) StartCodespace(ctx context.Context, token string, codespace *Codes
|
|||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
_, err = a.client.Do(req)
|
||||
resp, err := a.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading response body: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// Error response is numeric code and/or string message, not JSON.
|
||||
if len(b) > 100 {
|
||||
b = append(b[:97], "..."...)
|
||||
}
|
||||
return fmt.Errorf("failed to start codespace: %s", b)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -283,12 +298,17 @@ func (a *API) GetCodespaceRegionLocation(ctx context.Context) (string, error) {
|
|||
if err != nil {
|
||||
return "", fmt.Errorf("error making request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading response body: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response getCodespaceRegionLocationResponse
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
return "", fmt.Errorf("error unmarshaling response: %v", err)
|
||||
|
|
@ -297,14 +317,12 @@ func (a *API) GetCodespaceRegionLocation(ctx context.Context) (string, error) {
|
|||
return response.Current, nil
|
||||
}
|
||||
|
||||
type Skus []*Sku
|
||||
|
||||
type Sku struct {
|
||||
type SKU struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
}
|
||||
|
||||
func (a *API) GetCodespacesSkus(ctx context.Context, user *User, repository *Repository, location string) (Skus, error) {
|
||||
func (a *API) GetCodespacesSKUs(ctx context.Context, user *User, repository *Repository, location string) ([]*SKU, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, githubAPI+"/vscs_internal/user/"+user.Login+"/skus", nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("err creating request: %v", err)
|
||||
|
|
@ -320,20 +338,25 @@ func (a *API) GetCodespacesSkus(ctx context.Context, user *User, repository *Rep
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading response body: %v", err)
|
||||
}
|
||||
|
||||
response := struct {
|
||||
Skus Skus `json:"skus"`
|
||||
}{}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response struct {
|
||||
SKUs []*SKU `json:"skus"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling response: %v", err)
|
||||
}
|
||||
|
||||
return response.Skus, nil
|
||||
return response.SKUs, nil
|
||||
}
|
||||
|
||||
type createCodespaceRequest struct {
|
||||
|
|
@ -359,6 +382,7 @@ func (a *API) CreateCodespace(ctx context.Context, user *User, repository *Repos
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
|
|
@ -366,7 +390,7 @@ func (a *API) CreateCodespace(ctx context.Context, user *User, repository *Repos
|
|||
}
|
||||
|
||||
if resp.StatusCode > http.StatusAccepted {
|
||||
return nil, a.errorResponse(b)
|
||||
return nil, jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response Codespace
|
||||
|
|
@ -388,13 +412,14 @@ func (a *API) DeleteCodespace(ctx context.Context, user *User, token, codespaceN
|
|||
if err != nil {
|
||||
return fmt.Errorf("error making request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode > http.StatusAccepted {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading response body: %v", err)
|
||||
}
|
||||
return a.errorResponse(b)
|
||||
return jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -419,6 +444,7 @@ func (a *API) GetCodespaceRepositoryContents(ctx context.Context, codespace *Cod
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("error making request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return nil, nil
|
||||
|
|
@ -430,7 +456,7 @@ func (a *API) GetCodespaceRepositoryContents(ctx context.Context, codespace *Cod
|
|||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, a.errorResponse(b)
|
||||
return nil, jsonErrorResponse(b)
|
||||
}
|
||||
|
||||
var response getCodespaceRepositoryContentsResponse
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCodeCmd() *cobra.Command {
|
||||
func newCodeCmd() *cobra.Command {
|
||||
useInsiders := false
|
||||
|
||||
codeCmd := &cobra.Command{
|
||||
|
|
@ -24,7 +24,7 @@ func NewCodeCmd() *cobra.Command {
|
|||
if len(args) > 0 {
|
||||
codespaceName = args[0]
|
||||
}
|
||||
return Code(codespaceName, useInsiders)
|
||||
return code(codespaceName, useInsiders)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -34,10 +34,10 @@ func NewCodeCmd() *cobra.Command {
|
|||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(NewCodeCmd())
|
||||
rootCmd.AddCommand(newCodeCmd())
|
||||
}
|
||||
|
||||
func Code(codespaceName string, useInsiders bool) error {
|
||||
func code(codespaceName string, useInsiders bool) error {
|
||||
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
|
|||
|
|
@ -226,9 +226,9 @@ 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, location string, apiClient *api.API) (string, error) {
|
||||
skus, err := apiClient.GetCodespacesSkus(ctx, user, repo, location)
|
||||
skus, err := apiClient.GetCodespacesSKUs(ctx, user, repo, location)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error getting codespace skus: %v", err)
|
||||
return "", fmt.Errorf("error getting codespace SKUs: %v", err)
|
||||
}
|
||||
|
||||
// if user supplied a machine type, it must be valid
|
||||
|
|
@ -240,18 +240,18 @@ func getMachineName(ctx context.Context, machine string, user *api.User, repo *a
|
|||
}
|
||||
}
|
||||
|
||||
availableSkus := make([]string, len(skus))
|
||||
availableSKUs := make([]string, len(skus))
|
||||
for i := 0; i < len(skus); i++ {
|
||||
availableSkus[i] = skus[i].Name
|
||||
availableSKUs[i] = skus[i].Name
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("there is no such machine for the repository: %s\nAvailable machines: %v", machine, availableSkus)
|
||||
return "", fmt.Errorf("there is no such machine for the repository: %s\nAvailable machines: %v", machine, availableSKUs)
|
||||
} else if len(skus) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
skuNames := make([]string, 0, len(skus))
|
||||
skuByName := make(map[string]*api.Sku)
|
||||
skuByName := make(map[string]*api.SKU)
|
||||
for _, sku := range skus {
|
||||
nameParts := camelcase.Split(sku.Name)
|
||||
machineName := strings.Title(strings.ToLower(nameParts[0]))
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewDeleteCmd() *cobra.Command {
|
||||
func newDeleteCmd() *cobra.Command {
|
||||
deleteCmd := &cobra.Command{
|
||||
Use: "delete [<codespace>]",
|
||||
Short: "Delete a Codespace",
|
||||
|
|
@ -22,7 +22,7 @@ func NewDeleteCmd() *cobra.Command {
|
|||
if len(args) > 0 {
|
||||
codespaceName = args[0]
|
||||
}
|
||||
return Delete(codespaceName)
|
||||
return delete_(codespaceName)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ func NewDeleteCmd() *cobra.Command {
|
|||
Short: "Delete all Codespaces for the current user",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return DeleteAll()
|
||||
return deleteAll()
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ func NewDeleteCmd() *cobra.Command {
|
|||
Short: "Delete all Codespaces for a repository",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return DeleteByRepo(args[0])
|
||||
return deleteByRepo(args[0])
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -50,10 +50,10 @@ func NewDeleteCmd() *cobra.Command {
|
|||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(NewDeleteCmd())
|
||||
rootCmd.AddCommand(newDeleteCmd())
|
||||
}
|
||||
|
||||
func Delete(codespaceName string) error {
|
||||
func delete_(codespaceName string) error {
|
||||
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
|
||||
ctx := context.Background()
|
||||
log := output.NewLogger(os.Stdout, os.Stderr, false)
|
||||
|
|
@ -74,10 +74,10 @@ func Delete(codespaceName string) error {
|
|||
|
||||
log.Println("Codespace deleted.")
|
||||
|
||||
return List(&ListOptions{})
|
||||
return list(&listOptions{})
|
||||
}
|
||||
|
||||
func DeleteAll() error {
|
||||
func deleteAll() error {
|
||||
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
|
||||
ctx := context.Background()
|
||||
log := output.NewLogger(os.Stdout, os.Stderr, false)
|
||||
|
|
@ -105,10 +105,10 @@ func DeleteAll() error {
|
|||
log.Printf("Codespace deleted: %s\n", c.Name)
|
||||
}
|
||||
|
||||
return List(&ListOptions{})
|
||||
return list(&listOptions{})
|
||||
}
|
||||
|
||||
func DeleteByRepo(repo string) error {
|
||||
func deleteByRepo(repo string) error {
|
||||
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
|
||||
ctx := context.Background()
|
||||
log := output.NewLogger(os.Stdout, os.Stderr, false)
|
||||
|
|
@ -146,5 +146,5 @@ func DeleteByRepo(repo string) error {
|
|||
return fmt.Errorf("No codespace was found for repository: %s", repo)
|
||||
}
|
||||
|
||||
return List(&ListOptions{})
|
||||
return list(&listOptions{})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,32 +10,32 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type ListOptions struct {
|
||||
AsJSON bool
|
||||
type listOptions struct {
|
||||
asJSON bool
|
||||
}
|
||||
|
||||
func NewListCmd() *cobra.Command {
|
||||
opts := &ListOptions{}
|
||||
func newListCmd() *cobra.Command {
|
||||
opts := &listOptions{}
|
||||
|
||||
listCmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List your Codespaces",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return List(opts)
|
||||
return list(opts)
|
||||
},
|
||||
}
|
||||
|
||||
listCmd.Flags().BoolVar(&opts.AsJSON, "json", false, "Output as JSON")
|
||||
listCmd.Flags().BoolVar(&opts.asJSON, "json", false, "Output as JSON")
|
||||
|
||||
return listCmd
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(NewListCmd())
|
||||
rootCmd.AddCommand(newListCmd())
|
||||
}
|
||||
|
||||
func List(opts *ListOptions) error {
|
||||
func list(opts *listOptions) error {
|
||||
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ func List(opts *ListOptions) error {
|
|||
return fmt.Errorf("error getting codespaces: %v", err)
|
||||
}
|
||||
|
||||
table := output.NewTable(os.Stdout, opts.AsJSON)
|
||||
table := output.NewTable(os.Stdout, opts.asJSON)
|
||||
table.SetHeader([]string{"Name", "Repository", "Branch", "State", "Created At"})
|
||||
for _, codespace := range codespaces {
|
||||
table.Append([]string{
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewLogsCmd() *cobra.Command {
|
||||
func newLogsCmd() *cobra.Command {
|
||||
var tail bool
|
||||
|
||||
logsCmd := &cobra.Command{
|
||||
|
|
@ -24,7 +24,7 @@ func NewLogsCmd() *cobra.Command {
|
|||
if len(args) > 0 {
|
||||
codespaceName = args[0]
|
||||
}
|
||||
return Logs(tail, codespaceName)
|
||||
return logs(tail, codespaceName)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -34,10 +34,10 @@ func NewLogsCmd() *cobra.Command {
|
|||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(NewLogsCmd())
|
||||
rootCmd.AddCommand(newLogsCmd())
|
||||
}
|
||||
|
||||
func Logs(tail bool, codespaceName string) error {
|
||||
func logs(tail bool, codespaceName string) error {
|
||||
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
|
||||
ctx := context.Background()
|
||||
log := output.NewLogger(os.Stdout, os.Stderr, false)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
var Version = "DEV"
|
||||
var version = "DEV"
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "ghcs",
|
||||
|
|
@ -24,7 +24,7 @@ var rootCmd = &cobra.Command{
|
|||
|
||||
Running commands requires the GITHUB_TOKEN environment variable to be set to a
|
||||
token to access the GitHub API with.`,
|
||||
Version: Version,
|
||||
Version: version,
|
||||
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if os.Getenv("GITHUB_TOKEN") == "" {
|
||||
|
|
@ -42,5 +42,5 @@ func explainError(w io.Writer, err error) {
|
|||
fmt.Fprintln(w, "Make sure to enable SSO for your organizations after creating the token.")
|
||||
return
|
||||
}
|
||||
// fmt.Fprintf(w, "%v\n", err)
|
||||
fmt.Fprintf(w, "%v\n", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,19 +19,19 @@ import (
|
|||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// PortOptions represents the options accepted by the ports command.
|
||||
type PortsOptions struct {
|
||||
// portOptions represents the options accepted by the ports command.
|
||||
type portsOptions struct {
|
||||
// CodespaceName is the name of the codespace, optional.
|
||||
CodespaceName string
|
||||
codespaceName string
|
||||
|
||||
// AsJSON dictates whether the command returns a json output or not, optional.
|
||||
AsJSON bool
|
||||
asJSON bool
|
||||
}
|
||||
|
||||
// NewPortsCmd returns a Cobra "ports" command that displays a table of available ports,
|
||||
// newPortsCmd returns a Cobra "ports" command that displays a table of available ports,
|
||||
// according to the specified flags.
|
||||
func NewPortsCmd() *cobra.Command {
|
||||
opts := &PortsOptions{}
|
||||
func newPortsCmd() *cobra.Command {
|
||||
opts := &portsOptions{}
|
||||
|
||||
portsCmd := &cobra.Command{
|
||||
Use: "ports",
|
||||
|
|
@ -42,31 +42,31 @@ func NewPortsCmd() *cobra.Command {
|
|||
},
|
||||
}
|
||||
|
||||
portsCmd.Flags().StringVarP(&opts.CodespaceName, "codespace", "c", "", "The `name` of the Codespace to use")
|
||||
portsCmd.Flags().BoolVar(&opts.AsJSON, "json", false, "Output as JSON")
|
||||
portsCmd.Flags().StringVarP(&opts.codespaceName, "codespace", "c", "", "The `name` of the Codespace to use")
|
||||
portsCmd.Flags().BoolVar(&opts.asJSON, "json", false, "Output as JSON")
|
||||
|
||||
portsCmd.AddCommand(NewPortsPublicCmd())
|
||||
portsCmd.AddCommand(NewPortsPrivateCmd())
|
||||
portsCmd.AddCommand(NewPortsForwardCmd())
|
||||
portsCmd.AddCommand(newPortsPublicCmd())
|
||||
portsCmd.AddCommand(newPortsPrivateCmd())
|
||||
portsCmd.AddCommand(newPortsForwardCmd())
|
||||
|
||||
return portsCmd
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(NewPortsCmd())
|
||||
rootCmd.AddCommand(newPortsCmd())
|
||||
}
|
||||
|
||||
func ports(opts *PortsOptions) error {
|
||||
func ports(opts *portsOptions) error {
|
||||
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
|
||||
ctx := context.Background()
|
||||
log := output.NewLogger(os.Stdout, os.Stderr, opts.AsJSON)
|
||||
log := output.NewLogger(os.Stdout, os.Stderr, opts.asJSON)
|
||||
|
||||
user, err := apiClient.GetUser(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting user: %v", err)
|
||||
}
|
||||
|
||||
codespace, token, err := codespaces.GetOrChooseCodespace(ctx, apiClient, user, opts.CodespaceName)
|
||||
codespace, token, err := codespaces.GetOrChooseCodespace(ctx, apiClient, user, opts.codespaceName)
|
||||
if err != nil {
|
||||
if err == codespaces.ErrNoCodespaces {
|
||||
return err
|
||||
|
|
@ -88,17 +88,18 @@ func ports(opts *PortsOptions) error {
|
|||
}
|
||||
|
||||
devContainerResult := <-devContainerCh
|
||||
if devContainerResult.Err != nil {
|
||||
_, _ = log.Errorf("Failed to get port names: %v\n", devContainerResult.Err.Error())
|
||||
if devContainerResult.err != nil {
|
||||
// Warn about failure to read the devcontainer file. Not a ghcs command error.
|
||||
_, _ = log.Errorf("Failed to get port names: %v\n", devContainerResult.err.Error())
|
||||
}
|
||||
|
||||
table := output.NewTable(os.Stdout, opts.AsJSON)
|
||||
table := output.NewTable(os.Stdout, opts.asJSON)
|
||||
table.SetHeader([]string{"Label", "Port", "Public", "Browse URL"})
|
||||
for _, port := range ports {
|
||||
sourcePort := strconv.Itoa(port.SourcePort)
|
||||
var portName string
|
||||
if devContainerResult.DevContainer != nil {
|
||||
if attributes, ok := devContainerResult.DevContainer.PortAttributes[sourcePort]; ok {
|
||||
if devContainerResult.devContainer != nil {
|
||||
if attributes, ok := devContainerResult.devContainer.PortAttributes[sourcePort]; ok {
|
||||
portName = attributes.Label
|
||||
}
|
||||
}
|
||||
|
|
@ -130,8 +131,8 @@ func getPorts(ctx context.Context, lsclient *liveshare.Client) (liveshare.Ports,
|
|||
}
|
||||
|
||||
type devContainerResult struct {
|
||||
DevContainer *devContainer
|
||||
Err error
|
||||
devContainer *devContainer
|
||||
err error
|
||||
}
|
||||
|
||||
type devContainer struct {
|
||||
|
|
@ -173,9 +174,8 @@ func getDevContainer(ctx context.Context, apiClient *api.API, codespace *api.Cod
|
|||
return ch
|
||||
}
|
||||
|
||||
// NewPortsPublicCmd returns a Cobra "ports public" subcommand, which makes a given port public.
|
||||
// to make a given port public.
|
||||
func NewPortsPublicCmd() *cobra.Command {
|
||||
// newPortsPublicCmd returns a Cobra "ports public" subcommand, which makes a given port public.
|
||||
func newPortsPublicCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "public <codespace> <port>",
|
||||
Short: "Mark port as public",
|
||||
|
|
@ -187,8 +187,8 @@ func NewPortsPublicCmd() *cobra.Command {
|
|||
}
|
||||
}
|
||||
|
||||
// NewPortsPrivateCmd returns a Cobra "ports private" subcommand, which makes a given port private.
|
||||
func NewPortsPrivateCmd() *cobra.Command {
|
||||
// newPortsPrivateCmd returns a Cobra "ports private" subcommand, which makes a given port private.
|
||||
func newPortsPrivateCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "private <codespace> <port>",
|
||||
Short: "Mark port as private",
|
||||
|
|
@ -249,7 +249,7 @@ func updatePortVisibility(log *output.Logger, codespaceName, sourcePort string,
|
|||
|
||||
// NewPortsForwardCmd returns a Cobra "ports forward" subcommand, which forwards a set of
|
||||
// port pairs from the codespace to localhost.
|
||||
func NewPortsForwardCmd() *cobra.Command {
|
||||
func newPortsForwardCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "forward <codespace> <source-port>:<destination-port>",
|
||||
Short: "Forward ports",
|
||||
|
|
@ -299,14 +299,14 @@ func forwardPorts(log *output.Logger, codespaceName string, ports []string) erro
|
|||
for _, portPair := range portPairs {
|
||||
pp := portPair
|
||||
|
||||
srcstr := strconv.Itoa(portPair.Src)
|
||||
if err := server.StartSharing(gctx, "share-"+srcstr, pp.Src); err != nil {
|
||||
srcstr := strconv.Itoa(portPair.src)
|
||||
if err := server.StartSharing(gctx, "share-"+srcstr, pp.src); err != nil {
|
||||
return fmt.Errorf("start sharing port: %v", err)
|
||||
}
|
||||
|
||||
g.Go(func() error {
|
||||
log.Println("Forwarding port: " + srcstr + " ==> " + strconv.Itoa(pp.Dst))
|
||||
portForwarder := liveshare.NewPortForwarder(lsclient, server, pp.Dst)
|
||||
log.Println("Forwarding port: " + srcstr + " ==> " + strconv.Itoa(pp.dst))
|
||||
portForwarder := liveshare.NewPortForwarder(lsclient, server, pp.dst)
|
||||
if err := portForwarder.Start(gctx); err != nil {
|
||||
return fmt.Errorf("error forwarding port: %v", err)
|
||||
}
|
||||
|
|
@ -323,16 +323,17 @@ func forwardPorts(log *output.Logger, codespaceName string, ports []string) erro
|
|||
}
|
||||
|
||||
type portPair struct {
|
||||
Src, Dst int
|
||||
src, dst int
|
||||
}
|
||||
|
||||
// getPortPairs parses a list of strings of form "%d:%d" into pairs of numbers.
|
||||
func getPortPairs(ports []string) ([]portPair, error) {
|
||||
pp := make([]portPair, 0, len(ports))
|
||||
|
||||
for _, portString := range ports {
|
||||
parts := strings.Split(portString, ":")
|
||||
if len(parts) < 2 {
|
||||
return pp, fmt.Errorf("port pair: '%v' is not valid", portString)
|
||||
return nil, fmt.Errorf("port pair: '%v' is not valid", portString)
|
||||
}
|
||||
|
||||
srcp, err := strconv.Atoi(parts[0])
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewSSHCmd() *cobra.Command {
|
||||
func newSSHCmd() *cobra.Command {
|
||||
var sshProfile, codespaceName string
|
||||
var sshServerPort int
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ func NewSSHCmd() *cobra.Command {
|
|||
Short: "SSH into a Codespace",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return SSH(sshProfile, codespaceName, sshServerPort)
|
||||
return ssh(sshProfile, codespaceName, sshServerPort)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -36,10 +36,10 @@ func NewSSHCmd() *cobra.Command {
|
|||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(NewSSHCmd())
|
||||
rootCmd.AddCommand(newSSHCmd())
|
||||
}
|
||||
|
||||
func SSH(sshProfile, codespaceName string, sshServerPort int) error {
|
||||
func ssh(sshProfile, codespaceName string, sshServerPort int) error {
|
||||
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
|
||||
ctx := context.Background()
|
||||
log := output.NewLogger(os.Stdout, os.Stderr, false)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
|
|
@ -25,7 +26,9 @@ func ChooseCodespace(ctx context.Context, apiClient *api.API, user *api.User) (*
|
|||
return nil, ErrNoCodespaces
|
||||
}
|
||||
|
||||
codespaces.SortByCreatedAt()
|
||||
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))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue