feat: make checksum optional in upgrade validation (#16511)

This commit is contained in:
Julien Robert 2023-06-13 18:32:32 +02:00 committed by GitHub
parent ad1c5794dc
commit 62709adcc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 256 additions and 197 deletions

View File

@ -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.

View File

@ -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`).

View File

@ -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()},

View File

@ -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,
},
}

View File

@ -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

View File

@ -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=

View File

@ -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)
}

View File

@ -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".

View File

@ -42,8 +42,8 @@ func GetCurrentPlanCmd() *cobra.Command {
}
queryClient := types.NewQueryClient(clientCtx)
params := types.QueryCurrentPlanRequest{}
res, err := queryClient.CurrentPlan(cmd.Context(), &params)
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, &params)
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(), &params)
res, err := queryClient.ModuleVersions(cmd.Context(), &req)
if err != nil {
return err
}

View File

@ -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)")

View File

@ -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
}

View File

@ -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")
})

View File

@ -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

View File

@ -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 {