forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathinfo.go
More file actions
143 lines (121 loc) · 4.59 KB
/
Copy pathinfo.go
File metadata and controls
143 lines (121 loc) · 4.59 KB
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
package plan
import (
"encoding/json"
"errors"
"fmt"
neturl "net/url"
"os"
"path/filepath"
"regexp"
"strings"
"cosmossdk.io/x/upgrade/internal/conv"
)
// Info is the special structure that the Plan.Info string can be (as json).
type Info struct {
parseConfig ParseConfig `json:"-"`
Binaries BinaryDownloadURLMap `json:"binaries"`
}
// BinaryDownloadURLMap is a map of os/architecture strings to a URL where the binary can be downloaded.
type BinaryDownloadURLMap map[string]string
// ParseConfig is used to configure the parsing of a Plan.Info string.
type ParseConfig struct {
// EnforceChecksum, if true, will cause all downloaded files to be checked against their checksums.
// When false, checksums are not enforced to be present in the url.
EnforceChecksum bool
}
// ParseOption is used to configure the parsing of a Plan.Info string.
type ParseOption func(*ParseConfig)
// ParseOptionEnforceChecksum returns a ParseOption that sets the EnforceChecksum field of the ParseConfig.
func ParseOptionEnforceChecksum(enforce bool) ParseOption {
return func(c *ParseConfig) {
c.EnforceChecksum = enforce
}
}
// ParseInfo parses an info string into a map of os/arch strings to URL string.
// If the infoStr is a url, an GET request will be made to it, and its response will be parsed instead.
func ParseInfo(infoStr string, opts ...ParseOption) (*Info, error) {
parseConfig := &ParseConfig{}
for _, opt := range opts {
opt(parseConfig)
}
infoStr = strings.TrimSpace(infoStr)
if len(infoStr) == 0 {
return nil, errors.New("plan info must not be blank")
}
// If it's a url, download it and treat the result as the real info.
if _, err := neturl.ParseRequestURI(infoStr); err == nil {
if err := ValidateURL(infoStr, parseConfig.EnforceChecksum); err != nil {
return nil, err
}
infoStr, err = DownloadURL(infoStr)
if err != nil {
return nil, err
}
}
// Now, try to parse it into the expected structure.
var planInfo Info
if err := json.Unmarshal(conv.UnsafeStrToBytes(infoStr), &planInfo); err != nil {
return nil, fmt.Errorf("could not parse plan info: %w", err)
}
planInfo.parseConfig = *parseConfig
return &planInfo, nil
}
// ValidateFull does all possible validation of this Info.
// The provided daemonName is the name of the executable file expected in all downloaded directories.
// It checks that:
// - Binaries.ValidateBasic() doesn't return an error
// - Binaries.CheckURLs(daemonName) doesn't return an error.
//
// Warning: This is an expensive process. See BinaryDownloadURLMap.CheckURLs for more info.
func (m Info) ValidateFull(daemonName string) error {
if err := m.Binaries.ValidateBasic(m.parseConfig.EnforceChecksum); err != nil {
return err
}
if err := m.Binaries.CheckURLs(daemonName, m.parseConfig.EnforceChecksum); err != nil {
return err
}
return nil
}
// ValidateBasic does stateless validation of this BinaryDownloadURLMap.
// It validates that:
// - This has at least one entry.
// - All entry keys have the format "os/arch" or are "any".
// - All entry values are valid URLs.
// - When `enforceChecksum` is true all URLs must contain a checksum query parameter.
func (m BinaryDownloadURLMap) ValidateBasic(enforceChecksum bool) error {
// Make sure there's at least one.
if len(m) == 0 {
return errors.New("no \"binaries\" entries found")
}
osArchRx := regexp.MustCompile(`[a-zA-Z0-9]+/[a-zA-Z0-9]+`)
for key, val := range m {
if key != "any" && !osArchRx.MatchString(key) {
return fmt.Errorf("invalid os/arch format in key \"%s\"", key)
}
if err := ValidateURL(val, enforceChecksum); err != nil {
return fmt.Errorf("invalid url \"%s\" in binaries[%s]: %w", val, key, err)
}
}
return nil
}
// CheckURLs checks that all entries have valid URLs that return expected data.
// The provided daemonName is the name of the executable file expected in all downloaded directories.
// Warning: This is an expensive process.
// It will make an HTTP GET request to each URL and download the response.
func (m BinaryDownloadURLMap) CheckURLs(daemonName string, enforceChecksum bool) error {
tempDir, err := os.MkdirTemp("", "os-arch-downloads")
if err != nil {
return fmt.Errorf("could not create temp directory: %w", err)
}
defer os.RemoveAll(tempDir)
for osArch, url := range m {
dstRoot := filepath.Join(tempDir, strings.ReplaceAll(osArch, "/", "-"))
if err := ValidateURL(url, enforceChecksum); err != nil {
return fmt.Errorf("error validating url for os/arch %s: %w", osArch, err)
}
if err = DownloadUpgrade(dstRoot, url, daemonName); err != nil {
return fmt.Errorf("error downloading binary for os/arch %s: %w", osArch, err)
}
}
return nil
}