cosmos-sdk/cosmovisor/errors/multi.go
Robert Zaremba f6458f0795
fix(cosmovisor): udpate the help and version command output (#10458)
## Description

Closes: #10450

* `cosmovisor help` should not print any error. 
* `cosmovisor version` should print more clear message if it can't run `app version`.

## Before 

```
~/tmp ❯ cosmovisor version
Cosmosvisor - A process manager for Cosmos SDK application binaries.

Cosmovisor is a wrapper for a Cosmos SDK based App (set using the required DAEMON_NAME env variable).
It starts the App by passing all provided arguments and monitors the DAEMON_HOME/data/upgrade-info.json
file to perform an update. The upgrade-info.json file is created by the App x/upgrade module
when the blockchain height reaches an approved upgrade proposal. The file includes data from
the proposal. Cosmovisor interprets that data to perform an update: switch a current binary
and restart the App.

Configuration of Cosmovisor is done through environment variables, which are
documented in: https://github.com/cosmos/cosmos-sdk/tree/master/cosmovisor/README.md

8:03PM ERR multiple configuration errors found: module=cosmovisor
8:03PM ERR   1: error="DAEMON_NAME is not set" module=cosmovisor
8:03PM ERR   2: error="DAEMON_HOME is not set" module=cosmovisor
8:03PM ERR  error="2 errors: 1: DAEMON_NAME is not set, 2: DAEMON_HOME is not set" module=cosmovisor


❯ cosmovisor help
Cosmosvisor - A process manager for Cosmos SDK application binaries.

Cosmovisor is a wrapper for a Cosmos SDK based App (set using the required DAEMON_NAME env variable).
It starts the App by passing all provided arguments and monitors the DAEMON_HOME/data/upgrade-info.json
file to perform an update. The upgrade-info.json file is created by the App x/upgrade module
when the blockchain height reaches an approved upgrade proposal. The file includes data from
the proposal. Cosmovisor interprets that data to perform an update: switch a current binary
and restart the App.

Configuration of Cosmovisor is done through environment variables, which are
documented in: https://github.com/cosmos/cosmos-sdk/tree/master/cosmovisor/README.md

8:03PM ERR multiple configuration errors found: module=cosmovisor
8:03PM ERR   1: error="DAEMON_NAME is not set" module=cosmovisor
8:03PM ERR   2: error="DAEMON_HOME is not set" module=cosmovisor
8:03PM ERR  error="2 errors: 1: DAEMON_NAME is not set, 2: DAEMON_HOME is not set" module=cosmovisor
```

## After

```
❯ ./cosmovisor version
Cosmovisor Version:  v1.0.0-76-g66bddfb04
8:05PM ERR Can't run APP version
8:05PM ERR   1: error="DAEMON_NAME is not set"
8:05PM ERR   2: error="DAEMON_HOME is not set"


❯ ./cosmovisor help
Cosmosvisor - A process manager for Cosmos SDK application binaries.

Cosmovisor is a wrapper for a Cosmos SDK based App (set using the required DAEMON_NAME env variable).
It starts the App by passing all provided arguments and monitors the DAEMON_HOME/data/upgrade-info.json
file to perform an update. The upgrade-info.json file is created by the App x/upgrade module
when the blockchain height reaches an approved upgrade proposal. The file includes data from
the proposal. Cosmovisor interprets that data to perform an update: switch a current binary
and restart the App.

Configuration of Cosmovisor is done through environment variables, which are
documented in: https://github.com/cosmos/cosmos-sdk/tree/master/cosmovisor/README.md

To get help for the configured binary:
  cosmovisor run help
```




---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [x] added `!` to the type prefix if API or client breaking change
- [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [x] provided a link to the relevant issue or specification
- [x] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [x] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [x] updated the relevant documentation or specification
- [x] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
2021-11-19 17:35:56 +00:00

87 lines
2.0 KiB
Go

package errors
import (
"fmt"
"strings"
"github.com/rs/zerolog"
)
// MultiError is an error combining multiple other errors.
// It will never have 0 or 1 errors. It will always have two or more.
type MultiError struct {
errs []error
}
// FlattenErrors possibly creates a MultiError.
// Nil entries are ignored.
// If all provided errors are nil (or nothing is provided), nil is returned.
// If only one non-nil error is provided, it is returned unchanged.
// If two or more non-nil errors are provided, the returned error will be of type *MultiError
// and it will contain each non-nil error.
func FlattenErrors(errs ...error) error {
rv := MultiError{}
for _, err := range errs {
if err != nil {
if merr, isMerr := err.(*MultiError); isMerr {
rv.errs = append(rv.errs, merr.errs...)
} else {
rv.errs = append(rv.errs, err)
}
}
}
switch rv.Len() {
case 0:
return nil
case 1:
return rv.errs[0]
}
return &rv
}
// GetErrors gets all the errors that make up this MultiError.
func (e MultiError) GetErrors() []error {
// Return a copy of the errs slice to prevent alteration of the original slice.
rv := make([]error, e.Len())
copy(rv, e.errs)
return rv
}
// Len gets the number of errors in this MultiError.
func (e MultiError) Len() int {
return len(e.errs)
}
// Error implements the error interface for a MultiError.
func (e *MultiError) Error() string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("%d errors: ", len(e.errs)))
for i, err := range e.errs {
if i != 0 {
sb.WriteString(", ")
}
sb.WriteString(fmt.Sprintf("%d: %v", i+1, err))
}
return sb.String()
}
// String implements the string interface for a MultiError.
func (e MultiError) String() string {
return e.Error()
}
func LogErrors(logger zerolog.Logger, msg string, err error) {
switch err := err.(type) {
case *MultiError:
if msg != "" {
logger.Error().Msg(msg)
}
for i, e := range err.GetErrors() {
logger.Error().Err(e).Msg(fmt.Sprintf(" %d:", i+1))
}
default:
logger.Error().Err(err).Msg(msg)
}
}