Skip to content

Commit

Permalink
fix(cosmovisor): fix upgrade detection (#20585)
Browse files Browse the repository at this point in the history
  • Loading branch information
julienrbrt committed Aug 12, 2024
1 parent 3e41324 commit 76e1fa3
Show file tree
Hide file tree
Showing 13 changed files with 210 additions and 236 deletions.
13 changes: 13 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ jobs:
PATTERNS: |
**/*.go
*.go
!store/**
- name: run linting (short)
if: steps.lint_long.outcome == 'skipped' && env.GIT_DIFF
run: |
make lint
env:
GIT_DIFF: ${{ env.GIT_DIFF }}
LINT_DIFF: 1
- uses: technote-space/get-diff-action@v6.1.2
if: steps.lint_long.outcome == 'skipped'
with:
PATTERNS: |
store/**
- name: run linting (short)
if: steps.lint_long.outcome == 'skipped' && env.GIT_DIFF
run: |
Expand Down
9 changes: 5 additions & 4 deletions tools/cosmovisor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,11 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

## Features

* [#19764](https://github.com/cosmos/cosmos-sdk/issues/19764) Use config file for cosmovisor configuration.
## v1.6.0 - 2024-08-12

## Improvements

* [#20573](https://github.com/cosmos/cosmos-sdk/pull/20573) Bump `cosmossdk.io/x/upgrade` to v0.1.3 (including go-getter vulnerability fix)
* Bump `cosmossdk.io/x/upgrade` to v0.1.4 (including go-getter vulnerability fix)
* [#19995](https://github.com/cosmos/cosmos-sdk/pull/19995):
* `init command` writes the configuration to the config file only at the default path `DAEMON_HOME/cosmovisor/config.toml`.
* Provide `--cosmovisor-config` flag with value as args to provide the path to the configuration file in the `run` command. `run --cosmovisor-config <path> (other cmds with flags) ...`.
Expand All @@ -52,6 +50,9 @@ Ref: https://keepachangelog.com/en/1.0.0/
## Bug Fixes

* [#20062](https://github.com/cosmos/cosmos-sdk/pull/20062) Fixed cosmovisor add-upgrade permissions
* [#20585](https://github.com/cosmos/cosmos-sdk/pull/20585) Always parse stdout and stderr
* [#20585](https://github.com/cosmos/cosmos-sdk/pull/20585) Pass right home to command `status`
* [#20585](https://github.com/cosmos/cosmos-sdk/pull/20585) Fix upgrades applied automatically (check two casing of sync_info)

## v1.5.0 - 2023-07-17

Expand Down
37 changes: 17 additions & 20 deletions tools/cosmovisor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ sidebar_position: 1
`cosmovisor` is a process manager for Cosmos SDK application binaries that automates application binary switch at chain upgrades.
It polls the `upgrade-info.json` file that is created by the x/upgrade module at upgrade height, and then can automatically download the new binary, stop the current binary, switch from the old binary to the new one, and finally restart the node with the new binary.

* [Design](#design)
* [Contributing](#contributing)
* [Setup](#setup)
* [Installation](#installation)
* [Command Line Arguments And Environment Variables](#command-line-arguments-and-environment-variables)
* [Folder Layout](#folder-layout)
* [Usage](#usage)
* [Initialization](#initialization)
* [Detecting Upgrades](#detecting-upgrades)
* [Adding Upgrade Binary](#adding-upgrade-binary)
* [Auto-Download](#auto-download)
* [Example: SimApp Upgrade](#example-simapp-upgrade)
* [Chain Setup](#chain-setup)
* [Prepare Cosmovisor and Start the Chain](#prepare-cosmovisor-and-start-the-chain)
* [Update App](#update-app)
* [Cosmovisor](#cosmovisor)
* [Design](#design)
* [Contributing](#contributing)
* [Setup](#setup)
* [Installation](#installation)
* [Command Line Arguments And Environment Variables](#command-line-arguments-and-environment-variables)
* [Folder Layout](#folder-layout)
* [Usage](#usage)
* [Initialization](#initialization)
* [Detecting Upgrades](#detecting-upgrades)
* [Adding Upgrade Binary](#adding-upgrade-binary)
* [Auto-Download](#auto-download)
* [Example: SimApp Upgrade](#example-simapp-upgrade)
* [Chain Setup](#chain-setup)
* [Prepare Cosmovisor and Start the Chain](#prepare-cosmovisor-and-start-the-chain)
* [Update App](#update-app)

## Design

Expand Down Expand Up @@ -87,11 +88,7 @@ The first argument passed to `cosmovisor` is the action for `cosmovisor` to take

All arguments passed to `cosmovisor run` will be passed to the application binary (as a subprocess). `cosmovisor` will return `/dev/stdout` and `/dev/stderr` of the subprocess as its own. For this reason, `cosmovisor run` cannot accept any command-line arguments other than those available to the application binary.

:::warning
Use of `cosmovisor` without one of the action arguments is deprecated. For backwards compatibility, if the first argument is not an action argument, `run` is assumed. However, this fallback might be removed in future versions, so it is recommended that you always provide `run`.
:::

`cosmovisor` reads its configuration from environment variables:
`cosmovisor` reads its configuration from environment variables, or its configuration file (use `--cosmovisor-config <path>`):

* `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.).
Expand Down
27 changes: 15 additions & 12 deletions tools/cosmovisor/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,31 +156,32 @@ func (cfg *Config) CurrentBin() (string, error) {
return binpath, nil
}

// GetConfigFromFile will read the configuration from the file at the given path.
// If the file path is not provided, it will try to read the configuration from the ENV variables.
// GetConfigFromFile will read the configuration from the config file at the given path.
// If the file path is not provided, it will read the configuration from the ENV variables.
// If a file path is provided and ENV variables are set, they will override the values in the file.
func GetConfigFromFile(filePath string) (*Config, error) {
if filePath == "" {
return GetConfigFromEnv()
return GetConfigFromEnv(false)
}

// ensure the file exist
if _, err := os.Stat(filePath); err != nil {
return nil, fmt.Errorf("config not found: at %s : %w", filePath, err)
}

v := viper.New()
// read the configuration from the file
viper.SetConfigFile(filePath)
v.SetConfigFile(filePath)
// load the env variables
// if the env variable is set, it will override the value provided by the config
viper.AutomaticEnv()
v.AutomaticEnv()

if err := viper.ReadInConfig(); err != nil {
if err := v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}

cfg := &Config{}
if err := viper.Unmarshal(cfg); err != nil {
if err := v.Unmarshal(cfg); err != nil {
return nil, fmt.Errorf("failed to unmarshal configuration: %w", err)
}

Expand All @@ -203,7 +204,7 @@ func GetConfigFromFile(filePath string) (*Config, error) {

// GetConfigFromEnv will read the environmental variables into a config
// and then validate it is reasonable
func GetConfigFromEnv() (*Config, error) {
func GetConfigFromEnv(skipValidate bool) (*Config, error) {
var errs []error
cfg := &Config{
Home: os.Getenv(EnvHome),
Expand Down Expand Up @@ -281,9 +282,11 @@ func GetConfigFromEnv() (*Config, error) {
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, errors.Join(errs...)
if !skipValidate {
errs = append(errs, cfg.validate()...)
if len(errs) > 0 {
return nil, errors.Join(errs...)
}
}

return cfg, nil
Expand Down Expand Up @@ -574,7 +577,7 @@ func (cfg Config) DetailString() string {
return sb.String()
}

// Export exports the configuration to a file at the given path.
// Export exports the configuration to a file at the cosmovisor root directory.
func (cfg Config) Export() (string, error) {
// always use the default path
path := filepath.Clean(cfg.DefaultCfgPath())
Expand Down
2 changes: 1 addition & 1 deletion tools/cosmovisor/args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() {

s.T().Run(tc.name, func(t *testing.T) {
s.setEnv(t, &tc.envVals)
cfg, err := GetConfigFromEnv()
cfg, err := GetConfigFromEnv(false)
if tc.expectedErrCount == 0 {
assert.NoError(t, err)
} else if assert.Error(t, err) {
Expand Down
43 changes: 11 additions & 32 deletions tools/cosmovisor/cmd/cosmovisor/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"io"
"os"
"path/filepath"
"time"

"github.com/spf13/cobra"

Expand Down Expand Up @@ -45,11 +44,18 @@ func InitializeCosmovisor(logger log.Logger, args []string) error {
case exeInfo.IsDir():
return errors.New("invalid path to executable: must not be a directory")
}
cfg, err := getConfigForInitCmd()

// skipping validation to not check if directories exist
cfg, err := cosmovisor.GetConfigFromEnv(true)
if err != nil {
return err
}

// process to minimal validation
if err := minConfigValidate(cfg); err != nil {
return err
}

if logger == nil {
logger = cfg.Logger(os.Stdout)
}
Expand Down Expand Up @@ -98,37 +104,13 @@ func InitializeCosmovisor(logger log.Logger, args []string) error {
if err != nil {
return fmt.Errorf("failed to export configuration: %w", err)
}

logger.Info(fmt.Sprintf("config file present at: %s", filePath))
logger.Info(fmt.Sprintf("cosmovisor config.toml created at: %s", filePath))

return nil
}

// getConfigForInitCmd gets just the configuration elements needed to initialize cosmovisor.
func getConfigForInitCmd() (*cosmovisor.Config, error) {
func minConfigValidate(cfg *cosmovisor.Config) error {
var errs []error

// Note: Not using GetConfigFromEnv here because that checks that the directories already exist.
// We also don't care about the rest of the configuration stuff in here.
cfg := &cosmovisor.Config{
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 backup is not set, use the home directory
if cfg.DataBackupPath == "" {
cfg.DataBackupPath = cfg.Home
}

if len(cfg.Name) == 0 {
errs = append(errs, fmt.Errorf("%s is not set", cosmovisor.EnvName))
}
Expand All @@ -140,10 +122,7 @@ func getConfigForInitCmd() (*cosmovisor.Config, error) {
errs = append(errs, fmt.Errorf("%s must be an absolute path", cosmovisor.EnvHome))
}

if len(errs) > 0 {
return cfg, errors.Join(errs...)
}
return cfg, nil
return errors.Join(errs...)
}

// copyFile copies the file at the given source to the given destination.
Expand Down
8 changes: 4 additions & 4 deletions tools/cosmovisor/cmd/cosmovisor/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
fmt.Sprintf("making sure %q is executable", genBinExe),
"checking on the current symlink and creating it if needed",
fmt.Sprintf("the current symlink points to: %q", genBinExe),
fmt.Sprintf("config file present at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)),
fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)),
}

s.setEnv(s.T(), env)
Expand Down Expand Up @@ -555,7 +555,7 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
fmt.Sprintf("the %q file already exists", genBinDirExe),
fmt.Sprintf("making sure %q is executable", genBinDirExe),
fmt.Sprintf("the current symlink points to: %q", genBinDirExe),
fmt.Sprintf("config file present at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)),
fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)),
}

s.setEnv(t, env)
Expand Down Expand Up @@ -588,7 +588,7 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
fmt.Sprintf("copying executable into place: %q", genBinExe),
fmt.Sprintf("making sure %q is executable", genBinExe),
fmt.Sprintf("the current symlink points to: %q", genBinExe),
fmt.Sprintf("config file present at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)),
fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)),
}

s.setEnv(t, env)
Expand Down Expand Up @@ -620,7 +620,7 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
require.NoError(t, err, "calling InitializeCosmovisor")
bufferBz := buffer.Collect()
bufferStr := string(bufferBz)
assert.Contains(t, bufferStr, fmt.Sprintf("config file present at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)))
assert.Contains(t, bufferStr, fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)))
})
}

Expand Down
13 changes: 0 additions & 13 deletions tools/cosmovisor/cmd/cosmovisor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,7 @@ import (
)

func main() {
// error logger used only during configuration phase
cfg, _ := getConfigForInitCmd()
logger := cfg.Logger(os.Stderr)

if err := NewRootCmd().ExecuteContext(context.Background()); err != nil {
if errMulti, ok := err.(interface{ Unwrap() []error }); ok {
err := errMulti.Unwrap()
for _, e := range err {
logger.Error("", "error", e)
}
} else {
logger.Error("", "error", err)
}

os.Exit(1)
}
}
2 changes: 1 addition & 1 deletion tools/cosmovisor/cmd/cosmovisor/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var runCmd = &cobra.Command{
Use: "run",
Short: "Run an APP command.",
Long: `Run an APP command. This command is intended to be used by the cosmovisor binary.
Provide cosmovisor config file path in command args or set env variables to load configuration.
Provide '--cosmovisor-config' file path in command args or set env variables to load configuration.
`,
SilenceUsage: true,
DisableFlagParsing: true,
Expand Down
4 changes: 1 addition & 3 deletions tools/cosmovisor/go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
module cosmossdk.io/tools/cosmovisor

go 1.22.2

toolchain go1.22.4
go 1.22.4

require (
cosmossdk.io/log v1.4.0
Expand Down
2 changes: 1 addition & 1 deletion tools/cosmovisor/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type Launcher struct {
}

func NewLauncher(logger log.Logger, cfg *Config) (Launcher, error) {
fw, err := newUpgradeFileWatcher(cfg, logger)
fw, err := newUpgradeFileWatcher(cfg)
if err != nil {
return Launcher{}, err
}
Expand Down
Loading

0 comments on commit 76e1fa3

Please sign in to comment.