-
Notifications
You must be signed in to change notification settings - Fork 59
/
root.go
129 lines (112 loc) · 3.83 KB
/
root.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
package update
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"github.com/fastly/cli/pkg/cmd"
"github.com/fastly/cli/pkg/config"
"github.com/fastly/cli/pkg/errors"
"github.com/fastly/cli/pkg/filesystem"
"github.com/fastly/cli/pkg/revision"
fstruntime "github.com/fastly/cli/pkg/runtime"
"github.com/fastly/cli/pkg/text"
)
// RootCommand is the parent command for all subcommands in this package.
// It should be installed under the primary root command.
type RootCommand struct {
cmd.Base
cliVersioner Versioner
configFilePath string
}
// NewRootCommand returns a new command registered in the parent.
func NewRootCommand(parent cmd.Registerer, configFilePath string, cliVersioner Versioner, globals *config.Data) *RootCommand {
var c RootCommand
c.Globals = globals
c.CmdClause = parent.Command("update", "Update the CLI to the latest version")
c.cliVersioner = cliVersioner
c.configFilePath = configFilePath
return &c
}
// Exec implements the command interface.
func (c *RootCommand) Exec(in io.Reader, out io.Writer) error {
current, latest, shouldUpdate, err := Check(context.Background(), revision.AppVersion, c.cliVersioner)
if err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]interface{}{
"App version": revision.AppVersion,
})
return fmt.Errorf("error checking for latest version: %w", err)
}
text.Break(out)
text.Output(out, "Current version: %s", current)
text.Output(out, "Latest version: %s", latest)
text.Break(out)
progress := text.NewProgress(out, c.Globals.Verbose())
progress.Step("Updating versioning information...")
err = c.Globals.File.Load(c.Globals.File.CLI.RemoteConfig, config.FilePath, c.Globals.HTTPClient)
if err != nil {
progress.Fail()
return errors.RemediationError{
Inner: fmt.Errorf("there was a problem updating the versioning information for the Fastly CLI:\n\n%w", err),
Remediation: errors.BugRemediation,
}
}
progress.Step("Checking CLI binary update...")
if !shouldUpdate {
text.Output(out, "No update required.")
return nil
}
progress.Step("Fetching latest release...")
latestPath, err := c.cliVersioner.Download(context.Background(), latest)
if err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]interface{}{
"Current CLI version": current,
"Latest CLI version": latest,
})
progress.Fail()
return fmt.Errorf("error downloading latest release: %w", err)
}
defer os.RemoveAll(latestPath)
progress.Step("Replacing binary...")
execPath, err := os.Executable()
if err != nil {
c.Globals.ErrLog.Add(err)
progress.Fail()
return fmt.Errorf("error determining executable path: %w", err)
}
currentPath, err := filepath.Abs(execPath)
if err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]interface{}{
"Executable path": execPath,
})
progress.Fail()
return fmt.Errorf("error determining absolute target path: %w", err)
}
// Windows does not permit removing a running executable, however it will
// permit renaming it! So we first rename the running executable and then we
// move the executable that we downloaded to the same location as the
// original executable (which is allowed since we first renamed the running
// executable).
//
// Reference:
// https://github.com/golang/go/issues/21997#issuecomment-331744930
if fstruntime.Windows {
if err := os.Rename(execPath, execPath+"~"); err != nil {
os.Remove(execPath + "~")
}
}
if err := os.Rename(latestPath, currentPath); err != nil {
if err := filesystem.CopyFile(latestPath, currentPath); err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]interface{}{
"Executable (source)": latestPath,
"Executable (destination)": currentPath,
})
progress.Fail()
return fmt.Errorf("error moving latest binary in place: %w", err)
}
}
progress.Done()
text.Success(out, "Updated %s to %s.", currentPath, latest)
return nil
}