feat(cosmovisor): enable logger configuration with env variable (#16573)

Signed-off-by: Artur Troian <troian.ap@gmail.com>
This commit is contained in:
Artur Troian 2023-06-15 12:45:10 -04:00 committed by GitHub
parent 7b26ff5bec
commit a318ca9f86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 325 additions and 124 deletions

View File

@ -38,8 +38,9 @@ Ref: https://keepachangelog.com/en/1.0.0/
## Features
* [#12457](https://github.com/cosmos/cosmos-sdk/issues/12457) Add `cosmovisor pre-upgrade` command to manually add an upgrade to cosmovisor.
* [#16573](https://github.com/cosmos/cosmos-sdk/pull/16573) Extend `cosmovisor` configuration with new log format options
* [#15361](https://github.com/cosmos/cosmos-sdk/pull/15361) Add `cosmovisor config` command to display the configuration used by cosmovisor.
* [#12457](https://github.com/cosmos/cosmos-sdk/issues/12457) Add `cosmovisor pre-upgrade` command to manually add an upgrade to cosmovisor.
## Improvements

View File

@ -95,6 +95,8 @@ Use of `cosmovisor` without one of the action arguments is deprecated. For backw
* `UNSAFE_SKIP_BACKUP` (defaults to `false`), if set to `true`, upgrades directly without performing a backup. Otherwise (`false`, default) backs up the data before trying the upgrade. The default value of false is useful and recommended in case of failures and when a backup needed to rollback. We recommend using the default backup option `UNSAFE_SKIP_BACKUP=false`.
* `DAEMON_PREUPGRADE_MAX_RETRIES` (defaults to `0`). The maximum number of times to call [`pre-upgrade`](https://docs.cosmos.network/main/building-apps/app-upgrade#pre-upgrade-handling) in the application after exit status of `31`. After the maximum number of retries, Cosmovisor fails the upgrade.
* `COSMOVISOR_DISABLE_LOGS` (defaults to `false`). If set to true, this will disable Cosmovisor logs (but not the underlying process) completely. This may be useful, for example, when a Cosmovisor subcommand you are executing returns a valid JSON you are then parsing, as logs added by Cosmovisor make this output not a valid JSON.
* `COSMOVISOR_COLOR_LOGS` (defaults to `true`). If set to true, this will colorise Cosmovisor logs (but not the underlying process).
* `COSMOVISOR_TIMEFORMAT_LOGS` (defaults to `kitchen`). If set to a value (`layout|ansic|unixdate|rubydate|rfc822|rfc822z|rfc850|rfc1123|rfc1123z|rfc3339|rfc3339nano|kitchen`), this will add timestamp prefix to Cosmovisor logs (but not the underlying process).
### Folder Layout

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
@ -11,8 +12,10 @@ import (
"strings"
"time"
"cosmossdk.io/log"
"cosmossdk.io/x/upgrade/plan"
upgradetypes "cosmossdk.io/x/upgrade/types"
"github.com/rs/zerolog"
)
// environment variable names
@ -28,6 +31,8 @@ const (
EnvInterval = "DAEMON_POLL_INTERVAL"
EnvPreupgradeMaxRetries = "DAEMON_PREUPGRADE_MAX_RETRIES"
EnvDisableLogs = "COSMOVISOR_DISABLE_LOGS"
EnvColorLogs = "COSMOVISOR_COLOR_LOGS"
EnvTimeFormatLogs = "COSMOVISOR_TIMEFORMAT_LOGS"
)
const (
@ -53,7 +58,8 @@ type Config struct {
DataBackupPath string
PreupgradeMaxRetries int
DisableLogs bool
ColorLogs bool
TimeFormatLogs string
// currently running upgrade
currentUpgrade upgradetypes.Plan
}
@ -151,19 +157,25 @@ func GetConfigFromEnv() (*Config, error) {
}
var err error
if cfg.AllowDownloadBinaries, err = booleanOption(EnvDownloadBin, false); err != nil {
if cfg.AllowDownloadBinaries, err = BooleanOption(EnvDownloadBin, false); err != nil {
errs = append(errs, err)
}
if cfg.DownloadMustHaveChecksum, err = booleanOption(EnvDownloadMustHaveChecksum, false); err != nil {
if cfg.DownloadMustHaveChecksum, err = BooleanOption(EnvDownloadMustHaveChecksum, false); err != nil {
errs = append(errs, err)
}
if cfg.RestartAfterUpgrade, err = booleanOption(EnvRestartUpgrade, true); err != nil {
if cfg.RestartAfterUpgrade, err = BooleanOption(EnvRestartUpgrade, true); err != nil {
errs = append(errs, err)
}
if cfg.UnsafeSkipBackup, err = booleanOption(EnvSkipBackup, false); err != nil {
if cfg.UnsafeSkipBackup, err = BooleanOption(EnvSkipBackup, false); err != nil {
errs = append(errs, err)
}
if cfg.DisableLogs, err = booleanOption(EnvDisableLogs, false); err != nil {
if cfg.DisableLogs, err = BooleanOption(EnvDisableLogs, false); err != nil {
errs = append(errs, err)
}
if cfg.ColorLogs, err = BooleanOption(EnvColorLogs, true); err != nil {
errs = append(errs, err)
}
if cfg.TimeFormatLogs, err = TimeFormatOptionFromEnv(EnvTimeFormatLogs, time.Kitchen); err != nil {
errs = append(errs, err)
}
@ -203,6 +215,20 @@ func GetConfigFromEnv() (*Config, error) {
return cfg, nil
}
func (cfg *Config) Logger(dst io.Writer) log.Logger {
var logger log.Logger
if cfg.DisableLogs {
logger = log.NewCustomLogger(zerolog.Nop())
} else {
logger = log.NewLogger(dst,
log.ColorOption(cfg.ColorLogs),
log.TimeFormatOption(cfg.TimeFormatLogs)).With(log.ModuleKey, "cosmovisor")
}
return logger
}
func parseEnvDuration(input string) (time.Duration, error) {
duration, err := time.ParseDuration(input)
if err != nil {
@ -338,7 +364,7 @@ returnError:
}
// checks and validates env option
func booleanOption(name string, defaultVal bool) (bool, error) {
func BooleanOption(name string, defaultVal bool) (bool, error) {
p := strings.ToLower(os.Getenv(name))
switch p {
case "":
@ -351,6 +377,43 @@ 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)
}
// checks and validates env option
func TimeFormatOptionFromEnv(env, defaultVal string) (string, error) {
val, set := os.LookupEnv(env)
if !set {
return defaultVal, nil
}
switch val {
case "layout":
return time.Layout, nil
case "ansic":
return time.ANSIC, nil
case "unixdate":
return time.UnixDate, nil
case "rubydate":
return time.RubyDate, nil
case "rfc822":
return time.RFC822, nil
case "rfc822z":
return time.RFC822Z, nil
case "rfc850":
return time.RFC850, nil
case "rfc1123":
return time.RFC1123, nil
case "rfc1123z":
return time.RFC1123Z, nil
case "rfc3339":
return time.RFC3339, nil
case "rfc3339nano":
return time.RFC3339Nano, nil
case "kitchen":
return time.Kitchen, nil
case "":
return "", nil
}
return "", fmt.Errorf("env variable %q must have a timeformat value (\"layout|ansic|unixdate|rubydate|rfc822|rfc822z|rfc850|rfc1123|rfc1123z|rfc3339|rfc3339nano|kitchen\"), got %q", EnvTimeFormatLogs, val)
}
// DetailString returns a multi-line string with details about this config.
func (cfg Config) DetailString() string {
configEntries := []struct{ name, value string }{
@ -365,6 +428,8 @@ func (cfg Config) DetailString() string {
{EnvDataBackupPath, cfg.DataBackupPath},
{EnvPreupgradeMaxRetries, fmt.Sprintf("%d", cfg.PreupgradeMaxRetries)},
{EnvDisableLogs, fmt.Sprintf("%t", cfg.DisableLogs)},
{EnvColorLogs, fmt.Sprintf("%t", cfg.ColorLogs)},
{EnvTimeFormatLogs, cfg.TimeFormatLogs},
}
derivedEntries := []struct{ name, value string }{

View File

@ -35,22 +35,31 @@ type cosmovisorEnv struct {
Interval string
PreupgradeMaxRetries string
DisableLogs string
ColorLogs string
TimeFormatLogs string
}
type envMap struct {
val string
allowEmpty bool
}
// 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,
EnvDownloadMustHaveChecksum: c.DownloadMustHaveChecksum,
EnvRestartUpgrade: c.RestartUpgrade,
EnvRestartDelay: c.RestartDelay,
EnvSkipBackup: c.SkipBackup,
EnvDataBackupPath: c.DataBackupPath,
EnvInterval: c.Interval,
EnvPreupgradeMaxRetries: c.PreupgradeMaxRetries,
EnvDisableLogs: c.DisableLogs,
func (c cosmovisorEnv) ToMap() map[string]envMap {
return map[string]envMap{
EnvHome: {val: c.Home, allowEmpty: false},
EnvName: {val: c.Name, allowEmpty: false},
EnvDownloadBin: {val: c.DownloadBin, allowEmpty: false},
EnvDownloadMustHaveChecksum: {val: c.DownloadMustHaveChecksum, allowEmpty: false},
EnvRestartUpgrade: {val: c.RestartUpgrade, allowEmpty: false},
EnvRestartDelay: {val: c.RestartDelay, allowEmpty: false},
EnvSkipBackup: {val: c.SkipBackup, allowEmpty: false},
EnvDataBackupPath: {val: c.DataBackupPath, allowEmpty: false},
EnvInterval: {val: c.Interval, allowEmpty: false},
EnvPreupgradeMaxRetries: {val: c.PreupgradeMaxRetries, allowEmpty: false},
EnvDisableLogs: {val: c.DisableLogs, allowEmpty: false},
EnvColorLogs: {val: c.ColorLogs, allowEmpty: false},
EnvTimeFormatLogs: {val: c.TimeFormatLogs, allowEmpty: true},
}
}
@ -79,6 +88,10 @@ func (c *cosmovisorEnv) Set(envVar, envVal string) {
c.PreupgradeMaxRetries = envVal
case EnvDisableLogs:
c.DisableLogs = envVal
case EnvColorLogs:
c.ColorLogs = envVal
case EnvTimeFormatLogs:
c.TimeFormatLogs = envVal
default:
panic(fmt.Errorf("Unknown environment variable [%s]. Ccannot set field to [%s]. ", envVar, envVal))
}
@ -109,9 +122,9 @@ func (s *argsTestSuite) setEnv(t *testing.T, env *cosmovisorEnv) {
for envVar, envVal := range env.ToMap() {
var err error
var msg string
if len(envVal) != 0 {
err = os.Setenv(envVar, envVal)
msg = fmt.Sprintf("setting %s to %s", envVar, envVal)
if len(envVal.val) != 0 || envVal.allowEmpty {
err = os.Setenv(envVar, envVal.val)
msg = fmt.Sprintf("setting %s to %s", envVar, envVal.val)
} else {
err = os.Unsetenv(envVar)
msg = fmt.Sprintf("unsetting %s", envVar)
@ -283,7 +296,7 @@ func (s *argsTestSuite) TestBooleanOption() {
name := "COSMOVISOR_TEST_VAL"
check := func(def, expected, isErr bool, msg string) {
v, err := booleanOption(name, def)
v, err := BooleanOption(name, def)
if isErr {
s.Require().Error(err)
return
@ -317,6 +330,57 @@ func (s *argsTestSuite) TestBooleanOption() {
check(false, true, false, "should handle true value case not sensitive")
}
func (s *argsTestSuite) TestTimeFormat() {
initialEnv := s.clearEnv()
defer s.setEnv(nil, initialEnv)
name := "COSMOVISOR_TEST_VAL"
check := func(def, expected string, isErr bool, msg string) {
v, err := TimeFormatOptionFromEnv(name, def)
if isErr {
s.Require().Error(err)
return
}
s.Require().NoError(err)
s.Require().Equal(expected, v, msg)
}
os.Unsetenv(name)
check(time.Kitchen, time.Kitchen, false, "should correctly set default value")
os.Setenv(name, "")
check(time.Kitchen, "", false, "should correctly set to a none")
os.Setenv(name, "wrong")
check(time.Kitchen, "", true, "should error on wrong value")
os.Setenv(name, "layout")
check(time.Kitchen, time.Layout, false, "should handle layout value")
os.Setenv(name, "ansic")
check(time.Kitchen, time.ANSIC, false, "should handle ansic value")
os.Setenv(name, "unixdate")
check(time.Kitchen, time.UnixDate, false, "should handle unixdate value")
os.Setenv(name, "rubydate")
check(time.Kitchen, time.RubyDate, false, "should handle rubydate value")
os.Setenv(name, "rfc822")
check(time.Kitchen, time.RFC822, false, "should handle rfc822 value")
os.Setenv(name, "rfc822z")
check(time.Kitchen, time.RFC822Z, false, "should handle rfc822z value")
os.Setenv(name, "rfc850")
check(time.Kitchen, time.RFC850, false, "should handle rfc850 value")
os.Setenv(name, "rfc1123")
check(time.Kitchen, time.RFC1123, false, "should handle rfc1123 value")
os.Setenv(name, "rfc1123z")
check(time.Kitchen, time.RFC1123Z, false, "should handle rfc1123z value")
os.Setenv(name, "rfc3339")
check(time.Kitchen, time.RFC3339, false, "should handle rfc3339 value")
os.Setenv(name, "rfc3339nano")
check(time.Kitchen, time.RFC3339Nano, false, "should handle rfc3339nano value")
os.Setenv(name, "kitchen")
check(time.Kitchen, time.Kitchen, false, "should handle kitchen value")
}
func (s *argsTestSuite) TestDetailString() {
home := "/home"
name := "test-name"
@ -350,6 +414,9 @@ func (s *argsTestSuite) TestDetailString() {
fmt.Sprintf("%s: %t", EnvSkipBackup, unsafeSkipBackup),
fmt.Sprintf("%s: %s", EnvDataBackupPath, home),
fmt.Sprintf("%s: %d", EnvPreupgradeMaxRetries, preupgradeMaxRetries),
fmt.Sprintf("%s: %t", EnvDisableLogs, cfg.DisableLogs),
fmt.Sprintf("%s: %t", EnvColorLogs, cfg.ColorLogs),
fmt.Sprintf("%s: %s", EnvTimeFormatLogs, cfg.TimeFormatLogs),
"Derived Values:",
fmt.Sprintf("Root Dir: %s", home),
fmt.Sprintf("Upgrade Dir: %s", home),
@ -373,7 +440,18 @@ func (s *argsTestSuite) TestGetConfigFromEnv() {
absPath, perr := filepath.Abs(relPath)
s.Require().NoError(perr)
newConfig := func(home, name string, downloadBin, downloadMustHaveChecksum, restartUpgrade bool, restartDelay int, skipBackup bool, dataBackupPath string, interval, preupgradeMaxRetries int, disableLogs bool) *Config {
newConfig := func(
home, name string,
downloadBin bool,
downloadMustHaveChecksum bool,
restartUpgrade bool,
restartDelay int,
skipBackup bool,
dataBackupPath string,
interval, preupgradeMaxRetries int,
disableLogs, colorLogs bool,
timeFormatLogs string,
) *Config {
return &Config{
Home: home,
Name: name,
@ -386,6 +464,8 @@ func (s *argsTestSuite) TestGetConfigFromEnv() {
DataBackupPath: dataBackupPath,
PreupgradeMaxRetries: preupgradeMaxRetries,
DisableLogs: disableLogs,
ColorLogs: colorLogs,
TimeFormatLogs: timeFormatLogs,
}
}
@ -408,215 +488,248 @@ func (s *argsTestSuite) TestGetConfigFromEnv() {
DataBackupPath: "bad",
Interval: "bad",
PreupgradeMaxRetries: "bad",
TimeFormatLogs: "bad",
},
expectedCfg: nil,
expectedErrCount: 10,
expectedErrCount: 11,
},
{
name: "all good",
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),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen),
expectedErrCount: 0,
},
{
name: "nothing set",
envVals: cosmovisorEnv{"", "", "", "", "", "", "", "", "", "", "false"},
envVals: cosmovisorEnv{"", "", "", "", "", "", "", "", "", "", "false", "false", ""},
expectedCfg: nil,
expectedErrCount: 3,
},
// Note: Home and Name tests are done in TestValidate
// timeformat tests are done in the TestTimeFormat
{
name: "download bin bad",
envVals: cosmovisorEnv{absPath, "testname", "bad", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "bad", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "download bin not set",
envVals: cosmovisorEnv{absPath, "testname", "", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false),
name: "download bin not set",
envVals: cosmovisorEnv{absPath, "testname", "", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen),
expectedErrCount: 0,
},
{
name: "download bin 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),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen),
expectedErrCount: 0,
},
{
name: "download bin 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),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen),
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),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen),
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),
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: newConfig(absPath, "testname", true, false, false, 600, true, absPath, 303, 1, false, true, time.Kitchen),
expectedErrCount: 0,
},
{
name: "restart upgrade bad",
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "bad", "600ms", "true", "", "303ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "bad", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "restart upgrade not set",
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "", "600ms", "true", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false, true, time.Kitchen),
expectedErrCount: 0,
},
{
name: "restart upgrade true",
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),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "true", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false, true, time.Kitchen),
expectedErrCount: 0,
},
{
name: "restart upgrade 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),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen),
expectedErrCount: 0,
},
{
name: "skip unsafe backups bad",
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "bad", "", "303ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "bad", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "skip unsafe backups not set",
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false, true, time.Kitchen),
expectedErrCount: 0,
},
{
name: "skip unsafe backups 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),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen),
expectedErrCount: 0,
},
{
name: "skip unsafe backups 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),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "false", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false, true, time.Kitchen),
expectedErrCount: 0,
},
{
name: "poll interval bad",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "bad", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "bad", "1", "false", "true", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "poll interval 0",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "0", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "0", "1", "false", "true", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "poll interval not set",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "", "1", "false"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 300, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "", "1", "false", "false", "kitchen"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 300, 1, false, false, time.Kitchen),
expectedErrCount: 0,
},
{
name: "poll interval 600",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "600", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "600", "1", "false", "true", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "poll interval 1s",
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),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "1s", "1", "false", "false", "kitchen"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 1000, 1, false, false, time.Kitchen),
expectedErrCount: 0,
},
{
name: "poll interval -3m",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "-3m", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "-3m", "1", "false", "true", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "restart delay bad",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "bad", "false", "", "303ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "bad", "false", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "restart delay 0",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "0", "false", "", "303ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "0", "false", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "restart delay not set",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "", "false", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 0, false, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "", "false", "", "303ms", "1", "false", "false", "kitchen"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 0, false, absPath, 303, 1, false, false, time.Kitchen),
expectedErrCount: 0,
},
{
name: "restart delay 600",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600", "false", "", "300ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600", "false", "", "300ms", "1", "false", "true", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "restart delay 1s",
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),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "1s", "false", "", "303ms", "1", "false", "false", "kitchen"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 1000, false, absPath, 303, 1, false, false, time.Kitchen),
expectedErrCount: 0,
},
{
name: "restart delay -3m",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "-3m", "false", "", "303ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "-3m", "false", "", "303ms", "1", "false", "true", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "prepupgrade max retries bad",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "bad", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "bad", "false", "true", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "prepupgrade max retries 0",
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),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "0", "false", "false", "kitchen"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false, false, time.Kitchen),
expectedErrCount: 0,
},
{
name: "prepupgrade max retries not set",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "false"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "false", "false", "kitchen"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false, false, time.Kitchen),
expectedErrCount: 0,
},
{
name: "prepupgrade max retries 5",
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),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "false", "false", "kitchen"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 5, false, false, time.Kitchen),
expectedErrCount: 0,
},
{
name: "disable logs bad",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "bad"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "bad", "true", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "disable logs good",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "kitchen"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, false, time.Kitchen),
expectedErrCount: 0,
},
{
name: "disable logs color bad",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "true", "bad", "kitchen"},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "disable logs color good",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "kitchen"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, false, time.Kitchen),
expectedErrCount: 0,
},
{
name: "disable logs timestamp",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, false, ""),
expectedErrCount: 0,
},
{
name: "enable rf3339 logs timestamp",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, true, time.RFC3339),
expectedErrCount: 0,
},
{
name: "invalid logs timestamp format",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "invalid"},
expectedCfg: nil,
expectedErrCount: 1,
},
}
for _, tc := range tests {

View File

@ -5,10 +5,9 @@ import (
"os"
"path"
"cosmossdk.io/log"
"cosmossdk.io/tools/cosmovisor"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"cosmossdk.io/tools/cosmovisor"
)
func NewAddUpgradeCmd() *cobra.Command {
@ -32,10 +31,7 @@ func AddUpgrade(cmd *cobra.Command, args []string) error {
return err
}
logger := cmd.Context().Value(log.ContextKey).(log.Logger)
if cfg.DisableLogs {
logger = log.NewCustomLogger(zerolog.Nop())
}
logger := cfg.Logger(os.Stdout)
upgradeName := args[0]
if len(upgradeName) == 0 {

View File

@ -6,12 +6,14 @@ import (
"io"
"os"
"path/filepath"
"time"
"github.com/spf13/cobra"
"cosmossdk.io/log"
"cosmossdk.io/tools/cosmovisor"
"cosmossdk.io/x/upgrade/plan"
"cosmossdk.io/tools/cosmovisor"
)
var initCmd = &cobra.Command{
@ -19,8 +21,7 @@ var initCmd = &cobra.Command{
Short: "Initialize a cosmovisor daemon home directory.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
logger := cmd.Context().Value(log.ContextKey).(log.Logger)
return InitializeCosmovisor(logger, args)
return InitializeCosmovisor(nil, args)
},
}
@ -43,6 +44,10 @@ func InitializeCosmovisor(logger log.Logger, args []string) error {
return err
}
if logger == nil {
logger = cfg.Logger(os.Stdout)
}
logger.Info("checking on the genesis/bin directory")
genBinExe := cfg.GenesisBin()
genBinDir, _ := filepath.Split(genBinExe)
@ -95,6 +100,15 @@ func getConfigForInitCmd() (*cosmovisor.Config, error) {
Home: os.Getenv(cosmovisor.EnvHome),
Name: os.Getenv(cosmovisor.EnvName),
}
var err error
if cfg.ColorLogs, err = cosmovisor.BooleanOption(cosmovisor.EnvColorLogs, true); err != nil {
errs = append(errs, err)
}
if cfg.TimeFormatLogs, err = cosmovisor.TimeFormatOptionFromEnv(cosmovisor.EnvTimeFormatLogs, time.Kitchen); err != nil {
errs = append(errs, err)
}
if len(cfg.Name) == 0 {
errs = append(errs, fmt.Errorf("%s is not set", cosmovisor.EnvName))
}
@ -105,7 +119,7 @@ func getConfigForInitCmd() (*cosmovisor.Config, error) {
errs = append(errs, fmt.Errorf("%s must be an absolute path", cosmovisor.EnvHome))
}
if len(errs) > 0 {
return nil, errors.Join(errs...)
return cfg, errors.Join(errs...)
}
return cfg, nil
}

View File

@ -15,6 +15,7 @@ import (
"github.com/stretchr/testify/suite"
"cosmossdk.io/log"
"cosmossdk.io/tools/cosmovisor"
)
@ -28,15 +29,24 @@ func TestInitTestSuite(t *testing.T) {
// cosmovisorInitEnv are some string values of environment variables used to configure Cosmovisor, and used by the init command.
type cosmovisorInitEnv struct {
Home string
Name string
Home string
Name string
ColorLogs string
TimeFormatLogs string
}
type envMap struct {
val string
allowEmpty bool
}
// ToMap creates a map of the cosmovisorInitEnv where the keys are the env var names.
func (c cosmovisorInitEnv) ToMap() map[string]string {
return map[string]string{
cosmovisor.EnvHome: c.Home,
cosmovisor.EnvName: c.Name,
func (c cosmovisorInitEnv) ToMap() map[string]envMap {
return map[string]envMap{
cosmovisor.EnvHome: {val: c.Home, allowEmpty: false},
cosmovisor.EnvName: {val: c.Name, allowEmpty: false},
cosmovisor.EnvColorLogs: {val: c.ColorLogs, allowEmpty: false},
cosmovisor.EnvTimeFormatLogs: {val: c.TimeFormatLogs, allowEmpty: true},
}
}
@ -47,6 +57,10 @@ func (c *cosmovisorInitEnv) Set(envVar, envVal string) {
c.Home = envVal
case cosmovisor.EnvName:
c.Name = envVal
case cosmovisor.EnvColorLogs:
c.Name = envVal
case cosmovisor.EnvTimeFormatLogs:
c.Name = envVal
default:
panic(fmt.Errorf("Unknown environment variable [%s]. Cannot set field to [%s]. ", envVar, envVal))
}
@ -77,9 +91,9 @@ func (s *InitTestSuite) setEnv(t *testing.T, env *cosmovisorInitEnv) {
for envVar, envVal := range env.ToMap() {
var err error
var msg string
if len(envVal) != 0 {
err = os.Setenv(envVar, envVal)
msg = fmt.Sprintf("setting %s to %s", envVar, envVal)
if len(envVal.val) != 0 || envVal.allowEmpty {
err = os.Setenv(envVar, envVal.val)
msg = fmt.Sprintf("setting %s to %s", envVar, envVal.val)
} else {
err = os.Unsetenv(envVar)
msg = fmt.Sprintf("unsetting %s", envVar)

View File

@ -3,15 +3,14 @@ package main
import (
"context"
"os"
"cosmossdk.io/log"
)
func main() {
logger := log.NewLogger(os.Stdout).With(log.ModuleKey, "cosmovisor")
ctx := context.WithValue(context.Background(), log.ContextKey, logger)
// error logger used only during configuration phase
cfg, _ := getConfigForInitCmd()
logger := cfg.Logger(os.Stderr)
if err := NewRootCmd().ExecuteContext(ctx); err != nil {
if err := NewRootCmd().ExecuteContext(context.Background()); err != nil {
if errMulti, ok := err.(interface{ Unwrap() []error }); ok {
err := errMulti.Unwrap()
for _, e := range err {

View File

@ -1,10 +1,11 @@
package main
import (
"cosmossdk.io/log"
"cosmossdk.io/tools/cosmovisor"
"github.com/rs/zerolog"
"os"
"github.com/spf13/cobra"
"cosmossdk.io/tools/cosmovisor"
)
var runCmd = &cobra.Command{
@ -12,23 +13,19 @@ var runCmd = &cobra.Command{
Short: "Run an APP command.",
SilenceUsage: true,
DisableFlagParsing: true,
RunE: func(cmd *cobra.Command, args []string) error {
return Run(cmd, args)
RunE: func(_ *cobra.Command, args []string) error {
return run(args)
},
}
// Run runs the configured program with the given args and monitors it for upgrades.
func Run(cmd *cobra.Command, args []string, options ...RunOption) error {
// run runs the configured program with the given args and monitors it for upgrades.
func run(args []string, options ...RunOption) error {
cfg, err := cosmovisor.GetConfigFromEnv()
if err != nil {
return err
}
logger := cmd.Context().Value(log.ContextKey).(log.Logger)
if cfg.DisableLogs {
logger = log.NewCustomLogger(zerolog.Nop())
}
logger := cfg.Logger(os.Stdout)
runCfg := DefaultRunConfig
for _, opt := range options {

View File

@ -26,7 +26,7 @@ func StdOutRunOption(w io.Writer) RunOption {
}
}
// SdErrRunOption sets the StdErr writer for the Run command
// StdErrRunOption sets the StdErr writer for the Run command
func StdErrRunOption(w io.Writer) RunOption {
return func(cfg *RunConfig) {
cfg.StdErr = w

View File

@ -6,8 +6,9 @@ import (
"runtime/debug"
"strings"
"cosmossdk.io/tools/cosmovisor"
"github.com/spf13/cobra"
"cosmossdk.io/tools/cosmovisor"
)
func NewVersionCmd() *cobra.Command {
@ -45,7 +46,7 @@ func printVersion(cmd *cobra.Command, args []string, noAppVersion bool) error {
return nil
}
if err := Run(cmd, append([]string{"version"}, args...)); err != nil {
if err := run(append([]string{"version"}, args...)); err != nil {
return fmt.Errorf("failed to run version command: %w", err)
}
@ -59,8 +60,7 @@ func printVersionJSON(cmd *cobra.Command, args []string, noAppVersion bool) erro
}
buf := new(strings.Builder)
if err := Run(
cmd,
if err := run(
[]string{"version", "--long", "--output", "json"},
StdOutRunOption(buf),
); err != nil {