download an asset via GitHub Releases API by default

This commit is contained in:
rhysd 2018-01-19 12:05:41 +09:00
parent fd492d6b01
commit 6b4eeadeb7
5 changed files with 63 additions and 14 deletions

View File

@ -92,10 +92,13 @@ func (up *Updater) DetectLatest(slug string) (release *Release, found bool, err
release = &Release{ release = &Release{
AssetURL: url, AssetURL: url,
AssetByteSize: asset.GetSize(), AssetByteSize: asset.GetSize(),
AssetID: asset.GetID(),
URL: rel.GetHTMLURL(), URL: rel.GetHTMLURL(),
ReleaseNotes: rel.GetBody(), ReleaseNotes: rel.GetBody(),
Name: rel.GetName(), Name: rel.GetName(),
PublishedAt: &publishedAt, PublishedAt: &publishedAt,
RepoOwner: repo[0],
RepoName: repo[1],
} }
release.Version, err = semver.Make(tag) release.Version, err = semver.Make(tag)

View File

@ -36,9 +36,18 @@ func TestDetectReleaseWithVersionPrefix(t *testing.T) {
if r.AssetByteSize == 0 { if r.AssetByteSize == 0 {
t.Error("Asset's size is unexpectedly zero") t.Error("Asset's size is unexpectedly zero")
} }
if r.AssetID == 0 {
t.Error("Asset's ID is unexpectedly zero")
}
if r.PublishedAt.IsZero() { if r.PublishedAt.IsZero() {
t.Error("Release time is unexpectedly zero") t.Error("Release time is unexpectedly zero")
} }
if r.RepoOwner != "rhysd" {
t.Error("Repo owner is not correct:", r.RepoOwner)
}
if r.RepoName != "github-clone-all" {
t.Error("Repo name was not properly detectd:", r.RepoName)
}
} }
func TestDetectReleasesForVariousArchives(t *testing.T) { func TestDetectReleasesForVariousArchives(t *testing.T) {
@ -82,9 +91,18 @@ func TestDetectReleasesForVariousArchives(t *testing.T) {
if r.AssetByteSize == 0 { if r.AssetByteSize == 0 {
t.Error("Asset's size is unexpectedly zero") t.Error("Asset's size is unexpectedly zero")
} }
if r.AssetID == 0 {
t.Error("Asset's ID is unexpectedly zero")
}
if r.PublishedAt.IsZero() { if r.PublishedAt.IsZero() {
t.Error("Release time is unexpectedly zero") t.Error("Release time is unexpectedly zero")
} }
if r.RepoOwner != "rhysd-test" {
t.Error("Repo owner should be rhysd-test:", r.RepoOwner)
}
if !strings.HasPrefix(r.RepoName, "test-release-") {
t.Error("Repo name was not properly detectd:", r.RepoName)
}
}) })
} }
} }

View File

@ -13,6 +13,8 @@ type Release struct {
AssetURL string AssetURL string
// AssetSize represents the size of asset in bytes // AssetSize represents the size of asset in bytes
AssetByteSize int AssetByteSize int
// AssetID is the ID of the asset on GitHub
AssetID int
// URL is a URL to release page for browsing // URL is a URL to release page for browsing
URL string URL string
// ReleaseNotes is a release notes of the release // ReleaseNotes is a release notes of the release
@ -21,4 +23,8 @@ type Release struct {
Name string Name string
// PublishedAt is the time when the release was published // PublishedAt is the time when the release was published
PublishedAt *time.Time PublishedAt *time.Time
// RepoOwner is the owner of the repository of the release
RepoOwner string
// RepoName is the name of the repository of the release
RepoName string
} }

View File

