feat: make checksum optional in upgrade validation (#16511)
This commit is contained in:
parent
ad1c5794dc
commit
62709adcc7
@ -41,10 +41,6 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
* [#12457](https://github.com/cosmos/cosmos-sdk/issues/12457) Add `cosmovisor pre-upgrade` command to manually add an upgrade to cosmovisor.
|
||||
* [#15361](https://github.com/cosmos/cosmos-sdk/pull/15361) Add `cosmovisor config` command to display the configuration used by cosmovisor.
|
||||
|
||||
## Client Breaking Changes
|
||||
|
||||
* [#14881](https://github.com/cosmos/cosmos-sdk/pull/14881) Cosmovisor supports only upgrade plan with a checksum. This is enforced by the `x/upgrade` module for better security.
|
||||
|
||||
## Improvements
|
||||
|
||||
* [#14881](https://github.com/cosmos/cosmos-sdk/pull/14881) Refactor Cosmovisor to use `x/upgrade` validation logic.
|
||||
|
||||
@ -87,6 +87,7 @@ Use of `cosmovisor` without one of the action arguments is deprecated. For backw
|
||||
* `DAEMON_HOME` is the location where the `cosmovisor/` directory is kept that contains the genesis binary, the upgrade binaries, and any additional auxiliary files associated with each binary (e.g. `$HOME/.gaiad`, `$HOME/.regend`, `$HOME/.simd`, etc.).
|
||||
* `DAEMON_NAME` is the name of the binary itself (e.g. `gaiad`, `regend`, `simd`, etc.).
|
||||
* `DAEMON_ALLOW_DOWNLOAD_BINARIES` (*optional*), if set to `true`, will enable auto-downloading of new binaries (for security reasons, this is intended for full nodes rather than validators). By default, `cosmovisor` will not auto-download new binaries.
|
||||
* `DAEMON_DOWNLOAD_MUST_HAVE_CHECKSUM` (*optional*, default = `false`), if `true` cosmovisor will require that a checksum is provided in the upgrade plan for the binary to be downloaded. If `false`, cosmovisor will not require a checksum to be provided, but still check the checksum if one is provided.
|
||||
* `DAEMON_RESTART_AFTER_UPGRADE` (*optional*, default = `true`), if `true`, restarts the subprocess with the same command-line arguments and flags (but with the new binary) after a successful upgrade. Otherwise (`false`), `cosmovisor` stops running after an upgrade and requires the system administrator to manually restart it. Note restart is only after the upgrade and does not auto-restart the subprocess after an error occurs.
|
||||
* `DAEMON_RESTART_DELAY` (*optional*, default none), allow a node operator to define a delay between the node halt (for upgrade) and backup by the specified time. The value must be a duration (e.g. `1s`).
|
||||
* `DAEMON_POLL_INTERVAL` (*optional*, default 300 milliseconds), is the interval length for polling the upgrade plan file. The value must be a duration (e.g. `1s`).
|
||||
|
||||
@ -18,16 +18,17 @@ import (
|
||||
|
||||
// environment variable names
|
||||
const (
|
||||
EnvHome = "DAEMON_HOME"
|
||||
EnvName = "DAEMON_NAME"
|
||||
EnvDownloadBin = "DAEMON_ALLOW_DOWNLOAD_BINARIES"
|
||||
EnvRestartUpgrade = "DAEMON_RESTART_AFTER_UPGRADE"
|
||||
EnvRestartDelay = "DAEMON_RESTART_DELAY"
|
||||
EnvSkipBackup = "UNSAFE_SKIP_BACKUP"
|
||||
EnvDataBackupPath = "DAEMON_DATA_BACKUP_DIR"
|
||||
EnvInterval = "DAEMON_POLL_INTERVAL"
|
||||
EnvPreupgradeMaxRetries = "DAEMON_PREUPGRADE_MAX_RETRIES"
|
||||
EnvDisableLogs = "COSMOVISOR_DISABLE_LOGS"
|
||||
EnvHome = "DAEMON_HOME"
|
||||
EnvName = "DAEMON_NAME"
|
||||
EnvDownloadBin = "DAEMON_ALLOW_DOWNLOAD_BINARIES"
|
||||
EnvDownloadMustHaveChecksum = "DAEMON_DOWNLOAD_MUST_HAVE_CHECKSUM"
|
||||
EnvRestartUpgrade = "DAEMON_RESTART_AFTER_UPGRADE"
|
||||
EnvRestartDelay = "DAEMON_RESTART_DELAY"
|
||||
EnvSkipBackup = "UNSAFE_SKIP_BACKUP"
|
||||
EnvDataBackupPath = "DAEMON_DATA_BACKUP_DIR"
|
||||
EnvInterval = "DAEMON_POLL_INTERVAL"
|
||||
EnvPreupgradeMaxRetries = "DAEMON_PREUPGRADE_MAX_RETRIES"
|
||||
EnvDisableLogs = "COSMOVISOR_DISABLE_LOGS"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -42,16 +43,17 @@ const defaultFilename = "upgrade-info.json"
|
||||
|
||||
// Config is the information passed in to control the daemon
|
||||
type Config struct {
|
||||
Home string
|
||||
Name string
|
||||
AllowDownloadBinaries bool
|
||||
RestartAfterUpgrade bool
|
||||
RestartDelay time.Duration
|
||||
PollInterval time.Duration
|
||||
UnsafeSkipBackup bool
|
||||
DataBackupPath string
|
||||
PreupgradeMaxRetries int
|
||||
DisableLogs bool
|
||||
Home string
|
||||
Name string
|
||||
AllowDownloadBinaries bool
|
||||
DownloadMustHaveChecksum bool
|
||||
RestartAfterUpgrade bool
|
||||
RestartDelay time.Duration
|
||||
PollInterval time.Duration
|
||||
UnsafeSkipBackup bool
|
||||
DataBackupPath string
|
||||
PreupgradeMaxRetries int
|
||||
DisableLogs bool
|
||||
|
||||
// currently running upgrade
|
||||
currentUpgrade upgradetypes.Plan
|
||||
@ -153,6 +155,9 @@ func GetConfigFromEnv() (*Config, error) {
|
||||
if cfg.AllowDownloadBinaries, err = booleanOption(EnvDownloadBin, false); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if cfg.DownloadMustHaveChecksum, err = booleanOption(EnvDownloadMustHaveChecksum, false); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if cfg.RestartAfterUpgrade, err = booleanOption(EnvRestartUpgrade, true); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
@ -367,6 +372,7 @@ func (cfg Config) DetailString() string {
|
||||
{EnvHome, cfg.Home},
|
||||
{EnvName, cfg.Name},
|
||||
{EnvDownloadBin, fmt.Sprintf("%t", cfg.AllowDownloadBinaries)},
|
||||
{EnvDownloadMustHaveChecksum, fmt.Sprintf("%t", cfg.DownloadMustHaveChecksum)},
|
||||
{EnvRestartUpgrade, fmt.Sprintf("%t", cfg.RestartAfterUpgrade)},
|
||||
{EnvRestartDelay, cfg.RestartDelay.String()},
|
||||
{EnvInterval, cfg.PollInterval.String()},
|
||||
|
||||
@ -29,31 +29,33 @@ func TestArgsTestSuite(t *testing.T) {
|
||||
|
||||
// cosmovisorEnv are the string values of environment variables used to configure Cosmovisor.
|
||||
type cosmovisorEnv struct {
|
||||
Home string
|
||||
Name string
|
||||
DownloadBin string
|
||||
RestartUpgrade string
|
||||
RestartDelay string
|
||||
SkipBackup string
|
||||
DataBackupPath string
|
||||
Interval string
|
||||
PreupgradeMaxRetries string
|
||||
DisableLogs string
|
||||
Home string
|
||||
Name string
|
||||
DownloadBin string
|
||||
DownloadMustHaveChecksum string
|
||||
RestartUpgrade string
|
||||
RestartDelay string
|
||||
SkipBackup string
|
||||
DataBackupPath string
|
||||
Interval string
|
||||
PreupgradeMaxRetries string
|
||||
DisableLogs string
|
||||
}
|
||||
|
||||
// ToMap creates a map of the cosmovisorEnv where the keys are the env var names.
|
||||
func (c cosmovisorEnv) ToMap() map[string]string {
|
||||
return map[string]string{
|
||||
EnvHome: c.Home,
|
||||
EnvName: c.Name,
|
||||
EnvDownloadBin: c.DownloadBin,
|
||||
EnvRestartUpgrade: c.RestartUpgrade,
|
||||
EnvRestartDelay: c.RestartDelay,
|
||||
EnvSkipBackup: c.SkipBackup,
|
||||
EnvDataBackupPath: c.DataBackupPath,
|
||||
EnvInterval: c.Interval,
|
||||
EnvPreupgradeMaxRetries: c.PreupgradeMaxRetries,
|
||||
EnvDisableLogs: c.DisableLogs,
|
||||
EnvHome: c.Home,
|
||||
EnvName: c.Name,
|
||||
EnvDownloadBin: c.DownloadBin,
|
||||
EnvDownloadMustHaveChecksum: c.DownloadMustHaveChecksum,
|
||||
EnvRestartUpgrade: c.RestartUpgrade,
|
||||
EnvRestartDelay: c.RestartDelay,
|
||||
EnvSkipBackup: c.SkipBackup,
|
||||
EnvDataBackupPath: c.DataBackupPath,
|
||||
EnvInterval: c.Interval,
|
||||
EnvPreupgradeMaxRetries: c.PreupgradeMaxRetries,
|
||||
EnvDisableLogs: c.DisableLogs,
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,6 +68,8 @@ func (c *cosmovisorEnv) Set(envVar, envVal string) {
|
||||
c.Name = envVal
|
||||
case EnvDownloadBin:
|
||||
c.DownloadBin = envVal
|
||||
case EnvDownloadMustHaveChecksum:
|
||||
c.DownloadMustHaveChecksum = envVal
|
||||
case EnvRestartUpgrade:
|
||||
c.RestartUpgrade = envVal
|
||||
case EnvRestartDelay:
|
||||
@ -322,20 +326,22 @@ func (s *argsTestSuite) TestDetailString() {
|
||||
home := "/home"
|
||||
name := "test-name"
|
||||
allowDownloadBinaries := true
|
||||
downloadMustHaveChecksum := true
|
||||
restartAfterUpgrade := true
|
||||
pollInterval := 406 * time.Millisecond
|
||||
unsafeSkipBackup := false
|
||||
dataBackupPath := "/home"
|
||||
preupgradeMaxRetries := 8
|
||||
cfg := &Config{
|
||||
Home: home,
|
||||
Name: name,
|
||||
AllowDownloadBinaries: allowDownloadBinaries,
|
||||
RestartAfterUpgrade: restartAfterUpgrade,
|
||||
PollInterval: pollInterval,
|
||||
UnsafeSkipBackup: unsafeSkipBackup,
|
||||
DataBackupPath: dataBackupPath,
|
||||
PreupgradeMaxRetries: preupgradeMaxRetries,
|
||||
Home: home,
|
||||
Name: name,
|
||||
AllowDownloadBinaries: allowDownloadBinaries,
|
||||
DownloadMustHaveChecksum: downloadMustHaveChecksum,
|
||||
RestartAfterUpgrade: restartAfterUpgrade,
|
||||
PollInterval: pollInterval,
|
||||
UnsafeSkipBackup: unsafeSkipBackup,
|
||||
DataBackupPath: dataBackupPath,
|
||||
PreupgradeMaxRetries: preupgradeMaxRetries,
|
||||
}
|
||||
|
||||
expectedPieces := []string{
|
||||
@ -343,6 +349,7 @@ func (s *argsTestSuite) TestDetailString() {
|
||||
fmt.Sprintf("%s: %s", EnvHome, home),
|
||||
fmt.Sprintf("%s: %s", EnvName, name),
|
||||
fmt.Sprintf("%s: %t", EnvDownloadBin, allowDownloadBinaries),
|
||||
fmt.Sprintf("%s: %t", EnvDownloadMustHaveChecksum, downloadMustHaveChecksum),
|
||||
fmt.Sprintf("%s: %t", EnvRestartUpgrade, restartAfterUpgrade),
|
||||
fmt.Sprintf("%s: %s", EnvInterval, pollInterval),
|
||||
fmt.Sprintf("%s: %t", EnvSkipBackup, unsafeSkipBackup),
|
||||
@ -371,18 +378,19 @@ func (s *argsTestSuite) TestGetConfigFromEnv() {
|
||||
absPath, perr := filepath.Abs(relPath)
|
||||
s.Require().NoError(perr)
|
||||
|
||||
newConfig := func(home, name string, downloadBin, restartUpgrade bool, restartDelay int, skipBackup bool, dataBackupPath string, interval, preupgradeMaxRetries int, disableLogs bool) *Config {
|
||||
newConfig := func(home, name string, downloadBin, downloadMustHaveChecksum, restartUpgrade bool, restartDelay int, skipBackup bool, dataBackupPath string, interval, preupgradeMaxRetries int, disableLogs bool) *Config {
|
||||
return &Config{
|
||||
Home: home,
|
||||
Name: name,
|
||||
AllowDownloadBinaries: downloadBin,
|
||||
RestartAfterUpgrade: restartUpgrade,
|
||||
RestartDelay: time.Millisecond * time.Duration(restartDelay),
|
||||
PollInterval: time.Millisecond * time.Duration(interval),
|
||||
UnsafeSkipBackup: skipBackup,
|
||||
DataBackupPath: dataBackupPath,
|
||||
PreupgradeMaxRetries: preupgradeMaxRetries,
|
||||
DisableLogs: disableLogs,
|
||||
Home: home,
|
||||
Name: name,
|
||||
AllowDownloadBinaries: downloadBin,
|
||||
DownloadMustHaveChecksum: downloadMustHaveChecksum,
|
||||
RestartAfterUpgrade: restartUpgrade,
|
||||
RestartDelay: time.Millisecond * time.Duration(restartDelay),
|
||||
PollInterval: time.Millisecond * time.Duration(interval),
|
||||
UnsafeSkipBackup: skipBackup,
|
||||
DataBackupPath: dataBackupPath,
|
||||
PreupgradeMaxRetries: preupgradeMaxRetries,
|
||||
DisableLogs: disableLogs,
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,210 +403,223 @@ func (s *argsTestSuite) TestGetConfigFromEnv() {
|
||||
{
|
||||
name: "all bad",
|
||||
envVals: cosmovisorEnv{
|
||||
Home: "",
|
||||
Name: "",
|
||||
DownloadBin: "bad",
|
||||
RestartUpgrade: "bad",
|
||||
RestartDelay: "bad",
|
||||
SkipBackup: "bad",
|
||||
DataBackupPath: "bad",
|
||||
Interval: "bad",
|
||||
PreupgradeMaxRetries: "bad",
|
||||
Home: "",
|
||||
Name: "",
|
||||
DownloadBin: "bad",
|
||||
DownloadMustHaveChecksum: "bad",
|
||||
RestartUpgrade: "bad",
|
||||
RestartDelay: "bad",
|
||||
SkipBackup: "bad",
|
||||
DataBackupPath: "bad",
|
||||
Interval: "bad",
|
||||
PreupgradeMaxRetries: "bad",
|
||||
},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 9,
|
||||
expectedErrCount: 10,
|
||||
},
|
||||
{
|
||||
name: "all good",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "nothing set",
|
||||
envVals: cosmovisorEnv{"", "", "", "", "", "", "", "", "", "false"},
|
||||
envVals: cosmovisorEnv{"", "", "", "", "", "", "", "", "", "", "false"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 3,
|
||||
},
|
||||
// Note: Home and Name tests are done in TestValidate
|
||||
{
|
||||
name: "download bin bad",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "bad", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "bad", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "download bin not set",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, true, absPath, 303, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "download bin true",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "download bin false",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, true, absPath, 303, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "download ensure checksum true",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "download ensure checksum false",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, false, false, 600, true, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "restart upgrade bad",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "bad", "600ms", "true", "", "303ms", "1", "false"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "bad", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "restart upgrade not set",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, true, 600, true, absPath, 303, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "restart upgrade true",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, true, 600, true, absPath, 303, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "true", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "restart upgrade true",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "skip unsafe backups bad",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "bad", "", "303ms", "1", "false"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "bad", "", "303ms", "1", "false"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "skip unsafe backups not set",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, false, 600, false, absPath, 303, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "skip unsafe backups true",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "skip unsafe backups false",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "false", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, false, 600, false, absPath, 303, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "false", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "poll interval bad",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "bad", "1", "false"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "bad", "1", "false"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "poll interval 0",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "0", "1", "false"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "0", "1", "false"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "poll interval not set",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 300, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 300, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "poll interval 600",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "600", "1", "false"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "600", "1", "false"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "poll interval 1s",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "1s", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 1000, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "1s", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 1000, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "poll interval -3m",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "-3m", "1", "false"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "-3m", "1", "false"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "restart delay bad",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "bad", "false", "", "303ms", "1", "false"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "bad", "false", "", "303ms", "1", "false"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "restart delay 0",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "0", "false", "", "303ms", "1", "false"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "0", "false", "", "303ms", "1", "false"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "restart delay not set",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "", "false", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 0, false, absPath, 303, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "", "false", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, true, false, 0, false, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "restart delay 600",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600", "false", "", "300ms", "1", "false"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600", "false", "", "300ms", "1", "false"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "restart delay 1s",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "1s", "false", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 1000, false, absPath, 303, 1, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "1s", "false", "", "303ms", "1", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, true, false, 1000, false, absPath, 303, 1, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "restart delay -3m",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "-3m", "false", "", "303ms", "1", "false"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "-3m", "false", "", "303ms", "1", "false"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "prepupgrade max retries bad",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "bad", "false"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "bad", "false"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "prepupgrade max retries 0",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "0", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 0, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "0", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "prepupgrade max retries not set",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 0, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "prepupgrade max retries 5",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "5", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 5, false),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "false"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 5, false),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "disable logs bad",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "5", "bad"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "bad"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "disable logs good",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "", "true"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 0, true),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ go 1.20
|
||||
|
||||
require (
|
||||
cosmossdk.io/log v1.1.0
|
||||
cosmossdk.io/x/upgrade v0.0.0-20230607190716-2877190997a2
|
||||
cosmossdk.io/x/upgrade v0.0.0-20230613152654-3f214535967c
|
||||
github.com/otiai10/copy v1.11.0
|
||||
github.com/rs/zerolog v1.29.1
|
||||
github.com/spf13/cobra v1.7.0
|
||||
|
||||
@ -206,8 +206,8 @@ cosmossdk.io/store v0.1.0-alpha.1.0.20230606190835-3e18f4088b2c h1:A+FMPW9GtfcPB
|
||||
cosmossdk.io/store v0.1.0-alpha.1.0.20230606190835-3e18f4088b2c/go.mod h1:RbYGvXCbz8uNBCXrwS9Z8SyydeWi+W5x5MZ33muyzMw=
|
||||
cosmossdk.io/x/tx v0.8.0 h1:gLiGRL/Fy7fs6dd0IX8jOf0PrVr56/SG6XVMGQjyvJU=
|
||||
cosmossdk.io/x/tx v0.8.0/go.mod h1:T9uEumGNgKU61gJYRv1t3uzQwLnASpJGmSE229HM3xA=
|
||||
cosmossdk.io/x/upgrade v0.0.0-20230607190716-2877190997a2 h1:84kBxTNfeRkBUhzhiuZdo5uym9B+wffX8ehRpOREucA=
|
||||
cosmossdk.io/x/upgrade v0.0.0-20230607190716-2877190997a2/go.mod h1:CTz9FMom9siXKVeb9PKhexmqlixRH2xtXfxIuCWqMIM=
|
||||
cosmossdk.io/x/upgrade v0.0.0-20230613152654-3f214535967c h1:kmCU/rMaXMmZ9GPUNNLen/m4PpS6IV3hyBp7X0xju9M=
|
||||
cosmossdk.io/x/upgrade v0.0.0-20230613152654-3f214535967c/go.mod h1:Nqm1dOl9yTTtG+uibprZTQp50rW+pd+XjAYGVQ5+Ojc=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
|
||||
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
|
||||
@ -39,7 +39,7 @@ func UpgradeBinary(logger log.Logger, cfg *Config, p upgradetypes.Plan) error {
|
||||
return fmt.Errorf("unhandled error: %w", err)
|
||||
}
|
||||
|
||||
upgradeInfo, err := plan.ParseInfo(p.Info)
|
||||
upgradeInfo, err := plan.ParseInfo(p.Info, plan.ParseOptionEnforceChecksum(cfg.DownloadMustHaveChecksum))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse upgrade info: %w", err)
|
||||
}
|
||||
|
||||
@ -30,6 +30,9 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
* [#14880](https://github.com/cosmos/cosmos-sdk/pull/14880) Switch from using gov v1beta1 to gov v1 in upgrade CLIs.
|
||||
* [#14764](https://github.com/cosmos/cosmos-sdk/pull/14764) The `x/upgrade` module is extracted to have a separate go.mod file which allows it be a standalone module.
|
||||
|
||||
### API Breaking
|
||||
### API Breaking Changes
|
||||
|
||||
* (x/upgrade) [#16227](https://github.com/cosmos/cosmos-sdk/issues/16227) `NewKeeper` now takes a `KVStoreService` instead of a `StoreKey`, methods in the `Keeper` now take a `context.Context` instead of a `sdk.Context` and return an `error`. `UpgradeHandler` now receives a `context.Context`. `GetUpgradedClient`, `GetUpgradedConsensusState`, `GetUpgradePlan` now return a specific error for "not found".
|
||||
* [#16511](https://github.com/cosmos/cosmos-sdk/pull/16511) `BinaryDownloadURLMap.ValidateBasic()` and `BinaryDownloadURLMap.CheckURLs` now both take a checksum parameter when willing to ensure a checksum is provided for each URL.
|
||||
* [#16511](https://github.com/cosmos/cosmos-sdk/pull/16511) `plan.DownloadURLWithChecksum` has been renamed to `plan.DownloadURL` and does not validate the URL anymore. Call `plan.ValidateURL` before calling `plan.DownloadURL` to validate the URL.
|
||||
* [#16511](https://github.com/cosmos/cosmos-sdk/pull/16511) `plan.DownloadUpgrade` does not validate URL anymore. Call `plan.ValidateURL` before calling `plan.DownloadUpgrade` to validate the URL.
|
||||
* [#16227](https://github.com/cosmos/cosmos-sdk/issues/16227) `NewKeeper` now takes a `KVStoreService` instead of a `StoreKey`, methods in the `Keeper` now take a `context.Context` instead of a `sdk.Context` and return an `error`. `UpgradeHandler` now receives a `context.Context`. `GetUpgradedClient`, `GetUpgradedConsensusState`, `GetUpgradePlan` now return a specific error for "not found".
|
||||
|
||||
@ -42,8 +42,8 @@ func GetCurrentPlanCmd() *cobra.Command {
|
||||
}
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
|
||||
params := types.QueryCurrentPlanRequest{}
|
||||
res, err := queryClient.CurrentPlan(cmd.Context(), ¶ms)
|
||||
req := types.QueryCurrentPlanRequest{}
|
||||
res, err := queryClient.CurrentPlan(cmd.Context(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -77,8 +77,8 @@ func GetAppliedPlanCmd() *cobra.Command {
|
||||
}
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
ctx := cmd.Context()
|
||||
params := types.QueryAppliedPlanRequest{Name: args[0]}
|
||||
res, err := queryClient.AppliedPlan(ctx, ¶ms)
|
||||
req := types.QueryAppliedPlanRequest{Name: args[0]}
|
||||
res, err := queryClient.AppliedPlan(ctx, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -130,15 +130,15 @@ func GetModuleVersionsCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
queryClient := types.NewQueryClient(clientCtx)
|
||||
var params types.QueryModuleVersionsRequest
|
||||
var req types.QueryModuleVersionsRequest
|
||||
|
||||
if len(args) == 1 {
|
||||
params = types.QueryModuleVersionsRequest{ModuleName: args[0]}
|
||||
req = types.QueryModuleVersionsRequest{ModuleName: args[0]}
|
||||
} else {
|
||||
params = types.QueryModuleVersionsRequest{}
|
||||
req = types.QueryModuleVersionsRequest{}
|
||||
}
|
||||
|
||||
res, err := queryClient.ModuleVersions(cmd.Context(), ¶ms)
|
||||
res, err := queryClient.ModuleVersions(cmd.Context(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -19,11 +19,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
FlagUpgradeHeight = "upgrade-height"
|
||||
FlagUpgradeInfo = "upgrade-info"
|
||||
FlagNoValidate = "no-validate"
|
||||
FlagDaemonName = "daemon-name"
|
||||
FlagAuthority = "authority"
|
||||
FlagUpgradeHeight = "upgrade-height"
|
||||
FlagUpgradeInfo = "upgrade-info"
|
||||
FlagNoValidate = "no-validate"
|
||||
FlagNoChecksumRequired = "no-checksum-required"
|
||||
FlagDaemonName = "daemon-name"
|
||||
FlagAuthority = "authority"
|
||||
)
|
||||
|
||||
// GetTxCmd returns the transaction commands for this module
|
||||
@ -73,15 +74,21 @@ func NewCmdSubmitUpgradeProposal(ac addresscodec.Codec) *cobra.Command {
|
||||
}
|
||||
|
||||
if !noValidate {
|
||||
var daemonName string
|
||||
if daemonName, err = cmd.Flags().GetString(FlagDaemonName); err != nil {
|
||||
daemonName, err := cmd.Flags().GetString(FlagDaemonName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
noChecksum, err := cmd.Flags().GetBool(FlagNoChecksumRequired)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var planInfo *plan.Info
|
||||
if planInfo, err = plan.ParseInfo(p.Info); err != nil {
|
||||
if planInfo, err = plan.ParseInfo(p.Info, plan.ParseOptionEnforceChecksum(!noChecksum)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = planInfo.ValidateFull(daemonName); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -112,6 +119,7 @@ func NewCmdSubmitUpgradeProposal(ac addresscodec.Codec) *cobra.Command {
|
||||
cmd.Flags().Int64(FlagUpgradeHeight, 0, "The height at which the upgrade must happen")
|
||||
cmd.Flags().String(FlagUpgradeInfo, "", "Info for the upgrade plan such as new version download urls, etc.")
|
||||
cmd.Flags().Bool(FlagNoValidate, false, "Skip validation of the upgrade info (dangerous!)")
|
||||
cmd.Flags().Bool(FlagNoChecksumRequired, false, "Skip requirement of checksums for binaries in the upgrade info")
|
||||
cmd.Flags().String(FlagDaemonName, getDefaultDaemonName(), "The name of the executable being upgraded (for upgrade-info validation). Default is the DAEMON_NAME env var if set, or else this executable")
|
||||
cmd.Flags().String(FlagAuthority, "", "The address of the upgrade module authority (defaults to gov)")
|
||||
|
||||
|
||||
@ -11,8 +11,7 @@ import (
|
||||
"github.com/hashicorp/go-getter"
|
||||
)
|
||||
|
||||
// DownloadUpgrade downloads the given url into the provided directory and ensures it's valid.
|
||||
// The provided url must contain a checksum parameter that matches the file being downloaded.
|
||||
// DownloadUpgrade downloads the given url into the provided directory.
|
||||
// If this returns nil, the download was successful, and {dstRoot}/bin/{daemonName} is a regular executable file.
|
||||
// This is an opinionated directory structure that corresponds with Cosmovisor requirements.
|
||||
// If the url is not an archive, it is downloaded and saved to {dstRoot}/bin/{daemonName}.
|
||||
@ -21,12 +20,9 @@ import (
|
||||
// If the archive does not contain a /bin/{daemonName} file, then this will attempt to move /{daemonName} to /bin/{daemonName}.
|
||||
// If the archive does not contain either /bin/{daemonName} or /{daemonName}, an error is returned.
|
||||
//
|
||||
// Note: Because a checksum is required, this function cannot be used to download non-archive directories.
|
||||
// If dstRoot already exists, some or all of its contents might be updated.
|
||||
// NOTE: This functions does not check the provided url for validity.
|
||||
func DownloadUpgrade(dstRoot, url, daemonName string) error {
|
||||
if err := ValidateIsURLWithChecksum(url); err != nil {
|
||||
return err
|
||||
}
|
||||
target := filepath.Join(dstRoot, "bin", daemonName)
|
||||
// First try to download it as a single file. If there's no error, it's okay and we're done.
|
||||
if err := getter.GetFile(target, url); err != nil {
|
||||
@ -97,19 +93,16 @@ func EnsureBinary(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownloadURLWithChecksum gets the contents of the given url, ensuring the checksum is correct.
|
||||
// The provided url must contain a checksum parameter that matches the file being downloaded.
|
||||
// DownloadURL gets the contents of the given url.
|
||||
// The provided url can contain a checksum parameter that matches the file being downloaded.
|
||||
// If there isn't an error, the content returned by the url will be returned as a string.
|
||||
// Returns an error if:
|
||||
// - The url is not a URL or does not contain a checksum parameter.
|
||||
// - The url is not a URL or does not contain a checksum parameter (when required).
|
||||
// - Downloading the URL fails.
|
||||
// - The checksum does not match what is returned by the URL.
|
||||
// - The URL does not return a regular file.
|
||||
// - The downloaded file is empty or only whitespace.
|
||||
func DownloadURLWithChecksum(url string) (string, error) {
|
||||
if err := ValidateIsURLWithChecksum(url); err != nil {
|
||||
return "", err
|
||||
}
|
||||
func DownloadURL(url string) (string, error) {
|
||||
tempDir, err := os.MkdirTemp("", "reference")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not create temp directory: %w", err)
|
||||
@ -130,14 +123,16 @@ func DownloadURLWithChecksum(url string) (string, error) {
|
||||
return tempFileStr, nil
|
||||
}
|
||||
|
||||
// ValidateIsURLWithChecksum checks that the given string is a url and contains a checksum query parameter.
|
||||
func ValidateIsURLWithChecksum(urlStr string) error {
|
||||
// ValidateURL checks that the given string is a valid url and optionally contains a checksum query parameter.
|
||||
func ValidateURL(urlStr string, mustChecksum bool) error {
|
||||
url, err := neturl.Parse(urlStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(url.Query().Get("checksum")) == 0 {
|
||||
|
||||
if mustChecksum && len(url.Query().Get("checksum")) == 0 {
|
||||
return errors.New("missing checksum query parameter")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -158,14 +158,6 @@ func (s *DownloaderTestSuite) TestDownloadUpgrade() {
|
||||
assert.Contains(t, err.Error(), "no such file or directory")
|
||||
})
|
||||
|
||||
s.T().Run("url does not have checksum", func(t *testing.T) {
|
||||
dstRoot := getDstDir(t.Name())
|
||||
url := "file://" + justAFilePath
|
||||
err := DownloadUpgrade(dstRoot, url, justAFile.Name)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "missing checksum query parameter")
|
||||
})
|
||||
|
||||
s.T().Run("url has incorrect checksum", func(t *testing.T) {
|
||||
dstRoot := getDstDir(t.Name())
|
||||
badChecksum := "2c22e34510bd1d4ad2343cdc54f7165bccf30caef73f39af7dd1db2795a3da48"
|
||||
@ -248,7 +240,7 @@ func (s *DownloaderTestSuite) TestEnsureBinary() {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *DownloaderTestSuite) TestDownloadURLWithChecksum() {
|
||||
func (s *DownloaderTestSuite) TestDownloadURL() {
|
||||
planContents := `{"binaries":{"xxx/yyy":"url"}}`
|
||||
planFile := NewTestFile("plan-info.json", planContents)
|
||||
planPath := s.saveSrcTestFile(planFile)
|
||||
@ -259,21 +251,21 @@ func (s *DownloaderTestSuite) TestDownloadURLWithChecksum() {
|
||||
|
||||
s.T().Run("url does not exist", func(t *testing.T) {
|
||||
url := "file:///never-gonna-be-a-thing?checksum=sha256:2c22e34510bd1d4ad2343cdc54f7165bccf30caef73f39af7dd1db2795a3da48"
|
||||
_, err := DownloadURLWithChecksum(url)
|
||||
_, err := DownloadURL(url)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "could not download url")
|
||||
})
|
||||
|
||||
s.T().Run("without checksum", func(t *testing.T) {
|
||||
url := "file://" + planPath
|
||||
_, err := DownloadURLWithChecksum(url)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "missing checksum query parameter")
|
||||
actual, err := DownloadURL(url)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, planContents, actual)
|
||||
})
|
||||
|
||||
s.T().Run("with correct checksum", func(t *testing.T) {
|
||||
url := "file://" + planPath + "?checksum=sha256:" + planChecksum
|
||||
actual, err := DownloadURLWithChecksum(url)
|
||||
actual, err := DownloadURL(url)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, planContents, actual)
|
||||
})
|
||||
@ -281,7 +273,7 @@ func (s *DownloaderTestSuite) TestDownloadURLWithChecksum() {
|
||||
s.T().Run("with incorrect checksum", func(t *testing.T) {
|
||||
badChecksum := "2c22e34510bd1d4ad2343cdc54f7165bccf30caef73f39af7dd1db2795a3da48"
|
||||
url := "file://" + planPath + "?checksum=sha256:" + badChecksum
|
||||
_, err := DownloadURLWithChecksum(url)
|
||||
_, err := DownloadURL(url)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "Checksums did not match")
|
||||
assert.Contains(t, err.Error(), "Expected: "+badChecksum)
|
||||
@ -290,7 +282,7 @@ func (s *DownloaderTestSuite) TestDownloadURLWithChecksum() {
|
||||
|
||||
s.T().Run("plan is empty", func(t *testing.T) {
|
||||
url := "file://" + emptyPlanPath + "?checksum=sha256:" + emptyChecksum
|
||||
_, err := DownloadURLWithChecksum(url)
|
||||
_, err := DownloadURL(url)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "no content returned")
|
||||
})
|
||||
|
||||
@ -15,24 +15,51 @@ import (
|
||||
|
||||
// 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 stings 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) (*Info, error) {
|
||||
infoStr = strings.TrimSpace(infoStr)
|
||||
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.Parse(infoStr); err == nil {
|
||||
infoStr, err = DownloadURLWithChecksum(infoStr)
|
||||
if err := ValidateURL(infoStr, parseConfig.EnforceChecksum); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
infoStr, err = DownloadURL(infoStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -44,6 +71,8 @@ func ParseInfo(infoStr string) (*Info, error) {
|
||||
return nil, fmt.Errorf("could not parse plan info: %v", err)
|
||||
}
|
||||
|
||||
planInfo.parseConfig = *parseConfig
|
||||
|
||||
return &planInfo, nil
|
||||
}
|
||||
|
||||
@ -55,10 +84,10 @@ func ParseInfo(infoStr string) (*Info, 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(); err != nil {
|
||||
if err := m.Binaries.ValidateBasic(m.parseConfig.EnforceChecksum); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.Binaries.CheckURLs(daemonName); err != nil {
|
||||
if err := m.Binaries.CheckURLs(daemonName, m.parseConfig.EnforceChecksum); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@ -69,8 +98,8 @@ func (m Info) ValidateFull(daemonName string) error {
|
||||
// - This has at least one entry.
|
||||
// - All entry keys have the format "os/arch" or are "any".
|
||||
// - All entry values are valid URLs.
|
||||
// - All URLs contain a checksum query parameter.
|
||||
func (m BinaryDownloadURLMap) ValidateBasic() error {
|
||||
// - 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")
|
||||
@ -81,8 +110,9 @@ func (m BinaryDownloadURLMap) ValidateBasic() error {
|
||||
if key != "any" && !osArchRx.MatchString(key) {
|
||||
return fmt.Errorf("invalid os/arch format in key \"%s\"", key)
|
||||
}
|
||||
if err := ValidateIsURLWithChecksum(val); err != nil {
|
||||
return fmt.Errorf("invalid url \"%s\" in binaries[%s]: %v", val, key, err)
|
||||
|
||||
if err := ValidateURL(val, enforceChecksum); err != nil {
|
||||
return fmt.Errorf("invalid url \"%s\" in binaries[%s]: %w", val, key, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,7 +123,7 @@ func (m BinaryDownloadURLMap) ValidateBasic() error {
|
||||
// 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) error {
|
||||
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)
|
||||
@ -101,8 +131,12 @@ func (m BinaryDownloadURLMap) CheckURLs(daemonName string) error {
|
||||
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: %v", osArch, err)
|
||||
return fmt.Errorf("error downloading binary for os/arch %s: %w", osArch, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@ -191,9 +191,10 @@ func (s *InfoTestSuite) TestBinaryDownloadURLMapValidateBasic() {
|
||||
return url + "?checksum=sha256:b5a2c96250612366ea272ffac6d9744aaf4b45aacd96aa7cfcb931ee3b558259"
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
urlMap BinaryDownloadURLMap
|
||||
errs []string
|
||||
name string
|
||||
urlMap BinaryDownloadURLMap
|
||||
parseConfig ParseConfig
|
||||
errs []string
|
||||
}{
|
||||
{
|
||||
name: "empty map",
|
||||
@ -241,7 +242,8 @@ func (s *InfoTestSuite) TestBinaryDownloadURLMapValidateBasic() {
|
||||
urlMap: BinaryDownloadURLMap{
|
||||
"darwin/amd64": "https://v1.cosmos.network/sdk",
|
||||
},
|
||||
errs: []string{"invalid url", "darwin/amd64", "missing checksum query parameter"},
|
||||
parseConfig: ParseConfig{EnforceChecksum: false},
|
||||
errs: nil,
|
||||
},
|
||||
{
|
||||
name: "multiple valid entries but one bad url",
|
||||
@ -269,7 +271,7 @@ func (s *InfoTestSuite) TestBinaryDownloadURLMapValidateBasic() {
|
||||
|
||||
for _, tc := range tests {
|
||||
s.T().Run(tc.name, func(t *testing.T) {
|
||||
actualErr := tc.urlMap.ValidateBasic()
|
||||
actualErr := tc.urlMap.ValidateBasic(tc.parseConfig.EnforceChecksum)
|
||||
if len(tc.errs) > 0 {
|
||||
require.Error(t, actualErr)
|
||||
for _, expectedErr := range tc.errs {
|
||||
@ -291,9 +293,10 @@ func (s *InfoTestSuite) TestBinaryDownloadURLMapCheckURLs() {
|
||||
linux386URL := makeFileURL(s.T(), linux386Path)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
urlMap BinaryDownloadURLMap
|
||||
errs []string
|
||||
name string
|
||||
urlMap BinaryDownloadURLMap
|
||||
parseConfig ParseConfig
|
||||
errs []string
|
||||
}{
|
||||
{
|
||||
name: "two good entries",
|
||||
@ -321,7 +324,7 @@ func (s *InfoTestSuite) TestBinaryDownloadURLMapCheckURLs() {
|
||||
|
||||
for _, tc := range tests {
|
||||
s.T().Run(tc.name, func(t *testing.T) {
|
||||
actualErr := tc.urlMap.CheckURLs("daemon")
|
||||
actualErr := tc.urlMap.CheckURLs("daemon", tc.parseConfig.EnforceChecksum)
|
||||
if len(tc.errs) > 0 {
|
||||
require.Error(t, actualErr)
|
||||
for _, expectedErr := range tc.errs {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user