diff --git a/cosmovisor/go.mod b/cosmovisor/go.mod index c056e04e6662..b3e3aa406f3a 100644 --- a/cosmovisor/go.mod +++ b/cosmovisor/go.mod @@ -4,14 +4,10 @@ go 1.17 require ( github.com/cosmos/cosmos-sdk v0.46.0-beta2 - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-getter v1.5.11 github.com/otiai10/copy v1.7.0 github.com/rs/zerolog v1.26.1 github.com/stretchr/testify v1.7.1 - google.golang.org/api v0.63.0 // indirect - google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) require ( @@ -57,6 +53,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.2.1 // indirect @@ -111,11 +108,14 @@ require ( golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/api v0.63.0 // indirect google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf // indirect google.golang.org/grpc v1.45.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.66.3 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/cosmovisor/scanner.go b/cosmovisor/scanner.go index 4dbcb13d7f8d..255f737d4405 100644 --- a/cosmovisor/scanner.go +++ b/cosmovisor/scanner.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "time" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" @@ -29,18 +30,29 @@ func newUpgradeFileWatcher(filename string, interval time.Duration) (*fileWatche if filename == "" { return nil, errors.New("filename undefined") } + filenameAbs, err := filepath.Abs(filename) if err != nil { return nil, - fmt.Errorf("wrong path, %s must be a valid file path, [%w]", filename, err) + fmt.Errorf("invalid path; %s must be a valid file path: %w", filename, err) } + dirname := filepath.Dir(filename) info, err := os.Stat(dirname) if err != nil || !info.IsDir() { - return nil, fmt.Errorf("wrong path, %s must be an existing directory, [%w]", dirname, err) + return nil, fmt.Errorf("invalid path; %s must be an existing directory: %w", dirname, err) } - return &fileWatcher{filenameAbs, interval, upgradetypes.Plan{}, time.Time{}, make(chan bool), time.NewTicker(interval), false, false}, nil + return &fileWatcher{ + filename: filenameAbs, + interval: interval, + currentInfo: upgradetypes.Plan{}, + lastModTime: time.Time{}, + cancel: make(chan bool), + ticker: time.NewTicker(interval), + needsUpdate: false, + initialized: false, + }, nil } func (fw *fileWatcher) Stop() { @@ -64,11 +76,13 @@ func (fw *fileWatcher) MonitorUpdate(currentUpgrade upgradetypes.Plan) <-chan st done <- struct{}{} return } + case <-fw.cancel: return } } }() + return done } @@ -79,25 +93,33 @@ func (fw *fileWatcher) CheckUpdate(currentUpgrade upgradetypes.Plan) bool { if fw.needsUpdate { return true } + stat, err := os.Stat(fw.filename) - if err != nil { // file doesn't exists + if err != nil { + // file doesn't exists return false } + if !stat.ModTime().After(fw.lastModTime) { return false } + info, err := parseUpgradeInfoFile(fw.filename) if err != nil { - Logger.Fatal().Err(err).Msg("Can't parse upgrade info file") + Logger.Fatal().Err(err).Msg("failed to parse upgrade info file") return false } - if !fw.initialized { // daemon has restarted + + if !fw.initialized { + // daemon has restarted fw.initialized = true fw.currentInfo = info fw.lastModTime = stat.ModTime() - // heuristic: deamon has restarted, so we don't know if we successfully downloaded the upgrade or not. - // so we try to compare the running upgrade name (read from the cosmovisor file) with the upgrade info - if currentUpgrade.Name != fw.currentInfo.Name { + + // Heuristic: Deamon has restarted, so we don't know if we successfully + // downloaded the upgrade or not. So we try to compare the running upgrade + // name (read from the cosmovisor file) with the upgrade info. + if !strings.EqualFold(currentUpgrade.Name, fw.currentInfo.Name) { fw.needsUpdate = true return true } @@ -109,24 +131,32 @@ func (fw *fileWatcher) CheckUpdate(currentUpgrade upgradetypes.Plan) bool { fw.needsUpdate = true return true } + return false } func parseUpgradeInfoFile(filename string) (upgradetypes.Plan, error) { var ui upgradetypes.Plan + f, err := os.Open(filename) if err != nil { - return ui, err + return upgradetypes.Plan{}, err } defer f.Close() + d := json.NewDecoder(f) - err = d.Decode(&ui) - if err != nil { - return ui, err + if err := d.Decode(&ui); err != nil { + return upgradetypes.Plan{}, err } + // required values must be set - if ui.Height == 0 || ui.Name == "" { - return upgradetypes.Plan{}, fmt.Errorf("invalid upgrade-info.json content. Name and Hight must be not empty. Got: %v", ui) + if ui.Height <= 0 || ui.Name == "" { + return upgradetypes.Plan{}, fmt.Errorf("invalid upgrade-info.json content; name and height must be not empty; got: %v", ui) } + + // Normalize name to prevent operator error in upgrade name case sensitivity + // errors. + ui.Name = strings.ToLower(ui.Name) + return ui, err } diff --git a/cosmovisor/upgrade.go b/cosmovisor/upgrade.go index a91b40cea8d8..d9f44de32c1b 100644 --- a/cosmovisor/upgrade.go +++ b/cosmovisor/upgrade.go @@ -10,10 +10,9 @@ import ( "runtime" "strings" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" "github.com/hashicorp/go-getter" "github.com/otiai10/copy" - - upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" ) // DoUpgrade will be called after the log message has been parsed and the process has terminated.