Skip to content

Commit

Permalink
Merge pull request #217 from nkryuchkov/feature/update-available-endp…
Browse files Browse the repository at this point in the history
…oint

Implement endpoint for checking if visor update is available
  • Loading branch information
jdknives authored Mar 10, 2020
2 parents 4078d17 + 7ea1edf commit 5ca928d
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 42 deletions.
36 changes: 34 additions & 2 deletions pkg/hypervisor/hypervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/SkycoinProject/skywire-mainnet/internal/skyenv"
"github.com/SkycoinProject/skywire-mainnet/pkg/app"
"github.com/SkycoinProject/skywire-mainnet/pkg/routing"
"github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo"
"github.com/SkycoinProject/skywire-mainnet/pkg/visor"
)

Expand Down Expand Up @@ -176,6 +177,7 @@ func (hv *Hypervisor) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.Post("/visors/{pk}/restart", hv.restart())
r.Post("/visors/{pk}/exec", hv.exec())
r.Post("/visors/{pk}/update", hv.update())
r.Get("/visors/{pk}/update/available", hv.updateAvailable())
})
})

Expand Down Expand Up @@ -734,12 +736,42 @@ func (hv *Hypervisor) exec() http.HandlerFunc {

func (hv *Hypervisor) update() http.HandlerFunc {
return hv.withCtx(hv.visorCtx, func(w http.ResponseWriter, r *http.Request, ctx *httpCtx) {
if err := ctx.RPC.Update(); err != nil {
updated, err := ctx.RPC.Update()
if err != nil {
httputil.WriteJSON(w, r, http.StatusInternalServerError, err)
return
}

httputil.WriteJSON(w, r, http.StatusOK, true)
output := struct {
Updated bool `json:"updated"`
}{updated}

httputil.WriteJSON(w, r, http.StatusOK, output)
})
}

func (hv *Hypervisor) updateAvailable() http.HandlerFunc {
return hv.withCtx(hv.visorCtx, func(w http.ResponseWriter, r *http.Request, ctx *httpCtx) {
version, err := ctx.RPC.UpdateAvailable()
if err != nil {
httputil.WriteJSON(w, r, http.StatusInternalServerError, err)
return
}

output := struct {
Available bool `json:"available"`
CurrentVersion string `json:"current_version"`
AvailableVersion string `json:"available_version,omitempty"`
}{
Available: version != nil,
CurrentVersion: buildinfo.Version(),
}

if version != nil {
output.AvailableVersion = version.String()
}

httputil.WriteJSON(w, r, http.StatusOK, output)
})
}

Expand Down
62 changes: 39 additions & 23 deletions pkg/util/updater/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,36 +71,31 @@ func New(log *logging.Logger, restartCtx *restart.Context, appsPath string) *Upd

// Update performs an update operation.
// NOTE: Update may call os.Exit.
func (u *Updater) Update() (err error) {
func (u *Updater) Update() (updated bool, err error) {
if !atomic.CompareAndSwapInt32(&u.updating, 0, 1) {
return ErrAlreadyStarted
return false, ErrAlreadyStarted
}
defer atomic.StoreInt32(&u.updating, 0)

u.log.Infof("Looking for updates")

lastVersion, err := lastVersion()
latestVersion, err := u.UpdateAvailable()
if err != nil {
return fmt.Errorf("failed to get last Skywire version: %w", err)
return false, fmt.Errorf("failed to get last Skywire version: %w", err)
}

u.log.Infof("Last Skywire version: %q", lastVersion.String())

if !updateAvailable(lastVersion) {
u.log.Infof("You are using the latest version of Skywire")
return nil
if latestVersion == nil {
return false, nil
}

u.log.Infof("Update found, version: %q", lastVersion.String())
u.log.Infof("Update found, version: %q", latestVersion.String())

downloadedBinariesPath, err := u.download(lastVersion.String())
downloadedBinariesPath, err := u.download(latestVersion.String())
if err != nil {
return err
return false, err
}

currentBasePath := filepath.Dir(u.restartCtx.CmdPath())
if err := u.updateBinaries(downloadedBinariesPath, currentBasePath); err != nil {
return err
return false, err
}

if err := u.restartCurrentProcess(); err != nil {
Expand All @@ -109,7 +104,7 @@ func (u *Updater) Update() (err error) {

u.restore(currentVisorPath, oldVisorPath)

return err
return false, err
}

u.removeFiles(downloadedBinariesPath)
Expand All @@ -121,7 +116,28 @@ func (u *Updater) Update() (err error) {
}
}()

return nil
return true, nil
}

// UpdateAvailable checks if an update is available.
// If it is, the method returns the last available version.
// Otherwise, it returns nil.
func (u *Updater) UpdateAvailable() (*Version, error) {
u.log.Infof("Looking for updates")

latestVersion, err := latestVersion()
if err != nil {
return nil, err
}

u.log.Infof("Last Skywire version: %q", latestVersion.String())

if !needUpdate(latestVersion) {
u.log.Infof("You are using the latest version of Skywire")
return nil, nil
}

return latestVersion, nil
}

func (u *Updater) exitAfterDelay(delay time.Duration) {
Expand Down Expand Up @@ -392,7 +408,7 @@ func archiveFilename(file, version, os, arch string) string {
return file + "-" + version + "-" + os + "-" + arch + archiveFormat
}

func updateAvailable(last *Version) bool {
func needUpdate(last *Version) bool {
current, err := currentVersion()
if err != nil {
// Unknown versions should be updated.
Expand All @@ -402,20 +418,20 @@ func updateAvailable(last *Version) bool {
return last.Cmp(current) > 0
}

func lastVersion() (*Version, error) {
html, err := lastVersionHTML()
func latestVersion() (*Version, error) {
html, err := latestVersionHTML()
if err != nil {
return nil, err
}

return VersionFromString(extractLastVersion(string(html)))
return VersionFromString(extractLatestVersion(string(html)))
}

func currentVersion() (*Version, error) {
return VersionFromString(buildinfo.Version())
}

func lastVersionHTML() (data []byte, err error) {
func latestVersionHTML() (data []byte, err error) {
resp, err := http.Get(releaseURL)
if err != nil {
return nil, err
Expand All @@ -430,7 +446,7 @@ func lastVersionHTML() (data []byte, err error) {
return ioutil.ReadAll(resp.Body)
}

func extractLastVersion(buffer string) string {
func extractLatestVersion(buffer string) string {
// First occurrence is the latest version.
idx := strings.Index(buffer, urlText)
if idx == -1 {
Expand Down
4 changes: 2 additions & 2 deletions pkg/util/updater/updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/stretchr/testify/require"
)

func Test_extractLastVersion(t *testing.T) {
func Test_extractLatestVersion(t *testing.T) {
tests := []struct {
name string
buffer string
Expand All @@ -32,7 +32,7 @@ func Test_extractLastVersion(t *testing.T) {
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
got := extractLastVersion(tc.buffer)
got := extractLatestVersion(tc.buffer)
assert.Equal(t, tc.want, got)
})
}
Expand Down
25 changes: 22 additions & 3 deletions pkg/visor/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/SkycoinProject/skywire-mainnet/pkg/transport"
"github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo"
"github.com/SkycoinProject/skywire-mainnet/pkg/util/rpcutil"
"github.com/SkycoinProject/skywire-mainnet/pkg/util/updater"
)

const (
Expand Down Expand Up @@ -499,8 +500,26 @@ func (r *RPC) Exec(cmd *string, out *[]byte) (err error) {
}

// Update updates visor.
func (r *RPC) Update(_ *struct{}, _ *struct{}) (err error) {
defer rpcutil.LogCall(r.log, "Update", nil)(nil, &err)
func (r *RPC) Update(_ *struct{}, updated *bool) (err error) {
defer rpcutil.LogCall(r.log, "Update", nil)(updated, &err)

return r.visor.Update()
*updated, err = r.visor.Update()
return
}

// UpdateAvailable checks if visor update is available.
func (r *RPC) UpdateAvailable(_ *struct{}, version *updater.Version) (err error) {
defer rpcutil.LogCall(r.log, "UpdateAvailable", nil)(version, &err)

v, err := r.visor.UpdateAvailable()
if err != nil {
return err
}

if v == nil {
return nil
}

*version = *v
return nil
}
37 changes: 30 additions & 7 deletions pkg/visor/rpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/SkycoinProject/skywire-mainnet/pkg/snet/snettest"
"github.com/SkycoinProject/skywire-mainnet/pkg/transport"
"github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo"
"github.com/SkycoinProject/skywire-mainnet/pkg/util/updater"
)

var (
Expand Down Expand Up @@ -61,7 +62,8 @@ type RPCClient interface {

Restart() error
Exec(command string) ([]byte, error)
Update() error
Update() (bool, error)
UpdateAvailable() (*updater.Version, error)
}

// RPCClient provides methods to call an RPC Server.
Expand Down Expand Up @@ -250,9 +252,25 @@ func (rc *rpcClient) Exec(command string) ([]byte, error) {
}

// Update calls Update.
func (rc *rpcClient) Update() error {
err := rc.Call("Update", &struct{}{}, &struct{}{})
return err
func (rc *rpcClient) Update() (bool, error) {
var updated bool
err := rc.Call("Update", &struct{}{}, &updated)
return updated, err
}

// UpdateAvailable calls UpdateAvailable.
func (rc *rpcClient) UpdateAvailable() (*updater.Version, error) {
var version, empty updater.Version
err := rc.Call("UpdateAvailable", &struct{}{}, &version)
if err != nil {
return nil, err
}

if version == empty {
return nil, nil
}

return &version, err
}

// MockRPCClient mocks RPCClient.
Expand Down Expand Up @@ -614,7 +632,12 @@ func (mc *mockRPCClient) Exec(string) ([]byte, error) {
return []byte("mock"), nil
}

// Exec implements RPCClient.
func (mc *mockRPCClient) Update() error {
return nil
// Update implements RPCClient.
func (mc *mockRPCClient) Update() (bool, error) {
return false, nil
}

// UpdateAvailable implements RPCClient.
func (mc *mockRPCClient) UpdateAvailable() (*updater.Version, error) {
return nil, nil
}
23 changes: 18 additions & 5 deletions pkg/visor/visor.go
Original file line number Diff line number Diff line change
Expand Up @@ -675,15 +675,28 @@ func (visor *Visor) Exec(command string) ([]byte, error) {
return cmd.CombinedOutput()
}

// Update checks if visor update is available.
// Update updates visor.
// It checks if visor update is available.
// If it is, the method downloads a new visor versions, starts it and kills the current process.
func (visor *Visor) Update() error {
if err := visor.updater.Update(); err != nil {
func (visor *Visor) Update() (bool, error) {
updated, err := visor.updater.Update()
if err != nil {
visor.logger.Errorf("Failed to update visor: %v", err)
return err
return false, err
}

return nil
return updated, nil
}

// UpdateAvailable checks if visor update is available.
func (visor *Visor) UpdateAvailable() (*updater.Version, error) {
version, err := visor.updater.UpdateAvailable()
if err != nil {
visor.logger.Errorf("Failed to check if visor update is available: %v", err)
return nil, err
}

return version, nil
}

func (visor *Visor) setAutoStart(appName string, autoStart bool) error {
Expand Down

0 comments on commit 5ca928d

Please sign in to comment.