| | |
| | | # 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). |
| | | |
| | | --- |
| | | |
| | | [](https://godoc.org/github.com/zan8in/go-update) |
| | |  |
| | |  |
| | | |
| | | <a href="https://apex.sh"><img src="http://tjholowaychuk.com:6000/svg/sponsor"></a> |
| New file |
| | |
| | | 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) |
| | | } |
| New file |
| | |
| | | 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) |
| | | } |
| New file |
| | |
| | | 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 |
| | | ) |
| New file |
| | |
| | | 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= |
| New file |
| | |
| | | // 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, |
| | | } |
| | | } |
| New file |
| | |
| | | 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) |
| | | } |
| New file |
| | |
| | | // 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 |
| | | } |
| New file |
| | |
| | | // 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 |
| | | } |
| New file |
| | |
| | | // 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 |
| | | } |