Skip to content

Commit e174983

Browse files
author
Stefano Ciarcià
committed
feat: add convenience flags to use and install commands
1 parent a3a4961 commit e174983

File tree

6 files changed

+111
-98
lines changed

6 files changed

+111
-98
lines changed

.vscode/launch.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@
1111
"mode": "debug",
1212
"program": "${workspaceFolder}/main.go",
1313
"args": [
14-
"kubectl",
15-
"list-remote",
16-
// "--force",
17-
// "--limit",
18-
// "10"
14+
"talosctl",
15+
"use",
16+
"v1.11.4",
17+
"-i"
1918
],
2019
"env": {
2120
"GITHUB_TOKEN": "<my-token>"

internal/cli/common/command.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ func InitCommand(cmd *cobra.Command, tool string, repoConf github.RepoConfDef) {
1212
// list-remote
1313
cmd.AddCommand(newGithubListRemoteCommand(tool, repoConf))
1414
// install
15-
if repoConf.DownloadURL == "" {
16-
cmd.AddCommand(newGithubInstallCommand(tool, repoConf))
17-
} else {
18-
cmd.AddCommand(newDownloadInstallCommand(tool, repoConf))
15+
installType := InstallGitHubCmd
16+
if repoConf.DownloadURL != "" {
17+
installType = InstallDownloadCmd
1918
}
19+
cmd.AddCommand(newInstallCommand(tool, repoConf, installType))
2020
// use
2121
cmd.AddCommand(newUseCommand(tool))
2222
}

internal/cli/common/command_test.go

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package common
22

33
import (
4-
"reflect"
5-
"runtime"
6-
"strings"
74
"testing"
85

96
"github.com/spf13/cobra"
@@ -20,43 +17,6 @@ func findSubcmd(root *cobra.Command, use string) *cobra.Command {
2017
return nil
2118
}
2219

23-
func TestInitCommand_SelectsDownloadInstallWhenDownloadURLSet(t *testing.T) {
24-
root := &cobra.Command{Use: "root"}
25-
repo := github.RepoConfDef{DownloadURL: "https://example.com/foo.zip"}
26-
InitCommand(root, "foo", repo)
27-
28-
inst := findSubcmd(root, "install <version>")
29-
if inst == nil {
30-
t.Fatalf("install subcommand not found")
31-
}
32-
33-
if inst.RunE == nil {
34-
t.Fatalf("install subcommand has nil RunE")
35-
}
36-
name := runtime.FuncForPC(reflect.ValueOf(inst.RunE).Pointer()).Name()
37-
if !strings.Contains(name, "newDownloadInstallCommand") {
38-
t.Fatalf("expected install RunE to come from newDownloadInstallCommand, got %s", name)
39-
}
40-
}
41-
42-
func TestInitCommand_SelectsGithubInstallWhenDownloadURLEmpty(t *testing.T) {
43-
root := &cobra.Command{Use: "root"}
44-
repo := github.RepoConfDef{DownloadURL: ""}
45-
InitCommand(root, "bar", repo)
46-
47-
inst := findSubcmd(root, "install <version>")
48-
if inst == nil {
49-
t.Fatalf("install subcommand not found")
50-
}
51-
if inst.RunE == nil {
52-
t.Fatalf("install subcommand has nil RunE")
53-
}
54-
name := runtime.FuncForPC(reflect.ValueOf(inst.RunE).Pointer()).Name()
55-
if !strings.Contains(name, "newGithubInstallCommand") {
56-
t.Fatalf("expected install RunE to come from newGithubInstallCommand, got %s", name)
57-
}
58-
}
59-
6020
func TestInitCommand_RegistersCommonSubcommands(t *testing.T) {
6121
root := &cobra.Command{Use: "root"}
6222
repo := github.RepoConfDef{}

internal/cli/common/install.go

Lines changed: 69 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,76 +9,105 @@ import (
99
"github.com/stepbeta/vrsr/internal/utils"
1010
)
1111

12+
var useOnInstall bool
13+
14+
type InstallCmdType int
15+
16+
const (
17+
InstallGitHubCmd InstallCmdType = iota
18+
InstallDownloadCmd
19+
)
20+
1221
// newGithubInstallCommand creates a new 'install' command for the specified tool.
13-
// Installation is done via GitHub releases.
14-
func newGithubInstallCommand(tool string, repoConf github.RepoConfDef) *cobra.Command {
15-
return &cobra.Command{
22+
func newInstallCommand(tool string, repoConf github.RepoConfDef, installType InstallCmdType) *cobra.Command {
23+
installCmd := &cobra.Command{
1624
Use: "install <version>",
1725
Short: fmt.Sprintf("Download and install %s for the current OS/ARCH", tool),
1826
Long: fmt.Sprintf("Download the %s binary for the current OS/ARCH at the specified version.\n\n"+
1927
"This binary will be saved into the path specified by the \"bin-path\" flag. It will be named \"%s-$version\".\n\n"+
2028
"Make sure to check the \"use <version>\" command after installing a new version", tool, tool),
2129
Args: cobra.ExactArgs(1),
2230
RunE: func(cmd *cobra.Command, args []string) error {
23-
return installGithub(cmd, args[0], tool, repoConf)
31+
skipMsg := len(args) > 1 && args[1] == "true"
32+
return install(cmd, args[0], tool, repoConf, installType, skipMsg)
2433
},
2534
}
35+
// Bind flags to Viper keys so config file / env / flags work together.
36+
installCmd.Flags().BoolVarP(&useOnInstall, "use", "u", false, "Immediately use the version once installed (best effort)")
37+
if err := viper.BindPFlag(fmt.Sprintf("%s.install.use", tool), installCmd.Flags().Lookup("use")); err != nil {
38+
installCmd.PrintErr(err)
39+
panic(err)
40+
}
41+
return installCmd
2642
}
2743

28-
// installGithub downloads and installs the specified version of the tool from GitHub releases
29-
func installGithub(cmd *cobra.Command, vrs, tool string, repoConf github.RepoConfDef) error {
44+
// install downloads and installs the specified version of the tool from GitHub releases
45+
func install(cmd *cobra.Command, vrs, tool string, repoConf github.RepoConfDef, installType InstallCmdType, skipMsg bool) error {
3046
if utils.IsToolInUse(tool, vrs) {
31-
cmd.Printf("%s version %s is already in use. Nothing to do\n", tool, vrs)
47+
cmd.Printf("%s version %s is already installed and in use. Nothing to do\n", tool, vrs)
3248
return nil
3349
}
50+
useOnInstall = viper.GetBool(tool + ".install.use")
3451
if utils.IsToolInstalled(tool, vrs) {
3552
cmd.Printf("%s version %s is already installed.\n", tool, vrs)
36-
cmd.Printf("To switch to that version run `vrsr %s use %s`\n", tool, vrs)
37-
return nil
53+
if !useOnInstall {
54+
if !skipMsg {
55+
cmd.Printf("To switch to that version run `vrsr %s use %s`\n", tool, vrs)
56+
}
57+
return nil
58+
}
59+
if err := useOnInstallFn(cmd, vrs, tool); err != nil {
60+
return err
61+
}
3862
}
3963

4064
vrsPath := viper.GetString("vrs-path")
41-
ghc := github.New(nil)
42-
if err := ghc.DownloadRelease(tool, vrs, vrsPath, repoConf); err != nil {
43-
return err
65+
// depending on the install type we use the appropriate install method
66+
switch installType {
67+
case InstallGitHubCmd:
68+
ghc := github.New(nil)
69+
if err := ghc.DownloadRelease(tool, vrs, vrsPath, repoConf); err != nil {
70+
return err
71+
}
72+
case InstallDownloadCmd:
73+
if err := utils.DownloadBinary(repoConf.DownloadURL, tool, vrs, vrsPath, repoConf.Zipped); err != nil {
74+
return err
75+
}
76+
default:
77+
return fmt.Errorf("unknown install type")
4478
}
45-
4679
cmd.Printf("%s version %s successfully installed\n", tool, vrs)
47-
cmd.Printf("To switch to that version run `vrsr %s use %s`\n", tool, vrs)
48-
return nil
49-
}
5080

51-
// newDownloadInstallCommand creates a new 'install' command for the specified tool
52-
func newDownloadInstallCommand(tool string, repoConf github.RepoConfDef) *cobra.Command {
53-
return &cobra.Command{
54-
Use: "install <version>",
55-
Short: fmt.Sprintf("Download and install %s for the current OS/ARCH", tool),
56-
Long: fmt.Sprintf("Download the %s binary for the current OS/ARCH at the specified version.\n\n"+
57-
"This binary will be saved into the path specified by the \"bin-path\" flag. It will be named \"%s-$version\".\n\n"+
58-
"Make sure to check the \"use <version>\" command after installing a new version", tool, tool),
59-
Args: cobra.ExactArgs(1),
60-
RunE: func(cmd *cobra.Command, args []string) error {
61-
return installDownload(cmd, args[0], tool, repoConf)
62-
},
81+
if !useOnInstall {
82+
if !skipMsg {
83+
cmd.Printf("To switch to that version run `vrsr %s use %s`\n", tool, vrs)
84+
}
85+
return nil
6386
}
87+
return useOnInstallFn(cmd, vrs, tool)
6488
}
6589

66-
// installDownload downloads and installs the specified version of the tool from GitHub releases
67-
func installDownload(cmd *cobra.Command, vrs, tool string, repoConf github.RepoConfDef) error {
68-
if utils.IsToolInUse(tool, vrs) {
69-
cmd.Printf("%s version %s is already in use. Nothing to do\n", tool, vrs)
70-
return nil
90+
// useOnInstallFn attempts to use the installed version immediately
91+
func useOnInstallFn(cmd *cobra.Command, vrs, tool string) error {
92+
pCmd := cmd.Parent()
93+
if pCmd == nil {
94+
// this is a sign of something wrong, so we return an error
95+
return fmt.Errorf("internal error: install command has no parent")
7196
}
72-
if utils.IsToolInstalled(tool, vrs) {
73-
cmd.Printf("%s version %s is already installed.\n", tool, vrs)
97+
uCmd, _, err := pCmd.Find([]string{"use"})
98+
if err != nil || uCmd.Name() != "use" {
99+
cmd.Println("could not find sibling 'use' command")
100+
cmd.Println("Skipping action")
74101
cmd.Printf("To switch to that version run `vrsr %s use %s`\n", tool, vrs)
102+
// this is a best effort, if there's an error we just return
75103
return nil
76104
}
77-
vrsPath := viper.GetString("vrs-path")
78-
if err := utils.DownloadBinary(repoConf.DownloadURL, tool, vrs, vrsPath, repoConf.Zipped); err != nil {
79-
return err
105+
if err := uCmd.RunE(uCmd, []string{vrs}); err != nil {
106+
cmd.Println("Error executing use:", err)
107+
cmd.Println("Skipping action")
108+
cmd.Printf("To switch to that version run `vrsr %s use %s`\n", tool, vrs)
109+
// this is a best effort, if there's an error we just return
110+
return nil
80111
}
81-
cmd.Printf("%s version %s successfully installed\n", tool, vrs)
82-
cmd.Printf("To switch to that version run `vrsr %s use %s`\n", tool, vrs)
83112
return nil
84113
}

internal/cli/common/install_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,18 @@ func TestInstallCommands_EarlyReturnWhenInUseOrInstalled(t *testing.T) {
4141
viper.Set("vrs-path", vrsPath)
4242
viper.Set("bin-path", binPath)
4343

44-
// calling installGithub should return early (tool already in use)
45-
if err := installGithub(&cobra.Command{}, "1.2.3", "mytool", github.RepoConfDef{}); err != nil {
46-
t.Fatalf("installGithub expected nil error when tool in use, got: %v", err)
44+
// calling install Github should return early (tool already in use)
45+
if err := install(&cobra.Command{}, "1.2.3", "mytool", github.RepoConfDef{}, InstallGitHubCmd, false); err != nil {
46+
t.Fatalf("install Github expected nil error when tool in use, got: %v", err)
4747
}
4848

4949
// remove symlink to test IsToolInstalled early return
5050
if err := os.Remove(symlink); err != nil {
5151
t.Fatalf("failed to remove symlink: %v", err)
5252
}
5353

54-
// calling installDownload should return early (tool already installed)
55-
if err := installDownload(&cobra.Command{}, "1.2.3", "mytool", github.RepoConfDef{}); err != nil {
56-
t.Fatalf("installDownload expected nil error when tool installed, got: %v", err)
54+
// calling install Download should return early (tool already installed)
55+
if err := install(&cobra.Command{}, "1.2.3", "mytool", github.RepoConfDef{}, InstallDownloadCmd, false); err != nil {
56+
t.Fatalf("install Download expected nil error when tool installed, got: %v", err)
5757
}
5858
}

internal/cli/common/use.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import (
1212
)
1313

1414
var (
15+
installOnUse bool
1516
errVrsNotFound = errors.New("version not found")
1617
)
1718

1819
// newUseCommand creates a new 'use' command for the specified tool
1920
func newUseCommand(tool string) *cobra.Command {
20-
return &cobra.Command{
21+
useCmd := &cobra.Command{
2122
Use: "use <version>",
2223
Short: fmt.Sprintf("Set the specified %s version as the active one", tool),
2324
Long: fmt.Sprintf(`Create a symlink to the specified version with the name "%s".\n\nMake sure the "bin-path" is included in the $PATH variable.`, tool),
@@ -26,6 +27,13 @@ func newUseCommand(tool string) *cobra.Command {
2627
return use(cmd, args[0], tool)
2728
},
2829
}
30+
// Bind flags to Viper keys so config file / env / flags work together.
31+
useCmd.Flags().BoolVarP(&installOnUse, "install", "i", false, "Install the version if not yet present (best effort)")
32+
if err := viper.BindPFlag(fmt.Sprintf("%s.use.install", tool), useCmd.Flags().Lookup("install")); err != nil {
33+
useCmd.PrintErr(err)
34+
panic(err)
35+
}
36+
return useCmd
2937
}
3038

3139
// use sets the specified version of the tool as the active one
@@ -39,8 +47,25 @@ func use(cmd *cobra.Command, vrs, tool string) error {
3947
vrsPath := viper.GetString("vrs-path")
4048
fileName := filepath.Join(vrsPath, tool, tool+"-"+vrs)
4149
if _, err := os.Stat(fileName); errors.Is(err, os.ErrNotExist) {
42-
cmd.Printf("Error: specified version is not installed. Please install it first using `vrsr %s install <version>`", tool)
43-
return errVrsNotFound
50+
installOnUse = viper.GetBool(tool + ".use.install")
51+
if !installOnUse {
52+
cmd.Printf("Error: specified version is not installed. Please install it first using `vrsr %s install <version>`", tool)
53+
return errVrsNotFound
54+
}
55+
pCmd := cmd.Parent()
56+
if pCmd == nil {
57+
return fmt.Errorf("internal error: use command has no parent")
58+
}
59+
iCmd, _, err := pCmd.Find([]string{"install"})
60+
if err != nil || iCmd.Name() != "install" {
61+
return fmt.Errorf("could not find sibling 'install' command")
62+
}
63+
if err := iCmd.RunE(iCmd, []string{vrs, "true"}); err != nil {
64+
cmd.Println("Error executing install:", err)
65+
cmd.Println("Skipping action")
66+
return err
67+
}
68+
// here we should have installed the version, we assume it succeeded
4469
}
4570
target := filepath.Join(binPath, tool)
4671
// Check if the symlink already exists

0 commit comments

Comments
 (0)