forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 2
/
upgrade.go
195 lines (164 loc) · 5.44 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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package cosmovisor
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/hashicorp/go-getter"
"github.com/otiai10/copy"
)
// DoUpgrade will be called after the log message has been parsed and the process has terminated.
// We can now make any changes to the underlying directory without interference and leave it
// in a state, so we can make a proper restart
func DoUpgrade(cfg *Config, info *UpgradeInfo) error {
// Simplest case is to switch the link
err := EnsureBinary(cfg.UpgradeBin(info.Name))
if err == nil {
// we have the binary - do it
return cfg.SetCurrentUpgrade(info.Name)
}
// if auto-download is disabled, we fail
if !cfg.AllowDownloadBinaries {
return fmt.Errorf("binary not present, downloading disabled: %w", err)
}
// if the dir is there already, don't download either
if _, err := os.Stat(cfg.UpgradeDir(info.Name)); !os.IsNotExist(err) {
return errors.New("upgrade dir already exists, won't overwrite")
}
// If not there, then we try to download it... maybe
if err := DownloadBinary(cfg, info); err != nil {
return fmt.Errorf("cannot download binary: %w", err)
}
// and then set the binary again
if err := EnsureBinary(cfg.UpgradeBin(info.Name)); err != nil {
return fmt.Errorf("downloaded binary doesn't check out: %w", err)
}
return cfg.SetCurrentUpgrade(info.Name)
}
// DownloadBinary will grab the binary and place it in the proper directory
func DownloadBinary(cfg *Config, info *UpgradeInfo) error {
url, err := GetDownloadURL(info)
if err != nil {
return err
}
// download into the bin dir (works for one file)
binPath := cfg.UpgradeBin(info.Name)
err = getter.GetFile(binPath, url)
// if this fails, let's see if it is a zipped directory
if err != nil {
dirPath := cfg.UpgradeDir(info.Name)
err = getter.Get(dirPath, url)
if err != nil {
return err
}
err = EnsureBinary(binPath)
// copy binary to binPath from dirPath if zipped directory don't contain bin directory to wrap the binary
if err != nil {
err = copy.Copy(filepath.Join(dirPath, cfg.Name), binPath)
if err != nil {
return err
}
}
}
// if it is successful, let's ensure the binary is executable
return MarkExecutable(binPath)
}
// MarkExecutable will try to set the executable bits if not already set
// Fails if file doesn't exist or we cannot set those bits
func MarkExecutable(path string) error {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("stating binary: %w", err)
}
// end early if world exec already set
if info.Mode()&0001 == 1 {
return nil
}
// now try to set all exec bits
newMode := info.Mode().Perm() | 0111
return os.Chmod(path, newMode)
}
// UpgradeConfig is expected format for the info field to allow auto-download
type UpgradeConfig struct {
Binaries map[string]string `json:"binaries"`
}
// GetDownloadURL will check if there is an arch-dependent binary specified in Info
func GetDownloadURL(info *UpgradeInfo) (string, error) {
doc := strings.TrimSpace(info.Info)
// if this is a url, then we download that and try to get a new doc with the real info
if _, err := url.Parse(doc); err == nil {
tmpDir, err := ioutil.TempDir("", "upgrade-manager-reference")
if err != nil {
return "", fmt.Errorf("create tempdir for reference file: %w", err)
}
defer os.RemoveAll(tmpDir)
refPath := filepath.Join(tmpDir, "ref")
if err := getter.GetFile(refPath, doc); err != nil {
return "", fmt.Errorf("downloading reference link %s: %w", doc, err)
}
refBytes, err := ioutil.ReadFile(refPath)
if err != nil {
return "", fmt.Errorf("reading downloaded reference: %w", err)
}
// if download worked properly, then we use this new file as the binary map to parse
doc = string(refBytes)
}
// check if it is the upgrade config
var config UpgradeConfig
if err := json.Unmarshal([]byte(doc), &config); err == nil {
url, ok := config.Binaries[OSArch()]
if !ok {
url, ok = config.Binaries["any"]
}
if !ok {
return "", fmt.Errorf("cannot find binary for os/arch: neither %s, nor any", OSArch())
}
return url, nil
}
return "", errors.New("upgrade info doesn't contain binary map")
}
func OSArch() string {
return fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
}
// SetCurrentUpgrade sets the named upgrade to be the current link, returns error if this binary doesn't exist
func (cfg *Config) SetCurrentUpgrade(upgradeName string) error {
// ensure named upgrade exists
bin := cfg.UpgradeBin(upgradeName)
if err := EnsureBinary(bin); err != nil {
return err
}
// set a symbolic link
link := filepath.Join(cfg.Root(), currentLink)
safeName := url.PathEscape(upgradeName)
upgrade := filepath.Join(cfg.Root(), upgradesDir, safeName)
// remove link if it exists
if _, err := os.Stat(link); err == nil {
os.Remove(link)
}
// point to the new directory
if err := os.Symlink(upgrade, link); err != nil {
return fmt.Errorf("creating current symlink: %w", err)
}
return nil
}
// EnsureBinary ensures the file exists and is executable, or returns an error
func EnsureBinary(path string) error {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("cannot stat dir %s: %w", path, err)
}
if !info.Mode().IsRegular() {
return fmt.Errorf("%s is not a regular file", info.Name())
}
// this checks if the world-executable bit is set (we cannot check owner easily)
exec := info.Mode().Perm() & 0001
if exec == 0 {
return fmt.Errorf("%s is not world executable", info.Name())
}
return nil
}