Skip to content

Commit

Permalink
feat: Add cosmovisor --help (#10229)
Browse files Browse the repository at this point in the history
* [10126]: Create the help text and a func for checking if help is requested.

* [10126]: Check if help is requested and print it if so.

* [10126]: If help is requested, and it's possible, also run the binary with the --help flag.

* [10126]: Add a Config method for creating a detailed string.

* [10126]: Include the config detail string if the config is okay.

* [10126]: Create a MultiError error.

* [10126]: Get all configuration errors rather than just one at a time.

* [10126]: Create unit tests on the MultiError.

* [10126]: Remove an extra space from an output string.

* [10126]: Add unit tests on more of the args stuff.

* [10126]: Export the environment variable name strings.

* [10126]: Move the help command stuff into the new cmd area.

* [10126]: Move the unit tests on the help stuff that just got moved.

* [10126]: Lint fixes.

* [10126]: Export the EnvPreupgradeMaxRetries const and handle its error the same way as the others.

* [10126]: Update the args test with the new config entry.

* [10126]: Add EnvPreupgradeMaxRetries to the help text.

* [10126]: Output the full path in the error when the root isn't a directory.

* [10126]: Add some newlines that were missing from some help output.

* [10126]: Add a link to the documentation to the help text.

* [10126]: Don't allow MultiErrors to be in MultiErrors by extracting sub-errors while flattening.

* [10126]: Add some missing function comments.

* [10126]: Add changelog entry.

* [10126]: Fix changelog pull link.

* [10126]: Move multierror into its own errors package, then rename it to just multi.

* [10126]: Change the Config DetailString to use the environment variables instead of Config field names. Also add the missing PreupgradeMaxRetries entry to it.

* [10126]: Remove the environment variables from the help text and just defer to the cosmovisor README.

* [10126]: Update the help text as suggested.

* [10126]: Update ShouldGiveHelp. Give help if either name or home env vars aren't set. Give help if the first arg is help or any args are -h or --help. This reflects cobra defaults.

* [10126]: Pass all args when running a help command rather than just using --help.

* [10126]: Undo the changes to process.go. Instead, if an app is configured, just call it with the provided args.

* [10126]: Output help if any arg is help.

* [10126]: Reorg imports.

* [10126]: Change 'Monitored Upgrade Info File' to just 'Monitored File'. The rest of the filename gives the rest of the context.

* [10126]: Move the config error handling and output out of DoHelp and put it into the main Run function. That way, it's not being done twice in two different ways, and the setup is always logged.

* [10126]: Make checking for a help request case-insensitive (to match what's done in version).

* [10126]: Fix unit test that broke when I changed the Monitored File title.

* [10126]: Change some unit test env var stuff to use a struct instead of string slices.
  • Loading branch information
dwedul-figure authored Sep 28, 2021
1 parent a47bd59 commit 9cea19d
Show file tree
Hide file tree
Showing 9 changed files with 1,033 additions and 60 deletions.
2 changes: 1 addition & 1 deletion cosmovisor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
+ [\#10128](https://github.com/cosmos/cosmos-sdk/pull/10128) Change default value of `DAEMON_RESTART_AFTER_UPGRADE` to `true`.
+ [\#9999](https://github.com/cosmos/cosmos-sdk/pull/10103) Added `version` command that returns the cosmovisor version and the application version.
+ [\#9973](https://github.com/cosmos/cosmos-sdk/pull/10056) Added support for pre-upgrade command in Cosmovisor to be called before the binary is upgraded. Added new environmental variable `DAEMON_PREUPGRADE_MAX_RETRIES` that holds the maximum number of times to reattempt pre-upgrade before failing.

+ [\#10126](https://github.com/cosmos/cosmos-sdk/pull/10229) Added `help`.

### Improvements

Expand Down
140 changes: 93 additions & 47 deletions cosmovisor/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@ import (
"strconv"
"strings"
"time"

cverrors "github.com/cosmos/cosmos-sdk/cosmovisor/errors"
)

// environment variable names
const (
envHome = "DAEMON_HOME"
envName = "DAEMON_NAME"
envDownloadBin = "DAEMON_ALLOW_DOWNLOAD_BINARIES"
envRestartUpgrade = "DAEMON_RESTART_AFTER_UPGRADE"
envSkipBackup = "UNSAFE_SKIP_BACKUP"
envInterval = "DAEMON_POLL_INTERVAL"
envPreupgradeMaxRetries = "DAEMON_PREUPGRADE_MAX_RETRIES"
EnvHome = "DAEMON_HOME"
EnvName = "DAEMON_NAME"
EnvDownloadBin = "DAEMON_ALLOW_DOWNLOAD_BINARIES"
EnvRestartUpgrade = "DAEMON_RESTART_AFTER_UPGRADE"
EnvSkipBackup = "UNSAFE_SKIP_BACKUP"
EnvInterval = "DAEMON_POLL_INTERVAL"
EnvPreupgradeMaxRetries = "DAEMON_PREUPGRADE_MAX_RETRIES"
)

const (
Expand Down Expand Up @@ -67,10 +69,15 @@ func (cfg *Config) UpgradeBin(upgradeName string) string {
// UpgradeDir is the directory named upgrade
func (cfg *Config) UpgradeDir(upgradeName string) string {
safeName := url.PathEscape(upgradeName)
return filepath.Join(cfg.Home, rootName, upgradesDir, safeName)
return filepath.Join(cfg.BaseUpgradeDir(), safeName)
}

// BaseUpgradeDir is the directory containing the named upgrade directories.
func (cfg *Config) BaseUpgradeDir() string {
return filepath.Join(cfg.Root(), upgradesDir)
}

// UpgradeInfoFile is the expected upgrade-info filename created by `x/upgrade/keeper`.
// UpgradeInfoFilePath is the expected upgrade-info filename created by `x/upgrade/keeper`.
func (cfg *Config) UpgradeInfoFilePath() string {
return filepath.Join(cfg.Home, "data", defaultFilename)
}
Expand Down Expand Up @@ -118,72 +125,74 @@ func (cfg *Config) CurrentBin() (string, error) {
// GetConfigFromEnv will read the environmental variables into a config
// and then validate it is reasonable
func GetConfigFromEnv() (*Config, error) {
var errs []error
cfg := &Config{
Home: os.Getenv(envHome),
Name: os.Getenv(envName),
Home: os.Getenv(EnvHome),
Name: os.Getenv(EnvName),
}

var err error
if cfg.AllowDownloadBinaries, err = booleanOption(envDownloadBin, false); err != nil {
return nil, err
if cfg.AllowDownloadBinaries, err = booleanOption(EnvDownloadBin, false); err != nil {
errs = append(errs, err)
}
if cfg.RestartAfterUpgrade, err = booleanOption(envRestartUpgrade, true); err != nil {
return nil, err
if cfg.RestartAfterUpgrade, err = booleanOption(EnvRestartUpgrade, true); err != nil {
errs = append(errs, err)
}
if cfg.UnsafeSkipBackup, err = booleanOption(envSkipBackup, false); err != nil {
return nil, err
if cfg.UnsafeSkipBackup, err = booleanOption(EnvSkipBackup, false); err != nil {
errs = append(errs, err)
}

interval := os.Getenv(envInterval)
interval := os.Getenv(EnvInterval)
if interval != "" {
i, err := strconv.ParseUint(interval, 10, 32)
if err != nil {
return nil, err
switch i, e := strconv.ParseUint(interval, 10, 32); {
case e != nil:
errs = append(errs, fmt.Errorf("invalid %s: %w", EnvInterval, err))
case i == 0:
errs = append(errs, fmt.Errorf("invalid %s: cannot be 0", EnvInterval))
default:
cfg.PollInterval = time.Millisecond * time.Duration(i)
}
cfg.PollInterval = time.Millisecond * time.Duration(i)
} else {
cfg.PollInterval = 300 * time.Millisecond
}

if err := cfg.validate(); err != nil {
return nil, err
}

envPreupgradeMaxRetriesVal := os.Getenv(envPreupgradeMaxRetries)
envPreupgradeMaxRetriesVal := os.Getenv(EnvPreupgradeMaxRetries)
if cfg.PreupgradeMaxRetries, err = strconv.Atoi(envPreupgradeMaxRetriesVal); err != nil && envPreupgradeMaxRetriesVal != "" {
return nil, fmt.Errorf("%s could not be parsed to int: %w", envPreupgradeMaxRetries, err)
errs = append(errs, fmt.Errorf("%s could not be parsed to int: %w", EnvPreupgradeMaxRetries, err))
}

errs = append(errs, cfg.validate()...)

if len(errs) > 0 {
return nil, cverrors.FlattenErrors(errs...)
}
return cfg, nil
}

// validate returns an error if this config is invalid.
// it enforces Home/cosmovisor is a valid directory and exists,
// and that Name is set
func (cfg *Config) validate() error {
func (cfg *Config) validate() []error {
var errs []error
if cfg.Name == "" {
return errors.New(envName + " is not set")
}

if cfg.Home == "" {
return errors.New(envHome + " is not set")
}

if !filepath.IsAbs(cfg.Home) {
return errors.New(envHome + " must be an absolute path")
}

// ensure the root directory exists
info, err := os.Stat(cfg.Root())
if err != nil {
return fmt.Errorf("cannot stat home dir: %w", err)
errs = append(errs, errors.New(EnvName+" is not set"))
}

if !info.IsDir() {
return fmt.Errorf("%s is not a directory", info.Name())
switch {
case cfg.Home == "":
errs = append(errs, errors.New(EnvHome+" is not set"))
case !filepath.IsAbs(cfg.Home):
errs = append(errs, errors.New(EnvHome+" must be an absolute path"))
default:
switch info, err := os.Stat(cfg.Root()); {
case err != nil:
errs = append(errs, fmt.Errorf("cannot stat home dir: %w", err))
case !info.IsDir():
errs = append(errs, fmt.Errorf("%s is not a directory", cfg.Root()))
}
}

return nil
return errs
}

// SetCurrentUpgrade sets the named upgrade to be the current link, returns error if this binary doesn't exist
Expand Down Expand Up @@ -265,3 +274,40 @@ func booleanOption(name string, defaultVal bool) (bool, error) {
}
return false, fmt.Errorf("env variable %q must have a boolean value (\"true\" or \"false\"), got %q", name, p)
}

// DetailString returns a multi-line string with details about this config.
func (cfg Config) DetailString() string {
configEntries := []struct{ name, value string }{
{EnvHome, cfg.Home},
{EnvName, cfg.Name},
{EnvDownloadBin, fmt.Sprintf("%t", cfg.AllowDownloadBinaries)},
{EnvRestartUpgrade, fmt.Sprintf("%t", cfg.RestartAfterUpgrade)},
{EnvInterval, fmt.Sprintf("%s", cfg.PollInterval)},
{EnvSkipBackup, fmt.Sprintf("%t", cfg.UnsafeSkipBackup)},
{EnvPreupgradeMaxRetries, fmt.Sprintf("%d", cfg.PreupgradeMaxRetries)},
}
derivedEntries := []struct{ name, value string }{
{"Root Dir", cfg.Root()},
{"Upgrade Dir", cfg.BaseUpgradeDir()},
{"Genesis Bin", cfg.GenesisBin()},
{"Monitored File", cfg.UpgradeInfoFilePath()},
}

var sb strings.Builder
sb.WriteString("Configurable Values:\n")
for _, kv := range configEntries {
sb.WriteString(fmt.Sprintf(" %s: %s\n", kv.name, kv.value))
}
sb.WriteString("Derived Values:\n")
dnl := 0
for _, kv := range derivedEntries {
if len(kv.name) > dnl {
dnl = len(kv.name)
}
}
dFmt := fmt.Sprintf(" %%%ds: %%s\n", dnl)
for _, kv := range derivedEntries {
sb.WriteString(fmt.Sprintf(dFmt, kv.name, kv.value))
}
return sb.String()
}
Loading

0 comments on commit 9cea19d

Please sign in to comment.