From c751e88120baa4f25ec978a092e308d915f95cc3 Mon Sep 17 00:00:00 2001 From: Camilo Garcia La Rotta Date: Wed, 21 Jul 2021 19:56:08 -0400 Subject: [PATCH 1/6] feat: introduce repo, branch and machine flags for ghcs create --- cmd/ghcs/create.go | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/cmd/ghcs/create.go b/cmd/ghcs/create.go index 44bedb5f2..35cd49a2d 100644 --- a/cmd/ghcs/create.go +++ b/cmd/ghcs/create.go @@ -12,17 +12,45 @@ import ( "github.com/spf13/cobra" ) -var createCmd = &cobra.Command{ - Use: "create", - Short: "Create", - Long: "Create", - RunE: func(cmd *cobra.Command, args []string) error { - return Create() - }, +var repo, branch, machine string + +type machineType string + +const ( + basicMachine machineType = "basic" + standardMachine machineType = "standard" + premiumMachine machineType = "premium" + ExtremeMachine machineType = "extreme" +) + +func newCreateCmd() *cobra.Command { + createCmd := &cobra.Command{ + Use: "create", + Short: "Create a codespace", + Long: `Create a codespace for a given repository and branch. +You must also choose the type of machine to use.`, + RunE: func(cmd *cobra.Command, args []string) error { + if machine != "" { + switch machineType(machine) { + case basicMachine, standardMachine, premiumMachine, ExtremeMachine: + break + default: + return fmt.Errorf("invalid machine type: %s", machine) + } + } + return Create() + }, + } + + createCmd.Flags().StringVarP(&repo, "repo", "r", "", "repository name with owner: user/repo") + createCmd.Flags().StringVarP(&branch, "branch", "b", "", "repository branch") + createCmd.Flags().StringVarP(&machine, "machine", "m", "", "hardware specifications for the VM. Can be: basic, standard, premium, extreme") + + return createCmd } func init() { - rootCmd.AddCommand(createCmd) + rootCmd.AddCommand(newCreateCmd()) } var createSurvey = []*survey.Question{ From aab98ccc18fcaa74c7c59bdb52b6b4a11fa3b7e2 Mon Sep 17 00:00:00 2001 From: Camilo Garcia La Rotta Date: Wed, 21 Jul 2021 20:06:05 -0400 Subject: [PATCH 2/6] feat: break out repo and branch surveys --- cmd/ghcs/create.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/cmd/ghcs/create.go b/cmd/ghcs/create.go index 35cd49a2d..cc7436e6f 100644 --- a/cmd/ghcs/create.go +++ b/cmd/ghcs/create.go @@ -53,12 +53,14 @@ func init() { rootCmd.AddCommand(newCreateCmd()) } -var createSurvey = []*survey.Question{ +var repoSurvey = []*survey.Question{ { Name: "repository", Prompt: &survey.Input{Message: "Repository"}, Validate: survey.Required, }, +} +var branchSurvey = []*survey.Question{ { Name: "branch", Prompt: &survey.Input{Message: "Branch"}, @@ -72,16 +74,19 @@ func Create() error { locationCh := getLocation(ctx, apiClient) userCh := getUser(ctx, apiClient) - answers := struct { - Repository string - Branch string - }{} - - if err := survey.Ask(createSurvey, &answers); err != nil { - return fmt.Errorf("error getting answers: %v", err) + if repo == "" { + if err := survey.Ask(repoSurvey, &repo); err != nil { + return fmt.Errorf("error getting repository name: %v", err) + } } - repository, err := apiClient.GetRepository(ctx, answers.Repository) + if branch == "" { + if err := survey.Ask(branchSurvey, &branch); err != nil { + return fmt.Errorf("error getting branch name: %v", err) + } + } + + repository, err := apiClient.GetRepository(ctx, repo) if err != nil { return fmt.Errorf("error getting repository: %v", err) } @@ -136,7 +141,7 @@ func Create() error { sku := skuByName[skuAnswers.SKU] fmt.Println("Creating your codespace...") - codespace, err := apiClient.CreateCodespace(ctx, userResult.User, repository, sku, answers.Branch, locationResult.Location) + codespace, err := apiClient.CreateCodespace(ctx, userResult.User, repository, sku, branch, locationResult.Location) if err != nil { return fmt.Errorf("error creating codespace: %v", err) } From 3db217fef063ce2bc877d6d11f184ca5b4bc696e Mon Sep 17 00:00:00 2001 From: Camilo Garcia La Rotta Date: Wed, 21 Jul 2021 20:27:22 -0400 Subject: [PATCH 3/6] feat: make sku survey optional --- api/api.go | 4 +-- cmd/ghcs/create.go | 73 +++++++++++++++++++--------------------------- 2 files changed, 32 insertions(+), 45 deletions(-) diff --git a/api/api.go b/api/api.go index 8bf5e155d..bf16260ae 100644 --- a/api/api.go +++ b/api/api.go @@ -341,8 +341,8 @@ type createCodespaceRequest struct { SkuName string `json:"sku_name"` } -func (a *API) CreateCodespace(ctx context.Context, user *User, repository *Repository, sku *Sku, branch, location string) (*Codespace, error) { - requestBody, err := json.Marshal(createCodespaceRequest{repository.ID, branch, location, sku.Name}) +func (a *API) CreateCodespace(ctx context.Context, user *User, repository *Repository, sku, branch, location string) (*Codespace, error) { + requestBody, err := json.Marshal(createCodespaceRequest{repository.ID, branch, location, sku}) if err != nil { return nil, fmt.Errorf("error marshaling request: %v", err) } diff --git a/cmd/ghcs/create.go b/cmd/ghcs/create.go index cc7436e6f..0b0d9ebd3 100644 --- a/cmd/ghcs/create.go +++ b/cmd/ghcs/create.go @@ -14,15 +14,6 @@ import ( var repo, branch, machine string -type machineType string - -const ( - basicMachine machineType = "basic" - standardMachine machineType = "standard" - premiumMachine machineType = "premium" - ExtremeMachine machineType = "extreme" -) - func newCreateCmd() *cobra.Command { createCmd := &cobra.Command{ Use: "create", @@ -30,21 +21,13 @@ func newCreateCmd() *cobra.Command { Long: `Create a codespace for a given repository and branch. You must also choose the type of machine to use.`, RunE: func(cmd *cobra.Command, args []string) error { - if machine != "" { - switch machineType(machine) { - case basicMachine, standardMachine, premiumMachine, ExtremeMachine: - break - default: - return fmt.Errorf("invalid machine type: %s", machine) - } - } return Create() }, } createCmd.Flags().StringVarP(&repo, "repo", "r", "", "repository name with owner: user/repo") createCmd.Flags().StringVarP(&branch, "branch", "b", "", "repository branch") - createCmd.Flags().StringVarP(&machine, "machine", "m", "", "hardware specifications for the VM. Can be: basic, standard, premium, extreme") + createCmd.Flags().StringVarP(&machine, "machine", "m", "", "hardware specifications for the VM") return createCmd } @@ -111,37 +94,41 @@ func Create() error { return nil } - skuNames := make([]string, 0, len(skus)) - skuByName := make(map[string]*api.Sku) - for _, sku := range skus { - nameParts := camelcase.Split(sku.Name) - machineName := strings.Title(strings.ToLower(nameParts[0])) - skuName := fmt.Sprintf("%s - %s", machineName, sku.DisplayName) - skuNames = append(skuNames, skuName) - skuByName[skuName] = sku - } + if machine == "" { + skuNames := make([]string, 0, len(skus)) + skuByName := make(map[string]*api.Sku) + for _, sku := range skus { + nameParts := camelcase.Split(sku.Name) + machineName := strings.Title(strings.ToLower(nameParts[0])) + skuName := fmt.Sprintf("%s - %s", machineName, sku.DisplayName) + skuNames = append(skuNames, skuName) + skuByName[skuName] = sku + } - skuSurvey := []*survey.Question{ - { - Name: "sku", - Prompt: &survey.Select{ - Message: "Choose Machine Type:", - Options: skuNames, - Default: skuNames[0], + skuSurvey := []*survey.Question{ + { + Name: "sku", + Prompt: &survey.Select{ + Message: "Choose Machine Type:", + Options: skuNames, + Default: skuNames[0], + }, + Validate: survey.Required, }, - Validate: survey.Required, - }, + } + + skuAnswers := struct{ SKU string }{} + if err := survey.Ask(skuSurvey, &skuAnswers); err != nil { + return fmt.Errorf("error getting SKU: %v", err) + } + + sku := skuByName[skuAnswers.SKU] + machine = sku.Name } - skuAnswers := struct{ SKU string }{} - if err := survey.Ask(skuSurvey, &skuAnswers); err != nil { - return fmt.Errorf("error getting SKU: %v", err) - } - - sku := skuByName[skuAnswers.SKU] fmt.Println("Creating your codespace...") - codespace, err := apiClient.CreateCodespace(ctx, userResult.User, repository, sku, branch, locationResult.Location) + codespace, err := apiClient.CreateCodespace(ctx, userResult.User, repository, machine, branch, locationResult.Location) if err != nil { return fmt.Errorf("error creating codespace: %v", err) } From a68cda14698887808309dda7eaa0f11ed7c51829 Mon Sep 17 00:00:00 2001 From: Camilo Garcia La Rotta Date: Wed, 21 Jul 2021 20:54:18 -0400 Subject: [PATCH 4/6] refactor: break down Create() into smaller funcs --- cmd/ghcs/create.go | 153 +++++++++++++++++++++++++++------------------ 1 file changed, 93 insertions(+), 60 deletions(-) diff --git a/cmd/ghcs/create.go b/cmd/ghcs/create.go index 0b0d9ebd3..128a5ea44 100644 --- a/cmd/ghcs/create.go +++ b/cmd/ghcs/create.go @@ -36,37 +36,19 @@ func init() { rootCmd.AddCommand(newCreateCmd()) } -var repoSurvey = []*survey.Question{ - { - Name: "repository", - Prompt: &survey.Input{Message: "Repository"}, - Validate: survey.Required, - }, -} -var branchSurvey = []*survey.Question{ - { - Name: "branch", - Prompt: &survey.Input{Message: "Branch"}, - Validate: survey.Required, - }, -} - func Create() error { ctx := context.Background() apiClient := api.New(os.Getenv("GITHUB_TOKEN")) locationCh := getLocation(ctx, apiClient) userCh := getUser(ctx, apiClient) - if repo == "" { - if err := survey.Ask(repoSurvey, &repo); err != nil { - return fmt.Errorf("error getting repository name: %v", err) - } + repo, err := getRepoName() + if err != nil { + return fmt.Errorf("error getting repository name: %v", err) } - - if branch == "" { - if err := survey.Ask(branchSurvey, &branch); err != nil { - return fmt.Errorf("error getting branch name: %v", err) - } + branch, err := getBranchName() + if err != nil { + return fmt.Errorf("error getting branch name: %v", err) } repository, err := apiClient.GetRepository(ctx, repo) @@ -84,48 +66,15 @@ func Create() error { return fmt.Errorf("error getting codespace user: %v", userResult.Err) } - skus, err := apiClient.GetCodespacesSkus(ctx, userResult.User, repository, locationResult.Location) + machine, err := getMachineName(ctx, userResult.User, repository, locationResult.Location, apiClient) if err != nil { - return fmt.Errorf("error getting codespace skus: %v", err) + return fmt.Errorf("error getting machine type: %v", err) } - - if len(skus) == 0 { + if machine == "" { fmt.Println("There are no available machine types for this repository") return nil } - if machine == "" { - skuNames := make([]string, 0, len(skus)) - skuByName := make(map[string]*api.Sku) - for _, sku := range skus { - nameParts := camelcase.Split(sku.Name) - machineName := strings.Title(strings.ToLower(nameParts[0])) - skuName := fmt.Sprintf("%s - %s", machineName, sku.DisplayName) - skuNames = append(skuNames, skuName) - skuByName[skuName] = sku - } - - skuSurvey := []*survey.Question{ - { - Name: "sku", - Prompt: &survey.Select{ - Message: "Choose Machine Type:", - Options: skuNames, - Default: skuNames[0], - }, - Validate: survey.Required, - }, - } - - skuAnswers := struct{ SKU string }{} - if err := survey.Ask(skuSurvey, &skuAnswers); err != nil { - return fmt.Errorf("error getting SKU: %v", err) - } - - sku := skuByName[skuAnswers.SKU] - machine = sku.Name - } - fmt.Println("Creating your codespace...") codespace, err := apiClient.CreateCodespace(ctx, userResult.User, repository, machine, branch, locationResult.Location) @@ -165,3 +114,87 @@ func getLocation(ctx context.Context, apiClient *api.API) <-chan locationResult }() return ch } + +func getRepoName() (string, error) { + if repo != "" { + return repo, nil + } + + repoSurvey := []*survey.Question{ + { + Name: "repository", + Prompt: &survey.Input{Message: "Repository"}, + Validate: survey.Required, + }, + } + err := survey.Ask(repoSurvey, &repo) + return repo, err +} + +func getBranchName() (string, error) { + if branch != "" { + return branch, nil + } + + branchSurvey := []*survey.Question{ + { + Name: "branch", + Prompt: &survey.Input{Message: "Branch"}, + Validate: survey.Required, + }, + } + err := survey.Ask(branchSurvey, &branch) + return branch, err +} + +func getMachineName(ctx context.Context, user *api.User, repo *api.Repository, location string, apiClient *api.API) (string, error) { + skus, err := apiClient.GetCodespacesSkus(ctx, user, repo, location) + if err != nil { + return "", fmt.Errorf("error getting codespace skus: %v", err) + } + + // 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 { + return machine, nil + } + } + return "", fmt.Errorf("there are is no such machine for the repository: %s", machine) + } else if len(skus) == 0 { + return "", nil + } + + skuNames := make([]string, 0, len(skus)) + skuByName := make(map[string]*api.Sku) + for _, sku := range skus { + nameParts := camelcase.Split(sku.Name) + machineName := strings.Title(strings.ToLower(nameParts[0])) + skuName := fmt.Sprintf("%s - %s", machineName, sku.DisplayName) + skuNames = append(skuNames, skuName) + skuByName[skuName] = sku + } + + skuSurvey := []*survey.Question{ + { + Name: "sku", + Prompt: &survey.Select{ + Message: "Choose Machine Type:", + Options: skuNames, + Default: skuNames[0], + }, + Validate: survey.Required, + }, + } + + skuAnswers := struct{ SKU string }{} + if err := survey.Ask(skuSurvey, &skuAnswers); err != nil { + return "", fmt.Errorf("error getting SKU: %v", err) + } + + sku := skuByName[skuAnswers.SKU] + machine = sku.Name + + return machine, nil +} From 3ef0226e20e64088c4755b7b91a0d02a5fccf697 Mon Sep 17 00:00:00 2001 From: Camilo Garcia La Rotta Date: Thu, 22 Jul 2021 10:07:09 -0400 Subject: [PATCH 5/6] fix: output available machine names on --machine error --- cmd/ghcs/create.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/ghcs/create.go b/cmd/ghcs/create.go index 128a5ea44..b23e5631b 100644 --- a/cmd/ghcs/create.go +++ b/cmd/ghcs/create.go @@ -161,7 +161,13 @@ func getMachineName(ctx context.Context, user *api.User, repo *api.Repository, l return machine, nil } } - return "", fmt.Errorf("there are is no such machine for the repository: %s", machine) + + availableSkus := make([]string, len(skus)) + for i := 0; i < len(skus); i++ { + availableSkus[i] = skus[i].Name + } + + return "", fmt.Errorf("there are is no such machine for the repository: %s\nAvailable machines: %v", machine, availableSkus) } else if len(skus) == 0 { return "", nil } From 14468baba6d84fca7546e44e8d7633f0968c8aa5 Mon Sep 17 00:00:00 2001 From: Camilo Garcia La Rotta Date: Thu, 22 Jul 2021 10:13:20 -0400 Subject: [PATCH 6/6] config: bump to v0.7.0 --- cmd/ghcs/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ghcs/main.go b/cmd/ghcs/main.go index a89a544b4..0b5c001b2 100644 --- a/cmd/ghcs/main.go +++ b/cmd/ghcs/main.go @@ -15,7 +15,7 @@ var rootCmd = &cobra.Command{ Use: "ghcs", Short: "Unofficial GitHub Codespaces CLI.", Long: "Unofficial CLI tool to manage and interact with GitHub Codespaces.", - Version: "0.6.0", + Version: "0.7.0", } func Execute() {