golang的私有化定制自动更新插件
zanbin168
2023-05-16 086cd572479fb818542695a0ec0368234e5e1c0d
update
1个文件已修改
9个文件已添加
786 ■■■■■ 已修改文件
README.md 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
_example/latest/latest.go 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
_example/specific/specific.go 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
go.mod 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
go.sum 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
progress/progress.go 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
store.go 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
stores/apex/apex.go 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
stores/github/github.go 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
update.go 232 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md
@@ -1,2 +1,12 @@
# goupdate
Forked and adapted from https://github.com/tj/go-update
## go-update
Package update provides tooling to auto-update binary releases
from GitHub based on the user's current version and operating system. Used by command-line tools such as [Up](https://github.com/apex/up) and [Apex](https://github.com/apex/apex).
---
[![GoDoc](https://godoc.org/github.com/zan8in/go-update?status.svg)](https://godoc.org/github.com/zan8in/go-update)
![](https://img.shields.io/badge/license-MIT-blue.svg)
![](https://img.shields.io/badge/status-stable-green.svg)
<a href="https://apex.sh"><img src="http://tjholowaychuk.com:6000/svg/sponsor"></a>
_example/latest/latest.go
New file
@@ -0,0 +1,70 @@
package main
import (
    "fmt"
    "runtime"
    "github.com/apex/log"
    "github.com/kierdavis/ansi"
    "github.com/zan8in/go-update"
    "github.com/zan8in/go-update/progress"
    "github.com/zan8in/go-update/stores/github"
)
func init() {
    log.SetLevel(log.DebugLevel)
}
func main() {
    ansi.HideCursor()
    defer ansi.ShowCursor()
    // update polls(1) from tj/gh-polls on github
    m := &update.Manager{
        Command: "polls",
        Store: &github.Store{
            Owner:   "tj",
            Repo:    "gh-polls",
            Version: "0.0.3",
        },
    }
    // fetch the new releases
    releases, err := m.LatestReleases()
    if err != nil {
        log.Fatalf("error fetching releases: %s", err)
    }
    // no updates
    if len(releases) == 0 {
        log.Info("no updates")
        return
    }
    // latest release
    latest := releases[0]
    // find the tarball for this system
    a := latest.FindTarball(runtime.GOOS, runtime.GOARCH)
    if a == nil {
        log.Info("no binary for your system")
        return
    }
    // whitespace
    fmt.Println()
    // download tarball to a tmp dir
    tarball, err := a.DownloadProxy(progress.Reader)
    if err != nil {
        log.Fatalf("error downloading: %s", err)
    }
    // install it
    if err := m.Install(tarball); err != nil {
        log.Fatalf("error installing: %s", err)
    }
    fmt.Printf("Updated to %s\n", latest.Version)
}
_example/specific/specific.go
New file
@@ -0,0 +1,61 @@
package main
import (
    "fmt"
    "runtime"
    "github.com/apex/log"
    "github.com/kierdavis/ansi"
    "github.com/zan8in/go-update"
    "github.com/zan8in/go-update/progress"
    "github.com/zan8in/go-update/stores/github"
)
func init() {
    log.SetLevel(log.DebugLevel)
}
func main() {
    ansi.HideCursor()
    defer ansi.ShowCursor()
    // update polls(1) from tj/gh-polls on github
    m := &update.Manager{
        Command: "up",
        Store: &github.Store{
            Owner:   "apex",
            Repo:    "up",
            Version: "0.4.6",
        },
    }
    // fetch the target release
    release, err := m.GetRelease("0.4.5")
    if err != nil {
        log.Fatalf("error fetching release: %s", err)
    }
    // find the tarball for this system
    a := release.FindTarball(runtime.GOOS, runtime.GOARCH)
    if a == nil {
        log.Info("no binary for your system")
        return
    }
    // whitespace
    fmt.Println()
    // download tarball to a tmp dir
    tarball, err := a.DownloadProxy(progress.Reader)
    if err != nil {
        log.Fatalf("error downloading: %s", err)
    }
    // install it
    if err := m.Install(tarball); err != nil {
        log.Fatalf("error installing: %s", err)
    }
    fmt.Printf("Updated to %s\n", release.Version)
}
go.mod
New file
@@ -0,0 +1,14 @@
module github.com/zan8in/go-update
go 1.16
require (
    github.com/apex/log v1.9.0
    github.com/c4milo/unpackit v1.0.0
    github.com/google/go-github v17.0.0+incompatible
    github.com/google/go-querystring v1.1.0 // indirect
    github.com/gosuri/uilive v0.0.4 // indirect
    github.com/gosuri/uiprogress v0.0.1
    github.com/pkg/errors v0.9.1
    github.com/tj/go v1.8.7
)
go.sum
New file
@@ -0,0 +1,123 @@
github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs=
github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0=
github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA=
github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys=
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37/go.mod h1:u9UyCz2eTrSGy6fbupqJ54eY5c4IC8gREQ1053dK12U=
github.com/c4milo/unpackit v1.0.0 h1:Umce1lwtFvEHNFQev+xENObYiiYxdSmKhvGlkcufUGE=
github.com/c4milo/unpackit v1.0.0/go.mod h1:0cXRaRz5pMcJm7o9jYQmPAeBl6y1na9BKy3K+og0UJY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY=
github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI=
github.com/gosuri/uiprogress v0.0.1 h1:0kpv/XY/qTmFWl/SkaJykZXrBBzwwadmW8fRb7RJSxw=
github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0=
github.com/hooklift/assert v0.1.0 h1:UZzFxx5dSb9aBtvMHTtnPuvFnBvcEhHTPb9+0+jpEjs=
github.com/hooklift/assert v0.1.0/go.mod h1:pfexfvIHnKCdjh6CkkIZv5ic6dQ6aU2jhKghBlXuwwY=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stripe/stripe-go v70.15.0+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
github.com/tj/assert v0.0.1/go.mod h1:lsg+GHQ0XplTcWKGxFLf/XPcPxWO8x2ut5jminoR2rA=
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
github.com/tj/go v1.8.7 h1:a7M1Xo+QKmlUHEzZj2LX0LHqkh7/LpOa6Or8luBvY/c=
github.com/tj/go v1.8.7/go.mod h1:88DQADQo0c0fHmWNcr88pIGUHlV5du8aGtON+S1jr5A=
github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj52Uc=
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
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/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200602174320-3e3e88ca92fa/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
progress/progress.go
New file
@@ -0,0 +1,47 @@
// Package progress provides a proxy for download progress.
package progress
import (
    "io"
    "time"
    pb "github.com/gosuri/uiprogress"
)
// TODO: refactor to just check EOF
// reader wrapping a progress bar.
type reader struct {
    bars    *pb.Progress
    r       io.ReadCloser
    written int
}
// Read implementation.
func (r *reader) Read(b []byte) (int, error) {
    n, err := r.r.Read(b)
    r.written += n
    r.bars.Bars[0].Set(r.written)
    return n, err
}
// Close implementation.
func (r *reader) Close() error {
    r.bars.Stop()
    return r.r.Close()
}
// Reader returns a progress bar reader.
func Reader(size int, r io.ReadCloser) io.ReadCloser {
    bars := pb.New()
    bars.Width = 50
    bars.AddBar(size)
    bars.Start()
    bars.SetRefreshInterval(50 * time.Millisecond)
    bars.Bars[0].AppendCompleted()
    return &reader{
        bars: bars,
        r:    r,
    }
}
store.go
New file
@@ -0,0 +1,18 @@
package update
import "github.com/pkg/errors"
// TODO: the platform resolution should also be
// in the interface...
// Errors.
var (
    // ErrNotFound is returned from GetRelease if the release is not found.
    ErrNotFound = errors.New("release not found")
)
// Store is the interface used for listing and fetching releases.
type Store interface {
    GetRelease(version string) (*Release, error)
    LatestReleases() ([]*Release, error)
}
stores/apex/apex.go
New file
@@ -0,0 +1,124 @@
// Package apex provides an Apex release store.
package apex
import (
    "encoding/json"
    "fmt"
    "net/http"
    "time"
    "github.com/pkg/errors"
    "github.com/tj/go/http/request"
    "github.com/zan8in/go-update"
)
// Store is the store implementation.
type Store struct {
    URL       string
    Product   string
    Plan      string
    Version   string
    AccessKey string
}
// Release model.
type Release struct {
    Version   string    `json:"version"`
    Notes     string    `json:"notes"`
    Files     []*File   `json:"files"`
    CreatedAt time.Time `json:"created_at"`
}
// File model.
type File struct {
    Name string `json:"name"`
    Key  string `json:"key"`
    Size int64  `json:"size"`
    URL  string `json:"url"`
}
// GetRelease returns the specified release or ErrNotFound.
func (s *Store) GetRelease(version string) (*update.Release, error) {
    releases, err := s.releases()
    if err != nil {
        return nil, err
    }
    for _, r := range releases {
        if r.Version == version {
            return r, nil
        }
    }
    return nil, update.ErrNotFound
}
// LatestReleases returns releases newer than Version, or nil.
func (s *Store) LatestReleases() (latest []*update.Release, err error) {
    releases, err := s.releases()
    if err != nil {
        return
    }
    for _, r := range releases {
        if r.Version == s.Version {
            break
        }
        latest = append(latest, r)
    }
    return
}
// releases returns all releases.
func (s *Store) releases() (all []*update.Release, err error) {
    url := fmt.Sprintf("%s/%s/%s", s.URL, s.Product, s.Plan)
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return nil, errors.Wrap(err, "creating request")
    }
    req.Header.Set("Authorization", "Bearer "+s.AccessKey)
    res, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, errors.Wrap(err, "requesting")
    }
    defer res.Body.Close()
    if res.StatusCode >= 400 {
        return nil, request.Error(res.StatusCode)
    }
    var releases []*Release
    if err := json.NewDecoder(res.Body).Decode(&releases); err != nil {
        return nil, errors.Wrap(err, "unmarshaling")
    }
    for _, r := range releases {
        all = append(all, toRelease(r))
    }
    return
}
// toRelease returns a Release.
func toRelease(r *Release) *update.Release {
    out := &update.Release{
        Version:     r.Version,
        Notes:       r.Notes,
        PublishedAt: r.CreatedAt,
    }
    for _, f := range r.Files {
        out.Assets = append(out.Assets, &update.Asset{
            Name: f.Name,
            Size: int(f.Size),
            URL:  f.URL,
        })
    }
    return out
}
stores/github/github.go
New file
@@ -0,0 +1,83 @@
// Package github provides a GitHub release store.
package github
import (
    "context"
    "time"
    "github.com/google/go-github/github"
    "github.com/zan8in/go-update"
)
// Store is the store implementation.
type Store struct {
    Owner   string
    Repo    string
    Version string
}
// GetRelease returns the specified release or ErrNotFound.
func (s *Store) GetRelease(version string) (*update.Release, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    gh := github.NewClient(nil)
    r, res, err := gh.Repositories.GetReleaseByTag(ctx, s.Owner, s.Repo, "v"+version)
    if res.StatusCode == 404 {
        return nil, update.ErrNotFound
    }
    if err != nil {
        return nil, err
    }
    return githubRelease(r), nil
}
// LatestReleases returns releases newer than Version, or nil.
func (s *Store) LatestReleases() (latest []*update.Release, err error) {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    gh := github.NewClient(nil)
    releases, _, err := gh.Repositories.ListReleases(ctx, s.Owner, s.Repo, nil)
    if err != nil {
        return nil, err
    }
    for _, r := range releases {
        tag := r.GetTagName()
        if tag == s.Version || "v"+s.Version == tag {
            break
        }
        latest = append(latest, githubRelease(r))
    }
    return
}
// githubRelease returns a Release.
func githubRelease(r *github.RepositoryRelease) *update.Release {
    out := &update.Release{
        Version:     r.GetTagName(),
        Notes:       r.GetBody(),
        PublishedAt: r.GetPublishedAt().Time,
        URL:         r.GetURL(),
    }
    for _, a := range r.Assets {
        out.Assets = append(out.Assets, &update.Asset{
            Name:      a.GetName(),
            Size:      a.GetSize(),
            URL:       a.GetBrowserDownloadURL(),
            Downloads: a.GetDownloadCount(),
        })
    }
    return out
}
update.go
New file
@@ -0,0 +1,232 @@
// Package update provides tooling to auto-update binary releases
// from GitHub based on the user's current version and operating system.
package update
import (
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "os"
    "path/filepath"
    "runtime"
    "strconv"
    "strings"
    "time"
    "github.com/apex/log"
    "github.com/c4milo/unpackit"
    "github.com/pkg/errors"
)
// Proxy is used to proxy a reader, for example
// using https://github.com/cheggaaa/pb to provide
// progress updates.
type Proxy func(int, io.ReadCloser) io.ReadCloser
// NopProxy does nothing.
var NopProxy = func(size int, r io.ReadCloser) io.ReadCloser {
    return r
}
// Manager is the update manager.
type Manager struct {
    Store          // Store for releases such as Github or a custom private store.
    Command string // Command is the executable's name.
}
// Release represents a project release.
type Release struct {
    Version     string    // Version is the release version.
    Notes       string    // Notes is the markdown release notes.
    URL         string    // URL is the notes url.
    PublishedAt time.Time // PublishedAt is the publish time.
    Assets      []*Asset  // Assets is the release assets.
}
// Asset represents a project release asset.
type Asset struct {
    Name      string // Name of the asset.
    Size      int    // Size of the asset.
    URL       string // URL of the asset.
    Downloads int    // Downloads count.
}
// InstallTo binary to the given dir.
func (m *Manager) InstallTo(path, dir string) error {
    log.Debugf("unpacking %q", path)
    f, err := os.Open(path)
    if err != nil {
        return errors.Wrap(err, "opening tarball")
    }
    err = unpackit.Unpack(f, "")
    if err != nil {
        f.Close()
        return errors.Wrap(err, "unpacking tarball")
    }
    if err := f.Close(); err != nil {
        return errors.Wrap(err, "closing tarball")
    }
    bin := filepath.Join(path, m.Command)
    if err := os.Chmod(bin, 0755); err != nil {
        return errors.Wrap(err, "chmod")
    }
    dst := filepath.Join(dir, m.Command)
    tmp := dst + ".tmp"
    log.Debugf("copy %q to %q", bin, tmp)
    if err := copyFile(tmp, bin); err != nil {
        return errors.Wrap(err, "copying")
    }
    if runtime.GOOS == "windows" {
        old := dst + ".old"
        log.Debugf("windows workaround renaming %q to %q", dst, old)
        if err := os.Rename(dst, old); err != nil {
            return errors.Wrap(err, "windows renaming")
        }
    }
    log.Debugf("renaming %q to %q", tmp, dst)
    if err := os.Rename(tmp, dst); err != nil {
        return errors.Wrap(err, "renaming")
    }
    return nil
}
// Install binary to replace the current version.
func (m *Manager) Install(path string) error {
    bin, err := os.Executable()
    if err != nil {
        return errors.Wrapf(err, "looking up path of %q", m.Command)
    }
    dir := filepath.Dir(bin)
    return m.InstallTo(path, dir)
}
// FindTarball returns a tarball matching os and arch, or nil.
func (r *Release) FindTarball(os, arch string) *Asset {
    s := fmt.Sprintf("%s_%s", os, arch)
    for _, a := range r.Assets {
        ext := filepath.Ext(a.Name)
        if strings.Contains(a.Name, s) && ext == ".gz" {
            return a
        }
    }
    return nil
}
// FindZip returns a zipfile matching os and arch, or nil.
func (r *Release) FindZip(os, arch string) *Asset {
    s := fmt.Sprintf("%s_%s", os, arch)
    for _, a := range r.Assets {
        ext := filepath.Ext(a.Name)
        if strings.Contains(a.Name, s) && ext == ".zip" {
            return a
        }
    }
    return nil
}
// Download the asset to a tmp directory and return its path.
func (a *Asset) Download() (string, error) {
    return a.DownloadProxy(NopProxy)
}
// DownloadProxy the asset to a tmp directory and return its path.
func (a *Asset) DownloadProxy(proxy Proxy) (string, error) {
    f, err := ioutil.TempFile(os.TempDir(), "update-")
    if err != nil {
        return "", errors.Wrap(err, "creating temp file")
    }
    log.Debugf("fetch %q", a.URL)
    res, err := http.Get(a.URL)
    if err != nil {
        return "", errors.Wrap(err, "fetching asset")
    }
    kind := res.Header.Get("Content-Type")
    size, _ := strconv.Atoi(res.Header.Get("Content-Length"))
    log.Debugf("response %s – %s (%d KiB)", res.Status, kind, size/1024)
    body := proxy(size, res.Body)
    if res.StatusCode >= 400 {
        body.Close()
        return "", errors.Wrap(err, res.Status)
    }
    log.Debugf("copy to %q", f.Name())
    if _, err := io.Copy(f, body); err != nil {
        body.Close()
        return "", errors.Wrap(err, "copying body")
    }
    if err := body.Close(); err != nil {
        return "", errors.Wrap(err, "closing body")
    }
    if err := f.Close(); err != nil {
        return "", errors.Wrap(err, "closing file")
    }
    log.Debugf("copied")
    return f.Name(), nil
}
// copyFile copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it's contents will be replaced by the contents
// of the source file. The file mode will be copied from the source and
// the copied data is synced/flushed to stable storage.
func copyFile(dst, src string) (err error) {
    in, err := os.Open(src)
    if err != nil {
        return
    }
    defer in.Close()
    out, err := os.Create(dst)
    if err != nil {
        return
    }
    defer func() {
        if e := out.Close(); e != nil {
            err = e
        }
    }()
    _, err = io.Copy(out, in)
    if err != nil {
        return
    }
    err = out.Sync()
    if err != nil {
        return
    }
    si, err := os.Stat(src)
    if err != nil {
        return
    }
    err = os.Chmod(dst, si.Mode())
    if err != nil {
        return
    }
    return
}