-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathupgrade.go
152 lines (124 loc) · 3.79 KB
/
upgrade.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
package upgrade
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"github.com/getsavvyinc/upgrade-cli/checksum"
"github.com/getsavvyinc/upgrade-cli/release"
"github.com/getsavvyinc/upgrade-cli/release/asset"
"github.com/hashicorp/go-version"
)
type Upgrader interface {
IsNewVersionAvailable(ctx context.Context, currentVersion string) (bool, error)
// Upgrade upgrades the current binary to the latest version.
Upgrade(ctx context.Context, currentVersion string) error
}
type upgrader struct {
executablePath string
repo string
owner string
releaseGetter release.Getter
assetDownloader asset.Downloader
checksumDownloader checksum.Downloader
checksumValidator checksum.CheckSumValidator
}
var _ Upgrader = (*upgrader)(nil)
type Opt func(*upgrader)
func WithAssetDownloader(d asset.Downloader) Opt {
return func(u *upgrader) {
u.assetDownloader = d
}
}
func WithCheckSumDownloader(c checksum.Downloader) Opt {
return func(u *upgrader) {
u.checksumDownloader = c
}
}
func WithCheckSumValidator(c checksum.CheckSumValidator) Opt {
return func(u *upgrader) {
u.checksumValidator = c
}
}
func NewUpgrader(owner string, repo string, executablePath string, opts ...Opt) Upgrader {
u := &upgrader{
repo: repo,
owner: owner,
executablePath: executablePath,
releaseGetter: release.NewReleaseGetter(repo, owner),
assetDownloader: asset.NewAssetDownloader(executablePath, asset.WithLookupArchFallback(map[string]string{
"amd64": "x86_64",
"386": "i86",
})),
checksumDownloader: checksum.NewCheckSumDownloader(),
checksumValidator: checksum.NewCheckSumValidator(),
}
for _, opt := range opts {
opt(u)
}
return u
}
var ErrInvalidCheckSum = errors.New("invalid checksum")
func (u *upgrader) IsNewVersionAvailable(ctx context.Context, currentVersion string) (bool, error) {
curr, err := version.NewVersion(currentVersion)
if err != nil {
return false, fmt.Errorf("failed to parse current version: %s with err %w", currentVersion, err)
}
releaseInfo, err := u.releaseGetter.GetLatestRelease(ctx)
if err != nil {
return false, err
}
latest, err := version.NewVersion(releaseInfo.TagName)
if err != nil {
return false, fmt.Errorf("failed to parse latest version: %s with err %w", releaseInfo.TagName, err)
}
return latest.GreaterThan(curr), nil
}
func (u *upgrader) Upgrade(ctx context.Context, currentVersion string) error {
curr, err := version.NewVersion(currentVersion)
if err != nil {
return err
}
releaseInfo, err := u.releaseGetter.GetLatestRelease(ctx)
if err != nil {
return err
}
latest, err := version.NewVersion(releaseInfo.TagName)
if err != nil {
return err
}
if latest.LessThanOrEqual(curr) {
return nil
}
// from the releaseInfo, download the binary for the architecture
downloadInfo, cleanup, err := u.assetDownloader.DownloadAsset(ctx, releaseInfo.Assets)
if err != nil {
return err
}
if cleanup != nil {
defer cleanup()
}
// download the checksum file
checksumInfo, err := u.checksumDownloader.Download(ctx, releaseInfo.Assets)
if err != nil {
return err
}
executableName := filepath.Base(u.executablePath)
// verify the checksum
if !u.checksumValidator.IsCheckSumValid(ctx, executableName, checksumInfo, downloadInfo.Checksum) {
return ErrInvalidCheckSum
}
if err := replaceBinary(downloadInfo.DownloadedBinaryFilePath, u.executablePath); err != nil {
return fmt.Errorf("failed to replace binary: %w", err)
}
return nil
}
// replaceBinary replaces the current executable with the downloaded update.
func replaceBinary(tmpFilePath, currentBinaryPath string) error {
// Replace the current binary with the new binary
if err := os.Rename(tmpFilePath, currentBinaryPath); err != nil {
return fmt.Errorf("failed to replace binary: %w", err)
}
return nil
}