diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3a41d4e427..30973a615a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,28 +60,27 @@ jobs: - name: Build run: GOOS=linux CGO_ENABLED=1 GOARCH=${{ matrix.goarch }} CC=${{ matrix.gcc }} LEDGER_ENABLED=false make build -# TODO: disable test-cosmovisor; this test uses uploaded binary(cosmos-sdk) -# test-cosmovisor: -# runs-on: ubuntu-latest -# steps: -# - uses: actions/checkout@v3 -# - uses: actions/setup-go@v3.3.1 -# with: -# go-version: 1.18 -# - name: Display go version -# run: go version -# - uses: technote-space/get-diff-action@v6.1.1 -# id: git_diff -# with: -# PREFIX_FILTER: | -# cosmovisor -# PATTERNS: | -# **/**.go -# go.mod -# go.sum -# - name: Run cosmovisor tests -# run: cd cosmovisor; make -# if: env.GIT_DIFF + + test-cosmovisor: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3.3.1 + with: + go-version: 1.18 + - name: Display go version + run: go version + - uses: technote-space/get-diff-action@v6.1.1 + id: git_diff + with: + PATTERNS: | + **/**.go + go.mod + go.sum + tools/cosmovisor/** + - name: Run cosmovisor tests + run: make cosmovisor + if: env.GIT_DIFF split-test-files: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 8942a689c8..a6653214ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,6 +93,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (store,x/wasm) [\#742](https://github.com/line/lbm-sdk/pull/742) fix to add error message in GetByteCode() * (amino) [\#745](https://github.com/line/lbm-sdk/pull/745) apply the missing amino codec of `x/token`, `x/collection`, `x/wasm` and `x/foundation` * (x/foundation) [\#757](https://github.com/line/lbm-sdk/pull/757) remove redundant granter from x/foundation events +* (cosmovisor) [\#792](https://github.com/line/lbm-sdk/pull/792) Use upstream's cosmovisor ### Bug Fixes * (x/wasm) [\#453](https://github.com/line/lbm-sdk/pull/453) modify wasm grpc query api path diff --git a/Makefile b/Makefile index 76ba1cabf5..f700b98378 100644 --- a/Makefile +++ b/Makefile @@ -131,7 +131,7 @@ $(BUILDDIR)/: mkdir -p $(BUILDDIR)/ cosmovisor: - $(MAKE) -C cosmovisor cosmovisor + $(MAKE) -C tools/cosmovisor test .PHONY: build build-linux cosmovisor diff --git a/cosmovisor/Makefile b/cosmovisor/Makefile deleted file mode 100644 index bd5b5c5430..0000000000 --- a/cosmovisor/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/make -f - - -all: cosmovisor test - -cosmovisor: - go build -mod=readonly ./cmd/cosmovisor - -test: - go test -mod=readonly -race ./... - -.PHONY: all cosmovisor test diff --git a/cosmovisor/README.md b/cosmovisor/README.md deleted file mode 100644 index e263966a49..0000000000 --- a/cosmovisor/README.md +++ /dev/null @@ -1,236 +0,0 @@ -# Cosmosvisor Quick Start - -`cosmovisor` is a small process manager for Cosmos SDK application binaries that monitors the governance module via stdout for incoming chain upgrade proposals. If it sees a proposal that gets approved, `cosmovisor` 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. - -*Note: If new versions of the application are not set up to run in-place store migrations, migrations will need to be run manually before restarting `cosmovisor` with the new binary. For this reason, we recommend applications adopt in-place store migrations.* - -## Installation - -To install `cosmovisor`, run the following command: - -``` -go get github.com/cosmos/cosmos-sdk/cosmovisor/cmd/cosmovisor -``` - -## Command Line Arguments And Environment Variables - -All arguments passed to `cosmovisor` 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` cannot accept any command-line arguments other than those available to the application binary, nor will it print anything to output other than what is printed by the application binary. - -`cosmovisor` reads its configuration from environment variables: - -* `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.). -* `DAEMON_ALLOW_DOWNLOAD_BINARIES` (*optional*), if set to `true`, will enable auto-downloading of new binaries (for security reasons, this is intended for full nodes rather than validators). By default, `cosmovisor` will not auto-download new binaries. -* `DAEMON_RESTART_AFTER_UPGRADE` (*optional*), if set to `true`, will restart the subprocess with the same command-line arguments and flags (but with the new binary) after a successful upgrade. By default, `cosmovisor` stops running after an upgrade and requires the system administrator to manually restart it. Note that `cosmovisor` will not auto-restart the subprocess if there was an error. - -## Folder Layout - -`$DAEMON_HOME/cosmovisor` is expected to belong completely to `cosmovisor` and the subprocesses that are controlled by it. The folder content is organized as follows: - -``` -. -├── current -> genesis or upgrades/ -├── genesis -│   └── bin -│   └── $DAEMON_NAME -└── upgrades - └── - └── bin - └── $DAEMON_NAME -``` - -The `cosmovisor/` directory incudes a subdirectory for each version of the application (i.e. `genesis` or `upgrades/`). Within each subdirectory is the application binary (i.e. `bin/$DAEMON_NAME`) and any additional auxiliary files associated with each binary. `current` is a symbolic link to the currently active directory (i.e. `genesis` or `upgrades/`). The `name` variable in `upgrades/` is the URI-encoded name of the upgrade as specified in the upgrade module plan. - -Please note that `$DAEMON_HOME/cosmovisor` only stores the *application binaries*. The `cosmovisor` binary itself can be stored in any typical location (e.g. `/usr/local/bin`). The application will continue to store its data in the default data directory (e.g. `$HOME/.gaiad`) or the data directory specified with the `--home` flag. `$DAEMON_HOME` is independent of the data directory and can be set to any location. If you set `$DAEMON_HOME` to the same directory as the data directory, you will end up with a configuation like the following: - -``` -.gaiad -├── config -├── data -└── cosmovisor -``` - -## Usage - -The system administrator is responsible for: - -- installing the `cosmovisor` binary -- configuring the host's init system (e.g. `systemd`, `launchd`, etc.) -- appropriately setting the environmental variables -- manually installing the `genesis` folder -- manually installing the `upgrades/` folders - -`cosmovisor` will set the `current` link to point to `genesis` at first start (i.e. when no `current` link exists) and then handle switching binaries at the correct points in time so that the system administrator can prepare days in advance and relax at upgrade time. - -In order to support downloadable binaries, a tarball for each upgrade binary will need to be packaged up and made available through a canonical URL. Additionally, a tarball that includes the genesis binary and all available upgrade binaries can be packaged up and made available so that all the necessary binaries required to sync a fullnode from start can be easily downloaded. - -The `DAEMON` specific code and operations (e.g. tendermint config, the application db, syncing blocks, etc.) all work as expected. The application binaries' directives such as command-line flags and environment variables also work as expected. - -## Auto-Download - -Generally, `cosmovisor` requires that the system administrator place all relevant binaries on 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 `DAEMON_ALLOW_DOWNLOAD_BINARIES` is set to `true`, and no local binary can be found when an upgrade is triggered, `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 formats 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. For example: - -```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 (e.g. if you want to specify lots of binaries, changelog info, etc. without filling up the blockchain). For example: - -``` -https://example.com/testnet-1001-info.json?checksum=sha256:deaaa99fda9407c4dbe1d04bd49bab0cc3c1dd76fa392cd55a9425be074af01e -``` - -When `cosmovisor` is triggered to download the new binary, `cosmovisor` will parse the `"binaries"` field, download the new binary with [go-getter](https://github.com/hashicorp/go-getter), and unpack the new binary in the `upgrades/` folder so that it can be run as if it was installed 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. `go-getter` will also handle unpacking archives into directories (in this case the download link should point to a `zip` file of all data in the `bin` directory). - -To properly create a sha256 checksum on linux, you can use the `sha256sum` utility. For example: - -``` -sha256sum ./testdata/repo/zip_directory/autod.zip -``` - -The result will look something like the following: `29139e1381b8177aec909fab9a75d11381cab5adf7d3af0c05ff1c9c117743a7`. - -You can also use `sha512sum` if you would prefer to use longer hashes, or `md5sum` if you would prefer to use broken hashes. Whichever you choose, make sure to set the hash algorithm properly in the checksum argument to the URL. - -## Example: SimApp Upgrade - -The following instructions provide a demonstration of `cosmovisor` using the simulation application (`simapp`) shipped with the Cosmos SDK's source code. The following commands are to be run from within the `cosmos-sdk` repository. - -First, check out the latest `v0.42` release: - -``` -git checkout v0.42.7 -``` - -Compile the `simd` binary: - -``` -make build -``` - -Reset `~/.simapp` (never do this in a production environment): - -``` -./build/simd unsafe-reset-all -``` - -Configure the `simd` binary for testing: - -``` -./build/simd config chain-id test -./build/simd config keyring-backend test -./build/simd config broadcast-mode block -``` - -Initialize the node and overwrite any previous genesis file (never do this in a production environment): - - - -``` -./build/simd init test --chain-id test --overwrite -``` - -Set the minimum gas price to `0stake` in `~/.simapp/config/app.toml`: - -``` -minimum-gas-prices = "0stake" -``` - -Create a new key for the validator, then add a genesis account and transaction: - - - - -``` -./build/simd keys add validator -./build/simd add-genesis-account validator 1000000000stake --keyring-backend test -./build/simd gentx validator 1000000stake --chain-id test -./build/simd collect-gentxs -``` - -Set the required environment variables: - -``` -export DAEMON_NAME=simd -export DAEMON_HOME=$HOME/.simapp -``` - -Set the optional environment variable to trigger an automatic restart: - -``` -export DAEMON_RESTART_AFTER_UPGRADE=true -``` - -Create the folder for the genesis binary and copy the `simd` binary: - -``` -mkdir -p $DAEMON_HOME/cosmovisor/genesis/bin -cp ./build/simd $DAEMON_HOME/cosmovisor/genesis/bin -``` - -For the sake of this demonstration, amend `voting_period` in `genesis.json` to a reduced time of 20 seconds (`20s`): - -``` -cat <<< $(jq '.app_state.gov.voting_params.voting_period = "20s"' $HOME/.simapp/config/genesis.json) > $HOME/.simapp/config/genesis.json -``` - -Next, we will hardcode a modification in `simapp` to simulate a code change. In `simapp/app.go`, find the line containing the `UpgradeKeeper` initialization. It should look like the following: - -```go -app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, keys[upgradetypes.StoreKey], appCodec, homePath) -``` - -After that line, add the following: - -```go -app.UpgradeKeeper.SetUpgradeHandler("test1", func(ctx sdk.Context, plan upgradetypes.Plan) { - // Add some coins to a random account - addr, err := sdk.AccAddressFromBech32("cosmos18cgkqduwuh253twzmhedesw3l7v3fm37sppt58") - if err != nil { - panic(err) - } - err = app.BankKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.Coin{Denom: "stake", Amount: sdk.NewInt(345600000)}}) - if err != nil { - panic(err) - } -}) -``` - -Now recompile the `simd` binary with the added upgrade handler: - -``` -make build -``` - -Create the folder for the upgrade binary and copy the `simd` binary: - -``` -mkdir -p $DAEMON_HOME/cosmovisor/upgrades/test1/bin -cp ./build/simd $DAEMON_HOME/cosmovisor/upgrades/test1/bin -``` - -Start `cosmosvisor`: - -``` -cosmovisor start -``` - -Open a new terminal window and submit an upgrade proposal along with a deposit and a vote (these commands must be run within 20 seconds of each other): - -``` -./build/simd tx gov submit-proposal software-upgrade test1 --title upgrade --description upgrade --upgrade-height 20 --from validator --yes -./build/simd tx gov deposit 1 10000000stake --from validator --yes -./build/simd tx gov vote 1 yes --from validator --yes -``` - -The upgrade will occur automatically at height 20. diff --git a/cosmovisor/args.go b/cosmovisor/args.go deleted file mode 100644 index 60cbb7d601..0000000000 --- a/cosmovisor/args.go +++ /dev/null @@ -1,150 +0,0 @@ -package cosmovisor - -import ( - "bufio" - "errors" - "fmt" - "net/url" - "os" - "path/filepath" - "strconv" -) - -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 - LogBufferSize int -} - -// 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 - return filepath.Join(dest, "bin", cfg.Name), 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 - } - - logBufferSizeStr := os.Getenv("DAEMON_LOG_BUFFER_SIZE") - if logBufferSizeStr != "" { - logBufferSize, err := strconv.Atoi(logBufferSizeStr) - if err != nil { - return nil, err - } - cfg.LogBufferSize = logBufferSize * 1024 - } else { - cfg.LogBufferSize = bufio.MaxScanTokenSize - } - - 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 fmt.Errorf("cannot stat home dir: %w", err) - } - - if !info.IsDir() { - return fmt.Errorf("%s is not a directory", info.Name()) - } - - return nil -} diff --git a/cosmovisor/args_test.go b/cosmovisor/args_test.go deleted file mode 100644 index 9e0ce3bb04..0000000000 --- a/cosmovisor/args_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package cosmovisor - -import ( - "fmt" - "path/filepath" - "testing" - - "github.com/stretchr/testify/suite" -) - -type argsTestSuite struct { - suite.Suite -} - -func TestArgsTestSuite(t *testing.T) { - suite.Run(t, new(argsTestSuite)) -} - -func (s *argsTestSuite) TestConfigPaths() { - 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 _, tc := range cases { - s.Require().Equal(tc.cfg.Root(), filepath.FromSlash(tc.expectRoot)) - s.Require().Equal(tc.cfg.GenesisBin(), filepath.FromSlash(tc.expectGenesis)) - s.Require().Equal(tc.cfg.UpgradeBin(tc.upgradeName), filepath.FromSlash(tc.expectUpgrade)) - } -} - -// Test validate -func (s *argsTestSuite) TestValidate() { - relPath := filepath.Join("testdata", "validate") - absPath, err := filepath.Abs(relPath) - s.Require().NoError(err) - - testdata, err := filepath.Abs("testdata") - s.Require().NoError(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 _, tc := range cases { - err := tc.cfg.validate() - if tc.valid { - s.Require().NoError(err) - } else { - s.Require().Error(err) - } - } -} - -func (s *argsTestSuite) TestEnsureBin() { - relPath := filepath.Join("testdata", "validate") - absPath, err := filepath.Abs(relPath) - s.Require().NoError(err) - - cfg := Config{Home: absPath, Name: "dummyd"} - s.Require().NoError(cfg.validate()) - - s.Require().NoError(EnsureBinary(cfg.GenesisBin())) - - cases := map[string]struct { - upgrade string - hasBin bool - }{ - "proper": {"chain2", true}, - "no binary": {"nobin", false}, - "not executable": {"noexec", false}, - "no directory": {"foobarbaz", false}, - } - - for _, tc := range cases { - err := EnsureBinary(cfg.UpgradeBin(tc.upgrade)) - if tc.hasBin { - s.Require().NoError(err) - } else { - s.Require().Error(err) - } - } -} diff --git a/cosmovisor/cmd/cosmovisor/main.go b/cosmovisor/cmd/cosmovisor/main.go deleted file mode 100644 index f50a2d9236..0000000000 --- a/cosmovisor/cmd/cosmovisor/main.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/line/lbm-sdk/cosmovisor" -) - -func main() { - if err := Run(os.Args[1:]); err != nil { - fmt.Fprintf(os.Stderr, "%+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 -} diff --git a/cosmovisor/cosmovisor b/cosmovisor/cosmovisor deleted file mode 100755 index 3fc1b2e7ca..0000000000 Binary files a/cosmovisor/cosmovisor and /dev/null differ diff --git a/cosmovisor/go.mod b/cosmovisor/go.mod deleted file mode 100644 index 927fea1d79..0000000000 --- a/cosmovisor/go.mod +++ /dev/null @@ -1,10 +0,0 @@ -module github.com/line/lbm-sdk/cosmovisor - -go 1.14 - -require ( - github.com/hashicorp/go-getter v1.6.1 - github.com/otiai10/copy v1.2.0 - github.com/stretchr/testify v1.6.1 - github.com/ulikunitz/xz v0.5.10 // indirect -) diff --git a/cosmovisor/go.sum b/cosmovisor/go.sum deleted file mode 100644 index c68ada4c56..0000000000 --- a/cosmovisor/go.sum +++ /dev/null @@ -1,173 +0,0 @@ -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.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.6.1 h1:NASsgP4q6tL94WH6nJxKWj8As2H/2kop/bB1d8JMyRY= -github.com/hashicorp/go-getter v1.6.1/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA= -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/klauspost/compress v1.11.2 h1:MiK62aErc3gIiVEtyzKfeOHgW7atJb5g/KNX5m3c2nQ= -github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -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/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/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.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= -github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e h1:w36l2Uw3dRan1K3TyXriXvY+6T56GNmlKGcqiQUJDfM= -golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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= diff --git a/cosmovisor/process.go b/cosmovisor/process.go deleted file mode 100644 index 6a67f65e16..0000000000 --- a/cosmovisor/process.go +++ /dev/null @@ -1,153 +0,0 @@ -package cosmovisor - -import ( - "bufio" - "fmt" - "io" - "log" - "os" - "os/exec" - "os/signal" - "strings" - "sync" - "syscall" -) - -// 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, fmt.Errorf("error creating symlink to genesis: %w", err) - } - - if err := EnsureBinary(bin); err != nil { - return false, fmt.Errorf("current binary invalid: %w", err) - } - - 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)) - // set scanner's buffer size to cfg.LogBufferSize, and ensure larger than bufio.MaxScanTokenSize otherwise fallback to bufio.MaxScanTokenSize - var maxCapacity int - if cfg.LogBufferSize < bufio.MaxScanTokenSize { - maxCapacity = bufio.MaxScanTokenSize - } else { - maxCapacity = cfg.LogBufferSize - } - bufOut := make([]byte, maxCapacity) - bufErr := make([]byte, maxCapacity) - scanOut.Buffer(bufOut, maxCapacity) - scanErr.Buffer(bufErr, maxCapacity) - - if err := cmd.Start(); err != nil { - return false, fmt.Errorf("launching process %s %s: %w", bin, strings.Join(args, " "), err) - } - - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGQUIT, syscall.SIGTERM) - go func() { - sig := <-sigs - if err := cmd.Process.Signal(sig); err != nil { - log.Fatal(err) - } - }() - - // 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() -} diff --git a/cosmovisor/process_test.go b/cosmovisor/process_test.go deleted file mode 100644 index 709008a61a..0000000000 --- a/cosmovisor/process_test.go +++ /dev/null @@ -1,114 +0,0 @@ -// +build linux - -package cosmovisor_test - -import ( - "bytes" - "testing" - - "github.com/stretchr/testify/suite" - - "github.com/line/lbm-sdk/cosmovisor" -) - -type processTestSuite struct { - suite.Suite -} - -func TestProcessTestSuite(t *testing.T) { - suite.Run(t, new(processTestSuite)) -} - -// TestLaunchProcess will try running the script a few times and watch upgrades work properly -// and args are passed through -func (s *processTestSuite) TestLaunchProcess() { - home := copyTestData(s.T(), "validate") - cfg := &cosmovisor.Config{Home: home, Name: "dummyd"} - - // should run the genesis binary and produce expected output - var stdout, stderr bytes.Buffer - currentBin, err := cfg.CurrentBin() - s.Require().NoError(err) - - s.Require().Equal(cfg.GenesisBin(), currentBin) - - args := []string{"foo", "bar", "1234"} - doUpgrade, err := cosmovisor.LaunchProcess(cfg, args, &stdout, &stderr) - s.Require().NoError(err) - s.Require().True(doUpgrade) - s.Require().Equal("", stderr.String()) - s.Require().Equal("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() - s.Require().NoError(err) - s.Require().Equal(cfg.UpgradeBin("chain2"), currentBin) - args = []string{"second", "run", "--verbose"} - stdout.Reset() - stderr.Reset() - doUpgrade, err = cosmovisor.LaunchProcess(cfg, args, &stdout, &stderr) - s.Require().NoError(err) - s.Require().False(doUpgrade) - s.Require().Equal("", stderr.String()) - s.Require().Equal("Chain 2 is live!\nArgs: second run --verbose\nFinished successfully\n", stdout.String()) - - // ended without other upgrade - s.Require().Equal(cfg.UpgradeBin("chain2"), currentBin) -} - -// TestLaunchProcess will try running the script a few times and watch upgrades work properly -// and args are passed through -func (s *processTestSuite) TestLaunchProcessWithDownloads() { - // this is a fun path - // genesis -> "chain2" = zip_binary - // zip_binary -> "chain3" = ref_zipped -> zip_directory - // zip_directory no upgrade - home := copyTestData(s.T(), "download") - cfg := &cosmovisor.Config{Home: home, Name: "autod", AllowDownloadBinaries: true} - - // should run the genesis binary and produce expected output - var stdout, stderr bytes.Buffer - currentBin, err := cfg.CurrentBin() - s.Require().NoError(err) - - s.Require().Equal(cfg.GenesisBin(), currentBin) - args := []string{"some", "args"} - doUpgrade, err := cosmovisor.LaunchProcess(cfg, args, &stdout, &stderr) - s.Require().NoError(err) - s.Require().True(doUpgrade) - s.Require().Equal("", stderr.String()) - s.Require().Equal("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() - s.Require().NoError(err) - s.Require().Equal(cfg.UpgradeBin("chain2"), currentBin) - args = []string{"run", "--fast"} - stdout.Reset() - stderr.Reset() - doUpgrade, err = cosmovisor.LaunchProcess(cfg, args, &stdout, &stderr) - s.Require().NoError(err) - s.Require().True(doUpgrade) - s.Require().Equal("", stderr.String()) - s.Require().Equal("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() - s.Require().NoError(err) - s.Require().Equal(cfg.UpgradeBin("chain3"), currentBin) - // make sure this is the proper binary now.... - args = []string{"end", "--halt"} - stdout.Reset() - stderr.Reset() - doUpgrade, err = cosmovisor.LaunchProcess(cfg, args, &stdout, &stderr) - s.Require().NoError(err) - s.Require().False(doUpgrade) - s.Require().Equal("", stderr.String()) - s.Require().Equal("Chain 2 from zipped directory\nArgs: end --halt\n", stdout.String()) - - // and this doesn't upgrade - currentBin, err = cfg.CurrentBin() - s.Require().NoError(err) - s.Require().Equal(cfg.UpgradeBin("chain3"), currentBin) -} diff --git a/cosmovisor/scanner.go b/cosmovisor/scanner.go deleted file mode 100644 index 503e032160..0000000000 --- a/cosmovisor/scanner.go +++ /dev/null @@ -1,42 +0,0 @@ -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/line/lbm-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/line/lbm-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() -} diff --git a/cosmovisor/scanner_test.go b/cosmovisor/scanner_test.go deleted file mode 100644 index 94ca90a706..0000000000 --- a/cosmovisor/scanner_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package cosmovisor_test - -import ( - "bufio" - "io" - "testing" - - "github.com/line/lbm-sdk/cosmovisor" - - "github.com/stretchr/testify/require" -) - -func TestWaitForInfo(t *testing.T) { - cases := map[string]struct { - write []string - expectUpgrade *cosmovisor.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: &cosmovisor.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: &cosmovisor.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)) - require.NoError(t, err) - require.Equal(t, len(line), n) - } - w.Close() - }() - - // now scan the info - info, err := cosmovisor.WaitForUpdate(scan) - if tc.expectErr { - require.Error(t, err) - return - } - require.NoError(t, err) - require.Equal(t, tc.expectUpgrade, info) - }) - } -} diff --git a/cosmovisor/testdata/download/cosmovisor/genesis/bin/autod b/cosmovisor/testdata/download/cosmovisor/genesis/bin/autod deleted file mode 100755 index 113cce7f79..0000000000 --- a/cosmovisor/testdata/download/cosmovisor/genesis/bin/autod +++ /dev/null @@ -1,7 +0,0 @@ -#!/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!!! diff --git a/cosmovisor/testdata/repo/raw_binary/autod b/cosmovisor/testdata/repo/raw_binary/autod deleted file mode 100755 index 0022b84af2..0000000000 --- a/cosmovisor/testdata/repo/raw_binary/autod +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -echo Chain 2 is live! -echo Args: $@ -sleep 1 -echo Finished successfully diff --git a/cosmovisor/testdata/repo/ref_zipped b/cosmovisor/testdata/repo/ref_zipped deleted file mode 100644 index fd63c7161f..0000000000 --- a/cosmovisor/testdata/repo/ref_zipped +++ /dev/null @@ -1,5 +0,0 @@ -{ - "binaries": { - "linux/amd64": "https://github.com/cosmos/cosmos-sdk/raw/aa5d6140ad4011bb33d472dca8246a0dcbe223ee/cosmovisor/testdata/repo/zip_directory/autod.zip?checksum=sha256:3784e4574cad69b67e34d4ea4425eff140063a3870270a301d6bb24a098a27ae" - } -} \ No newline at end of file diff --git a/cosmovisor/testdata/repo/zip_binary/autod b/cosmovisor/testdata/repo/zip_binary/autod deleted file mode 100755 index 4ed1dea36d..0000000000 --- a/cosmovisor/testdata/repo/zip_binary/autod +++ /dev/null @@ -1,8 +0,0 @@ -#!/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' diff --git a/cosmovisor/testdata/repo/zip_binary/autod.zip b/cosmovisor/testdata/repo/zip_binary/autod.zip deleted file mode 100644 index 19cc593ab8..0000000000 Binary files a/cosmovisor/testdata/repo/zip_binary/autod.zip and /dev/null differ diff --git a/cosmovisor/testdata/repo/zip_directory/autod.zip b/cosmovisor/testdata/repo/zip_directory/autod.zip deleted file mode 100644 index 225cd4672a..0000000000 Binary files a/cosmovisor/testdata/repo/zip_directory/autod.zip and /dev/null differ diff --git a/cosmovisor/testdata/repo/zip_directory/bin/autod b/cosmovisor/testdata/repo/zip_directory/bin/autod deleted file mode 100755 index ea3a6f8311..0000000000 --- a/cosmovisor/testdata/repo/zip_directory/bin/autod +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -echo Chain 2 from zipped directory -echo Args: $@ diff --git a/cosmovisor/testdata/validate/cosmovisor/genesis/bin/dummyd b/cosmovisor/testdata/validate/cosmovisor/genesis/bin/dummyd deleted file mode 100755 index c240b802a0..0000000000 --- a/cosmovisor/testdata/validate/cosmovisor/genesis/bin/dummyd +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -echo Genesis $@ -sleep 1 -echo 'UPGRADE "chain2" NEEDED at height: 49: {}' -sleep 2 -echo Never should be printed!!! diff --git a/cosmovisor/testdata/validate/cosmovisor/upgrades/chain2/bin/dummyd b/cosmovisor/testdata/validate/cosmovisor/upgrades/chain2/bin/dummyd deleted file mode 100755 index 0022b84af2..0000000000 --- a/cosmovisor/testdata/validate/cosmovisor/upgrades/chain2/bin/dummyd +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -echo Chain 2 is live! -echo Args: $@ -sleep 1 -echo Finished successfully diff --git a/cosmovisor/testdata/validate/cosmovisor/upgrades/chain3/bin/dummyd b/cosmovisor/testdata/validate/cosmovisor/upgrades/chain3/bin/dummyd deleted file mode 100755 index edfb6c403e..0000000000 --- a/cosmovisor/testdata/validate/cosmovisor/upgrades/chain3/bin/dummyd +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -echo Chain 3 finally! -echo Args: $@ -sleep 1 -echo Finished successfully diff --git a/cosmovisor/testdata/validate/cosmovisor/upgrades/nobin/bin/.keep b/cosmovisor/testdata/validate/cosmovisor/upgrades/nobin/bin/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/cosmovisor/testdata/validate/cosmovisor/upgrades/noexec/bin/dummyd b/cosmovisor/testdata/validate/cosmovisor/upgrades/noexec/bin/dummyd deleted file mode 100644 index 9fa65cddeb..0000000000 --- a/cosmovisor/testdata/validate/cosmovisor/upgrades/noexec/bin/dummyd +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -echo 'exec flag not set' diff --git a/cosmovisor/upgrade.go b/cosmovisor/upgrade.go deleted file mode 100644 index 529ddaca5c..0000000000 --- a/cosmovisor/upgrade.go +++ /dev/null @@ -1,194 +0,0 @@ -package cosmovisor - -import ( - "encoding/json" - "errors" - "fmt" - "net/url" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/hashicorp/go-getter" - "github.com/otiai10/copy" -) - -// 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 { - // Simplest case is to switch the link - err := EnsureBinary(cfg.UpgradeBin(info.Name)) - 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 fmt.Errorf("binary not present, downloading disabled: %w", err) - } - - // if the dir is there already, don't download either - if _, err := os.Stat(cfg.UpgradeDir(info.Name)); !os.IsNotExist(err) { - return errors.New("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 fmt.Errorf("cannot download binary: %w", err) - } - - // and then set the binary again - if err := EnsureBinary(cfg.UpgradeBin(info.Name)); err != nil { - return fmt.Errorf("downloaded binary doesn't check out: %w", err) - } - - 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 - } - err = EnsureBinary(binPath) - // copy binary to binPath from dirPath if zipped directory don't contain bin directory to wrap the binary - if err != nil { - err = copy.Copy(filepath.Join(dirPath, cfg.Name), binPath) - 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 fmt.Errorf("stating binary: %w", err) - } - // 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 := os.MkdirTemp("", "upgrade-manager-reference") - if err != nil { - return "", fmt.Errorf("create tempdir for reference file: %w", err) - } - defer os.RemoveAll(tmpDir) - - refPath := filepath.Join(tmpDir, "ref") - if err := getter.GetFile(refPath, doc); err != nil { - return "", fmt.Errorf("downloading reference link %s: %w", doc, err) - } - - refBytes, err := os.ReadFile(refPath) - if err != nil { - return "", fmt.Errorf("reading downloaded reference: %w", err) - } - // 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 - - if err := json.Unmarshal([]byte(doc), &config); err == nil { - url, ok := config.Binaries[OSArch()] - if !ok { - url, ok = config.Binaries["any"] - } - if !ok { - return "", fmt.Errorf("cannot find binary for os/arch: neither %s, nor any", 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 fmt.Errorf("creating current symlink: %w", err) - } - - 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 fmt.Errorf("cannot stat dir %s: %w", path, err) - } - - if !info.Mode().IsRegular() { - return fmt.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 fmt.Errorf("%s is not world executable", info.Name()) - } - - return nil -} diff --git a/cosmovisor/upgrade_test.go b/cosmovisor/upgrade_test.go deleted file mode 100644 index 049cde99ad..0000000000 --- a/cosmovisor/upgrade_test.go +++ /dev/null @@ -1,279 +0,0 @@ -// +build linux - -package cosmovisor_test - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/suite" - - "github.com/otiai10/copy" - "github.com/stretchr/testify/require" - - "github.com/line/lbm-sdk/cosmovisor" -) - -type upgradeTestSuite struct { - suite.Suite -} - -func TestUpgradeTestSuite(t *testing.T) { - suite.Run(t, new(upgradeTestSuite)) -} - -func (s *upgradeTestSuite) TestCurrentBin() { - home := copyTestData(s.T(), "validate") - cfg := cosmovisor.Config{Home: home, Name: "dummyd"} - - currentBin, err := cfg.CurrentBin() - s.Require().NoError(err) - - s.Require().Equal(cfg.GenesisBin(), currentBin) - - // ensure we cannot set this to an invalid value - for _, name := range []string{"missing", "nobin", "noexec"} { - s.Require().Error(cfg.SetCurrentUpgrade(name), name) - - currentBin, err := cfg.CurrentBin() - s.Require().NoError(err) - - s.Require().Equal(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) - s.Require().NoError(err) - // we should see current point to the new upgrade dir - currentBin, err := cfg.CurrentBin() - s.Require().NoError(err) - - s.Require().Equal(cfg.UpgradeBin(upgrade), currentBin) - } -} - -func (s *upgradeTestSuite) TestCurrentAlwaysSymlinkToDirectory() { - home := copyTestData(s.T(), "validate") - cfg := cosmovisor.Config{Home: home, Name: "dummyd"} - - currentBin, err := cfg.CurrentBin() - s.Require().NoError(err) - s.Require().Equal(cfg.GenesisBin(), currentBin) - s.assertCurrentLink(cfg, "genesis") - - err = cfg.SetCurrentUpgrade("chain2") - s.Require().NoError(err) - currentBin, err = cfg.CurrentBin() - s.Require().NoError(err) - s.Require().Equal(cfg.UpgradeBin("chain2"), currentBin) - s.assertCurrentLink(cfg, filepath.Join("upgrades", "chain2")) -} - -func (s *upgradeTestSuite) assertCurrentLink(cfg cosmovisor.Config, target string) { - link := filepath.Join(cfg.Root(), "current") - // ensure this is a symlink - info, err := os.Lstat(link) - s.Require().NoError(err) - s.Require().Equal(os.ModeSymlink, info.Mode()&os.ModeSymlink) - - dest, err := os.Readlink(link) - s.Require().NoError(err) - expected := filepath.Join(cfg.Root(), target) - s.Require().Equal(expected, dest) -} - -// TODO: test with download (and test all download functions) -func (s *upgradeTestSuite) TestDoUpgradeNoDownloadUrl() { - home := copyTestData(s.T(), "validate") - cfg := &cosmovisor.Config{Home: home, Name: "dummyd", AllowDownloadBinaries: true} - - currentBin, err := cfg.CurrentBin() - s.Require().NoError(err) - - s.Require().Equal(cfg.GenesisBin(), currentBin) - - // do upgrade ignores bad files - for _, name := range []string{"missing", "nobin", "noexec"} { - info := &cosmovisor.UpgradeInfo{Name: name} - err = cosmovisor.DoUpgrade(cfg, info) - s.Require().Error(err, name) - currentBin, err := cfg.CurrentBin() - s.Require().NoError(err) - s.Require().Equal(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 := &cosmovisor.UpgradeInfo{Name: upgrade} - err = cosmovisor.DoUpgrade(cfg, info) - s.Require().NoError(err) - // we should see current point to the new upgrade dir - upgradeBin := cfg.UpgradeBin(upgrade) - currentBin, err := cfg.CurrentBin() - s.Require().NoError(err) - - s.Require().Equal(upgradeBin, currentBin) - } -} - -func (s *upgradeTestSuite) TestOsArch() { - // all download tests will fail if we are not on linux... - s.Require().Equal("linux/amd64", cosmovisor.OSArch()) -} - -func (s *upgradeTestSuite) TestGetDownloadURL() { - // all download tests will fail if we are not on linux... - ref, err := filepath.Abs(filepath.FromSlash("./testdata/repo/ref_zipped")) - s.Require().NoError(err) - badref, err := filepath.Abs(filepath.FromSlash("./testdata/repo/zip_binary/autod.zip")) - s.Require().NoError(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/", - }, - "any architecture not used": { - info: `{"binaries": {"linux/amd64": "https://foo.bar/", "*": "https://something.else"}}`, - url: "https://foo.bar/", - }, - "any architecture used": { - info: `{"binaries": {"linux/arm": "https://foo.bar/arm-only", "any": "https://foo.bar/portable"}}`, - url: "https://foo.bar/portable", - }, - "missing binary": { - info: `{"binaries": {"linux/arm": "https://foo.bar/"}}`, - isErr: true, - }, - } - - for _, tc := range cases { - url, err := cosmovisor.GetDownloadURL(&cosmovisor.UpgradeInfo{Info: tc.info}) - if tc.isErr { - s.Require().Error(err) - } else { - s.Require().NoError(err) - s.Require().Equal(tc.url, url) - } - } -} - -func (s *upgradeTestSuite) TestDownloadBinary() { - 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 _, tc := range cases { - var err error - // make temp dir - home := copyTestData(s.T(), "download") - - cfg := &cosmovisor.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) - s.Require().NoError(err) - } - - upgrade := "amazonas" - info := &cosmovisor.UpgradeInfo{ - Name: upgrade, - Info: fmt.Sprintf(`{"binaries":{"%s": "%s"}}`, cosmovisor.OSArch(), url), - } - - err = cosmovisor.DownloadBinary(cfg, info) - if !tc.canDownload { - s.Require().Error(err) - return - } - s.Require().NoError(err) - - err = cosmovisor.EnsureBinary(cfg.UpgradeBin(upgrade)) - if tc.validBinary { - s.Require().NoError(err) - } else { - s.Require().Error(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(t *testing.T, subdir string) string { - t.Helper() - - tmpdir := t.TempDir() - - require.NoError(t, copy.Copy(filepath.Join("testdata", subdir), tmpdir)) - - return tmpdir -} diff --git a/tools/cosmovisor/Makefile b/tools/cosmovisor/Makefile new file mode 100644 index 0000000000..3375e54265 --- /dev/null +++ b/tools/cosmovisor/Makefile @@ -0,0 +1,23 @@ +#!/usr/bin/make -f + +COSMOVISOR_TAG=v1.4.0 + +DEBUG=true + +install: + go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@$(COSMOVISOR_TAG) + +install-simd: + go install ./../../... + +.PHONY: install install-simd + +test: test-single + +test-single: test-single-manual-download test-single-auto-download + +test-single-manual-download: install install-simd + DEBUG=$(DEBUG) sh test_single.sh + +test-single-auto-download: install install-simd + DEBUG=$(DEBUG) DAEMON_ALLOW_DOWNLOAD_BINARIES=true sh test_single.sh diff --git a/tools/cosmovisor/alter_genesis.jq b/tools/cosmovisor/alter_genesis.jq new file mode 100644 index 0000000000..5257e19ea8 --- /dev/null +++ b/tools/cosmovisor/alter_genesis.jq @@ -0,0 +1,2 @@ +.app_state.gov.voting_params.voting_period = "1s" | +.app_state.gov.deposit_params.min_deposit[0].amount = "1" diff --git a/tools/cosmovisor/configure.sh b/tools/cosmovisor/configure.sh new file mode 100644 index 0000000000..4782c8727a --- /dev/null +++ b/tools/cosmovisor/configure.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +set -e + +# cosmovisor config assertions +[ -n "$DAEMON_HOME" ] +[ -d $DAEMON_HOME ] +[ -n "$DAEMON_NAME" ] +[ -x "$(which $DAEMON_NAME)" ] + +# app config assertions +[ -n "$CHAIN_ID" ] +keyring_backend=test +[ -n "$KEY_MNEMONIC" ] +[ -n "$KEY_INDEX" ] +[ $KEY_INDEX -ge 0 ] + +# setup the chain +$DAEMON_NAME --home $DAEMON_HOME init validator$KEY_INDEX --chain-id $CHAIN_ID >/dev/null 2>&1 + +# modify genesis for the upgrade test +genesis_file=$DAEMON_HOME/config/genesis.json +genesis=$(cat $genesis_file) +echo $genesis | jq -f alter_genesis.jq >$genesis_file + +# add validators +id=$KEY_INDEX +echo $KEY_MNEMONIC | $DAEMON_NAME --home $DAEMON_HOME keys --keyring-backend $keyring_backend add validator$id --recover --account $KEY_INDEX >/dev/null +$DAEMON_NAME --home $DAEMON_HOME add-genesis-account --keyring-backend $keyring_backend validator$id 1000000000stake +$DAEMON_NAME --home $DAEMON_HOME gentx --keyring-backend $keyring_backend validator$id 1000000stake --chain-id $CHAIN_ID +$DAEMON_NAME --home $DAEMON_HOME collect-gentxs diff --git a/tools/cosmovisor/dummyd b/tools/cosmovisor/dummyd new file mode 100755 index 0000000000..38b9c78d55 --- /dev/null +++ b/tools/cosmovisor/dummyd @@ -0,0 +1,5 @@ +#!/bin/sh + +# dummy for simd +echo "I'm a new binary!" +echo "Chain continues..." diff --git a/tools/cosmovisor/start.sh b/tools/cosmovisor/start.sh new file mode 100644 index 0000000000..0ed5e5ac8a --- /dev/null +++ b/tools/cosmovisor/start.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +set -e + +[ -n "$DAEMON_HOME" ] +[ -d $DAEMON_HOME ] +[ -n "$RESULT" ] +[ -p $RESULT ] + +if cosmovisor run start --home $DAEMON_HOME +then + echo OK >>$RESULT +else + echo FAILED >>$RESULT +fi diff --git a/tools/cosmovisor/test_single.sh b/tools/cosmovisor/test_single.sh new file mode 100644 index 0000000000..c4e0b7e33d --- /dev/null +++ b/tools/cosmovisor/test_single.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +set -e + +# app configs +CHAIN_ID=sim +KEY_MNEMONIC="mind flame tobacco sense move hammer drift crime ring globe art gaze cinnamon helmet cruise special produce notable negative wait path scrap recall have" + +# set DEBUG to non empty to retain workdir +cleanup() { + if [ -n "$workdir" ] + then + if [ -n "$DEBUG" ] + then + echo "workdir at: $workdir" + find $workdir + else + rm -rf $workdir + fi + fi +} +trap cleanup TERM INT EXIT + +export DAEMON_NAME=simd + +# make a temporary working directory +workdir=$(mktemp -d) +export DAEMON_HOME=$workdir + +# init +cosmovisor init $(which $DAEMON_NAME) +CHAIN_ID=$CHAIN_ID KEY_MNEMONIC="$KEY_MNEMONIC" KEY_INDEX=0 sh configure.sh + +result=$workdir/result.fifo +mkfifo $result + +RESULT=$result sh start.sh & +CHAIN_ID=$CHAIN_ID sh upgrade.sh + +case $(cat $result) in + OK) + ;; + *) + false + ;; +esac diff --git a/tools/cosmovisor/upgrade.sh b/tools/cosmovisor/upgrade.sh new file mode 100644 index 0000000000..ca0f72dfd4 --- /dev/null +++ b/tools/cosmovisor/upgrade.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +set -e + +[ -n "$DAEMON_HOME" ] +[ -d $DAEMON_HOME ] +[ -n "$DAEMON_NAME" ] +[ -x $(which $DAEMON_NAME) ] + +[ -n "$CHAIN_ID" ] + +UPGRADE_HEIGHT=5 + +assert_begin() { + info=$($DAEMON_NAME q block 2>/dev/null) + if [ -z "$info" ] + then + return 1 + fi + + height=$(echo "$info" | jq -r '.block.header.height') + if [ "$height" -ge 0 ] 2>/dev/null + then + return 0 + else + return 1 + fi +} + +wait_for_begin() { + while ! assert_begin + do + sleep 1 + done +} + +keyring_backend=test + +new_binary=$(realpath ./dummyd) +prepare_upgrade() { + name=$1 + bindir=$DAEMON_HOME/cosmovisor/upgrades/$name/bin + mkdir -p $bindir + cp $new_binary $bindir/$DAEMON_NAME +} + +submit_upgrade() { + name=$1 + checksum=sha256:$(sha256sum $new_binary | awk '{print $1}') + info='{"binaries":{"any":"file://'$new_binary'?checksum='$checksum'"}}' + $DAEMON_NAME --home $DAEMON_HOME tx --keyring-backend $keyring_backend gov submit-proposal software-upgrade $name --upgrade-height $UPGRADE_HEIGHT --upgrade-info $info --title upgrade --description "test upgrade" --deposit 1stake --broadcast-mode block --from validator0 --chain-id $CHAIN_ID --yes +} + +vote() { + proposal=$1 + $DAEMON_NAME --home $DAEMON_HOME tx --keyring-backend $keyring_backend gov vote $proposal VOTE_OPTION_YES --broadcast-mode sync --from validator0 --chain-id $CHAIN_ID --yes +} + +wait_for_begin + +if [ "$DAEMON_ALLOW_DOWNLOAD_BINARIES" != true ] +then + prepare_upgrade testing +fi +submit_upgrade testing +vote 1