From f98ed5dc05ff994e567e243eafaa508a3918a745 Mon Sep 17 00:00:00 2001 From: Jono Wenger <jono@elixxir.io> Date: Wed, 19 Oct 2022 22:06:05 +0000 Subject: [PATCH] XX-4271 / Versioning --- README.md | 9 +++- main.go | 6 +++ utils/version.go | 97 +++++++++++++++++++++++++++++++++++++++ utils/version_test.go | 102 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 utils/version.go create mode 100644 utils/version_test.go diff --git a/README.md b/README.md index b080e856..d1f59cd4 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,16 @@ This repository contains the WebAssembly bindings for xxDK. It also includes examples and a test server to serve the compiled WebAssembly module. **Note:** If you are updating the version of Go that this repository uses, you -need to ensure that you update the wasm_exec.js file as described +need to ensure that you update the `wasm_exec.js` file as described [below](#wasm_execjs). +## Updates + +The current semantic version of this repository is stored in `SEMVER` in +`version.go`. When making major updates or updates that create an +incompatibility in the storage or databases, the semantic version needs to be +updated and an upgrade path needs to be provided. + ## Building The repository can only be compiled to a WebAssembly binary using `GOOS=js` and diff --git a/main.go b/main.go index 0e067b2e..a7484593 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,12 @@ func init() { ll := wasm.NewJsConsoleLogListener(jww.LevelInfo) jww.SetLogListeners(ll.Listen) jww.SetStdoutThreshold(jww.LevelFatal + 1) + + // Check that the WASM binary version is correct + err := utils.CheckAndStoreVersions() + if err != nil { + jww.FATAL.Panicf("WASM binary version error: %+v", err) + } } func main() { diff --git a/utils/version.go b/utils/version.go new file mode 100644 index 00000000..ab26b463 --- /dev/null +++ b/utils/version.go @@ -0,0 +1,97 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 xx foundation // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file. // +//////////////////////////////////////////////////////////////////////////////// + +//go:build js && wasm + +package utils + +import ( + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/bindings" + "os" +) + +// SEMVER is the current semantic version of xxDK WASM. +const SEMVER = "0.0.0" + +// Storage keys. +const ( + semverKey = "xxdkWasmSemanticVersion" + clientVerKey = "xxdkClientSemanticVersion" +) + +// CheckAndStoreVersions checks that the stored xxDK WASM version matches the +// current version and if not, upgrades it. It also stored the current xxDK +// client to storage. +// +// On first load, only the xxDK WASM and xxDK client versions are stored. +func CheckAndStoreVersions() error { + return checkAndStoreVersions( + SEMVER, bindings.GetVersion(), GetLocalStorage()) +} + +func checkAndStoreVersions( + currentWasmVer, currentClientVer string, ls *LocalStorage) error { + // Get the stored client and WASM versions, if they exists + storedClientVer, err := initOrLoadStoredSemver( + clientVerKey, currentClientVer, ls) + if err != nil { + return err + } + storedWasmVer, err := initOrLoadStoredSemver(semverKey, currentWasmVer, ls) + if err != nil { + return err + } + + // Check if client needs an update + if storedClientVer != currentClientVer { + jww.INFO.Printf("xxDK client out of date; upgrading version: v%s → v%s", + storedClientVer, currentClientVer) + } else { + jww.INFO.Printf("xxDK client version is current: v%s", storedClientVer) + } + + // Check if WASM needs an update + if storedWasmVer != currentWasmVer { + jww.INFO.Printf("xxDK WASM out of date; upgrading version: v%s → v%s", + storedWasmVer, currentWasmVer) + } else { + jww.INFO.Printf("xxDK WASM version is current: v%s", storedWasmVer) + } + + // Upgrade path code goes here + + // Save current versions + ls.SetItem(clientVerKey, []byte(currentClientVer)) + ls.SetItem(semverKey, []byte(currentWasmVer)) + + return nil +} + +// initOrLoadStoredSemver returns the semantic version stored at the key in +// local storage. If no version is stored, then the current version is stored +// and returned. +func initOrLoadStoredSemver( + key, currentVersion string, ls *LocalStorage) (string, error) { + storedVersion, err := ls.GetItem(key) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + // Save the current version if this is the first run + jww.INFO.Printf("Initialising %s to v%s", key, currentVersion) + ls.SetItem(key, []byte(currentVersion)) + return currentVersion, nil + } else { + // If the item exists, but cannot be loaded, return an error + return "", errors.Errorf( + "could not load %s from storage: %+v", key, err) + } + } + + // Return the stored version + return string(storedVersion), nil +} diff --git a/utils/version_test.go b/utils/version_test.go new file mode 100644 index 00000000..f5446503 --- /dev/null +++ b/utils/version_test.go @@ -0,0 +1,102 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 xx foundation // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file. // +//////////////////////////////////////////////////////////////////////////////// + +//go:build js && wasm + +package utils + +import ( + "testing" +) + +// Tests that checkAndStoreVersions correct initialises the client and WASM +// versions on first run and upgrades them correctly on subsequent runs. +func Test_checkAndStoreVersions(t *testing.T) { + ls := GetLocalStorage() + ls.Clear() + oldWasmVer := "0.1" + newWasmVer := "1.0" + oldClientVer := "2.5" + newClientVer := "2.6" + err := checkAndStoreVersions(oldWasmVer, oldClientVer, ls) + if err != nil { + t.Errorf("CheckAndStoreVersions error: %+v", err) + } + + // Check client version + storedClientVer, err := ls.GetItem(clientVerKey) + if err != nil { + t.Errorf("Failed to get client version from storage: %+v", err) + } + if string(storedClientVer) != oldClientVer { + t.Errorf("Loaded client version does not match expected."+ + "\nexpected: %s\nreceived: %s", oldClientVer, storedClientVer) + } + + // Check WASM version + storedWasmVer, err := ls.GetItem(semverKey) + if err != nil { + t.Errorf("Failed to get WASM version from storage: %+v", err) + } + if string(storedWasmVer) != oldWasmVer { + t.Errorf("Loaded WASM version does not match expected."+ + "\nexpected: %s\nreceived: %s", oldWasmVer, storedWasmVer) + } + + err = checkAndStoreVersions(newWasmVer, newClientVer, ls) + if err != nil { + t.Errorf("CheckAndStoreVersions error: %+v", err) + } + + // Check client version + storedClientVer, err = ls.GetItem(clientVerKey) + if err != nil { + t.Errorf("Failed to get client version from storage: %+v", err) + } + if string(storedClientVer) != newClientVer { + t.Errorf("Loaded client version does not match expected."+ + "\nexpected: %s\nreceived: %s", newClientVer, storedClientVer) + } + + // Check WASM version + storedWasmVer, err = ls.GetItem(semverKey) + if err != nil { + t.Errorf("Failed to get WASM version from storage: %+v", err) + } + if string(storedWasmVer) != newWasmVer { + t.Errorf("Loaded WASM version does not match expected."+ + "\nexpected: %s\nreceived: %s", newWasmVer, storedWasmVer) + } +} + +// Tests that initOrLoadStoredSemver initialises the correct version on first run +// and returns the same version on subsequent runs. +func Test_initOrLoadStoredSemver(t *testing.T) { + ls := GetLocalStorage() + key := "testKey" + oldVersion := "0.1" + + loadedVersion, err := initOrLoadStoredSemver(key, oldVersion, ls) + if err != nil { + t.Errorf("Failed to intilaise version: %+v", err) + } + + if loadedVersion != oldVersion { + t.Errorf("Loaded version does not match expected."+ + "\nexpected: %s\nreceived: %s", oldVersion, loadedVersion) + } + + loadedVersion, err = initOrLoadStoredSemver(key, "something", ls) + if err != nil { + t.Errorf("Failed to load version: %+v", err) + } + + if loadedVersion != oldVersion { + t.Errorf("Loaded version does not match expected."+ + "\nexpected: %s\nreceived: %s", oldVersion, loadedVersion) + } +} -- GitLab