-
Notifications
You must be signed in to change notification settings - Fork 60
/
update.go
173 lines (152 loc) · 5.02 KB
/
update.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package updatecmd
import (
"errors"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/ava-labs/avalanche-cli/pkg/application"
"github.com/ava-labs/avalanche-cli/pkg/binutils"
"github.com/ava-labs/avalanche-cli/pkg/constants"
"github.com/ava-labs/avalanche-cli/pkg/ux"
"github.com/spf13/cobra"
"go.uber.org/zap"
"golang.org/x/mod/semver"
)
var (
ErrUserAbortedInstallation = errors.New("user canceled installation")
ErrNoVersion = errors.New("failed to find current version - did you install following official instructions?")
app *application.Avalanche
yes bool
)
func NewCmd(injectedApp *application.Avalanche, version string) *cobra.Command {
app = injectedApp
cmd := &cobra.Command{
Use: "update",
Short: "Check for latest updates of Avalanche-CLI",
Long: `Check if an update is available, and prompt the user to install it`,
RunE: runUpdate,
Args: cobra.ExactArgs(0),
SilenceUsage: true,
Version: version,
}
cmd.Flags().BoolVarP(&yes, "confirm", "c", false, "Assume yes for installation")
return cmd
}
func runUpdate(cmd *cobra.Command, _ []string) error {
isUserCalled := true
return Update(cmd, isUserCalled, "", &application.LastActions{})
}
func Update(cmd *cobra.Command, isUserCalled bool, version string, lastActs *application.LastActions) error {
// first check if there is a new version exists
url := binutils.GetGithubLatestReleaseURL(constants.AvaLabsOrg, constants.CliRepoName)
latest, err := app.Downloader.GetLatestReleaseVersion(url)
if err != nil {
app.Log.Warn("failed to get latest version for cli from repo", zap.Error(err))
return err
}
if lastActs == nil {
lastActs = &application.LastActions{}
}
lastActs.LastCheckGit = time.Now()
app.WriteLastActionsFile(lastActs)
// the current version info should be in this variable
this := cmd.Version
if this == "" {
if version != "" {
this = version
} else {
// try loading from file system
verFile := "VERSION"
bver, err := os.ReadFile(verFile)
if err != nil {
app.Log.Warn("failed to read version from file on disk", zap.Error(err))
return ErrNoVersion
}
this = strings.TrimSpace(string(bver))
}
}
thisVFmt := "v" + this
// check this version needs update
// we skip if compare returns -1 (latest < this)
// or 0 (latest == this)
if semver.Compare(latest, thisVFmt) < 1 {
txt := "No new version found upstream; skipping update"
app.Log.Debug(txt)
if isUserCalled {
ux.Logger.PrintToUser(txt)
}
return nil
}
// flag not provided
if !yes {
ux.Logger.PrintToUser("We found a new version of Avalanche-CLI %s upstream. You are running %s", latest, thisVFmt)
y, err := app.Prompt.CaptureYesNo("Do you want to update?")
if err != nil {
return nil
}
if !y {
ux.Logger.PrintToUser("Aborted by user")
return ErrUserAbortedInstallation
}
}
// where is the tool running from?
ex, err := os.Executable()
if err != nil {
return err
}
execPath := filepath.Dir(ex)
defaultDir := filepath.Join(os.ExpandEnv("$HOME"), "bin")
/* #nosec G204 */
downloadCmd := exec.Command("curl", "-sSfL", constants.CliInstallationURL)
// -s is for the sh command, -- separates the args for our install script,
// -n skips shell completion installation, which would result in an error,
// as it requires to launch the binary, but we are already executing it
installCmdArgs := []string{"-s", "--", "-n"}
// custom installation path
if execPath != defaultDir {
installCmdArgs = append(installCmdArgs, "-b", execPath)
}
app.Log.Debug("installing new version", zap.String("path", execPath))
installCmd := exec.Command("sh", installCmdArgs...)
// redirect the download command to the install
installCmd.Stdin, err = downloadCmd.StdoutPipe()
if err != nil {
return err
}
// we are going to collect the output from the command into a string
// instead of writing directly to the string
var outbuf, errbuf strings.Builder
installCmd.Stdout = &outbuf
installCmd.Stderr = &errbuf
ux.Logger.PrintToUser("Starting update...")
if err := installCmd.Start(); err != nil {
return err
}
ux.Logger.PrintToUser("Downloading new release...")
if err := downloadCmd.Run(); err != nil {
return err
}
ux.Logger.PrintToUser("Installing new release...")
if err := installCmd.Wait(); err != nil {
ux.Logger.PrintToUser("installation failed: %s", err.Error())
return err
}
// write to file when last updated
lastActs, err = app.ReadLastActionsFile()
if err != nil {
if errors.Is(err, os.ErrNotExist) {
lastActs = &application.LastActions{}
}
}
lastActs.LastUpdated = time.Now()
app.WriteLastActionsFile(lastActs)
app.Log.Debug(outbuf.String())
app.Log.Debug(errbuf.String())
ux.Logger.PrintToUser("Installation successful. Please run the shell completion update manually after this process terminates.")
ux.Logger.PrintToUser("The new version will be used on next command execution")
return nil
}