From ab4a86349fd9387b598c3244fe9504e94988c4ef Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 9 Mar 2020 20:50:22 +0100 Subject: [PATCH 1/2] Implement endpoint for checking if visor update is available --- pkg/hypervisor/hypervisor.go | 36 +++++++++++++++++++++++++-- pkg/util/updater/updater.go | 48 ++++++++++++++++++++++++------------ pkg/visor/rpc.go | 25 ++++++++++++++++--- pkg/visor/rpc_client.go | 37 +++++++++++++++++++++------ pkg/visor/visor.go | 23 +++++++++++++---- 5 files changed, 136 insertions(+), 33 deletions(-) diff --git a/pkg/hypervisor/hypervisor.go b/pkg/hypervisor/hypervisor.go index e07a85de97..e5b0176f78 100644 --- a/pkg/hypervisor/hypervisor.go +++ b/pkg/hypervisor/hypervisor.go @@ -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" ) @@ -176,6 +177,7 @@ func (hv *Hypervisor) ServeHTTP(w http.ResponseWriter, req *http.Request) { r.Get("/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()) }) }) @@ -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) }) } diff --git a/pkg/util/updater/updater.go b/pkg/util/updater/updater.go index 92e54c8489..b15fd522b1 100644 --- a/pkg/util/updater/updater.go +++ b/pkg/util/updater/updater.go @@ -71,35 +71,30 @@ 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.CompareAndSwapUint32(&u.updating, 0, 1) { - return ErrAlreadyStarted + return false, ErrAlreadyStarted } - u.log.Infof("Looking for updates") - - lastVersion, err := lastVersion() + lastVersion, 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 lastVersion == nil { + return false, nil } u.log.Infof("Update found, version: %q", lastVersion.String()) downloadedBinariesPath, err := u.download(lastVersion.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 { @@ -108,7 +103,7 @@ func (u *Updater) Update() (err error) { u.restore(currentVisorPath, oldVisorPath) - return err + return false, err } u.removeFiles(downloadedBinariesPath) @@ -120,7 +115,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") + + lastVersion, err := lastVersion() + if err != nil { + return nil, err + } + + u.log.Infof("Last Skywire version: %q", lastVersion.String()) + + if !needUpdate(lastVersion) { + u.log.Infof("You are using the latest version of Skywire") + return nil, nil + } + + return lastVersion, nil } func (u *Updater) exitAfterDelay(delay time.Duration) { @@ -391,7 +407,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. diff --git a/pkg/visor/rpc.go b/pkg/visor/rpc.go index 48f741e0a1..6e06f50e2c 100644 --- a/pkg/visor/rpc.go +++ b/pkg/visor/rpc.go @@ -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 ( @@ -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 } diff --git a/pkg/visor/rpc_client.go b/pkg/visor/rpc_client.go index fa0b01984a..49f11be974 100644 --- a/pkg/visor/rpc_client.go +++ b/pkg/visor/rpc_client.go @@ -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 ( @@ -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. @@ -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. @@ -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 } diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index 7361fc4066..efda3e5ea4 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -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 { From 7ea1edf22734faed54deb3311188dc99aa2f0fa3 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 10 Mar 2020 14:50:24 +0100 Subject: [PATCH 2/2] Rename lastVersion to latestVersion --- pkg/util/updater/updater.go | 26 +++++++++++++------------- pkg/util/updater/updater_test.go | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pkg/util/updater/updater.go b/pkg/util/updater/updater.go index e077e2b6d7..22b51ca273 100644 --- a/pkg/util/updater/updater.go +++ b/pkg/util/updater/updater.go @@ -77,18 +77,18 @@ func (u *Updater) Update() (updated bool, err error) { } defer atomic.StoreInt32(&u.updating, 0) - lastVersion, err := u.UpdateAvailable() + latestVersion, err := u.UpdateAvailable() if err != nil { return false, fmt.Errorf("failed to get last Skywire version: %w", err) } - if lastVersion == 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 false, err } @@ -125,19 +125,19 @@ func (u *Updater) Update() (updated bool, err error) { func (u *Updater) UpdateAvailable() (*Version, error) { u.log.Infof("Looking for updates") - lastVersion, err := lastVersion() + latestVersion, err := latestVersion() if err != nil { return nil, err } - u.log.Infof("Last Skywire version: %q", lastVersion.String()) + u.log.Infof("Last Skywire version: %q", latestVersion.String()) - if !needUpdate(lastVersion) { + if !needUpdate(latestVersion) { u.log.Infof("You are using the latest version of Skywire") return nil, nil } - return lastVersion, nil + return latestVersion, nil } func (u *Updater) exitAfterDelay(delay time.Duration) { @@ -418,20 +418,20 @@ func needUpdate(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 @@ -446,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 { diff --git a/pkg/util/updater/updater_test.go b/pkg/util/updater/updater_test.go index 33c2d12283..d895f4ac3a 100644 --- a/pkg/util/updater/updater_test.go +++ b/pkg/util/updater/updater_test.go @@ -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 @@ -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) }) }