feat: Add upgrade proposal plan validation to CLI (#10379)
<!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description Closes: #10286 When submitting a software upgrade proposal (e.g. `$DAEMON tx gov submit-proposal software-upgrade`) * Validate the plan info by default. * Add flag `--no-validate` to allow skipping that validation. * Add flag `--daemon-name` to designate the executable name (needed for validation). * The daemon name comes first from the `--daemon-name` flag. If that's not provided, it looks for a `DAEMON_NAME` environment variable (to match what's used by Cosmovisor). If that's not set, the name of the currently running executable is used. Things that are validated: * The plan info cannot be empty or blank. * If the plan info is a url: * It must have a `checksum` query parameter. * It must return properly formatted plan info JSON. * The `checksum` is correct. * If the plan info is not a url: * It must be propery formatted plan info JSON. * There is at least one entry in the `binaries` field. * The keys of the `binaries` field are either "any" or in the format of "os/arch". * All URLs contain a `checksum` query parameter. * Each URL contains a usable response. * The `checksum` is correct for each URL. Note: With this change, either a valid `--upgrade-info` will need to be provided, or else `--no-validate` must be provided. If no `--upgrade-info` is given, a validation error is returned. --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] ~~added `!` to the type prefix if API or client breaking change~~ _N/A_ - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [x] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [x] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [ ] reviewed "Files changed" and left comments if necessary - [x] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
This commit is contained in:
parent
363b51e58c
commit
181ba0e41a
|
@ -49,6 +49,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
|||
* [\#9837](https://github.com/cosmos/cosmos-sdk/issues/9837) `--generate-only` flag will accept the keyname now.
|
||||
* [\#10326](https://github.com/cosmos/cosmos-sdk/pull/10326) `x/authz` add query all grants by granter query.
|
||||
* [\#10348](https://github.com/cosmos/cosmos-sdk/pull/10348) Add `fee.{payer,granter}` and `tip` fields to StdSignDoc for signing tipped transactions.
|
||||
* [\#10379](https://github.com/cosmos/cosmos-sdk/pull/10379) Add validation to `x/upgrade` CLI `software-upgrade` command `--plan-info` value.
|
||||
|
||||
### Improvements
|
||||
|
||||
|
|
18
go.mod
18
go.mod
|
@ -23,6 +23,8 @@ require (
|
|||
github.com/gorilla/mux v1.8.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
|
||||
github.com/hashicorp/go-getter v1.4.1
|
||||
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
|
||||
github.com/hdevalence/ed25519consensus v0.0.0-20210204194344-59a8610d2b87
|
||||
github.com/improbable-eng/grpc-web v0.15.0
|
||||
|
@ -56,10 +58,14 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.93.3 // indirect
|
||||
cloud.google.com/go/storage v1.10.0 // indirect
|
||||
filippo.io/edwards25519 v1.0.0-beta.2 // indirect
|
||||
github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect
|
||||
github.com/Workiva/go-datastructures v1.0.52 // indirect
|
||||
github.com/aws/aws-sdk-go v1.27.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
|
@ -78,16 +84,21 @@ require (
|
|||
github.com/go-logfmt/logfmt v0.5.0 // indirect
|
||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/google/orderedcode v0.0.1 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.1.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
|
||||
github.com/gtank/merlin v0.1.1 // indirect
|
||||
github.com/gtank/ristretto255 v0.1.2 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/go-version v1.2.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jmhodges/levigo v1.0.0 // indirect
|
||||
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect
|
||||
github.com/klauspost/compress v1.12.3 // indirect
|
||||
|
@ -96,6 +107,8 @@ require (
|
|||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect
|
||||
github.com/minio/highwayhash v1.0.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.2 // indirect
|
||||
github.com/mtibben/percent v0.2.1 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
|
@ -111,12 +124,17 @@ require (
|
|||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
|
||||
github.com/ulikunitz/xz v0.5.5 // indirect
|
||||
github.com/zondax/hid v0.9.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.5 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
|
||||
golang.org/x/sys v0.0.0-20210903071746-97244b99971b // indirect
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
google.golang.org/api v0.56.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/ini.v1 v1.63.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
|
|
31
go.sum
31
go.sum
|
@ -24,6 +24,7 @@ cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAV
|
|||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
||||
cloud.google.com/go v0.93.3 h1:wPBktZFzYBcCZVARvwVKqH1uEj+aLXofJEtrb4oOsio=
|
||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
|
@ -44,6 +45,7 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
|
|||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
|
@ -115,6 +117,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
|
|||
github.com/armon/go-radix v1.0.0/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.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
|
||||
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/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo=
|
||||
|
@ -130,6 +134,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
|||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
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/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
|
@ -170,6 +176,7 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
|
|||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
||||
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
|
@ -359,6 +366,7 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er
|
|||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
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=
|
||||
|
@ -416,9 +424,11 @@ github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSN
|
|||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64=
|
||||
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/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/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
|
||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us=
|
||||
github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20=
|
||||
|
@ -443,6 +453,7 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||
github.com/google/uuid v1.1.5/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/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
|
||||
|
@ -489,7 +500,10 @@ github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOj
|
|||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
|
||||
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-getter v1.4.1 h1:3A2Mh8smGFcf5M+gmcv898mZdrxpseik45IpcyISLsA=
|
||||
github.com/hashicorp/go-getter v1.4.1/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY=
|
||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
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=
|
||||
|
@ -499,11 +513,15 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+
|
|||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
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-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
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.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
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=
|
||||
|
@ -551,8 +569,11 @@ github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M
|
|||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jhump/protoreflect v1.10.1 h1:iH+UZfsbRE6vpyZH7asAjTPWJf7RJbpZ9j/N3lDlKs0=
|
||||
github.com/jhump/protoreflect v1.10.1/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
|
||||
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
|
||||
|
@ -643,6 +664,7 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9
|
|||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
|
@ -657,7 +679,9 @@ github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLT
|
|||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
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=
|
||||
|
@ -943,6 +967,8 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
|
|||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
|
||||
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
|
@ -982,6 +1008,7 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
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=
|
||||
|
@ -1130,6 +1157,7 @@ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ
|
|||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
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=
|
||||
|
@ -1357,6 +1385,7 @@ google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtuk
|
|||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
||||
google.golang.org/api v0.56.0 h1:08F9XVYTLOGeSQb3xI9C0gXMuQanhdGed0cWFhDozbI=
|
||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
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=
|
||||
|
@ -1364,6 +1393,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
|||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
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=
|
||||
|
@ -1455,6 +1485,7 @@ gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8
|
|||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
|
@ -8,12 +11,15 @@ import (
|
|||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov/client/cli"
|
||||
gov "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/upgrade/plan"
|
||||
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
|
||||
)
|
||||
|
||||
const (
|
||||
FlagUpgradeHeight = "upgrade-height"
|
||||
FlagUpgradeInfo = "upgrade-info"
|
||||
FlagNoValidate = "no-validate"
|
||||
FlagDaemonName = "daemon-name"
|
||||
)
|
||||
|
||||
// GetTxCmd returns the transaction commands for this module
|
||||
|
@ -45,6 +51,24 @@ func NewCmdSubmitUpgradeProposal() *cobra.Command {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
noValidate, err := cmd.Flags().GetBool(FlagNoValidate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !noValidate {
|
||||
prop := content.(*types.SoftwareUpgradeProposal)
|
||||
var daemonName string
|
||||
if daemonName, err = cmd.Flags().GetString(FlagDaemonName); err != nil {
|
||||
return err
|
||||
}
|
||||
var planInfo *plan.Info
|
||||
if planInfo, err = plan.ParseInfo(prop.Plan.Info); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = planInfo.ValidateFull(daemonName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
from := clientCtx.GetFromAddress()
|
||||
|
||||
|
@ -70,7 +94,9 @@ func NewCmdSubmitUpgradeProposal() *cobra.Command {
|
|||
cmd.Flags().String(cli.FlagDescription, "", "description of proposal")
|
||||
cmd.Flags().String(cli.FlagDeposit, "", "deposit of proposal")
|
||||
cmd.Flags().Int64(FlagUpgradeHeight, 0, "The height at which the upgrade must happen")
|
||||
cmd.Flags().String(FlagUpgradeInfo, "", "Optional info for the planned upgrade such as commit hash, etc.")
|
||||
cmd.Flags().String(FlagUpgradeInfo, "", "Info for the upgrade plan such as new version download urls, etc.")
|
||||
cmd.Flags().Bool(FlagNoValidate, false, "Skip validation of the upgrade info")
|
||||
cmd.Flags().String(FlagDaemonName, getDefaultDaemonName(), "The name of the executable being upgraded (for upgrade-info validation). Default is the DAEMON_NAME env var if set, or else this executable")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
@ -154,3 +180,15 @@ func parseArgsToContent(cmd *cobra.Command, name string) (gov.Content, error) {
|
|||
content := types.NewSoftwareUpgradeProposal(title, description, plan)
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// getDefaultDaemonName gets the default name to use for the daemon.
|
||||
// If a DAEMON_NAME env var is set, that is used.
|
||||
// Otherwise, the last part of the currently running executable is used.
|
||||
func getDefaultDaemonName() string {
|
||||
// DAEMON_NAME is specifically used here to correspond with the Comsovisor setup env vars.
|
||||
name := os.Getenv("DAEMON_NAME")
|
||||
if len(name) == 0 {
|
||||
_, name = filepath.Split(os.Args[0])
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
package plan
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
neturl "net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-getter"
|
||||
)
|
||||
|
||||
// DownloadUpgrade downloads the given url into the provided directory and ensures it's valid.
|
||||
// The provided url must contain a checksum parameter that matches the file being downloaded.
|
||||
// If this returns nil, the download was successful, and {dstRoot}/bin/{daemonName} is a regular executable file.
|
||||
// This is an opinionated directory structure that corresponds with Cosmovisor requirements.
|
||||
// If the url is not an archive, it is downloaded and saved to {dstRoot}/bin/{daemonName}.
|
||||
// If the url is an archive, it is downloaded and unpacked to {dstRoot}.
|
||||
// If the archive does not contain a /bin/{daemonName} file, then this will attempt to move /{daemonName} to /bin/{daemonName}.
|
||||
// If the archive does not contain either /bin/{daemonName} or /{daemonName}, an error is returned.
|
||||
// Note: Because a checksum is required, this function cannot be used to download non-archive directories.
|
||||
// If dstRoot already exists, some or all of its contents might be updated.
|
||||
func DownloadUpgrade(dstRoot, url, daemonName string) error {
|
||||
if err := ValidateIsURLWithChecksum(url); err != nil {
|
||||
return err
|
||||
}
|
||||
target := filepath.Join(dstRoot, "bin", daemonName)
|
||||
// First try to download it as a single file. If there's no error, it's okay and we're done.
|
||||
if err := getter.GetFile(target, url); err != nil {
|
||||
// If it was a checksum error, no need to try as directory.
|
||||
if _, ok := err.(*getter.ChecksumError); ok {
|
||||
return err
|
||||
}
|
||||
// File download didn't work, try it as an archive.
|
||||
if err = downloadUpgradeAsArchive(dstRoot, url, daemonName); err != nil {
|
||||
// Out of options, send back the error.
|
||||
return err
|
||||
}
|
||||
}
|
||||
return EnsureBinary(target)
|
||||
}
|
||||
|
||||
// downloadUpgradeAsArchive tries to download the given url as an archive.
|
||||
// The archive is unpacked and saved in dstDir.
|
||||
// If the archive contains /{daemonName} and not /bin/{daemonName}, then /{daemonName} will be moved to /bin/{daemonName}.
|
||||
// If this returns nil, the download was successful, and {dstDir}/bin/{daemonName} is a regular executable file.
|
||||
func downloadUpgradeAsArchive(dstDir, url, daemonName string) error {
|
||||
err := getter.Get(dstDir, url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If bin/{daemonName} exists, we're done.
|
||||
dstDirBinFile := filepath.Join(dstDir, "bin", daemonName)
|
||||
err = EnsureBinary(dstDirBinFile)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise, check for a root {daemonName} file and move it to the bin/ directory if found.
|
||||
dstDirFile := filepath.Join(dstDir, daemonName)
|
||||
err = EnsureBinary(dstDirFile)
|
||||
if err == nil {
|
||||
err = os.Rename(dstDirFile, dstDirBinFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not move %s to the bin directory: %w", daemonName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("url \"%s\" result does not contain a bin/%s or %s file", url, daemonName, daemonName)
|
||||
}
|
||||
|
||||
// EnsureBinary checks that the given file exists as a regular file and is executable.
|
||||
// An error is returned if:
|
||||
// - The file does not exist.
|
||||
// - The path exists, but is one of: Dir, Symlink, NamedPipe, Socket, Device, CharDevice, or Irregular.
|
||||
// - The file exists, is not executable by all three of User, Group, and Other, and cannot be made executable.
|
||||
func EnsureBinary(path string) error {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
_, f := filepath.Split(path)
|
||||
return fmt.Errorf("%s is not a regular file", f)
|
||||
}
|
||||
// Make sure all executable bits are set.
|
||||
oldMode := info.Mode().Perm()
|
||||
newMode := oldMode | 0111 // Set the three execute bits to on (a+x).
|
||||
if oldMode != newMode {
|
||||
return os.Chmod(path, newMode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownloadURLWithChecksum gets the contents of the given url, ensuring the checksum is correct.
|
||||
// The provided url must contain a checksum parameter that matches the file being downloaded.
|
||||
// If there isn't an error, the content returned by the url will be returned as a string.
|
||||
// Returns an error if:
|
||||
// - The url is not a URL or does not contain a checksum parameter.
|
||||
// - Downloading the URL fails.
|
||||
// - The checksum does not match what is returned by the URL.
|
||||
// - The URL does not return a regular file.
|
||||
// - The downloaded file is empty or only whitespace.
|
||||
func DownloadURLWithChecksum(url string) (string, error) {
|
||||
if err := ValidateIsURLWithChecksum(url); err != nil {
|
||||
return "", err
|
||||
}
|
||||
tempDir, err := os.MkdirTemp("", "reference")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not create temp directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
tempFile := filepath.Join(tempDir, "content")
|
||||
if err = getter.GetFile(tempFile, url); err != nil {
|
||||
return "", fmt.Errorf("could not download url \"%s\": %w", url, err)
|
||||
}
|
||||
tempFileBz, rerr := os.ReadFile(tempFile)
|
||||
if rerr != nil {
|
||||
return "", fmt.Errorf("could not read downloaded temporary file: %w", rerr)
|
||||
}
|
||||
tempFileStr := strings.TrimSpace(string(tempFileBz))
|
||||
if len(tempFileStr) == 0 {
|
||||
return "", fmt.Errorf("no content returned by \"%s\"", url)
|
||||
}
|
||||
return tempFileStr, nil
|
||||
}
|
||||
|
||||
// ValidateIsURLWithChecksum checks that the given string is a url and contains a checksum query parameter.
|
||||
func ValidateIsURLWithChecksum(urlStr string) error {
|
||||
url, err := neturl.Parse(urlStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(url.Query().Get("checksum")) == 0 {
|
||||
return errors.New("missing checksum query parameter")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,299 @@
|
|||
package plan
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type DownloaderTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
// Home is a temporary directory for use in these tests.
|
||||
// It will have a src/ for things to download.
|
||||
Home string
|
||||
}
|
||||
|
||||
func (s *DownloaderTestSuite) SetupTest() {
|
||||
s.Home = s.T().TempDir()
|
||||
s.Assert().NoError(os.MkdirAll(filepath.Join(s.Home, "src"), 0777), "creating src/ dir")
|
||||
s.T().Logf("Home: [%s]", s.Home)
|
||||
}
|
||||
|
||||
func TestDownloaderTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(DownloaderTestSuite))
|
||||
}
|
||||
|
||||
// TestFile represents a file that will be used for a test.
|
||||
type TestFile struct {
|
||||
// Name is the relative path and name of the file.
|
||||
Name string
|
||||
// Contents is the contents of the file.
|
||||
Contents []byte
|
||||
}
|
||||
|
||||
func NewTestFile(name, contents string) *TestFile {
|
||||
return &TestFile{
|
||||
Name: name,
|
||||
Contents: []byte(contents),
|
||||
}
|
||||
}
|
||||
|
||||
// SaveIn saves this TestFile in the given path.
|
||||
// The full path to the file is returned.
|
||||
func (f TestFile) SaveIn(path string) (string, error) {
|
||||
name := filepath.Join(path, f.Name)
|
||||
file, err := os.Create(name)
|
||||
if err != nil {
|
||||
return name, err
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = file.Write(f.Contents)
|
||||
return name, err
|
||||
}
|
||||
|
||||
// TestZip represents a collection of TestFile objects to be zipped into an archive.
|
||||
type TestZip []*TestFile
|
||||
|
||||
func NewTestZip(testFiles ...*TestFile) TestZip {
|
||||
tz := make([]*TestFile, len(testFiles))
|
||||
for i, tf := range testFiles {
|
||||
tz[i] = tf
|
||||
}
|
||||
return tz
|
||||
}
|
||||
|
||||
// SaveAs saves this TestZip at the given path.
|
||||
func (z TestZip) SaveAs(path string) error {
|
||||
archive, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer archive.Close()
|
||||
zipper := zip.NewWriter(archive)
|
||||
for _, tf := range z {
|
||||
zfw, zfwerr := zipper.Create(tf.Name)
|
||||
if zfwerr != nil {
|
||||
return zfwerr
|
||||
}
|
||||
_, err = zfw.Write(tf.Contents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return zipper.Close()
|
||||
}
|
||||
|
||||
// saveTestZip saves a TestZip in this test's Home/src directory with the given name.
|
||||
// The full path to the saved archive is returned.
|
||||
func (s DownloaderTestSuite) saveSrcTestZip(name string, z TestZip) string {
|
||||
fullName := filepath.Join(s.Home, "src", name)
|
||||
s.Require().NoError(z.SaveAs(fullName), "saving test zip %s", name)
|
||||
return fullName
|
||||
}
|
||||
|
||||
// saveSrcTestFile saves a TestFile in this test's Home/src directory.
|
||||
// The full path to the saved file is returned.
|
||||
func (s DownloaderTestSuite) saveSrcTestFile(f *TestFile) string {
|
||||
path := filepath.Join(s.Home, "src")
|
||||
fullName, err := f.SaveIn(path)
|
||||
s.Require().NoError(err, "saving test file %s", f.Name)
|
||||
return fullName
|
||||
}
|
||||
|
||||
// requireFileExistsAndIsExecutable requires that the given file exists and is executable.
|
||||
func requireFileExistsAndIsExecutable(t *testing.T, path string) {
|
||||
info, err := os.Stat(path)
|
||||
require.NoError(t, err, "stat error")
|
||||
perm := info.Mode().Perm()
|
||||
// Checks if at least one executable bit is set (user, group, or other)
|
||||
isExe := perm&0111 != 0
|
||||
require.True(t, isExe, "is executable: permissions = %s", perm)
|
||||
}
|
||||
|
||||
// requireFileEquals requires that the contents of the file at the given path
|
||||
// is equal to the contents of the given TestFile.
|
||||
func requireFileEquals(t *testing.T, path string, tf *TestFile) {
|
||||
file, err := os.ReadFile(path)
|
||||
require.NoError(t, err, "reading file")
|
||||
require.Equal(t, string(tf.Contents), string(file), "file contents")
|
||||
}
|
||||
|
||||
// makeFileUrl converts the given path to a URL with the correct checksum query parameter.
|
||||
func makeFileURL(t *testing.T, path string) string {
|
||||
f, err := os.Open(path)
|
||||
require.NoError(t, err, "opening file")
|
||||
defer f.Close()
|
||||
hasher := sha256.New()
|
||||
_, err = io.Copy(hasher, f)
|
||||
require.NoError(t, err, "copying file to hasher")
|
||||
return fmt.Sprintf("file://%s?checksum=sha256:%x", path, hasher.Sum(nil))
|
||||
}
|
||||
|
||||
func (s *DownloaderTestSuite) TestDownloadUpgrade() {
|
||||
justAFile := NewTestFile("just-a-file", "#!/usr/bin\necho 'I am just a file'\n")
|
||||
someFileName := "some-file"
|
||||
someFileInBin := NewTestFile("bin"+someFileName, "#!/usr/bin\necho 'I am some file in bin'\n")
|
||||
anotherFile := NewTestFile("another-file", "#!/usr/bin\necho 'I am just another file'\n")
|
||||
justAFilePath := s.saveSrcTestFile(justAFile)
|
||||
justAFileZip := s.saveSrcTestZip(justAFile.Name+".zip", NewTestZip(justAFile))
|
||||
someFileInBinZip := s.saveSrcTestZip(someFileInBin.Name+".zip", NewTestZip(someFileInBin))
|
||||
allFilesZip := s.saveSrcTestZip(anotherFile.Name+".zip", NewTestZip(justAFile, someFileInBin, anotherFile))
|
||||
getDstDir := func(testName string) string {
|
||||
_, tName := filepath.Split(testName)
|
||||
return s.Home + "/dst/" + tName
|
||||
}
|
||||
|
||||
s.T().Run("url does not exist", func(t *testing.T) {
|
||||
dstRoot := getDstDir(t.Name())
|
||||
url := "file:///never/gonna/be/a/thing.zip?checksum=sha256:2c22e34510bd1d4ad2343cdc54f7165bccf30caef73f39af7dd1db2795a3da48"
|
||||
err := DownloadUpgrade(dstRoot, url, "nothing")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "no such file or directory")
|
||||
})
|
||||
|
||||
s.T().Run("url does not have checksum", func(t *testing.T) {
|
||||
dstRoot := getDstDir(t.Name())
|
||||
url := "file://" + justAFilePath
|
||||
err := DownloadUpgrade(dstRoot, url, justAFile.Name)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "missing checksum query parameter")
|
||||
})
|
||||
|
||||
s.T().Run("url has incorrect checksum", func(t *testing.T) {
|
||||
dstRoot := getDstDir(t.Name())
|
||||
badChecksum := "2c22e34510bd1d4ad2343cdc54f7165bccf30caef73f39af7dd1db2795a3da48"
|
||||
url := "file://" + justAFilePath + "?checksum=sha256:" + badChecksum
|
||||
err := DownloadUpgrade(dstRoot, url, justAFile.Name)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "Checksums did not match")
|
||||
assert.Contains(t, err.Error(), "Expected: "+badChecksum)
|
||||
})
|
||||
|
||||
s.T().Run("url returns single file", func(t *testing.T) {
|
||||
dstRoot := getDstDir(t.Name())
|
||||
url := makeFileURL(t, justAFilePath)
|
||||
err := DownloadUpgrade(dstRoot, url, justAFile.Name)
|
||||
require.NoError(t, err)
|
||||
expectedFile := filepath.Join(dstRoot, "bin", justAFile.Name)
|
||||
requireFileExistsAndIsExecutable(t, expectedFile)
|
||||
requireFileEquals(t, expectedFile, justAFile)
|
||||
})
|
||||
|
||||
s.T().Run("url returns archive with file in bin", func(t *testing.T) {
|
||||
dstRoot := getDstDir(t.Name())
|
||||
url := makeFileURL(t, someFileInBinZip)
|
||||
err := DownloadUpgrade(dstRoot, url, someFileName)
|
||||
require.NoError(t, err)
|
||||
expectedFile := filepath.Join(dstRoot, "bin", someFileName)
|
||||
requireFileExistsAndIsExecutable(t, expectedFile)
|
||||
requireFileEquals(t, expectedFile, someFileInBin)
|
||||
})
|
||||
|
||||
s.T().Run("url returns archive with just expected file", func(t *testing.T) {
|
||||
dstRoot := getDstDir(t.Name())
|
||||
url := makeFileURL(t, justAFileZip)
|
||||
err := DownloadUpgrade(dstRoot, url, justAFile.Name)
|
||||
require.NoError(t, err)
|
||||
expectedFile := filepath.Join(dstRoot, "bin", justAFile.Name)
|
||||
requireFileExistsAndIsExecutable(t, expectedFile)
|
||||
requireFileEquals(t, expectedFile, justAFile)
|
||||
})
|
||||
|
||||
s.T().Run("url returns archive without expected file", func(t *testing.T) {
|
||||
dstRoot := getDstDir(t.Name())
|
||||
url := makeFileURL(t, allFilesZip)
|
||||
err := DownloadUpgrade(dstRoot, url, "not-expected")
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "result does not contain a bin/not-expected or not-expected file")
|
||||
})
|
||||
}
|
||||
|
||||
func (s *DownloaderTestSuite) TestEnsureBinary() {
|
||||
nonExeName := s.saveSrcTestFile(NewTestFile("non-exe.txt", "Not executable"))
|
||||
s.Require().NoError(os.Chmod(nonExeName, 0600), "chmod error nonExeName")
|
||||
isExeName := s.saveSrcTestFile(NewTestFile("is-exe.sh", "#!/bin/bash\necho 'executing'\n"))
|
||||
s.Require().NoError(os.Chmod(isExeName, 0777), "chmod error isExeName")
|
||||
|
||||
s.T().Run("file does not exist", func(t *testing.T) {
|
||||
name := filepath.Join(s.Home, "does-not-exist.txt")
|
||||
actual := EnsureBinary(name)
|
||||
require.Error(t, actual)
|
||||
})
|
||||
|
||||
s.T().Run("file is a directory", func(t *testing.T) {
|
||||
name := filepath.Join(s.Home, "src")
|
||||
actual := EnsureBinary(name)
|
||||
require.EqualError(t, actual, fmt.Sprintf("%s is not a regular file", "src"))
|
||||
})
|
||||
|
||||
s.T().Run("file exists and becomes executable", func(t *testing.T) {
|
||||
name := nonExeName
|
||||
actual := EnsureBinary(name)
|
||||
require.NoError(t, actual, "EnsureBinary error")
|
||||
requireFileExistsAndIsExecutable(t, name)
|
||||
})
|
||||
|
||||
s.T().Run("file is already executable", func(t *testing.T) {
|
||||
name := isExeName
|
||||
actual := EnsureBinary(name)
|
||||
require.NoError(t, actual, "EnsureBinary error")
|
||||
requireFileExistsAndIsExecutable(t, name)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *DownloaderTestSuite) TestDownloadURLWithChecksum() {
|
||||
planContents := `{"binaries":{"xxx/yyy":"url"}}`
|
||||
planFile := NewTestFile("plan-info.json", planContents)
|
||||
planPath := s.saveSrcTestFile(planFile)
|
||||
planChecksum := fmt.Sprintf("%x", sha256.Sum256(planFile.Contents))
|
||||
emptyFile := NewTestFile("empty-plan-info.json", "")
|
||||
emptyPlanPath := s.saveSrcTestFile(emptyFile)
|
||||
emptyChecksum := fmt.Sprintf("%x", sha256.Sum256(emptyFile.Contents))
|
||||
|
||||
s.T().Run("url does not exist", func(t *testing.T) {
|
||||
url := "file:///never-gonna-be-a-thing?checksum=sha256:2c22e34510bd1d4ad2343cdc54f7165bccf30caef73f39af7dd1db2795a3da48"
|
||||
_, err := DownloadURLWithChecksum(url)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "could not download url")
|
||||
})
|
||||
|
||||
s.T().Run("without checksum", func(t *testing.T) {
|
||||
url := "file://" + planPath
|
||||
_, err := DownloadURLWithChecksum(url)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "missing checksum query parameter")
|
||||
})
|
||||
|
||||
s.T().Run("with correct checksum", func(t *testing.T) {
|
||||
url := "file://" + planPath + "?checksum=sha256:" + planChecksum
|
||||
actual, err := DownloadURLWithChecksum(url)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, planContents, actual)
|
||||
})
|
||||
|
||||
s.T().Run("with incorrect checksum", func(t *testing.T) {
|
||||
badChecksum := "2c22e34510bd1d4ad2343cdc54f7165bccf30caef73f39af7dd1db2795a3da48"
|
||||
url := "file://" + planPath + "?checksum=sha256:" + badChecksum
|
||||
_, err := DownloadURLWithChecksum(url)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "Checksums did not match")
|
||||
assert.Contains(t, err.Error(), "Expected: "+badChecksum)
|
||||
assert.Contains(t, err.Error(), "Got: "+planChecksum)
|
||||
})
|
||||
|
||||
s.T().Run("plan is empty", func(t *testing.T) {
|
||||
url := "file://" + emptyPlanPath + "?checksum=sha256:" + emptyChecksum
|
||||
_, err := DownloadURLWithChecksum(url)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "no content returned")
|
||||
})
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package plan
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
neturl "net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Info is the special structure that the Plan.Info string can be (as json).
|
||||
type Info struct {
|
||||
Binaries BinaryDownloadURLMap `json:"binaries"`
|
||||
}
|
||||
|
||||
// BinaryDownloadURLMap is a map of os/architecture stings to a URL where the binary can be downloaded.
|
||||
type BinaryDownloadURLMap map[string]string
|
||||
|
||||
// ParseInfo parses an info string into a map of os/arch strings to URL string.
|
||||
// If the infoStr is a url, an GET request will be made to it, and its response will be parsed instead.
|
||||
func ParseInfo(infoStr string) (*Info, error) {
|
||||
infoStr = strings.TrimSpace(infoStr)
|
||||
|
||||
if len(infoStr) == 0 {
|
||||
return nil, errors.New("plan info must not be blank")
|
||||
}
|
||||
|
||||
// If it's a url, download it and treat the result as the real info.
|
||||
if _, err := neturl.Parse(infoStr); err == nil {
|
||||
infoStr, err = DownloadURLWithChecksum(infoStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Now, try to parse it into the expected structure.
|
||||
var planInfo Info
|
||||
if err := json.Unmarshal([]byte(infoStr), &planInfo); err != nil {
|
||||
return nil, fmt.Errorf("could not parse plan info: %v", err)
|
||||
}
|
||||
|
||||
return &planInfo, nil
|
||||
}
|
||||
|
||||
// ValidateFull does all possible validation of this Info.
|
||||
// The provided daemonName is the name of the executable file expected in all downloaded directories.
|
||||
// It checks that:
|
||||
// * Binaries.ValidateBasic() doesn't return an error
|
||||
// * Binaries.CheckURLs(daemonName) doesn't return an error.
|
||||
// Warning: This is an expensive process. See BinaryDownloadURLMap.CheckURLs for more info.
|
||||
func (m Info) ValidateFull(daemonName string) error {
|
||||
if err := m.Binaries.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.Binaries.CheckURLs(daemonName); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateBasic does stateless validation of this BinaryDownloadURLMap.
|
||||
// It validates that:
|
||||
// * This has at least one entry.
|
||||
// * All entry keys have the format "os/arch" or are "any".
|
||||
// * All entry values are valid URLs.
|
||||
// * All URLs contain a checksum query parameter.
|
||||
func (m BinaryDownloadURLMap) ValidateBasic() error {
|
||||
// Make sure there's at least one.
|
||||
if len(m) == 0 {
|
||||
return errors.New("no \"binaries\" entries found")
|
||||
}
|
||||
|
||||
osArchRx := regexp.MustCompile(`[a-zA-Z0-9]+/[a-zA-Z0-9]+`)
|
||||
for key, val := range m {
|
||||
if key != "any" && !osArchRx.MatchString(key) {
|
||||
return fmt.Errorf("invalid os/arch format in key \"%s\"", key)
|
||||
}
|
||||
if err := ValidateIsURLWithChecksum(val); err != nil {
|
||||
return fmt.Errorf("invalid url \"%s\" in binaries[%s]: %v", val, key, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckURLs checks that all entries have valid URLs that return expected data.
|
||||
// The provided daemonName is the name of the executable file expected in all downloaded directories.
|
||||
// Warning: This is an expensive process.
|
||||
// It will make an HTTP GET request to each URL and download the response.
|
||||
func (m BinaryDownloadURLMap) CheckURLs(daemonName string) error {
|
||||
tempDir, err := os.MkdirTemp("", "os-arch-downloads")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create temp directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
for osArch, url := range m {
|
||||
dstRoot := filepath.Join(tempDir, strings.ReplaceAll(osArch, "/", "-"))
|
||||
if err = DownloadUpgrade(dstRoot, url, daemonName); err != nil {
|
||||
return fmt.Errorf("error downloading binary for os/arch %s: %v", osArch, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,335 @@
|
|||
package plan
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type InfoTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
// Home is a temporary directory for use in these tests.
|
||||
Home string
|
||||
}
|
||||
|
||||
func (s *InfoTestSuite) SetupTest() {
|
||||
s.Home = s.T().TempDir()
|
||||
s.T().Logf("Home: [%s]", s.Home)
|
||||
}
|
||||
|
||||
func TestInfoTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(InfoTestSuite))
|
||||
}
|
||||
|
||||
// saveSrcTestFile saves a TestFile in this test's Home/src directory.
|
||||
// The full path to the saved file is returned.
|
||||
func (s InfoTestSuite) saveTestFile(f *TestFile) string {
|
||||
fullName, err := f.SaveIn(s.Home)
|
||||
s.Require().NoError(err, "saving test file %s", f.Name)
|
||||
return fullName
|
||||
}
|
||||
|
||||
func (s InfoTestSuite) TestParseInfo() {
|
||||
goodJSON := `{"binaries":{"os1/arch1":"url1","os2/arch2":"url2"}}`
|
||||
binariesWrongJSON := `{"binaries":["foo","bar"]}`
|
||||
binariesWrongValueJSON := `{"binaries":{"os1/arch1":1,"os2/arch2":2}}`
|
||||
goodJSONPath := s.saveTestFile(NewTestFile("good.json", goodJSON))
|
||||
binariesWrongJSONPath := s.saveTestFile(NewTestFile("binaries-wrong.json", binariesWrongJSON))
|
||||
binariesWrongValueJSONPath := s.saveTestFile(NewTestFile("binaries-wrong-value.json", binariesWrongValueJSON))
|
||||
goodJSONAsInfo := &Info{
|
||||
Binaries: BinaryDownloadURLMap{
|
||||
"os1/arch1": "url1",
|
||||
"os2/arch2": "url2",
|
||||
},
|
||||
}
|
||||
makeInfoStrFuncString := func(val string) func(t *testing.T) string {
|
||||
return func(t *testing.T) string {
|
||||
return val
|
||||
}
|
||||
}
|
||||
makeInfoStrFuncURL := func(file string) func(t *testing.T) string {
|
||||
return func(t *testing.T) string {
|
||||
return makeFileURL(t, file)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
infoStrMaker func(t *testing.T) string
|
||||
expectedInfo *Info
|
||||
expectedInError []string
|
||||
}{
|
||||
{
|
||||
name: "json good",
|
||||
infoStrMaker: makeInfoStrFuncString(goodJSON),
|
||||
expectedInfo: goodJSONAsInfo,
|
||||
expectedInError: nil,
|
||||
},
|
||||
{
|
||||
name: "blank string",
|
||||
infoStrMaker: makeInfoStrFuncString(" "),
|
||||
expectedInfo: nil,
|
||||
expectedInError: []string{"plan info must not be blank"},
|
||||
},
|
||||
{
|
||||
name: "json binaries is wrong data type",
|
||||
infoStrMaker: makeInfoStrFuncString(binariesWrongJSON),
|
||||
expectedInfo: nil,
|
||||
expectedInError: []string{"could not parse plan info", "cannot unmarshal array into Go struct field Info.binaries"},
|
||||
},
|
||||
{
|
||||
name: "json wrong data type in binaries value",
|
||||
infoStrMaker: makeInfoStrFuncString(binariesWrongValueJSON),
|
||||
expectedInfo: nil,
|
||||
expectedInError: []string{"could not parse plan info", "cannot unmarshal number into Go struct field Info.binaries"},
|
||||
},
|
||||
{
|
||||
name: "url does not exist",
|
||||
infoStrMaker: makeInfoStrFuncString("file:///this/file/does/not/exist?checksum=sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
|
||||
expectedInfo: nil,
|
||||
expectedInError: []string{"could not download url", "file:///this/file/does/not/exist"},
|
||||
},
|
||||
{
|
||||
name: "url good",
|
||||
infoStrMaker: makeInfoStrFuncURL(goodJSONPath),
|
||||
expectedInfo: goodJSONAsInfo,
|
||||
expectedInError: nil,
|
||||
},
|
||||
{
|
||||
name: "url binaries is wrong data type",
|
||||
infoStrMaker: makeInfoStrFuncURL(binariesWrongJSONPath),
|
||||
expectedInfo: nil,
|
||||
expectedInError: []string{"could not parse plan info", "cannot unmarshal array into Go struct field Info.binaries"},
|
||||
},
|
||||
{
|
||||
name: "url wrong data type in binaries value",
|
||||
infoStrMaker: makeInfoStrFuncURL(binariesWrongValueJSONPath),
|
||||
expectedInfo: nil,
|
||||
expectedInError: []string{"could not parse plan info", "cannot unmarshal number into Go struct field Info.binaries"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
s.T().Run(tc.name, func(t *testing.T) {
|
||||
infoStr := tc.infoStrMaker(t)
|
||||
actualInfo, actualErr := ParseInfo(infoStr)
|
||||
if len(tc.expectedInError) > 0 {
|
||||
require.Error(t, actualErr)
|
||||
for _, expectedErr := range tc.expectedInError {
|
||||
assert.Contains(t, actualErr.Error(), expectedErr)
|
||||
}
|
||||
} else {
|
||||
require.NoError(t, actualErr)
|
||||
}
|
||||
assert.Equal(t, tc.expectedInfo, actualInfo)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s InfoTestSuite) TestInfoValidateFull() {
|
||||
darwinAMD64File := NewTestFile("darwin_amd64", "#!/usr/bin\necho 'darwin/amd64'\n")
|
||||
linux386File := NewTestFile("linux_386", "#!/usr/bin\necho 'darwin/amd64'\n")
|
||||
darwinAMD64Path := s.saveTestFile(darwinAMD64File)
|
||||
linux386Path := s.saveTestFile(linux386File)
|
||||
darwinAMD64URL := makeFileURL(s.T(), darwinAMD64Path)
|
||||
linux386URL := makeFileURL(s.T(), linux386Path)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
planInfo *Info
|
||||
errs []string
|
||||
}{
|
||||
// Positive test case
|
||||
{
|
||||
name: "two good entries",
|
||||
planInfo: &Info{
|
||||
Binaries: BinaryDownloadURLMap{
|
||||
"darwin/amd64": darwinAMD64URL,
|
||||
"linux/386": linux386URL,
|
||||
},
|
||||
},
|
||||
errs: nil,
|
||||
},
|
||||
// a failure from BinaryDownloadURLMap.ValidateBasic
|
||||
{
|
||||
name: "empty binaries",
|
||||
planInfo: &Info{Binaries: BinaryDownloadURLMap{}},
|
||||
errs: []string{"no \"binaries\" entries found"},
|
||||
},
|
||||
// a failure from BinaryDownloadURLMap.CheckURLS
|
||||
{
|
||||
name: "url does not exist",
|
||||
planInfo: &Info{
|
||||
Binaries: BinaryDownloadURLMap{
|
||||
"darwin/arm64": "file:///no/such/file/exists/hopefully.zip?checksum=sha256:b5a2c96250612366ea272ffac6d9744aaf4b45aacd96aa7cfcb931ee3b558259",
|
||||
},
|
||||
},
|
||||
errs: []string{"error downloading binary", "darwin/arm64", "no such file or directory"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
s.T().Run(tc.name, func(t *testing.T) {
|
||||
actualErr := tc.planInfo.ValidateFull("daemon")
|
||||
if len(tc.errs) > 0 {
|
||||
require.Error(t, actualErr)
|
||||
for _, expectedErr := range tc.errs {
|
||||
assert.Contains(t, actualErr.Error(), expectedErr)
|
||||
}
|
||||
} else {
|
||||
require.NoError(t, actualErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s InfoTestSuite) TestBinaryDownloadURLMapValidateBasic() {
|
||||
addDummyChecksum := func(url string) string {
|
||||
return url + "?checksum=sha256:b5a2c96250612366ea272ffac6d9744aaf4b45aacd96aa7cfcb931ee3b558259"
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
urlMap BinaryDownloadURLMap
|
||||
errs []string
|
||||
}{
|
||||
{
|
||||
name: "empty map",
|
||||
urlMap: BinaryDownloadURLMap{},
|
||||
errs: []string{"no \"binaries\" entries found"},
|
||||
},
|
||||
{
|
||||
name: "key with empty string",
|
||||
urlMap: BinaryDownloadURLMap{
|
||||
"": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
},
|
||||
errs: []string{"invalid os/arch", `""`},
|
||||
},
|
||||
{
|
||||
name: "invalid key format",
|
||||
urlMap: BinaryDownloadURLMap{
|
||||
"badkey": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
},
|
||||
errs: []string{"invalid os/arch", "badkey"},
|
||||
},
|
||||
{
|
||||
name: "any key is valid",
|
||||
urlMap: BinaryDownloadURLMap{
|
||||
"any": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
},
|
||||
errs: nil,
|
||||
},
|
||||
{
|
||||
name: "os arch key is valid",
|
||||
urlMap: BinaryDownloadURLMap{
|
||||
"darwin/amd64": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
},
|
||||
errs: nil,
|
||||
},
|
||||
{
|
||||
name: "not a url",
|
||||
urlMap: BinaryDownloadURLMap{
|
||||
"isa/url": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
"nota/url": addDummyChecksum("https://v1.cosmos.network:not-a-port/sdk"),
|
||||
},
|
||||
errs: []string{"invalid url", "nota/url", "invalid port"},
|
||||
},
|
||||
{
|
||||
name: "url without checksum",
|
||||
urlMap: BinaryDownloadURLMap{
|
||||
"darwin/amd64": "https://v1.cosmos.network/sdk",
|
||||
},
|
||||
errs: []string{"invalid url", "darwin/amd64", "missing checksum query parameter"},
|
||||
},
|
||||
{
|
||||
name: "multiple valid entries but one bad url",
|
||||
urlMap: BinaryDownloadURLMap{
|
||||
"any": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
"darwin/amd64": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
"darwin/arm64": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
"windows/bad": addDummyChecksum("https://v1.cosmos.network:not-a-port/sdk"),
|
||||
"linux/386": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
},
|
||||
errs: []string{"invalid url", "windows/bad", "invalid port"},
|
||||
},
|
||||
{
|
||||
name: "multiple valid entries but one bad key",
|
||||
urlMap: BinaryDownloadURLMap{
|
||||
"any": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
"darwin/amd64": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
"badkey": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
"darwin/arm64": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
"linux/386": addDummyChecksum("https://v1.cosmos.network/sdk"),
|
||||
},
|
||||
errs: []string{"invalid os/arch", "badkey"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
s.T().Run(tc.name, func(t *testing.T) {
|
||||
actualErr := tc.urlMap.ValidateBasic()
|
||||
if len(tc.errs) > 0 {
|
||||
require.Error(t, actualErr)
|
||||
for _, expectedErr := range tc.errs {
|
||||
assert.Contains(t, actualErr.Error(), expectedErr)
|
||||
}
|
||||
} else {
|
||||
require.NoError(t, actualErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s InfoTestSuite) TestBinaryDownloadURLMapCheckURLs() {
|
||||
darwinAMD64File := NewTestFile("darwin_amd64", "#!/usr/bin\necho 'darwin/amd64'\n")
|
||||
linux386File := NewTestFile("linux_386", "#!/usr/bin\necho 'darwin/amd64'\n")
|
||||
darwinAMD64Path := s.saveTestFile(darwinAMD64File)
|
||||
linux386Path := s.saveTestFile(linux386File)
|
||||
darwinAMD64URL := makeFileURL(s.T(), darwinAMD64Path)
|
||||
linux386URL := makeFileURL(s.T(), linux386Path)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
urlMap BinaryDownloadURLMap
|
||||
errs []string
|
||||
}{
|
||||
{
|
||||
name: "two good entries",
|
||||
urlMap: BinaryDownloadURLMap{
|
||||
"darwin/amd64": darwinAMD64URL,
|
||||
"linux/386": linux386URL,
|
||||
},
|
||||
errs: nil,
|
||||
},
|
||||
{
|
||||
name: "url does not exist",
|
||||
urlMap: BinaryDownloadURLMap{
|
||||
"darwin/arm64": "file:///no/such/file/exists/hopefully.zip?checksum=sha256:b5a2c96250612366ea272ffac6d9744aaf4b45aacd96aa7cfcb931ee3b558259",
|
||||
},
|
||||
errs: []string{"error downloading binary", "darwin/arm64", "no such file or directory"},
|
||||
},
|
||||
{
|
||||
name: "bad checksum",
|
||||
urlMap: BinaryDownloadURLMap{
|
||||
"darwin/amd64": "file://" + darwinAMD64Path + "?checksum=sha256:b5a2c96250612366ea272ffac6d9744aaf4b45aacd96aa7cfcb931ee3b558259",
|
||||
},
|
||||
errs: []string{"error downloading binary", "darwin/amd64", "Checksums did not match", "b5a2c96250612366ea272ffac6d9744aaf4b45aacd96aa7cfcb931ee3b558259"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
s.T().Run(tc.name, func(t *testing.T) {
|
||||
actualErr := tc.urlMap.CheckURLs("daemon")
|
||||
if len(tc.errs) > 0 {
|
||||
require.Error(t, actualErr)
|
||||
for _, expectedErr := range tc.errs {
|
||||
assert.Contains(t, actualErr.Error(), expectedErr)
|
||||
}
|
||||
} else {
|
||||
require.NoError(t, actualErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue