diff --git a/selfupdate/detect.go b/selfupdate/detect.go index b302eaf..b625aa6 100644 --- a/selfupdate/detect.go +++ b/selfupdate/detect.go @@ -92,10 +92,13 @@ func (up *Updater) DetectLatest(slug string) (release *Release, found bool, err release = &Release{ AssetURL: url, AssetByteSize: asset.GetSize(), + AssetID: asset.GetID(), URL: rel.GetHTMLURL(), ReleaseNotes: rel.GetBody(), Name: rel.GetName(), PublishedAt: &publishedAt, + RepoOwner: repo[0], + RepoName: repo[1], } release.Version, err = semver.Make(tag) diff --git a/selfupdate/detect_test.go b/selfupdate/detect_test.go index e044382..38aef2c 100644 --- a/selfupdate/detect_test.go +++ b/selfupdate/detect_test.go @@ -36,9 +36,18 @@ func TestDetectReleaseWithVersionPrefix(t *testing.T) { if r.AssetByteSize == 0 { t.Error("Asset's size is unexpectedly zero") } + if r.AssetID == 0 { + t.Error("Asset's ID is unexpectedly zero") + } if r.PublishedAt.IsZero() { 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) { @@ -82,9 +91,18 @@ func TestDetectReleasesForVariousArchives(t *testing.T) { if r.AssetByteSize == 0 { t.Error("Asset's size is unexpectedly zero") } + if r.AssetID == 0 { + t.Error("Asset's ID is unexpectedly zero") + } if r.PublishedAt.IsZero() { 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) + } }) } } diff --git a/selfupdate/release.go b/selfupdate/release.go index b8bcdff..7515250 100644 --- a/selfupdate/release.go +++ b/selfupdate/release.go @@ -13,6 +13,8 @@ type Release struct { AssetURL string // AssetSize represents the size of asset in bytes AssetByteSize int + // AssetID is the ID of the asset on GitHub + AssetID int // URL is a URL to release page for browsing URL string // ReleaseNotes is a release notes of the release @@ -21,4 +23,8 @@ type Release struct { Name string // PublishedAt is the time when the release was published 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 } diff --git a/selfupdate/update.go b/selfupdate/update.go index 5814898..dfd2bc5 100644 --- a/selfupdate/update.go +++ b/selfupdate/update.go @@ -2,6 +2,7 @@ package selfupdate import ( "fmt" + "io" "net/http" "os" "path/filepath" @@ -12,10 +13,26 @@ import ( "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 (up *Updater) UpdateTo(assetURL, cmdPath string) error { - // TODO: Use GitHub API client to download assets +func readAndUpdate(src io.ReadCloser, assetURL, cmdPath string) error { + defer src.Close() + _, 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) if err != nil { 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) } - defer res.Body.Close() - _, cmd := filepath.Split(cmdPath) - asset, err := UncompressCommand(res.Body, assetURL, cmd) + return readAndUpdate(res.Body, assetURL, cmdPath) +} + +// 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 { return err } - - log.Println("Will update", cmdPath, "to the latest downloaded from", assetURL) - return update.Apply(asset, update.Options{ - TargetPath: cmdPath, - }) + if redirectURL != "" { + 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 UpdateTo(redirectURL, cmdPath) + } + return readAndUpdate(src, rel.AssetURL, cmdPath) } // 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 } 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 rel, nil diff --git a/selfupdate/update_test.go b/selfupdate/update_test.go index a6a8b05..68b1326 100644 --- a/selfupdate/update_test.go +++ b/selfupdate/update_test.go @@ -237,7 +237,7 @@ func TestInvalidSlugForUpdate(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 { t.Fatal("Error should occur for URL not found") } @@ -248,7 +248,7 @@ func TestInvalidAssetURL(t *testing.T) { func TestBrokenAsset(t *testing.T) { 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 { t.Fatal("Error should occur for URL not found") }