Compare commits

...

49 Commits

Author SHA1 Message Date
Renovate Bot 14b987cb42 Update module github.com/blang/semver to v3.8.0 2022-06-10 18:06:24 +02:00
Renovate Bot fed5b400de Update module github.com/ulikunitz/xz to v0.5.10 2022-06-10 18:06:13 +02:00
Oskar 5aea9fd5f3
Update go.yml 2021-10-27 23:01:16 +02:00
MedzikUser e33f9e522e update 2021-10-27 22:59:25 +02:00
MedzikUser 542dc5db61 Revert "update"
This reverts commit c9e5fe26ca.
2021-10-27 22:56:40 +02:00
MedzikUser 4b201b07f5 update 2021-10-27 22:54:30 +02:00
MedzikUser c9e5fe26ca update 2021-10-27 22:54:03 +02:00
MedzikUser e8ecb189e5 update 2021-10-27 22:48:35 +02:00
MedzikUser a75b2492cb update 2021-10-27 22:41:38 +02:00
MedzikUser 8a2cf5e596 update 2021-10-27 22:35:41 +02:00
Oskar ec571e8c14
Create go.yml 2021-10-27 22:26:02 +02:00
MedzikUser 9c76d66885 update 2021-10-27 22:24:36 +02:00
MedzikUser 9abefd0460 update 2021-10-27 22:23:51 +02:00
Oskar 8ab4b94ea1
Update renovate.json 2021-10-27 22:18:50 +02:00
Oskar 13766db3e1
Create renovate.json 2021-08-18 20:46:00 +02:00
Medzik f29763a683 Update 2021-07-24 19:39:58 +00:00
rhysd 04a545f8ea update changelog for v1.2.3 2021-01-13 21:25:45 +09:00
Linda_pp 1aa7d81a46
Merge pull request #38 from bhamail/update_x_text
update x/text. Fix CVE-2020-14040
2021-01-13 16:43:16 +09:00
Dan Rollo f315b89e29 update x/text. Fix CVE-2020-14040 2021-01-12 23:03:40 -05:00
Linda_pp 4161c14e0c
Merge pull request #37 from bhamail/update_x_crypto
update x/crypto. Fix CVE-2019-11840
2021-01-13 12:55:27 +09:00
Dan Rollo ed69ef6c6b Merge branch 'master' into update_x_crypto
# Conflicts:
#	go.mod
2021-01-12 22:48:47 -05:00
Linda_pp 466402f90d
Merge pull request #36 from bhamail/update_xz
update to latest release of xz lib. Fix CVE-2020-16845
2021-01-13 10:41:36 +09:00
Dan Rollo a894e020c3 update x/crypto. Fix CVE-2019-11840 2021-01-12 19:03:43 -05:00
Dan Rollo 40792dc985 update to latest release of xz lib. Fix CVE-2020-16845 2021-01-12 18:31:49 -05:00
Linda_pp 5766a1046b
Merge pull request #29 from michaelbirdflyt/master
update to go-github v30.1.0
2020-04-10 15:26:13 +09:00
Mike Bird 17cd45ef2c update to go-github v30.1.0 2020-04-09 17:28:13 -07:00
rhysd 6cf6ce8c7a update changelog for v1.2.1 2019-12-19 18:08:27 +09:00
rhysd 905eb9f670 fix .tgz file is not handled on UncompressCommand 2019-12-19 16:28:00 +09:00
rhysd 3b082d183d update CHANGELOG for v1.2.0 2019-12-19 13:08:04 +09:00
rhysd b5118a9ebf test validator file suffix 2019-12-19 11:40:54 +09:00
rhysd a4d48e7891 add tests for initializing filter regexes 2019-12-19 11:28:39 +09:00
rhysd 351a78b1eb improve error message when regex for filter is broken 2019-12-19 11:28:08 +09:00
Linda_pp 53849d13bc
Merge pull request #25 from fredbi/master
Added option to filter multiple assets in release
2019-12-19 10:49:43 +09:00
Frederic BIDON 12e3317156
nitpick: added cr on logging
Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
2019-12-18 18:34:02 +01:00
Frederic BIDON 7fc1c7ebec
tidy up unit test
Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
2019-12-18 09:49:52 +01:00
Frederic BIDON 63b0fb3fc1
* reverted choice of Option functor and added filters to Config
* moved regexp-base filtering loop upwards (fail early)
* added unit test for asset lookup with or without filters

Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
2019-12-18 09:32:48 +01:00
Frederic BIDON 0148193fae
Added option to filter multiple assets in release
Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
2019-12-16 20:12:11 +01:00
Linda_pp 80762b859d
Merge pull request #22 from flowonyx/patch-2
Corrected some typos in README
2019-10-30 12:39:08 +09:00
Joel Williams 677875a59b
Corrected some typos
Made some corrections to grammar and deleted a repeated section. I hope this is helpful for you.
2019-10-30 00:25:24 +02:00
Linda_pp c328ee2035
Merge pull request #21 from pieterclaerhout/master
Update detect.go to add support for tgz files
2019-10-20 00:30:02 +09:00
Pieter Claerhout fdad9d2127
Update detect.go
https://github.com/rhysd/go-github-selfupdate/issues/20
2019-10-19 12:46:25 +02:00
rhysd 04c8f18cca describe executable file name convention contained in assets 2019-07-25 18:37:40 +09:00
rhysd 051f309b75 allow {cmd}_{os}_{arch} name for executable in asset (#19) 2019-07-25 18:31:38 +09:00
rhysd 87b01fbf06 remove go.sum temporarily
It seems that how to calculate hashes is depending on Go
toolchain toolchain version. It means that this library may not be
installed due to 'broken hash' with different Go toolchain from mine. I
believe it is temporal since Go module feature is actively developed.

I decided to remove `go.sum` temporarily. It should be restored in the
future.
2019-03-01 20:11:33 +09:00
Linda_pp 5592b885d8
Merge pull request #18 from bertuss/patch-1
Fix small typo in Readme
2019-01-14 01:11:49 +09:00
Bertus Steenberg 687eda561c
s/dpeneding/depending/ 2019-01-13 17:18:19 +02:00
rhysd ff8cf0cdf6 versioning go-get-release 2018-11-10 23:16:45 +09:00
rhysd 8823218ebf add changelog 2018-11-10 13:08:05 +09:00
rhysd b179163923 tweak README [skip ci] 2018-11-10 12:52:51 +09:00
22 changed files with 627 additions and 115 deletions

26
.github/workflows/go.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: Go
on:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
- run: find . -type f -exec sed -i 's,MedzikUser/go-github-selfupdate,rhysd/go-github-selfupdate,g' {} +
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...

40
CHANGELOG.md Normal file
View File

@ -0,0 +1,40 @@
## [v1.2.3] - 2021-01-13
- Fix security issues in dependencies; CVE-2020-16845, CVE-2019-11840, CVE-2020-14040 (Thanks to [@bhamail](https://github.com/bhamail)).
## [v1.2.2] - 2020-04-10
- Update `go-github` dependency to v30.1.0
## [v1.2.1] - 2019-12-19
- Fix `.tgz` file was not handled as `.tar.gz`.
## [v1.2.0] - 2019-12-19
- New Feature: Filtering releases by matching regular expressions to release names (Thanks to [@fredbi](https://github.com/fredbi)).
Regular expression strings specified at `Filters` field in `Config` struct are used on detecting the
latest release. Please read [documentation](https://godoc.org/github.com/MedzikUser/go-github-selfupdate/selfupdate#Config)
for more details.
- Allow `{cmd}_{os}_{arch}` format for executable names.
- `.tgz` file name suffix was supported.
## [v1.1.0] - 2018-11-10
- New Feature: Signature validation for release assets (Thanks to [@tobiaskohlbau](https://github.com/tobiaskohlbau)).
Please read [the instruction](https://github.com/MedzikUser/go-github-selfupdate#hash-or-signature-validation) for usage.
## [v1.0.0] - 2018-09-23
First release! :tada:
[v1.2.3]: https://github.com/MedzikUser/go-github-selfupdate/compare/v1.2.2...v1.2.3
[v1.2.2]: https://github.com/MedzikUser/go-github-selfupdate/compare/v1.2.1...v1.2.2
[v1.2.1]: https://github.com/MedzikUser/go-github-selfupdate/compare/v1.2.0...v1.2.1
[v1.2.0]: https://github.com/MedzikUser/go-github-selfupdate/compare/go-get-release...v1.2.0
[v1.1.0]: https://github.com/MedzikUser/go-github-selfupdate/compare/v1.0.0...v1.1.0
[v1.0.0]: https://github.com/MedzikUser/go-github-selfupdate/compare/example-1.2.4...v1.0.0

101
README.md
View File

@ -1,4 +1,4 @@
Self-Update Mechanism for Go Commands using GitHub
Self-Update Mechanism for Go Commands Using GitHub
==================================================
[![GoDoc Badge][]][GoDoc]
@ -6,30 +6,30 @@ Self-Update Mechanism for Go Commands using GitHub
[![AppVeyor Status][]][AppVeyor]
[![Codecov Status][]][Codecov]
[go-github-selfupdate][] is a Go library to provide self-update mechanism to command line tools.
[go-github-selfupdate][] is a Go library to provide a self-update mechanism to command line tools.
Go does not provide the way to install/update the stable version of tools. By default, Go command line
Go does not provide a way to install/update the stable version of tools. By default, Go command line
tools are updated:
1. using `go get -u`, but it is not stable because HEAD of the repository is built
2. using system's package manager, but it is harder to release because of dpeneding on the platform
3. downloading executables from GitHub release page, but it requires users to download and put it manually
2. using system's package manager, but it is harder to release because of depending on the platform
3. downloading executables from GitHub release page, but it requires users to download and put it in an executable path manually
[go-github-selfupdate][] resolves the problem of 3. by detecting the latest release, downloading it and
putting it to `$GOPATH/bin` automatically.
[go-github-selfupdate][] resolves the problem of 3 by detecting the latest release, downloading it and
putting it in `$GOPATH/bin` automatically.
[go-github-selfupdate][] detects the information of the latest release via [GitHub Releases API][] and
checks the current version. If newer version than itself is detected, it downloads released binary from
checks the current version. If a newer version than itself is detected, it downloads the released binary from
GitHub and replaces itself.
- Automatically detects the latest version of released binary on GitHub
- Automatically detect the latest version of released binary on GitHub
- Retrieve the proper binary for the OS and arch where the binary is running
- Update the binary with rollback support on failure
- Tested on Linux, macOS and Windows (using Travis CI and AppVeyor)
- Many archive and compression formats are supported (zip, tar, gzip, xzip)
- Support private repositories
- Support [GitHub Enterprise][]
- Support hash, signature validation
- Support hash, signature validation (thanks to [@tobiaskohlbau](https://github.com/tobiaskohlbau))
And small wrapper CLIs are provided:
@ -38,9 +38,11 @@ And small wrapper CLIs are provided:
[Slide at GoCon 2018 Spring (Japanese)](https://speakerdeck.com/rhysd/go-selfupdate-github-de-turuwozi-ji-atupudetosuru)
[go-github-selfupdate]: https://github.com/rhysd/go-github-selfupdate
[go-github-selfupdate]: https://github.com/MedzikUser/go-github-selfupdate
[GitHub Releases API]: https://developer.github.com/v3/repos/releases/
## Try Out Example
Example to understand what this library does is prepared as [CLI](./cmd/selfupdate-example/main.go).
@ -48,7 +50,7 @@ Example to understand what this library does is prepared as [CLI](./cmd/selfupda
Install it at first.
```
$ go get -u github.com/rhysd/go-github-selfupdate/cmd/selfupdate-example
$ go get -u github.com/MedzikUser/go-github-selfupdate/cmd/selfupdate-example
```
And check the version by `-version`. `-help` flag is also available to know all flags.
@ -65,7 +67,7 @@ Then run `-selfupdate`
$ selfupdate-example -selfupdate
```
It should replace itself and finally shows a message containing release notes.
It should replace itself and finally show a message containing release notes.
Please check the binary version is updated to `v1.2.4` with `-version`. The binary is up-to-date.
So running `-selfupdate` again only shows 'Current binary is the latest version'.
@ -81,6 +83,8 @@ Following tools are using this library.
- [akashic](https://github.com/cowlick/akashic)
- [butler](https://github.com/netzkern/butler)
## Usage
### Code Usage
@ -102,7 +106,7 @@ Following is the easiest way to use this package.
import (
"log"
"github.com/blang/semver"
"github.com/rhysd/go-github-selfupdate/selfupdate"
"github.com/MedzikUser/go-github-selfupdate/selfupdate"
)
const version = "1.2.3"
@ -130,7 +134,7 @@ Following asks user to update or not.
import (
"bufio"
"github.com/blang/semver"
"github.com/rhysd/go-github-selfupdate/selfupdate"
"github.com/MedzikUser/go-github-selfupdate/selfupdate"
"log"
"os"
)
@ -183,7 +187,7 @@ please use `os.Executable()`.
Please see [the documentation page][GoDoc] for more detail.
This library should work with [GitHub Enterprise][]. To configure API base URL, please setup `Updater`
instance and use its method instead (Actually all functions above are just a shortcuts of methods of
instance and use its methods instead (actually all functions above are just a shortcuts of methods of an
`Updater` instance).
Following is an example of usage with GitHub Enterprise.
@ -192,7 +196,7 @@ Following is an example of usage with GitHub Enterprise.
import (
"log"
"github.com/blang/semver"
"github.com/rhysd/go-github-selfupdate/selfupdate"
"github.com/MedzikUser/go-github-selfupdate/selfupdate"
)
const version = "1.2.3"
@ -222,8 +226,9 @@ If `APIToken` field is not given, it tries to retrieve API token from `[token]`
or `$GITHUB_TOKEN` environment variable. If no token is found, it raises an error because GitHub Enterprise
API does not work without authentication.
If your GitHub Enterprise instance's upload URL is different from the base URL, please also set `EnterpriseUploadURL`
field
If your GitHub Enterprise instance's upload URL is different from the base URL, please also set the `EnterpriseUploadURL`
field.
### Naming Rules of Released Binaries
@ -249,11 +254,17 @@ For example, if your command name is `foo-bar`, one of followings is expected to
page on GitHub as binary for platform `linux` and arch `amd64`.
- `foo-bar_linux_amd64` (executable)
- `foo-bar_linux_amd64.zip` (zip file containing `foo-bar`)
- `foo-bar_linux_amd64.tar.gz` (tar file containing `foo-bar`)
- `foo-bar_linux_amd64.xz` (xzip file of the executable `foo-bar`)
- `foo-bar_linux_amd64.zip` (zip file)
- `foo-bar_linux_amd64.tar.gz` (tar file)
- `foo-bar_linux_amd64.xz` (xzip file)
- `foo-bar-linux-amd64.tar.gz` (`-` is also ok for separator)
If you compress and/or archive your release asset, it must contain an executable named one of followings:
- `foo-bar` (only command name)
- `foo-bar_linux_amd64` (full name)
- `foo-bar-linux-amd64` (`-` is also ok for separator)
To archive the executable directly on Windows, `.exe` can be added before file extension like
`foo-bar_windows_amd64.exe.zip`.
@ -296,8 +307,6 @@ In summary, structure of releases on GitHub looks like:
- ... (Other binaries for v1.1.3)
- ... (older versions)
Tags which don't contain a version number are ignored (i.e. `nightly`). And releases marked
as `pre-release` are also ignored.
### Hash or Signature Validation
@ -317,7 +326,7 @@ type Validator interface {
}
```
## SHA256
#### SHA256
To verify the integrity by SHA256 generate a hash sum and save it within a file which has the
same naming as original file with the suffix `.sha256`.
@ -326,7 +335,7 @@ For e.g. use sha256sum, the file `selfupdate/testdata/foo.zip.sha256` is generat
sha256sum foo.zip > foo.zip.sha256
```
## ECDSA
#### ECDSA
To verify the signature by ECDSA generate a signature and save it within a file which has the
same naming as original file with the suffix `.sig`.
For e.g. use openssl, the file `selfupdate/testdata/foo.zip.sig` is generated with:
@ -337,9 +346,11 @@ openssl dgst -sha256 -sign Test.pem -out foo.zip.sig foo.zip
go-github-selfupdate makes use of go internal crypto package. Therefore the used private key
has to be compatbile with FIPS 186-3.
### Development
#### Running tests
## Development
### Running tests
All library sources are put in `/selfupdate` directory. So you can run tests as following
at the top of the repository:
@ -356,25 +367,29 @@ $ export GITHUB_TOKEN="{token generated by you}"
$ go test -v ./selfupdate
```
Above command run almost all tests and it's enough to check the behavior before creating a pull request.
The above command runs almost all tests and it's enough to check the behavior before creating a pull request.
Some tests are still not tested because they depend on my personal API access token, though; for repositories
on GitHub Enterprise or private repositories on GitHub.
#### Debugging
### Debugging
This library can output logs for debugging. By default, logger is disabled.
You can enable the logger by following and can know the details of the self update.
You can enable the logger by the following and can know the details of the self update.
```go
selfupdate.EnableLog()
```
#### CI
### CI
Tests run on CIs (Travis CI, Appveyor) are run with the token I generated. However, because of security
reason, it is not used for the tests for pull requests. In the tests, a GitHub API token is not set and
reasons, it is not used for the tests for pull requests. In the tests, a GitHub API token is not set and
API rate limit is often exceeding. So please ignore the test failures on creating a pull request.
## Dependencies
This library utilizes
@ -396,9 +411,11 @@ This library utilizes
[semver]: https://github.com/blang/semver
[xz]: https://github.com/ulikunitz/xz
## What is different from [tj/go-update][]?
This library goal is the same as tj/go-update, but it's different in following points.
This library's goal is the same as tj/go-update, but it's different in following points.
tj/go-update:
@ -410,16 +427,18 @@ tj/go-update:
[tj/go-update]: https://github.com/tj/go-update
## License
Distributed under the [MIT License](LICENSE)
[GoDoc Badge]: https://godoc.org/github.com/rhysd/go-github-selfupdate/selfupdate?status.svg
[GoDoc]: https://godoc.org/github.com/rhysd/go-github-selfupdate/selfupdate
[TravisCI Status]: https://travis-ci.org/rhysd/go-github-selfupdate.svg?branch=master
[TravisCI]: https://travis-ci.org/rhysd/go-github-selfupdate
[GoDoc Badge]: https://godoc.org/github.com/MedzikUser/go-github-selfupdate/selfupdate?status.svg
[GoDoc]: https://godoc.org/github.com/MedzikUser/go-github-selfupdate/selfupdate
[TravisCI Status]: https://travis-ci.org/MedzikUser/go-github-selfupdate.svg?branch=master
[TravisCI]: https://travis-ci.org/MedzikUser/go-github-selfupdate
[AppVeyor Status]: https://ci.appveyor.com/api/projects/status/1tpyd9q9tw3ime5u/branch/master?svg=true
[AppVeyor]: https://ci.appveyor.com/project/rhysd/go-github-selfupdate/branch/master
[Codecov Status]: https://codecov.io/gh/rhysd/go-github-selfupdate/branch/master/graph/badge.svg
[Codecov]: https://codecov.io/gh/rhysd/go-github-selfupdate
[AppVeyor]: https://ci.appveyor.com/project/MedzikUser/go-github-selfupdate/branch/master
[Codecov Status]: https://codecov.io/gh/MedzikUser/go-github-selfupdate/branch/master/graph/badge.svg
[Codecov]: https://codecov.io/gh/MedzikUser/go-github-selfupdate
[GitHub Enterprise]: https://enterprise.github.com/home

View File

@ -1,9 +1,9 @@
This command line tool is a small wrapper of [`selfupdate.DetectLatest()`](https://godoc.org/github.com/rhysd/go-github-selfupdate/selfupdate#DetectLatest).
This command line tool is a small wrapper of [`selfupdate.DetectLatest()`](https://godoc.org/github.com/MedzikUser/go-github-selfupdate/selfupdate#DetectLatest).
Please install using `go get`.
```
$ go get -u github.com/rhysd/go-github-selfupdate/cmd/detect-latest-release
$ go get -u github.com/MedzikUser/go-github-selfupdate/cmd/detect-latest-release
```
To know the usage, please try the command without any argument.

View File

@ -3,7 +3,7 @@ package main
import (
"flag"
"fmt"
"github.com/rhysd/go-github-selfupdate/selfupdate"
"github.com/MedzikUser/go-github-selfupdate/selfupdate"
"os"
"regexp"
"strings"

View File

@ -1,20 +1,23 @@
Like `go get`, but it downloads and installs the latest release binary from GitHub instead.
Please install using `go get`.
Please download a binary from [release page](https://github.com/MedzikUser/go-github-selfupdate/releases/tag/go-get-release)
and put it in `$PATH` or build from source with `go get`.
```
$ go get -u github.com/rhysd/go-github-selfupdate/cmd/go-get-release
$ go get -u github.com/MedzikUser/go-github-selfupdate/cmd/go-get-release
```
Usage is quite similar to `go get`. But `{package}` must be hosted on GitHub. So it needs to start with `github.com/`.
```
$ detect-latest-release {package}
$ go-get-release {package}
```
Please note that this command assumes that specified package is following Git tag naming rules and released binaries naming rules described in [README](../../README.md).
Please note that this command assumes that specified package is following Git tag naming rules and
released binaries naming rules described in [README](../../README.md).
For example, following command downloads and installs the released binary of [ghr](https://github.com/tcnksm/ghr) to `$GOPATH/bin`.
For example, following command downloads and installs the released binary of [ghr](https://github.com/tcnksm/ghr)
to `$GOPATH/bin`.
```
$ go-get-release github.com/tcnksm/ghr

View File

@ -3,7 +3,7 @@ package main
import (
"flag"
"fmt"
"github.com/rhysd/go-github-selfupdate/selfupdate"
"github.com/MedzikUser/go-github-selfupdate/selfupdate"
"go/build"
"io"
"net/http"
@ -12,6 +12,8 @@ import (
"strings"
)
var version = "1.0.0"
func usage() {
fmt.Fprintln(os.Stderr, `Usage: go-get-release [flags] {package}
@ -76,10 +78,16 @@ func installFrom(url, cmd, path string) error {
func main() {
help := flag.Bool("help", false, "Show help")
ver := flag.Bool("version", false, "Show version")
flag.Usage = usage
flag.Parse()
if *ver {
fmt.Println(version)
os.Exit(0)
}
if *help || flag.NArg() != 1 || !strings.HasPrefix(flag.Arg(0), "github.com/") {
usage()
os.Exit(1)

View File

@ -4,7 +4,7 @@ import (
"flag"
"fmt"
"github.com/blang/semver"
"github.com/rhysd/go-github-selfupdate/selfupdate"
"github.com/MedzikUser/go-github-selfupdate/selfupdate"
"os"
)
@ -37,7 +37,7 @@ func main() {
help := flag.Bool("help", false, "Show this help")
ver := flag.Bool("version", false, "Show version")
update := flag.Bool("selfupdate", false, "Try go-github-selfupdate via GitHub")
slug := flag.String("slug", "rhysd/go-github-selfupdate", "Repository of this command")
slug := flag.String("slug", "MedzikUser/go-github-selfupdate", "Repository of this command")
flag.Usage = usage
flag.Parse()

16
go.mod
View File

@ -1,18 +1,18 @@
module github.com/rhysd/go-github-selfupdate
module github.com/MedzikUser/go-github-selfupdate
require (
github.com/blang/semver v3.5.1+incompatible
github.com/google/go-github v17.0.0+incompatible
github.com/google/go-querystring v1.0.0 // indirect
github.com/blang/semver v3.8.0+incompatible
github.com/google/go-github/v30 v30.1.0
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
github.com/kr/pretty v0.1.0 // indirect
github.com/onsi/gomega v1.4.2 // indirect
github.com/tcnksm/go-gitconfig v0.1.2
github.com/ulikunitz/xz v0.5.5
golang.org/x/net v0.0.0-20181108082009-03003ca0c849 // indirect
github.com/ulikunitz/xz v0.5.10
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 // indirect
golang.org/x/text v0.3.5 // indirect
google.golang.org/appengine v1.3.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)
go 1.13

41
go.sum
View File

@ -1,11 +1,15 @@
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver v3.8.0+incompatible h1:pJL/7tIY4048kLKL5Bvbif+Q2WxiKQcJprKDla+x33U=
github.com/blang/semver v3.8.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
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/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo=
github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
@ -23,27 +27,38 @@ github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
github.com/ulikunitz/xz v0.5.9/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=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 h1:JIqe8uIcRBHXDQVvZtHwp80ai3Lw3IJAeJEs55Dc1W0=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 h1:YoY1wS6JYVRpIfFngRf2HHo9R9dAne3xbkGOQ5rJXjU=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
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/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -7,12 +7,14 @@ import (
"strings"
"github.com/blang/semver"
"github.com/google/go-github/github"
"github.com/google/go-github/v30/github"
)
var reVersion = regexp.MustCompile(`\d+\.\d+\.\d+`)
func findAssetFromReleasse(rel *github.RepositoryRelease, suffixes []string, targetVersion string) (*github.ReleaseAsset, semver.Version, bool) {
func findAssetFromRelease(rel *github.RepositoryRelease,
suffixes []string, targetVersion string, filters []*regexp.Regexp) (*github.ReleaseAsset, semver.Version, bool) {
if targetVersion != "" && targetVersion != rel.GetTagName() {
log.Println("Skip", rel.GetTagName(), "not matching to specified version", targetVersion)
return nil, semver.Version{}, false
@ -48,9 +50,26 @@ func findAssetFromReleasse(rel *github.RepositoryRelease, suffixes []string, tar
for _, asset := range rel.Assets {
name := asset.GetName()
if len(filters) > 0 {
// if some filters are defined, match them: if any one matches, the asset is selected
matched := false
for _, filter := range filters {
if filter.MatchString(name) {
log.Println("Selected filtered asset", name)
matched = true
break
}
log.Printf("Skipping asset %q not matching filter %v\n", name, filter)
}
if !matched {
continue
}
}
for _, s := range suffixes {
if strings.HasSuffix(name, s) {
return &asset, ver, true
if strings.HasSuffix(name, s) { // require version, arch etc
// default: assume single artifact
return asset, ver, true
}
}
}
@ -62,17 +81,19 @@ func findAssetFromReleasse(rel *github.RepositoryRelease, suffixes []string, tar
func findValidationAsset(rel *github.RepositoryRelease, validationName string) (*github.ReleaseAsset, bool) {
for _, asset := range rel.Assets {
if asset.GetName() == validationName {
return &asset, true
return asset, true
}
}
return nil, false
}
func findReleaseAndAsset(rels []*github.RepositoryRelease, targetVersion string) (*github.RepositoryRelease, *github.ReleaseAsset, semver.Version, bool) {
func findReleaseAndAsset(rels []*github.RepositoryRelease,
targetVersion string,
filters []*regexp.Regexp) (*github.RepositoryRelease, *github.ReleaseAsset, semver.Version, bool) {
// Generate candidates
suffixes := make([]string, 0, 2*7*2)
for _, sep := range []rune{'_', '-'} {
for _, ext := range []string{".zip", ".tar.gz", ".gzip", ".gz", ".tar.xz", ".xz", ""} {
for _, ext := range []string{".zip", ".tar.gz", ".tgz", ".gzip", ".gz", ".tar.xz", ".xz", ""} {
suffix := fmt.Sprintf("%s%c%s%s", runtime.GOOS, sep, runtime.GOARCH, ext)
suffixes = append(suffixes, suffix)
if runtime.GOOS == "windows" {
@ -88,9 +109,9 @@ func findReleaseAndAsset(rels []*github.RepositoryRelease, targetVersion string)
// Find the latest version from the list of releases.
// Returned list from GitHub API is in the order of the date when created.
// ref: https://github.com/rhysd/go-github-selfupdate/issues/11
// ref: https://github.com/MedzikUser/go-github-selfupdate/issues/11
for _, rel := range rels {
if a, v, ok := findAssetFromReleasse(rel, suffixes, targetVersion); ok {
if a, v, ok := findAssetFromRelease(rel, suffixes, targetVersion, filters); ok {
// Note: any version with suffix is less than any version without suffix.
// e.g. 0.0.1 > 0.0.1-beta
if release == nil || v.GTE(ver) {
@ -138,7 +159,7 @@ func (up *Updater) DetectVersion(slug string, version string) (release *Release,
return nil, false, err
}
rel, asset, ver, found := findReleaseAndAsset(rels, version)
rel, asset, ver, found := findReleaseAndAsset(rels, version, up.filters)
if !found {
return nil, false, nil
}
@ -162,7 +183,7 @@ func (up *Updater) DetectVersion(slug string, version string) (release *Release,
}
if up.validator != nil {
validationName := asset.GetName()+up.validator.Suffix()
validationName := asset.GetName() + up.validator.Suffix()
validationAsset, ok := findValidationAsset(rel, validationName)
if !ok {
return nil, false, fmt.Errorf("Failed finding validation file %q", validationName)

View File

@ -3,10 +3,12 @@ package selfupdate
import (
"fmt"
"os"
"regexp"
"strings"
"testing"
"github.com/blang/semver"
"github.com/google/go-github/v30/github"
)
func TestDetectReleaseWithVersionPrefix(t *testing.T) {
@ -240,3 +242,216 @@ func TestDetectFromGitHubEnterpriseRepo(t *testing.T) {
t.Error("")
}
}
func TestFindReleaseAndAsset(t *testing.T) {
EnableLog()
type findReleaseAndAssetFixture struct {
name string
rels *github.RepositoryRelease
targetVersion string
filters []*regexp.Regexp
expectedAsset string
expectedVersion string
expectedFound bool
}
rel1 := "rel1"
v1 := "1.0.0"
rel11 := "rel11"
v11 := "1.1.0"
asset1 := "asset1.gz"
asset2 := "asset2.gz"
wrongAsset1 := "asset1.yaml"
asset11 := "asset11.gz"
url1 := "https://asset1"
url2 := "https://asset2"
url11 := "https://asset11"
for _, fixture := range []findReleaseAndAssetFixture{
{
name: "empty fixture",
rels: nil,
targetVersion: "",
filters: nil,
expectedFound: false,
},
{
name: "find asset, no filters",
rels: &github.RepositoryRelease{
Name: &rel1,
TagName: &v1,
Assets: []*github.ReleaseAsset{
{
Name: &asset1,
URL: &url1,
},
},
},
targetVersion: "1.0.0",
expectedAsset: asset1,
expectedVersion: "1.0.0",
expectedFound: true,
},
{
name: "don't find asset with wrong extension, no filters",
rels: &github.RepositoryRelease{
Name: &rel11,
TagName: &v11,
Assets: []*github.ReleaseAsset{
{
Name: &wrongAsset1,
URL: &url11,
},
},
},
targetVersion: "1.1.0",
expectedFound: false,
},
{
name: "find asset with different name, no filters",
rels: &github.RepositoryRelease{
Name: &rel11,
TagName: &v11,
Assets: []*github.ReleaseAsset{
{
Name: &asset1,
URL: &url11,
},
},
},
targetVersion: "1.1.0",
expectedAsset: asset1,
expectedVersion: "1.1.0",
expectedFound: true,
},
{
name: "find asset, no filters (2)",
rels: &github.RepositoryRelease{
Name: &rel11,
TagName: &v11,
Assets: []*github.ReleaseAsset{
{
Name: &asset11,
URL: &url11,
},
},
},
targetVersion: "1.1.0",
expectedAsset: asset11,
expectedVersion: "1.1.0",
filters: nil,
expectedFound: true,
},
{
name: "find asset, match filter",
rels: &github.RepositoryRelease{
Name: &rel11,
TagName: &v11,
Assets: []*github.ReleaseAsset{
{
Name: &asset11,
URL: &url11,
},
{
Name: &asset1,
URL: &url1,
},
},
},
targetVersion: "1.1.0",
filters: []*regexp.Regexp{regexp.MustCompile("11")},
expectedAsset: asset11,
expectedVersion: "1.1.0",
expectedFound: true,
},
{
name: "find asset, match another filter",
rels: &github.RepositoryRelease{
Name: &rel11,
TagName: &v11,
Assets: []*github.ReleaseAsset{
{
Name: &asset11,
URL: &url11,
},
{
Name: &asset1,
URL: &url1,
},
},
},
targetVersion: "1.1.0",
filters: []*regexp.Regexp{regexp.MustCompile("([^1])1{1}([^1])")},
expectedAsset: asset1,
expectedVersion: "1.1.0",
expectedFound: true,
},
{
name: "find asset, match any filter",
rels: &github.RepositoryRelease{
Name: &rel11,
TagName: &v11,
Assets: []*github.ReleaseAsset{
{
Name: &asset11,
URL: &url11,
},
{
Name: &asset2,
URL: &url2,
},
},
},
targetVersion: "1.1.0",
filters: []*regexp.Regexp{
regexp.MustCompile("([^1])1{1}([^1])"),
regexp.MustCompile("([^1])2{1}([^1])"),
},
expectedAsset: asset2,
expectedVersion: "1.1.0",
expectedFound: true,
},
{
name: "find asset, match no filter",
rels: &github.RepositoryRelease{
Name: &rel11,
TagName: &v11,
Assets: []*github.ReleaseAsset{
{
Name: &asset11,
URL: &url11,
},
{
Name: &asset2,
URL: &url2,
},
},
},
targetVersion: "1.1.0",
filters: []*regexp.Regexp{
regexp.MustCompile("another"),
regexp.MustCompile("binary"),
},
expectedFound: false,
},
} {
asset, ver, found := findAssetFromRelease(fixture.rels, []string{".gz"}, fixture.targetVersion, fixture.filters)
if fixture.expectedFound {
if !found {
t.Errorf("expected to find an asset for this fixture: %q", fixture.name)
continue
}
if asset.Name == nil {
t.Errorf("invalid asset struct returned from fixture: %q, got: %v", fixture.name, asset)
continue
}
if *asset.Name != fixture.expectedAsset {
t.Errorf("expected asset %q in fixture: %q, got: %s", fixture.expectedAsset, fixture.name, *asset.Name)
continue
}
t.Logf("asset %v, %v", asset, ver)
} else if found {
t.Errorf("expected not to find an asset for this fixture: %q, but got: %v", fixture.name, asset)
}
}
}

View File

@ -23,16 +23,16 @@ If newer version than itself is detected, it downloads released binary from GitH
There are some naming rules. Please read following links.
Naming Rules of Released Binaries:
https://github.com/rhysd/go-github-selfupdate#naming-rules-of-released-binaries
https://github.com/MedzikUser/go-github-selfupdate#naming-rules-of-released-binaries
Naming Rules of Git Tags:
https://github.com/rhysd/go-github-selfupdate#naming-rules-of-git-tags
https://github.com/MedzikUser/go-github-selfupdate#naming-rules-of-git-tags
This package is hosted on GitHub:
https://github.com/rhysd/go-github-selfupdate
https://github.com/MedzikUser/go-github-selfupdate
Small CLI tools as wrapper of this library are available also:
https://github.com/rhysd/go-github-selfupdate/cmd/detect-latest-release
https://github.com/rhysd/go-github-selfupdate/cmd/go-get-release
https://github.com/MedzikUser/go-github-selfupdate/cmd/detect-latest-release
https://github.com/MedzikUser/go-github-selfupdate/cmd/go-get-release
*/
package selfupdate

BIN
selfupdate/testdata/foo.tgz vendored Normal file

Binary file not shown.

View File

@ -10,9 +10,32 @@ import (
"io"
"io/ioutil"
"path/filepath"
"runtime"
"strings"
)
func matchExecutableName(cmd, target string) bool {
if cmd == target {
return true
}
o, a := runtime.GOOS, runtime.GOARCH
// When the contained executable name is full name (e.g. foo_darwin_amd64),
// it is also regarded as a target executable file. (#19)
for _, d := range []rune{'_', '-'} {
c := fmt.Sprintf("%s%c%s%c%s", cmd, d, o, d, a)
if o == "windows" {
c += ".exe"
}
if c == target {
return true
}
}
return false
}
func unarchiveTar(src io.Reader, url, cmd string) (io.Reader, error) {
t := tar.NewReader(src)
for {
@ -24,7 +47,7 @@ func unarchiveTar(src io.Reader, url, cmd string) (io.Reader, error) {
return nil, fmt.Errorf("Failed to unarchive .tar file: %s", err)
}
_, name := filepath.Split(h.Name)
if name == cmd {
if matchExecutableName(cmd, name) {
log.Println("Executable file", h.Name, "was found in tar archive")
return t, nil
}
@ -36,7 +59,7 @@ func unarchiveTar(src io.Reader, url, cmd string) (io.Reader, error) {
// UncompressCommand uncompresses the given source. Archive and compression format is
// automatically detected from 'url' parameter, which represents the URL of asset.
// This returns a reader for the uncompressed command given by 'cmd'. '.zip',
// '.tar.gz', '.tar.xz', '.gz' and '.xz' are supported.
// '.tar.gz', '.tar.xz', '.tgz', '.gz' and '.xz' are supported.
func UncompressCommand(src io.Reader, url, cmd string) (io.Reader, error) {
if strings.HasSuffix(url, ".zip") {
log.Println("Uncompressing zip file", url)
@ -56,14 +79,14 @@ func UncompressCommand(src io.Reader, url, cmd string) (io.Reader, error) {
for _, file := range z.File {
_, name := filepath.Split(file.Name)
if !file.FileInfo().IsDir() && name == cmd {
if !file.FileInfo().IsDir() && matchExecutableName(cmd, name) {
log.Println("Executable file", file.Name, "was found in zip archive")
return file.Open()
}
}
return nil, fmt.Errorf("File '%s' for the command is not found in %s", cmd, url)
} else if strings.HasSuffix(url, ".tar.gz") {
} else if strings.HasSuffix(url, ".tar.gz") || strings.HasSuffix(url, ".tgz") {
log.Println("Uncompressing tar.gz file", url)
gz, err := gzip.NewReader(src)
@ -81,7 +104,7 @@ func UncompressCommand(src io.Reader, url, cmd string) (io.Reader, error) {
}
name := r.Header.Name
if name != cmd {
if !matchExecutableName(cmd, name) {
return nil, fmt.Errorf("File name '%s' does not match to command '%s' found in %s", name, cmd, url)
}
@ -103,6 +126,7 @@ func UncompressCommand(src io.Reader, url, cmd string) (io.Reader, error) {
if err != nil {
return nil, fmt.Errorf("Failed to uncompress xzip file downloaded from %s: %s", url, err)
}
log.Println("Uncompressed file from xzip is assumed to be an executable", cmd)
return xzip, nil
}

View File

@ -44,6 +44,7 @@ func TestUncompress(t *testing.T) {
"testdata/single-file.gz",
"testdata/single-file.gzip",
"testdata/foo.tar.gz",
"testdata/foo.tgz",
"testdata/foo.tar.xz",
"testdata/single-file.xz",
} {

View File

@ -15,9 +15,13 @@ import (
"github.com/inconshreveable/go-update"
)
func uncompressAndUpdate(src io.Reader, assetURL, cmdPath string) error {
_, cmd := filepath.Split(cmdPath)
asset, err := UncompressCommand(src, assetURL, cmd)
func uncompressAndUpdate(src io.Reader, assetURL, cmdPath string, binaryName string) error {
if binaryName == "" {
_, binaryName = filepath.Split(cmdPath)
} else if runtime.GOOS == "windows" {
binaryName += ".exe"
}
asset, err := UncompressCommand(src, assetURL, binaryName)
if err != nil {
return err
}
@ -56,7 +60,8 @@ func (up *Updater) downloadDirectlyFromURL(assetURL string) (io.ReadCloser, erro
// It downloads a release asset via GitHub Releases API so this function is available for update releases on private repository.
// If a redirect occurs, it fallbacks into directly downloading from the redirect URL.
func (up *Updater) UpdateTo(rel *Release, cmdPath string) error {
src, redirectURL, err := up.api.Repositories.DownloadReleaseAsset(up.apiCtx, rel.RepoOwner, rel.RepoName, rel.AssetID)
var client http.Client
src, redirectURL, err := up.api.Repositories.DownloadReleaseAsset(up.apiCtx, rel.RepoOwner, rel.RepoName, rel.AssetID, &client)
if err != nil {
return fmt.Errorf("Failed to call GitHub Releases API for getting an asset(ID: %d) for repository '%s/%s': %s", rel.AssetID, rel.RepoOwner, rel.RepoName, err)
}
@ -75,10 +80,10 @@ func (up *Updater) UpdateTo(rel *Release, cmdPath string) error {
}
if up.validator == nil {
return uncompressAndUpdate(bytes.NewReader(data), rel.AssetURL, cmdPath)
return uncompressAndUpdate(bytes.NewReader(data), rel.AssetURL, cmdPath, up.binaryName)
}
validationSrc, validationRedirectURL, err := up.api.Repositories.DownloadReleaseAsset(up.apiCtx, rel.RepoOwner, rel.RepoName, rel.ValidationAssetID)
validationSrc, validationRedirectURL, err := up.api.Repositories.DownloadReleaseAsset(up.apiCtx, rel.RepoOwner, rel.RepoName, rel.ValidationAssetID, &client)
if err != nil {
return fmt.Errorf("Failed to call GitHub Releases API for getting an validation asset(ID: %d) for repository '%s/%s': %s", rel.ValidationAssetID, rel.RepoOwner, rel.RepoName, err)
}
@ -101,7 +106,7 @@ func (up *Updater) UpdateTo(rel *Release, cmdPath string) error {
return fmt.Errorf("Failed validating asset content: %v", err)
}
return uncompressAndUpdate(bytes.NewReader(data), rel.AssetURL, cmdPath)
return uncompressAndUpdate(bytes.NewReader(data), rel.AssetURL, cmdPath, up.binaryName)
}
// UpdateCommand updates a given command binary to the latest version.
@ -164,7 +169,7 @@ func UpdateTo(assetURL, cmdPath string) error {
return err
}
defer src.Close()
return uncompressAndUpdate(src, assetURL, cmdPath)
return uncompressAndUpdate(src, assetURL, cmdPath, up.binaryName)
}
// UpdateCommand updates a given command binary to the latest version.

View File

@ -1,25 +1,44 @@
package selfupdate
import (
"github.com/blang/semver"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/blang/semver"
)
func setupTestBinary() {
if err := exec.Command("go", "build", "./testdata/github-release-test/").Run(); err != nil {
func setupTestBinary(name ...string) {
var options []string
var output string
if len(name) == 0 {
options = []string{"build", "./testdata/github-release-test/"}
} else {
output = name[0]
if runtime.GOOS == "windows" {
output += ".exe"
}
options = []string{"build", "-o", output, "./testdata/github-release-test/"}
}
if err := exec.Command("go", options...).Run(); err != nil {
panic(err)
}
}
func teardownTestBinary() {
bin := "github-release-test"
func teardownTestBinary(name ...string) {
var bin string
if len(name) == 0 {
bin = "github-release-test"
} else {
bin = name[0]
}
if runtime.GOOS == "windows" {
bin = "github-release-test.exe"
bin += ".exe"
}
if err := os.Remove(bin); err != nil {
panic(err)
@ -63,6 +82,41 @@ func TestUpdateCommand(t *testing.T) {
}
}
func TestUpdateWithDifferentBinaryName(t *testing.T) {
setupTestBinary("gh-release-test")
defer teardownTestBinary("gh-release-test")
latest := semver.MustParse("1.2.3")
prev := semver.MustParse("1.2.2")
_, err := UpdateCommand("gh-release-test", prev, "rhysd-test/test-release-zip")
if err == nil {
t.Fatal("Error should occur for broken package")
}
if !strings.Contains(err.Error(), "the command is not found") {
t.Fatal("Unexpected error:", err)
}
up, err := NewUpdater(Config{BinaryName: "github-release-test", Filters: []string{"github-release-test"}})
if err != nil {
t.Fatal(err)
}
rel, err := up.UpdateCommand("gh-release-test", prev, "rhysd-test/test-release-zip")
if err != nil {
t.Fatal(err)
}
if rel.Version.NE(latest) {
t.Error("Version is not latest", rel.Version)
}
bytes, err := exec.Command(filepath.FromSlash("./gh-release-test")).Output()
if err != nil {
t.Fatal("Failed to run test binary after update:", err)
}
out := string(bytes)
if out != "v1.2.3\n" {
t.Error("Output from test binary after update is unexpected:", out)
}
}
func TestUpdateViaSymlink(t *testing.T) {
if testing.Short() {
t.Skip("skip tests in short mode.")

View File

@ -2,10 +2,12 @@ package selfupdate
import (
"context"
"fmt"
"net/http"
"os"
"regexp"
"github.com/google/go-github/github"
"github.com/google/go-github/v30/github"
gitconfig "github.com/tcnksm/go-gitconfig"
"golang.org/x/oauth2"
)
@ -13,9 +15,11 @@ import (
// Updater is responsible for managing the context of self-update.
// It contains GitHub client and its context.
type Updater struct {
api *github.Client
apiCtx context.Context
validator Validator
api *github.Client
apiCtx context.Context
validator Validator
filters []*regexp.Regexp
binaryName string
}
// Config represents the configuration of self-update.
@ -30,6 +34,14 @@ type Config struct {
EnterpriseUploadURL string
// Validator represents types which enable additional validation of downloaded release.
Validator Validator
// Filters are regexp used to filter on specific assets for releases with multiple assets.
// An asset is selected if it matches any of those, in addition to the regular tag, os, arch, extensions.
// Please make sure that your filter(s) uniquely match an asset.
Filters []string
// BinaryName represents the name of the binary extracted from the archive downloaded from GitHub.
// If unset, the current executable's name will be used to match.
BinaryName string
}
func newHTTPClient(ctx context.Context, token string) *http.Client {
@ -53,9 +65,18 @@ func NewUpdater(config Config) (*Updater, error) {
ctx := context.Background()
hc := newHTTPClient(ctx, token)
filtersRe := make([]*regexp.Regexp, 0, len(config.Filters))
for _, filter := range config.Filters {
re, err := regexp.Compile(filter)
if err != nil {
return nil, fmt.Errorf("Could not compile regular expression %q for filtering releases: %v", filter, err)
}
filtersRe = append(filtersRe, re)
}
if config.EnterpriseBaseURL == "" {
client := github.NewClient(hc)
return &Updater{client, ctx, config.Validator}, nil
return &Updater{api: client, apiCtx: ctx, validator: config.Validator, filters: filtersRe, binaryName: config.BinaryName}, nil
}
u := config.EnterpriseUploadURL
@ -66,7 +87,8 @@ func NewUpdater(config Config) (*Updater, error) {
if err != nil {
return nil, err
}
return &Updater{api: client, apiCtx: ctx, validator: config.Validator}, nil
return &Updater{api: client, apiCtx: ctx, validator: config.Validator, filters: filtersRe, binaryName: config.BinaryName}, nil
}
// DefaultUpdater creates a new updater instance with default configuration.

View File

@ -2,6 +2,7 @@ package selfupdate
import (
"os"
"strings"
"testing"
)
@ -67,3 +68,39 @@ func TestGitHubEnterpriseClientInvalidURL(t *testing.T) {
t.Fatal("Invalid URL should raise an error")
}
}
func TestCompileRegexForFiltering(t *testing.T) {
filters := []string{
"^hello$",
"^(\\d\\.)+\\d$",
}
up, err := NewUpdater(Config{
Filters: filters,
})
if err != nil {
t.Fatal(err)
}
if len(up.filters) != 2 {
t.Fatalf("Wanted 2 regexes but got %d", len(up.filters))
}
for i, r := range up.filters {
want := filters[i]
got := r.String()
if want != got {
t.Errorf("Compiled regex is %q but specified was %q", got, want)
}
}
}
func TestFilterRegexIsBroken(t *testing.T) {
_, err := NewUpdater(Config{
Filters: []string{"(foo"},
})
if err == nil {
t.Fatal("Error unexpectedly did not occur")
}
msg := err.Error()
if !strings.Contains(msg, "Could not compile regular expression \"(foo\" for filtering releases") {
t.Fatalf("Error message is unexpected: %q", msg)
}
}

View File

@ -46,7 +46,7 @@ type ECDSAValidator struct {
}
// Validate validates the ECDSA signature the release against the signature
// contained in an addtional asset file.
// contained in an additional asset file.
// additional asset file.
func (v *ECDSAValidator) Validate(input, signature []byte) error {
h := sha256.New()

View File

@ -112,3 +112,25 @@ func TestECDSAValidatorFail(t *testing.T) {
t.Fatal(err)
}
}
func TestValidatorSuffix(t *testing.T) {
for _, test := range []struct {
v Validator
suffix string
}{
{
v: &SHA2Validator{},
suffix: ".sha256",
},
{
v: &ECDSAValidator{},
suffix: ".sig",
},
} {
want := test.suffix
got := test.v.Suffix()
if want != got {
t.Errorf("Wanted %q but got %q", want, got)
}
}
}