Merge cosmosd (#6820)

Closes: #6521

Co-authored-by: Alexander Bezobchuk <alexanderbez@users.noreply.github.com>
Co-authored-by: Alessio Treglia <alessio@tendermint.com>
Co-authored-by: Anil Kumar Kammari <anil@vitwit.com>
This commit is contained in:
Jonathan Gimeno 2020-08-19 17:58:43 +02:00 committed by GitHub
parent 8f51c150de
commit bf0142b272
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1518 additions and 6 deletions

View File

@ -15,6 +15,22 @@ jobs:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
if: "!startsWith(github.ref, 'refs/tags/') && github.ref != 'refs/heads/master'"
test-cosmovisor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: technote-space/get-diff-action@v1
id: git_diff
with:
PREFIX_FILTER: |
cosmovisor
SUFFIX_FILTER: |
.go
.mod
.sum
- name: Run cosmovisor tests
run: cd cosmovisor; go test .
if: "env.GIT_DIFF != ''"
split-test-files:
runs-on: ubuntu-latest
steps:

147
cosmovisor/README.md Normal file
View File

@ -0,0 +1,147 @@
# Cosmovisor
This is a tiny shim around Cosmos SDK binaries that use the upgrade
module that allows for smooth and configurable management of upgrading
binaries as a live chain is upgraded, and can be used to simplify validator
devops while doing upgrades or to make syncing a full node for genesis
simple. The `cosmovisor` will monitor the stdout of the daemon to look
for messages from the upgrade module indicating a pending or required upgrade
and act appropriately. (With better integrations possible in the future).
## Arguments
`cosmovisor` is a shim around a native binary. All arguments passed to the `cosmovisor`
command will be passed to the current daemon binary (as a subprocess).
It will return stdout and stderr of the subprocess as
it's own. Because of that, it cannot accept any command line arguments, nor
print anything to output (unless it dies before executing a binary).
Configuration will be passed in the following environmental variables:
* `DAEMON_HOME` is the location where upgrade binaries should be kept (can
be `$HOME/.gaiad` or `$HOME/.xrnd`)
* `DAEMON_NAME` is the name of the binary itself (eg. `xrnd`, `gaiad`, `simd`)
* `DAEMON_ALLOW_DOWNLOAD_BINARIES` (optional) if set to `true` will enable auto-downloading of new binaries
(for security reasons, this is intended for fullnodes rather than validators)
* `DAEMON_RESTART_AFTER_UPGRADE` (optional) if set to `true` it will restart the sub-process with the same args
(but new binary) after a successful upgrade. By default, the `cosmovisor` dies afterward and allows the cosmovisor
to restart it if needed. Note that this will not auto-restart the child if there was an error.
## Folder Layout
`$DAEMON_HOME/cosmovisor` is expected to belong completely to the cosmovisor and
subprocesses
controlled by it. Under this folder, we will see the following:
```
.
├── current -> genesis or upgrades/<name>
├── genesis
│   └── bin
│   └── $DAEMON_NAME
└── upgrades
└── <name>
└── bin
└── $DAEMON_NAME
```
Each version of the chain is stored under either `genesis` or `upgrades/<name>`, which holds `bin/$DAEMON_NAME`
along with any other needed files (maybe the cli client? maybe some dlls?). `current` is a symlink to the currently
active folder (so `current/bin/$DAEMON_NAME` is the binary)
Note: the `<name>` after `upgrades` is the URI-encoded name of the upgrade as specified in the upgrade module plan.
Please note that `$DAEMON_HOME/cosmovisor` just stores the *binaries* and associated *program code*.
The `cosmovisor` binary can be stored in any typical location (eg `/usr/local/bin`). The actual blockchain
program will store it's data under `$GAIA_HOME` etc, which is independent of the `$DAEMON_HOME`. You can
choose to export `GAIA_HOME=$DAEMON_HOME` and then end up with a configuation like the following, but this
is left as a choice to the admin for best directory layout.
```
.gaiad
├── config
├── data
└── cosmovisor
```
## Usage
Basic Usage:
* The admin is responsible for installing the `cosmovisor` and setting it as a eg. systemd service to auto-restart, along with proper environmental variables
* The admin is responsible for installing the `genesis` folder manually
* The `cosmovisor` will set the `current` link to point to `genesis` at first start (when no `current` link exists)
* The admin is (generally) responsible for installing the `upgrades/<name>` folders manually
* The `cosmovisor` handles switching over the binaries at the correct points, so the admin can prepare days in advance and relax at upgrade time
Note that chains that wish to support upgrades may package up a genesis `cosmovisor` tar file with this info, just as they
prepare the genesis binary tar file. In fact, they may offer a tar file will all upgrades up to current point for easy download
for those who wish to sync a fullnode from start.
The `DAEMON` specific code, like the tendermint config, the application db, syncing blocks, etc is done as normal.
The same eg. `GAIA_HOME` directives and command-line flags work, just the binary name is different.
## Upgradeable Binary Specification
In the basic version, the `cosmovisor` will read the stdout log messages
to determine when an upgrade is needed. We are considering more complex solutions
via signaling of some sort, but starting with the simple design:
* when an upgrade is needed the binary will print a line that matches this
regular expression: `UPGRADE "(.*)" NEEDED at height (\d+):(.*)`.
* the second match in the above regular expression can be a JSON object with
a `binaries` key as described above
The name (first regexp) will be used to select the new binary to run. If it is present,
the current subprocess will be killed, `current` will be upgraded to the new directory,
and the new binary will be launched.
**Question** should we just kill the `cosmovisor` after it does the updates?
so it gets a clean restart and just runs the new binary (under `current`).
it should be safe to restart (as a service).
## Auto-Download
Generally, the system requires that the administrator place all relevant binaries
on the disk before the upgrade happens. However, for people who don't need such
control and want an easier setup (maybe they are syncing a non-validating fullnode
and want to do little maintenance), there is another option.
If you set `DAEMON_ALLOW_DOWNLOAD_BINARIES=on` then when an upgrade is triggered and no local binary
can be found, the `cosmovisor` will attempt to download and install the binary itself.
The plan stored in the upgrade module has an info field for arbitrary json.
This info is expected to be outputed on the halt log message. There are two
valid format to specify a download in such a message:
1. Store an os/architecture -> binary URI map in the upgrade plan info field
as JSON under the `"binaries"` key, eg:
```json
{
"binaries": {
"linux/amd64":"https://example.com/gaia.zip?checksum=sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f"
}
}
```
2. Store a link to a file that contains all information in the above format (eg. if you want
to specify lots of binaries, changelog info, etc without filling up the blockchain).
e.g `https://example.com/testnet-1001-info.json?checksum=sha256:deaaa99fda9407c4dbe1d04bd49bab0cc3c1dd76fa392cd55a9425be074af01e`
This file contained in link will be retrieved by [go-getter](https://github.com/hashicorp/go-getter)
and the "binaries" field will be parsed as above.
If there is no local binary, `DAEMON_ALLOW_DOWNLOAD_BINARIES=true`, and we can access a canonical url for the new binary,
then the `cosmovisor` will download it with [go-getter](https://github.com/hashicorp/go-getter) and
unpack it into the `upgrades/<name>` folder to be run as if we installed it manually
Note that for this mechanism to provide strong security guarantees, all URLs should include a
sha{256,512} checksum. This ensures that no false binary is run, even if someone hacks the server
or hijacks the dns. go-getter will always ensure the downloaded file matches the checksum if it
is provided. And also handles unpacking archives into directories (so these download links should be
a zip of all data in the bin directory).
To properly create a checksum on linux, you can use the `sha256sum` utility. eg.
`sha256sum ./testdata/repo/zip_directory/autod.zip`
which should return `29139e1381b8177aec909fab9a75d11381cab5adf7d3af0c05ff1c9c117743a7`.
You can also use `sha512sum` if you like longer hashes, or `md5sum` if you like to use broken hashes.
Make sure to set the hash algorithm properly in the checksum argument to the url.

131
cosmovisor/args.go Normal file
View File

@ -0,0 +1,131 @@
package cosmovisor
import (
"net/url"
"os"
"path/filepath"
"github.com/pkg/errors"
)
const (
rootName = "cosmovisor"
genesisDir = "genesis"
upgradesDir = "upgrades"
currentLink = "current"
)
// Config is the information passed in to control the daemon
type Config struct {
Home string
Name string
AllowDownloadBinaries bool
RestartAfterUpgrade bool
}
// Root returns the root directory where all info lives
func (cfg *Config) Root() string {
return filepath.Join(cfg.Home, rootName)
}
// GenesisBin is the path to the genesis binary - must be in place to start manager
func (cfg *Config) GenesisBin() string {
return filepath.Join(cfg.Root(), genesisDir, "bin", cfg.Name)
}
// UpgradeBin is the path to the binary for the named upgrade
func (cfg *Config) UpgradeBin(upgradeName string) string {
return filepath.Join(cfg.UpgradeDir(upgradeName), "bin", cfg.Name)
}
// UpgradeDir is the directory named upgrade
func (cfg *Config) UpgradeDir(upgradeName string) string {
safeName := url.PathEscape(upgradeName)
return filepath.Join(cfg.Root(), upgradesDir, safeName)
}
// Symlink to genesis
func (cfg *Config) SymLinkToGenesis() (string, error) {
genesis := filepath.Join(cfg.Root(), genesisDir)
link := filepath.Join(cfg.Root(), currentLink)
if err := os.Symlink(genesis, link); err != nil {
return "", err
}
// and return the genesis binary
return cfg.GenesisBin(), nil
}
// CurrentBin is the path to the currently selected binary (genesis if no link is set)
// This will resolve the symlink to the underlying directory to make it easier to debug
func (cfg *Config) CurrentBin() (string, error) {
cur := filepath.Join(cfg.Root(), currentLink)
// if nothing here, fallback to genesis
info, err := os.Lstat(cur)
if err != nil {
//Create symlink to the genesis
return cfg.SymLinkToGenesis()
}
// if it is there, ensure it is a symlink
if info.Mode()&os.ModeSymlink == 0 {
//Create symlink to the genesis
return cfg.SymLinkToGenesis()
}
// resolve it
dest, err := os.Readlink(cur)
if err != nil {
//Create symlink to the genesis
return cfg.SymLinkToGenesis()
}
// and return the binary
dest = filepath.Join(dest, "bin", cfg.Name)
return dest, nil
}
// GetConfigFromEnv will read the environmental variables into a config
// and then validate it is reasonable
func GetConfigFromEnv() (*Config, error) {
cfg := &Config{
Home: os.Getenv("DAEMON_HOME"),
Name: os.Getenv("DAEMON_NAME"),
}
if os.Getenv("DAEMON_ALLOW_DOWNLOAD_BINARIES") == "true" {
cfg.AllowDownloadBinaries = true
}
if os.Getenv("DAEMON_RESTART_AFTER_UPGRADE") == "true" {
cfg.RestartAfterUpgrade = true
}
if err := cfg.validate(); err != nil {
return nil, err
}
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 {
if cfg.Name == "" {
return errors.New("DAEMON_NAME is not set")
}
if cfg.Home == "" {
return errors.New("DAEMON_HOME is not set")
}
if !filepath.IsAbs(cfg.Home) {
return errors.New("DAEMON_HOME must be an absolute path")
}
// ensure the root directory exists
info, err := os.Stat(cfg.Root())
if err != nil {
return errors.Wrap(err, "cannot stat home dir")
}
if !info.IsDir() {
return errors.Errorf("%s is not a directory", info.Name())
}
return nil
}

130
cosmovisor/args_test.go Normal file
View File

@ -0,0 +1,130 @@
package cosmovisor
import (
"fmt"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestConfigPaths(t *testing.T) {
cases := map[string]struct {
cfg Config
upgradeName string
expectRoot string
expectGenesis string
expectUpgrade string
}{
"simple": {
cfg: Config{Home: "/foo", Name: "myd"},
upgradeName: "bar",
expectRoot: fmt.Sprintf("/foo/%s", rootName),
expectGenesis: fmt.Sprintf("/foo/%s/genesis/bin/myd", rootName),
expectUpgrade: fmt.Sprintf("/foo/%s/upgrades/bar/bin/myd", rootName),
},
"handle space": {
cfg: Config{Home: "/longer/prefix/", Name: "yourd"},
upgradeName: "some spaces",
expectRoot: fmt.Sprintf("/longer/prefix/%s", rootName),
expectGenesis: fmt.Sprintf("/longer/prefix/%s/genesis/bin/yourd", rootName),
expectUpgrade: "/longer/prefix/cosmovisor/upgrades/some%20spaces/bin/yourd",
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
assert.Equal(t, tc.cfg.Root(), filepath.FromSlash(tc.expectRoot))
assert.Equal(t, tc.cfg.GenesisBin(), filepath.FromSlash(tc.expectGenesis))
assert.Equal(t, tc.cfg.UpgradeBin(tc.upgradeName), filepath.FromSlash(tc.expectUpgrade))
})
}
}
// Test validate
func TestValidate(t *testing.T) {
relPath := filepath.Join("testdata", "validate")
absPath, err := filepath.Abs(relPath)
assert.NoError(t, err)
testdata, err := filepath.Abs("testdata")
assert.NoError(t, err)
cases := map[string]struct {
cfg Config
valid bool
}{
"happy": {
cfg: Config{Home: absPath, Name: "bind"},
valid: true,
},
"happy with download": {
cfg: Config{Home: absPath, Name: "bind", AllowDownloadBinaries: true},
valid: true,
},
"missing home": {
cfg: Config{Name: "bind"},
valid: false,
},
"missing name": {
cfg: Config{Home: absPath},
valid: false,
},
"relative path": {
cfg: Config{Home: relPath, Name: "bind"},
valid: false,
},
"no upgrade manager subdir": {
cfg: Config{Home: testdata, Name: "bind"},
valid: false,
},
"no such dir": {
cfg: Config{Home: filepath.FromSlash("/no/such/dir"), Name: "bind"},
valid: false,
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
err := tc.cfg.validate()
if tc.valid {
assert.NoError(t, err)
} else {
assert.Error(t, err)
}
})
}
}
func TestEnsureBin(t *testing.T) {
relPath := filepath.Join("testdata", "validate")
absPath, err := filepath.Abs(relPath)
assert.NoError(t, err)
cfg := Config{Home: absPath, Name: "dummyd"}
assert.NoError(t, cfg.validate())
err = EnsureBinary(cfg.GenesisBin())
assert.NoError(t, err)
cases := map[string]struct {
upgrade string
hasBin bool
}{
"proper": {"chain2", true},
"no binary": {"nobin", false},
"not executable": {"noexec", false},
"no directory": {"foobarbaz", false},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
err := EnsureBinary(cfg.UpgradeBin(tc.upgrade))
if tc.hasBin {
assert.NoError(t, err)
} else {
assert.Error(t, err)
}
})
}
}

31
cosmovisor/cmd/main.go Normal file
View File

@ -0,0 +1,31 @@
package main
import (
"fmt"
"os"
cosmovisor "github.com/cosmos/cosmos-sdk/cosmovisor"
)
func main() {
err := Run(os.Args[1:])
if err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
}
// Run is the main loop, but returns an error
func Run(args []string) error {
cfg, err := cosmovisor.GetConfigFromEnv()
if err != nil {
return err
}
doUpgrade, err := cosmovisor.LaunchProcess(cfg, args, os.Stdout, os.Stderr)
// if RestartAfterUpgrade, we launch after a successful upgrade (only condition LaunchProcess returns nil)
for cfg.RestartAfterUpgrade && err == nil && doUpgrade {
doUpgrade, err = cosmovisor.LaunchProcess(cfg, args, os.Stdout, os.Stderr)
}
return err
}

10
cosmovisor/go.mod Normal file
View File

@ -0,0 +1,10 @@
module github.com/cosmos/cosmos-sdk/cosmovisor
go 1.14
require (
github.com/hashicorp/go-getter v1.4.1
github.com/otiai10/copy v1.2.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.6.1
)

172
cosmovisor/go.sum Normal file
View File

@ -0,0 +1,172 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1 h1:lRi0CHyU+ytlvylOlFKKq0af6JncuyoRh1J+QJBqQx0=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/aws/aws-sdk-go v1.15.78 h1:LaXy6lWR0YK7LKyuU0QWy2ws/LWTPfYV/UgfiBu4tvY=
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-getter v1.4.1 h1:3A2Mh8smGFcf5M+gmcv898mZdrxpseik45IpcyISLsA=
github.com/hashicorp/go-getter v1.4.1/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY=
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k=
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc=
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0 h1:jbyannxz0XFD3zdjgrSUsaJbgpH4eTrkdhRChkHPfO8=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

128
cosmovisor/process.go Normal file
View File

@ -0,0 +1,128 @@
package cosmovisor
import (
"bufio"
"io"
"os/exec"
"strings"
"sync"
"github.com/pkg/errors"
)
// LaunchProcess runs a subprocess and returns when the subprocess exits,
// either when it dies, or *after* a successful upgrade.
func LaunchProcess(cfg *Config, args []string, stdout, stderr io.Writer) (bool, error) {
bin, err := cfg.CurrentBin()
if err != nil {
return false, errors.Wrap(err, "error creating symlink to genesis")
}
err = EnsureBinary(bin)
if err != nil {
return false, errors.Wrap(err, "current binary invalid")
}
cmd := exec.Command(bin, args...)
outpipe, err := cmd.StdoutPipe()
if err != nil {
return false, err
}
errpipe, err := cmd.StderrPipe()
if err != nil {
return false, err
}
scanOut := bufio.NewScanner(io.TeeReader(outpipe, stdout))
scanErr := bufio.NewScanner(io.TeeReader(errpipe, stderr))
err = cmd.Start()
if err != nil {
return false, errors.Wrapf(err, "launching process %s %s", bin, strings.Join(args, " "))
}
// three ways to exit - command ends, find regexp in scanOut, find regexp in scanErr
upgradeInfo, err := WaitForUpgradeOrExit(cmd, scanOut, scanErr)
if err != nil {
return false, err
}
if upgradeInfo != nil {
return true, DoUpgrade(cfg, upgradeInfo)
}
return false, nil
}
// WaitResult is used to wrap feedback on cmd state with some mutex logic.
// This is needed as multiple go-routines can affect this - two read pipes that can trigger upgrade
// As well as the command, which can fail
type WaitResult struct {
// both err and info may be updated from several go-routines
// access is wrapped by mutex and should only be done through methods
err error
info *UpgradeInfo
mutex sync.Mutex
}
// AsResult reads the data protected by mutex to avoid race conditions
func (u *WaitResult) AsResult() (*UpgradeInfo, error) {
u.mutex.Lock()
defer u.mutex.Unlock()
return u.info, u.err
}
// SetError will set with the first error using a mutex
// don't set it once info is set, that means we chose to kill the process
func (u *WaitResult) SetError(myErr error) {
u.mutex.Lock()
defer u.mutex.Unlock()
if u.info == nil && myErr != nil {
u.err = myErr
}
}
// SetUpgrade sets first non-nil upgrade info, ensure error is then nil
// pass in a command to shutdown on successful upgrade
func (u *WaitResult) SetUpgrade(up *UpgradeInfo) {
u.mutex.Lock()
defer u.mutex.Unlock()
if u.info == nil && up != nil {
u.info = up
u.err = nil
}
}
// WaitForUpgradeOrExit listens to both output streams of the process, as well as the process state itself
// When it returns, the process is finished and all streams have closed.
//
// It returns (info, nil) if an upgrade should be initiated (and we killed the process)
// It returns (nil, err) if the process died by itself, or there was an issue reading the pipes
// It returns (nil, nil) if the process exited normally without triggering an upgrade. This is very unlikely
// to happened with "start" but may happened with short-lived commands like `gaiad export ...`
func WaitForUpgradeOrExit(cmd *exec.Cmd, scanOut, scanErr *bufio.Scanner) (*UpgradeInfo, error) {
var res WaitResult
waitScan := func(scan *bufio.Scanner) {
upgrade, err := WaitForUpdate(scan)
if err != nil {
res.SetError(err)
} else if upgrade != nil {
res.SetUpgrade(upgrade)
// now we need to kill the process
_ = cmd.Process.Kill()
}
}
// wait for the scanners, which can trigger upgrade and kill cmd
go waitScan(scanOut)
go waitScan(scanErr)
// if the command exits normally (eg. short command like `gaiad version`), just return (nil, nil)
// we often get broken read pipes if it runs too fast.
// if we had upgrade info, we would have killed it, and thus got a non-nil error code
err := cmd.Wait()
if err == nil {
return nil, nil
}
// this will set the error code if it wasn't killed due to upgrade
res.SetError(err)
return res.AsResult()
}

110
cosmovisor/process_test.go Normal file
View File

@ -0,0 +1,110 @@
// +build linux
package cosmovisor
import (
"bytes"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestLaunchProcess will try running the script a few times and watch upgrades work properly
// and args are passed through
func TestLaunchProcess(t *testing.T) {
home, err := copyTestData("validate")
cfg := &Config{Home: home, Name: "dummyd"}
require.NoError(t, err)
defer os.RemoveAll(home)
// should run the genesis binary and produce expected output
var stdout, stderr bytes.Buffer
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
require.Equal(t, cfg.GenesisBin(), currentBin)
args := []string{"foo", "bar", "1234"}
doUpgrade, err := LaunchProcess(cfg, args, &stdout, &stderr)
require.NoError(t, err)
assert.True(t, doUpgrade)
assert.Equal(t, "", stderr.String())
assert.Equal(t, "Genesis foo bar 1234\nUPGRADE \"chain2\" NEEDED at height: 49: {}\n", stdout.String())
// ensure this is upgraded now and produces new output
currentBin, err = cfg.CurrentBin()
require.NoError(t, err)
require.Equal(t, cfg.UpgradeBin("chain2"), currentBin)
args = []string{"second", "run", "--verbose"}
stdout.Reset()
stderr.Reset()
doUpgrade, err = LaunchProcess(cfg, args, &stdout, &stderr)
require.NoError(t, err)
assert.False(t, doUpgrade)
assert.Equal(t, "", stderr.String())
assert.Equal(t, "Chain 2 is live!\nArgs: second run --verbose\nFinished successfully\n", stdout.String())
// ended without other upgrade
require.Equal(t, cfg.UpgradeBin("chain2"), currentBin)
}
// TestLaunchProcess will try running the script a few times and watch upgrades work properly
// and args are passed through
func TestLaunchProcessWithDownloads(t *testing.T) {
// this is a fun path
// genesis -> "chain2" = zip_binary
// zip_binary -> "chain3" = ref_zipped -> zip_directory
// zip_directory no upgrade
home, err := copyTestData("download")
cfg := &Config{Home: home, Name: "autod", AllowDownloadBinaries: true}
require.NoError(t, err)
defer os.RemoveAll(home)
// should run the genesis binary and produce expected output
var stdout, stderr bytes.Buffer
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
require.Equal(t, cfg.GenesisBin(), currentBin)
args := []string{"some", "args"}
doUpgrade, err := LaunchProcess(cfg, args, &stdout, &stderr)
require.NoError(t, err)
assert.True(t, doUpgrade)
assert.Equal(t, "", stderr.String())
assert.Equal(t, "Preparing auto-download some args\n"+`ERROR: UPGRADE "chain2" NEEDED at height: 49: {"binaries":{"linux/amd64":"https://github.com/cosmos/cosmos-sdk/raw/51249cb93130810033408934454841c98423ed4b/cosmovisor/testdata/repo/zip_binary/autod.zip?checksum=sha256:dc48829b4126ae95bc0db316c66d4e9da5f3db95e212665b6080638cca77e998"}} module=main`+"\n", stdout.String())
// ensure this is upgraded now and produces new output
currentBin, err = cfg.CurrentBin()
require.NoError(t, err)
require.Equal(t, cfg.UpgradeBin("chain2"), currentBin)
args = []string{"run", "--fast"}
stdout.Reset()
stderr.Reset()
doUpgrade, err = LaunchProcess(cfg, args, &stdout, &stderr)
require.NoError(t, err)
assert.True(t, doUpgrade)
assert.Equal(t, "", stderr.String())
assert.Equal(t, "Chain 2 from zipped binary link to referral\nArgs: run --fast\n"+`ERROR: UPGRADE "chain3" NEEDED at height: 936: https://github.com/cosmos/cosmos-sdk/raw/0eae1a50612b8bf803336d35055896fbddaa1ddd/cosmovisor/testdata/repo/ref_zipped?checksum=sha256:0a428575de718ed3cf0771c9687eefaf6f19359977eca4d94a0abd0e11ef8e64 module=main`+"\n", stdout.String())
// ended with one more upgrade
currentBin, err = cfg.CurrentBin()
require.NoError(t, err)
require.Equal(t, cfg.UpgradeBin("chain3"), currentBin)
// make sure this is the proper binary now....
args = []string{"end", "--halt"}
stdout.Reset()
stderr.Reset()
doUpgrade, err = LaunchProcess(cfg, args, &stdout, &stderr)
require.NoError(t, err)
assert.False(t, doUpgrade)
assert.Equal(t, "", stderr.String())
assert.Equal(t, "Chain 2 from zipped directory\nArgs: end --halt\n", stdout.String())
// and this doesn't upgrade
currentBin, err = cfg.CurrentBin()
require.NoError(t, err)
require.Equal(t, cfg.UpgradeBin("chain3"), currentBin)
}

42
cosmovisor/scanner.go Normal file
View File

@ -0,0 +1,42 @@
package cosmovisor
import (
"bufio"
"regexp"
)
// Trim off whitespace around the info - match least greedy, grab as much space on both sides
// Defined here: https://github.com/cosmos/cosmos-sdk/blob/release/v0.38.2/x/upgrade/abci.go#L38
// fmt.Sprintf("UPGRADE \"%s\" NEEDED at %s: %s", plan.Name, plan.DueAt(), plan.Info)
// DueAt defined here: https://github.com/cosmos/cosmos-sdk/blob/release/v0.38.2/x/upgrade/internal/types/plan.go#L73-L78
//
// if !p.Time.IsZero() {
// return fmt.Sprintf("time: %s", p.Time.UTC().Format(time.RFC3339))
// }
// return fmt.Sprintf("height: %d", p.Height)
var upgradeRegex = regexp.MustCompile(`UPGRADE "(.*)" NEEDED at ((height): (\d+)|(time): (\S+)):\s+(\S*)`)
// UpgradeInfo is the details from the regexp
type UpgradeInfo struct {
Name string
Info string
}
// WaitForUpdate will listen to the scanner until a line matches upgradeRegexp.
// It returns (info, nil) on a matching line
// It returns (nil, err) if the input stream errored
// It returns (nil, nil) if the input closed without ever matching the regexp
func WaitForUpdate(scanner *bufio.Scanner) (*UpgradeInfo, error) {
for scanner.Scan() {
line := scanner.Text()
if upgradeRegex.MatchString(line) {
subs := upgradeRegex.FindStringSubmatch(line)
info := UpgradeInfo{
Name: subs[1],
Info: subs[7],
}
return &info, nil
}
}
return nil, scanner.Err()
}

View File

@ -0,0 +1,61 @@
package cosmovisor
import (
"bufio"
"io"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWaitForInfo(t *testing.T) {
cases := map[string]struct {
write []string
expectUpgrade *UpgradeInfo
expectErr bool
}{
"no match": {
write: []string{"some", "random\ninfo\n"},
},
"match name with no info": {
write: []string{"first line\n", `UPGRADE "myname" NEEDED at height: 123: `, "\nnext line\n"},
expectUpgrade: &UpgradeInfo{
Name: "myname",
Info: "",
},
},
"match name with info": {
write: []string{"first line\n", `UPGRADE "take2" NEEDED at height: 123: DownloadData here!`, "\nnext line\n"},
expectUpgrade: &UpgradeInfo{
Name: "take2",
Info: "DownloadData",
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
r, w := io.Pipe()
scan := bufio.NewScanner(r)
// write all info in separate routine
go func() {
for _, line := range tc.write {
n, err := w.Write([]byte(line))
assert.NoError(t, err)
assert.Equal(t, len(line), n)
}
w.Close()
}()
// now scan the info
info, err := WaitForUpdate(scan)
if tc.expectErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tc.expectUpgrade, info)
})
}
}

View File

@ -0,0 +1,7 @@
#!/bin/sh
echo Preparing auto-download $@
sleep 1
echo 'ERROR: UPGRADE "chain2" NEEDED at height: 49: {"binaries":{"linux/amd64":"https://github.com/cosmos/cosmos-sdk/raw/51249cb93130810033408934454841c98423ed4b/cosmovisor/testdata/repo/zip_binary/autod.zip?checksum=sha256:dc48829b4126ae95bc0db316c66d4e9da5f3db95e212665b6080638cca77e998"}} module=main'
sleep 4
echo Never should be printed!!!

6
cosmovisor/testdata/repo/raw_binary/autod vendored Executable file
View File

@ -0,0 +1,6 @@
#!/bin/sh
echo Chain 2 is live!
echo Args: $@
sleep 1
echo Finished successfully

5
cosmovisor/testdata/repo/ref_zipped vendored Normal file
View File

@ -0,0 +1,5 @@
{
"binaries": {
"linux/amd64": "https://github.com/cosmos/cosmos-sdk/raw/aa5d6140ad4011bb33d472dca8246a0dcbe223ee/cosmovisor/testdata/repo/zip_directory/autod.zip?checksum=sha256:3784e4574cad69b67e34d4ea4425eff140063a3870270a301d6bb24a098a27ae"
}
}

8
cosmovisor/testdata/repo/zip_binary/autod vendored Executable file
View File

@ -0,0 +1,8 @@
#!/bin/sh
echo Chain 2 from zipped binary link to referral
echo Args: $@
# note that we just have a url (follow the ref), not a full link
echo 'ERROR: UPGRADE "chain3" NEEDED at height: 936: https://github.com/cosmos/cosmos-sdk/raw/0eae1a50612b8bf803336d35055896fbddaa1ddd/cosmovisor/testdata/repo/ref_zipped?checksum=sha256:0a428575de718ed3cf0771c9687eefaf6f19359977eca4d94a0abd0e11ef8e64 module=main'
sleep 4
echo 'Do not print'

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,4 @@
#!/bin/sh
echo Chain 2 from zipped directory
echo Args: $@

View File

@ -0,0 +1,7 @@
#!/bin/sh
echo Genesis $@
sleep 1
echo 'UPGRADE "chain2" NEEDED at height: 49: {}'
sleep 2
echo Never should be printed!!!

View File

@ -0,0 +1,6 @@
#!/bin/sh
echo Chain 2 is live!
echo Args: $@
sleep 1
echo Finished successfully

View File

@ -0,0 +1,6 @@
#!/bin/sh
echo Chain 3 finally!
echo Args: $@
sleep 1
echo Finished successfully

View File

@ -0,0 +1,3 @@
#!/bin/sh
echo 'exec flag not set'

177
cosmovisor/upgrade.go Normal file
View File

@ -0,0 +1,177 @@
package cosmovisor
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/hashicorp/go-getter"
"github.com/pkg/errors"
)
// DoUpgrade will be called after the log message has been parsed and the process has terminated.
// We can now make any changes to the underlying directory without interference and leave it
// in a state, so we can make a proper restart
func DoUpgrade(cfg *Config, info *UpgradeInfo) error {
err := EnsureBinary(cfg.UpgradeBin(info.Name))
// Simplest case is to switch the link
if err == nil {
// we have the binary - do it
return cfg.SetCurrentUpgrade(info.Name)
}
// if auto-download is disabled, we fail
if !cfg.AllowDownloadBinaries {
return errors.Wrap(err, "binary not present, downloading disabled")
}
// if the dir is there already, don't download either
_, err = os.Stat(cfg.UpgradeDir(info.Name))
if !os.IsNotExist(err) {
return errors.Errorf("upgrade dir already exists, won't overwrite")
}
// If not there, then we try to download it... maybe
if err := DownloadBinary(cfg, info); err != nil {
return errors.Wrap(err, "cannot download binary")
}
// and then set the binary again
err = EnsureBinary(cfg.UpgradeBin(info.Name))
if err != nil {
return errors.Wrap(err, "downloaded binary doesn't check out")
}
return cfg.SetCurrentUpgrade(info.Name)
}
// DownloadBinary will grab the binary and place it in the proper directory
func DownloadBinary(cfg *Config, info *UpgradeInfo) error {
url, err := GetDownloadURL(info)
if err != nil {
return err
}
// download into the bin dir (works for one file)
binPath := cfg.UpgradeBin(info.Name)
err = getter.GetFile(binPath, url)
// if this fails, let's see if it is a zipped directory
if err != nil {
dirPath := cfg.UpgradeDir(info.Name)
err = getter.Get(dirPath, url)
}
if err != nil {
return err
}
// if it is successful, let's ensure the binary is executable
return MarkExecutable(binPath)
}
// MarkExecutable will try to set the executable bits if not already set
// Fails if file doesn't exist or we cannot set those bits
func MarkExecutable(path string) error {
info, err := os.Stat(path)
if err != nil {
return errors.Wrap(err, "stating binary")
}
// end early if world exec already set
if info.Mode()&0001 == 1 {
return nil
}
// now try to set all exec bits
newMode := info.Mode().Perm() | 0111
return os.Chmod(path, newMode)
}
// UpgradeConfig is expected format for the info field to allow auto-download
type UpgradeConfig struct {
Binaries map[string]string `json:"binaries"`
}
// GetDownloadURL will check if there is an arch-dependent binary specified in Info
func GetDownloadURL(info *UpgradeInfo) (string, error) {
doc := strings.TrimSpace(info.Info)
// if this is a url, then we download that and try to get a new doc with the real info
if _, err := url.Parse(doc); err == nil {
tmpDir, err := ioutil.TempDir("", "upgrade-manager-reference")
if err != nil {
return "", errors.Wrap(err, "create tempdir for reference file")
}
defer os.RemoveAll(tmpDir)
refPath := filepath.Join(tmpDir, "ref")
err = getter.GetFile(refPath, doc)
if err != nil {
return "", errors.Wrapf(err, "downloading reference link %s", doc)
}
refBytes, err := ioutil.ReadFile(refPath)
if err != nil {
return "", errors.Wrap(err, "reading downloaded reference")
}
// if download worked properly, then we use this new file as the binary map to parse
doc = string(refBytes)
}
// check if it is the upgrade config
var config UpgradeConfig
err := json.Unmarshal([]byte(doc), &config)
if err == nil {
url, ok := config.Binaries[osArch()]
if !ok {
return "", errors.Errorf("cannot find binary for os/arch: %s", osArch())
}
return url, nil
}
return "", errors.New("upgrade info doesn't contain binary map")
}
func osArch() string {
return fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
}
// SetCurrentUpgrade sets the named upgrade to be the current link, returns error if this binary doesn't exist
func (cfg *Config) SetCurrentUpgrade(upgradeName string) error {
// ensure named upgrade exists
bin := cfg.UpgradeBin(upgradeName)
if err := EnsureBinary(bin); err != nil {
return err
}
// set a symbolic link
link := filepath.Join(cfg.Root(), currentLink)
safeName := url.PathEscape(upgradeName)
upgrade := filepath.Join(cfg.Root(), upgradesDir, safeName)
// remove link if it exists
if _, err := os.Stat(link); err == nil {
os.Remove(link)
}
// point to the new directory
if err := os.Symlink(upgrade, link); err != nil {
return errors.Wrap(err, "creating current symlink")
}
return nil
}
// EnsureBinary ensures the file exists and is executable, or returns an error
func EnsureBinary(path string) error {
info, err := os.Stat(path)
if err != nil {
return errors.Wrap(err, "cannot stat home dir")
}
if !info.Mode().IsRegular() {
return errors.Errorf("%s is not a regular file", info.Name())
}
// this checks if the world-executable bit is set (we cannot check owner easily)
exec := info.Mode().Perm() & 0001
if exec == 0 {
return errors.Errorf("%s is not world executable", info.Name())
}
return nil
}

284
cosmovisor/upgrade_test.go Normal file
View File

@ -0,0 +1,284 @@
// +build linux
package cosmovisor
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
copy2 "github.com/otiai10/copy"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCurrentBin(t *testing.T) {
home, err := copyTestData("validate")
require.NoError(t, err)
defer os.RemoveAll(home)
cfg := Config{Home: home, Name: "dummyd"}
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
assert.Equal(t, cfg.GenesisBin(), currentBin)
// ensure we cannot set this to an invalid value
for _, name := range []string{"missing", "nobin", "noexec"} {
err = cfg.SetCurrentUpgrade(name)
require.Error(t, err, name)
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
assert.Equal(t, cfg.GenesisBin(), currentBin, name)
}
// try a few times to make sure this can be reproduced
for _, upgrade := range []string{"chain2", "chain3", "chain2"} {
// now set it to a valid upgrade and make sure CurrentBin is now set properly
err = cfg.SetCurrentUpgrade(upgrade)
require.NoError(t, err)
// we should see current point to the new upgrade dir
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
assert.Equal(t, cfg.UpgradeBin(upgrade), currentBin)
}
}
func TestCurrentAlwaysSymlinkToDirectory(t *testing.T) {
home, err := copyTestData("validate")
require.NoError(t, err)
defer os.RemoveAll(home)
cfg := Config{Home: home, Name: "dummyd"}
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
assert.Equal(t, cfg.GenesisBin(), currentBin)
assertCurrentLink(t, cfg, "genesis")
err = cfg.SetCurrentUpgrade("chain2")
require.NoError(t, err)
currentBin, err = cfg.CurrentBin()
require.NoError(t, err)
assert.Equal(t, cfg.UpgradeBin("chain2"), currentBin)
assertCurrentLink(t, cfg, filepath.Join("upgrades", "chain2"))
}
func assertCurrentLink(t *testing.T, cfg Config, target string) {
link := filepath.Join(cfg.Root(), currentLink)
// ensure this is a symlink
info, err := os.Lstat(link)
require.NoError(t, err)
require.Equal(t, os.ModeSymlink, info.Mode()&os.ModeSymlink)
dest, err := os.Readlink(link)
require.NoError(t, err)
expected := filepath.Join(cfg.Root(), target)
require.Equal(t, expected, dest)
}
// TODO: test with download (and test all download functions)
func TestDoUpgradeNoDownloadUrl(t *testing.T) {
home, err := copyTestData("validate")
require.NoError(t, err)
defer os.RemoveAll(home)
cfg := &Config{Home: home, Name: "dummyd", AllowDownloadBinaries: true}
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
assert.Equal(t, cfg.GenesisBin(), currentBin)
// do upgrade ignores bad files
for _, name := range []string{"missing", "nobin", "noexec"} {
info := &UpgradeInfo{Name: name}
err = DoUpgrade(cfg, info)
require.Error(t, err, name)
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
assert.Equal(t, cfg.GenesisBin(), currentBin, name)
}
// make sure it updates a few times
for _, upgrade := range []string{"chain2", "chain3"} {
// now set it to a valid upgrade and make sure CurrentBin is now set properly
info := &UpgradeInfo{Name: upgrade}
err = DoUpgrade(cfg, info)
require.NoError(t, err)
// we should see current point to the new upgrade dir
upgradeBin := cfg.UpgradeBin(upgrade)
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
assert.Equal(t, upgradeBin, currentBin)
}
}
func TestOsArch(t *testing.T) {
// all download tests will fail if we are not on linux...
assert.Equal(t, "linux/amd64", osArch())
}
func TestGetDownloadURL(t *testing.T) {
// all download tests will fail if we are not on linux...
ref, err := filepath.Abs(filepath.FromSlash("./testdata/repo/ref_zipped"))
require.NoError(t, err)
badref, err := filepath.Abs(filepath.FromSlash("./testdata/repo/zip_binary/autod.zip"))
require.NoError(t, err)
cases := map[string]struct {
info string
url string
isErr bool
}{
"missing": {
isErr: true,
},
"follow reference": {
info: ref,
url: "https://github.com/cosmos/cosmos-sdk/raw/aa5d6140ad4011bb33d472dca8246a0dcbe223ee/cosmovisor/testdata/repo/zip_directory/autod.zip?checksum=sha256:3784e4574cad69b67e34d4ea4425eff140063a3870270a301d6bb24a098a27ae",
},
"malformated reference target": {
info: badref,
isErr: true,
},
"missing link": {
info: "https://no.such.domain/exists.txt",
isErr: true,
},
"proper binary": {
info: `{"binaries": {"linux/amd64": "https://foo.bar/", "windows/amd64": "https://something.else"}}`,
url: "https://foo.bar/",
},
"missing binary": {
info: `{"binaries": {"linux/arm": "https://foo.bar/"}}`,
isErr: true,
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
url, err := GetDownloadURL(&UpgradeInfo{Info: tc.info})
if tc.isErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tc.url, url)
}
})
}
}
func TestDownloadBinary(t *testing.T) {
cases := map[string]struct {
url string
canDownload bool
validBinary bool
}{
"get raw binary": {
url: "./testdata/repo/raw_binary/autod",
canDownload: true,
validBinary: true,
},
"get raw binary with checksum": {
// sha256sum ./testdata/repo/raw_binary/autod
url: "./testdata/repo/raw_binary/autod?checksum=sha256:e6bc7851600a2a9917f7bf88eb7bdee1ec162c671101485690b4deb089077b0d",
canDownload: true,
validBinary: true,
},
"get raw binary with invalid checksum": {
url: "./testdata/repo/raw_binary/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906",
canDownload: false,
},
"get zipped directory": {
url: "./testdata/repo/zip_directory/autod.zip",
canDownload: true,
validBinary: true,
},
"get zipped directory with valid checksum": {
// sha256sum ./testdata/repo/zip_directory/autod.zip
url: "./testdata/repo/zip_directory/autod.zip?checksum=sha256:3784e4574cad69b67e34d4ea4425eff140063a3870270a301d6bb24a098a27ae",
canDownload: true,
validBinary: true,
},
"get zipped directory with invalid checksum": {
url: "./testdata/repo/zip_directory/autod.zip?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906",
canDownload: false,
},
"invalid url": {
url: "./testdata/repo/bad_dir/autod",
canDownload: false,
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
// make temp dir
home, err := copyTestData("download")
require.NoError(t, err)
defer os.RemoveAll(home)
cfg := &Config{
Home: home,
Name: "autod",
AllowDownloadBinaries: true,
}
// if we have a relative path, make it absolute, but don't change eg. https://... urls
url := tc.url
if strings.HasPrefix(url, "./") {
url, err = filepath.Abs(url)
require.NoError(t, err)
}
upgrade := "amazonas"
info := &UpgradeInfo{
Name: upgrade,
Info: fmt.Sprintf(`{"binaries":{"%s": "%s"}}`, osArch(), url),
}
err = DownloadBinary(cfg, info)
if !tc.canDownload {
assert.Error(t, err)
return
}
require.NoError(t, err)
err = EnsureBinary(cfg.UpgradeBin(upgrade))
if tc.validBinary {
require.NoError(t, err)
} else {
require.Error(t, err)
}
})
}
}
// copyTestData will make a tempdir and then
// "cp -r" a subdirectory under testdata there
// returns the directory (which can now be used as Config.Home) and modified safely
func copyTestData(subdir string) (string, error) {
tmpdir, err := ioutil.TempDir("", "upgrade-manager-test")
if err != nil {
return "", errors.Wrap(err, "create temp dir")
}
src := filepath.Join("testdata", subdir)
err = copy2.Copy(src, tmpdir)
if err != nil {
os.RemoveAll(tmpdir)
return "", errors.Wrap(err, "copying files")
}
return tmpdir, nil
}