@ -2,6 +2,7 @@ package selfupdate
import ( import (
"fmt" "fmt"
"io"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -12,10 +13,26 @@ import (
"github.com/inconshreveable/go-update" "github.com/inconshreveable/go-update"
) )
// UpdateTo download an executable from assetURL and replace the current binary with the downloaded one. cmdPath is a file path to command executable. func readAndUpdate(src io.ReadCloser, assetURL, cmdPath string) error {
func (up *Updater) UpdateTo(assetURL, cmdPath string) error { defer src.Close()
// TODO: Use GitHub API client to download assets
_, cmd := filepath.Split(cmdPath)
asset, err := UncompressCommand(src, assetURL, cmd)
if err != nil {
return err
}
log.Println("Will update", cmdPath, "to the latest downloaded from", assetURL)
return update.Apply(asset, update.Options{
TargetPath: cmdPath,
})
}
// UpdateTo downloads an executable from assetURL and replace the current binary with the downloaded one.
// This function is low-level API to update the binary. Because it does not use GitHub API and downloads asset directly from the URL via HTTP,
// this function is not available to update a release for private repositories.
// cmdPath is a file path to command executable.
func UpdateTo(assetURL, cmdPath string) error {
res, err := http.Get(assetURL) res, err := http.Get(assetURL)
if err != nil { if err != nil {
return fmt.Errorf("Failed to download a release file from %s: %s", assetURL, err) return fmt.Errorf("Failed to download a release file from %s: %s", assetURL, err)
@ -25,17 +42,22 @@ func (up *Updater) UpdateTo(assetURL, cmdPath string) error {
return fmt.Errorf("Failed to download a release file from %s", assetURL) return fmt.Errorf("Failed to download a release file from %s", assetURL)
} }
defer res.Body.Close() return readAndUpdate(res.Body, assetURL, cmdPath)
_, cmd := filepath.Split(cmdPath) }
asset, err := UncompressCommand(res.Body, assetURL, cmd)
// UpdateTo downloads an executable from GitHub Releases API and replace current binary with the downloaded one.
// 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)
if err != nil { if err != nil {
return err return err
} }
if redirectURL != "" {
log.Println("Will update", cmdPath, "to the latest downloaded from", assetURL) log.Println("Redirect URL was returned while trying to download a release asset from GitHub API. Falling back to downloading from asset URL directly:", redirectURL)
return update.Apply(asset, update.Options{ return UpdateTo(redirectURL, cmdPath)
TargetPath: cmdPath, }
}) return readAndUpdate(src, rel.AssetURL, cmdPath)
} }
// UpdateCommand updates a given command binary to the latest version. // UpdateCommand updates a given command binary to the latest version.
@ -71,7 +93,7 @@ func (up *Updater) UpdateCommand(cmdPath string, current semver.Version, slug st
return rel, nil return rel, nil
} }
log.Println("Will update", cmdPath, "to the latest version", rel.Version) log.Println("Will update", cmdPath, "to the latest version", rel.Version)
if err := up.UpdateTo(rel.AssetURL, cmdPath); err != nil { if err := up.UpdateTo(rel, cmdPath); err != nil {
return nil, err return nil, err
} }
return rel, nil return rel, nil

View File

@ -237,7 +237,7 @@ func TestInvalidSlugForUpdate(t *testing.T) {
} }
func TestInvalidAssetURL(t *testing.T) { func TestInvalidAssetURL(t *testing.T) {
err := NewUpdater(Config{}).UpdateTo("https://github.com/rhysd/non-existing-repo/releases/download/v1.2.3/foo.zip", "foo") err := UpdateTo("https://github.com/rhysd/non-existing-repo/releases/download/v1.2.3/foo.zip", "foo")
if err == nil { if err == nil {
t.Fatal("Error should occur for URL not found") t.Fatal("Error should occur for URL not found")
} }
@ -248,7 +248,7 @@ func TestInvalidAssetURL(t *testing.T) {
func TestBrokenAsset(t *testing.T) { func TestBrokenAsset(t *testing.T) {
asset := "https://github.com/rhysd-test/test-incorrect-release/releases/download/invalid/broken-zip.zip" asset := "https://github.com/rhysd-test/test-incorrect-release/releases/download/invalid/broken-zip.zip"
err := NewUpdater(Config{}).UpdateTo(asset, "foo") err := UpdateTo(asset, "foo")
if err == nil { if err == nil {
t.Fatal("Error should occur for URL not found") t.Fatal("Error should occur for URL not found")
} }