Initial commit
This commit is contained in:
21
vendor/github.com/hyperhq/hypercli/pkg/selfupdate/LICENSE.md
generated
vendored
Normal file
21
vendor/github.com/hyperhq/hypercli/pkg/selfupdate/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Mark Sanborn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
57
vendor/github.com/hyperhq/hypercli/pkg/selfupdate/requester.go
generated
vendored
Normal file
57
vendor/github.com/hyperhq/hypercli/pkg/selfupdate/requester.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package selfupdate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Requester interface allows developers to customize the method in which
|
||||
// requests are made to retrieve the version and binary
|
||||
type Requester interface {
|
||||
Fetch(url string) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// HTTPRequester is the normal requester that is used and does an HTTP
|
||||
// to the url location requested to retrieve the specified data.
|
||||
type HTTPRequester struct {
|
||||
}
|
||||
|
||||
// Fetch will return an HTTP request to the specified url and return
|
||||
// the body of the result. An error will occur for a non 200 status code.
|
||||
func (httpRequester *HTTPRequester) Fetch(url string) (io.ReadCloser, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("bad http status from %s: %v", url, resp.Status)
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// mockRequester used for some mock testing to ensure the requester contract
|
||||
// works as specified.
|
||||
type mockRequester struct {
|
||||
currentIndex int
|
||||
fetches []func(string) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
func (mr *mockRequester) handleRequest(requestHandler func(string) (io.ReadCloser, error)) {
|
||||
if mr.fetches == nil {
|
||||
mr.fetches = []func(string) (io.ReadCloser, error){}
|
||||
}
|
||||
mr.fetches = append(mr.fetches, requestHandler)
|
||||
}
|
||||
|
||||
func (mr *mockRequester) Fetch(url string) (io.ReadCloser, error) {
|
||||
if len(mr.fetches) <= mr.currentIndex {
|
||||
return nil, fmt.Errorf("No for currentIndex %d to mock", mr.currentIndex)
|
||||
}
|
||||
current := mr.fetches[mr.currentIndex]
|
||||
mr.currentIndex++
|
||||
|
||||
return current(url)
|
||||
}
|
||||
322
vendor/github.com/hyperhq/hypercli/pkg/selfupdate/selfupdate.go
generated
vendored
Normal file
322
vendor/github.com/hyperhq/hypercli/pkg/selfupdate/selfupdate.go
generated
vendored
Normal file
@@ -0,0 +1,322 @@
|
||||
// Update protocol:
|
||||
//
|
||||
// GET hk.heroku.com/hk/linux-amd64.json
|
||||
//
|
||||
// 200 ok
|
||||
// {
|
||||
// "Version": "2",
|
||||
// "Sha256": "..." // base64
|
||||
// }
|
||||
//
|
||||
// then
|
||||
//
|
||||
// GET hkpatch.s3.amazonaws.com/hk/1/2/linux-amd64
|
||||
//
|
||||
// 200 ok
|
||||
// [bsdiff data]
|
||||
//
|
||||
// or
|
||||
//
|
||||
// GET hkdist.s3.amazonaws.com/hk/2/linux-amd64.gz
|
||||
//
|
||||
// 200 ok
|
||||
// [gzipped executable data]
|
||||
//
|
||||
//
|
||||
package selfupdate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/kardianos/osext"
|
||||
"github.com/kr/binarydist"
|
||||
"gopkg.in/inconshreveable/go-update.v0"
|
||||
)
|
||||
|
||||
const (
|
||||
upcktimePath = "cktime.json"
|
||||
plat = runtime.GOOS + "-" + runtime.GOARCH
|
||||
)
|
||||
|
||||
const devValidTime = 7 * 24 * time.Hour
|
||||
|
||||
var ErrHashMismatch = errors.New("new file hash mismatch after patch")
|
||||
var up = update.New()
|
||||
var defaultHTTPRequester = HTTPRequester{}
|
||||
|
||||
type updateTimeRecord struct {
|
||||
LastUpdate time.Time `json:"lastUpdate"`
|
||||
}
|
||||
|
||||
// Updater is the configuration and runtime data for doing an update.
|
||||
//
|
||||
// Note that ApiURL, BinURL and DiffURL should have the same value if all files are available at the same location.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// updater := &selfupdate.Updater{
|
||||
// CurrentVersion: version,
|
||||
// ApiURL: "http://updates.yourdomain.com/",
|
||||
// BinURL: "http://updates.yourdownmain.com/",
|
||||
// DiffURL: "http://updates.yourdomain.com/",
|
||||
// Dir: "update/",
|
||||
// CmdName: "myapp", // app name
|
||||
// }
|
||||
// if updater != nil {
|
||||
// go updater.BackgroundRun()
|
||||
// }
|
||||
type Updater struct {
|
||||
CurrentVersion string // Currently running version.
|
||||
ApiURL string // Base URL for API requests (json files).
|
||||
CmdName string // Command name is appended to the ApiURL like http://apiurl/CmdName/. This represents one binary.
|
||||
BinURL string // Base URL for full binary downloads.
|
||||
DiffURL string // Base URL for diff downloads.
|
||||
Dir string // Directory to store selfupdate state.
|
||||
ForceCheck bool // Check for update regardless of cktime timestamp
|
||||
Requester Requester //Optional parameter to override existing http request handler
|
||||
Info struct {
|
||||
Version string
|
||||
Sha256 []byte
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Updater) getExecRelativeDir(dir string) string {
|
||||
filename, _ := osext.Executable()
|
||||
path := filepath.Join(filepath.Dir(filename), dir)
|
||||
return path
|
||||
}
|
||||
|
||||
// BackgroundRun starts the update check and apply cycle.
|
||||
func (u *Updater) BackgroundRun(update bool) error {
|
||||
if _, err := os.Stat(u.Dir); err != nil && os.IsNotExist(err) {
|
||||
os.MkdirAll(u.Dir, 0700)
|
||||
}
|
||||
if update {
|
||||
if err := up.CanUpdate(); err != nil {
|
||||
// fail
|
||||
return err
|
||||
}
|
||||
//self, err := osext.Executable()
|
||||
//if err != nil {
|
||||
// fail update, couldn't figure out path to self
|
||||
//return
|
||||
//}
|
||||
// TODO(bgentry): logger isn't on Windows. Replace w/ proper error reports.
|
||||
if err := u.update(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Updater) WantUpdate() bool {
|
||||
path := filepath.Join(u.Dir, upcktimePath)
|
||||
if u.CurrentVersion == "dev" || (!u.ForceCheck && readTime(path).After(time.Now())) {
|
||||
//log.Println("not update")
|
||||
return false
|
||||
}
|
||||
defer func() {
|
||||
wait := 24*time.Hour + randDuration(24*time.Hour)
|
||||
writeTime(path, time.Now().Add(wait))
|
||||
}()
|
||||
if u.fetchInfo() != nil {
|
||||
return false
|
||||
}
|
||||
if u.Info.Version == u.CurrentVersion {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *Updater) update() error {
|
||||
path, err := osext.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
old, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer old.Close()
|
||||
|
||||
/*
|
||||
err = u.fetchInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if u.Info.Version == u.CurrentVersion {
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
bin, err := u.fetchAndVerifyPatch(old)
|
||||
if err != nil {
|
||||
if err == ErrHashMismatch {
|
||||
//log.Println("update: hash mismatch from patched binary")
|
||||
} else {
|
||||
if u.DiffURL != "" {
|
||||
//log.Println("update: patching binary,", err)
|
||||
}
|
||||
}
|
||||
|
||||
bin, err = u.fetchAndVerifyFullBin()
|
||||
if err != nil {
|
||||
if err == ErrHashMismatch {
|
||||
//log.Println("update: hash mismatch from full binary")
|
||||
} else {
|
||||
//log.Println("update: fetching full binary,", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// close the old binary before installing because on windows
|
||||
// it can't be renamed if a handle to the file is still open
|
||||
old.Close()
|
||||
|
||||
err, errRecover := up.FromStream(bytes.NewBuffer(bin))
|
||||
if errRecover != nil {
|
||||
return fmt.Errorf("update and recovery errors: %q %q", err, errRecover)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Updater) fetchInfo() error {
|
||||
r, err := u.fetch(u.ApiURL + u.CmdName + "/" + plat + ".json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
err = json.NewDecoder(r).Decode(&u.Info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(u.Info.Sha256) != sha256.Size {
|
||||
return errors.New("bad cmd hash in info")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Updater) fetchAndVerifyPatch(old io.Reader) ([]byte, error) {
|
||||
bin, err := u.fetchAndApplyPatch(old)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !verifySha(bin, u.Info.Sha256) {
|
||||
return nil, ErrHashMismatch
|
||||
}
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
func (u *Updater) fetchAndApplyPatch(old io.Reader) ([]byte, error) {
|
||||
r, err := u.fetch(u.DiffURL + u.CmdName + "/" + u.CurrentVersion + "/" + u.Info.Version + "/" + plat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
var buf bytes.Buffer
|
||||
err = binarydist.Patch(old, &buf, r)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
func (u *Updater) fetchAndVerifyFullBin() ([]byte, error) {
|
||||
bin, err := u.fetchBin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
verified := verifySha(bin, u.Info.Sha256)
|
||||
if !verified {
|
||||
return nil, ErrHashMismatch
|
||||
}
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
func (u *Updater) fetchBin() ([]byte, error) {
|
||||
r, err := u.fetch(u.BinURL + u.CmdName + "/" + u.Info.Version + "/" + plat + ".gz")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
buf := new(bytes.Buffer)
|
||||
gz, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = io.Copy(buf, gz); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// returns a random duration in [0,n).
|
||||
func randDuration(n time.Duration) time.Duration {
|
||||
return time.Duration(rand.Int63n(int64(n)))
|
||||
}
|
||||
|
||||
func (u *Updater) fetch(url string) (io.ReadCloser, error) {
|
||||
if u.Requester == nil {
|
||||
return defaultHTTPRequester.Fetch(url)
|
||||
}
|
||||
|
||||
readCloser, err := u.Requester.Fetch(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if readCloser == nil {
|
||||
return nil, fmt.Errorf("Fetch was expected to return non-nil ReadCloser")
|
||||
}
|
||||
|
||||
return readCloser, nil
|
||||
}
|
||||
|
||||
func readTime(path string) time.Time {
|
||||
p, err := os.Open(path)
|
||||
if os.IsNotExist(err) {
|
||||
return time.Time{}
|
||||
}
|
||||
if err != nil {
|
||||
//log.Println(err)
|
||||
return time.Now().Add(1000 * time.Hour)
|
||||
}
|
||||
var update updateTimeRecord
|
||||
if err = json.NewDecoder(p).Decode(&update); err != nil {
|
||||
return time.Now().Add(1000 * time.Hour)
|
||||
}
|
||||
return update.LastUpdate
|
||||
}
|
||||
|
||||
func verifySha(bin []byte, sha []byte) bool {
|
||||
h := sha256.New()
|
||||
h.Write(bin)
|
||||
return bytes.Equal(h.Sum(nil), sha)
|
||||
}
|
||||
|
||||
func writeTime(path string, t time.Time) bool {
|
||||
data, err := json.Marshal(updateTimeRecord{t})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
err = ioutil.WriteFile(path, data, 0600)
|
||||
if err != nil {
|
||||
//log.Println(err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
79
vendor/github.com/hyperhq/hypercli/pkg/selfupdate/selfupdate_test.go
generated
vendored
Normal file
79
vendor/github.com/hyperhq/hypercli/pkg/selfupdate/selfupdate_test.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
package selfupdate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testHash = sha256.New()
|
||||
|
||||
func TestUpdaterFetchMustReturnNonNilReaderCloser(t *testing.T) {
|
||||
mr := &mockRequester{}
|
||||
mr.handleRequest(
|
||||
func(url string) (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
})
|
||||
updater := createUpdater(mr)
|
||||
err := updater.BackgroundRun()
|
||||
if err != nil {
|
||||
equals(t, "Fetch was expected to return non-nil ReadCloser", err.Error())
|
||||
} else {
|
||||
t.Log("Expected an error")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUpdaterWithEmptyPaloadNoErrorNoUpdate(t *testing.T) {
|
||||
mr := &mockRequester{}
|
||||
mr.handleRequest(
|
||||
func(url string) (io.ReadCloser, error) {
|
||||
equals(t, "http://updates.yourdomain.com/myapp/darwin-amd64.json", url)
|
||||
return newTestReaderCloser("{}"), nil
|
||||
})
|
||||
updater := createUpdater(mr)
|
||||
|
||||
err := updater.BackgroundRun()
|
||||
if err != nil {
|
||||
t.Errorf("Error occured: %#v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func createUpdater(mr *mockRequester) *Updater {
|
||||
return &Updater{
|
||||
CurrentVersion: "1.2",
|
||||
ApiURL: "http://updates.yourdomain.com/",
|
||||
BinURL: "http://updates.yourdownmain.com/",
|
||||
DiffURL: "http://updates.yourdomain.com/",
|
||||
Dir: "update/",
|
||||
CmdName: "myapp", // app name
|
||||
Requester: mr,
|
||||
}
|
||||
}
|
||||
|
||||
func equals(t *testing.T, expected, actual interface{}) {
|
||||
if expected != actual {
|
||||
t.Log(fmt.Sprintf("Expected: %#v %#v\n", expected, actual))
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
type testReadCloser struct {
|
||||
buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
func newTestReaderCloser(payload string) io.ReadCloser {
|
||||
return &testReadCloser{buffer: bytes.NewBufferString(payload)}
|
||||
}
|
||||
|
||||
func (trc *testReadCloser) Read(p []byte) (n int, err error) {
|
||||
return trc.buffer.Read(p)
|
||||
}
|
||||
|
||||
func (trc *testReadCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user