26
go.sum
View File

@ -4,11 +4,16 @@ cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSR
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/99designs/keyring v1.1.5 h1:wLv7QyzYpFIyMSwOADq1CLTF9KbjbBfcnfmOGJ64aO4=
@ -49,6 +54,7 @@ github.com/armon/go-metrics v0.3.4/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@ -177,12 +183,11 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -214,12 +219,14 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -254,6 +261,7 @@ github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
@ -266,6 +274,7 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@ -285,6 +294,7 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
@ -296,6 +306,7 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
@ -339,6 +350,7 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
@ -550,6 +562,7 @@ go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@ -578,6 +591,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@ -587,6 +601,7 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@ -619,6 +634,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrS
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -699,11 +715,13 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0 h1:Q3Ui3V3/CVinFWFiW39Iw0kMuVrRzYX0wN6OPFp0lTA=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@ -733,7 +751,6 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
@ -780,9 +797,8 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

View File

@ -41,7 +41,7 @@ func BeginBlocker(k keeper.Keeper, ctx sdk.Context, _ abci.RequestBeginBlock) {
}
if !k.HasHandler(plan.Name) {
upgradeMsg := fmt.Sprintf("UPGRADE \"%s\" NEEDED at %s: %s", plan.Name, plan.DueAt(), plan.Info)
upgradeMsg := BuildUpgradeNeededMsg(plan)
// We don't have an upgrade handler for this upgrade name, meaning this software is out of date so shutdown
ctx.Logger().Error(upgradeMsg)
@ -69,3 +69,8 @@ func BeginBlocker(k keeper.Keeper, ctx sdk.Context, _ abci.RequestBeginBlock) {
panic(downgradeMsg)
}
}
// BuildUpgradeNeededMsg prints the message that notifies that an upgrade is needed.
func BuildUpgradeNeededMsg(plan types.Plan) string {
return fmt.Sprintf("UPGRADE \"%s\" NEEDED at %s: %s", plan.Name, plan.DueAt(), plan.Info)
}