Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nex update stage 1 #200

Merged
merged 9 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/ltb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ jobs:
-
name: Run test suite
working-directory: .
run: go test -v -race ./test
if: runner.os != 'Windows'
run: |
mv spec _spec
jordan-rash marked this conversation as resolved.
Show resolved Hide resolved
go test -v -race ./...

build:
timeout-minutes: 10
Expand Down
32 changes: 22 additions & 10 deletions nex/nex.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ var (
COMMIT = ""
BUILDDATE = ""

updatable = ""

blue = color.New(color.FgBlue).SprintFunc()

ncli = fisk.New("nex", fmt.Sprintf("%s\nNATS Execution Engine CLI Version %s\n", blue(Banner), VERSION))
Expand All @@ -30,15 +32,16 @@ var (
_ = ncli.HelpFlag.Short('h')
_ = ncli.WithCheats().CheatCommand.Hidden()

tui = ncli.Command("tui", "Start the Nex TUI [BETA]").Alias("ui")
nodes = ncli.Command("node", "Interact with execution engine nodes")
run = ncli.Command("run", "Run a workload on a target node")
yeet = ncli.Command("devrun", "Run a workload locating reasonable defaults (developer mode)").Alias("yeet")
stop = ncli.Command("stop", "Stop a running workload")
logs = ncli.Command("logs", "Live monitor workload log emissions")
evts = ncli.Command("events", "Live monitor events from nex nodes")
rootfs = ncli.Command("rootfs", "Build custom rootfs").Alias("fs")
lame = ncli.Command("lameduck", "Command a node to enter lame duck mdoe")
tui = ncli.Command("tui", "Start the Nex TUI [BETA]").Alias("ui")
nodes = ncli.Command("node", "Interact with execution engine nodes").Alias("nodes")
run = ncli.Command("run", "Run a workload on a target node")
yeet = ncli.Command("devrun", "Run a workload locating reasonable defaults (developer mode)").Alias("yeet")
stop = ncli.Command("stop", "Stop a running workload")
logs = ncli.Command("logs", "Live monitor workload log emissions")
evts = ncli.Command("events", "Live monitor events from nex nodes")
rootfs = ncli.Command("rootfs", "Build custom rootfs").Alias("fs")
lame = ncli.Command("lameduck", "Command a node to enter lame duck mode")
upgrade = ncli.Command("upgrade", "Upgrade the NEX CLI to the latest version")

nodesLs = nodes.Command("ls", "List nodes")
nodesInfo = nodes.Command("info", "Get information for an engine node")
Expand All @@ -62,7 +65,7 @@ var (
)

func init() {
_ = versionCheck()
updatable, _ = versionCheck()

ncli.Flag("server", "NATS server urls").Short('s').Envar("NATS_URL").Default(nats.DefaultURL).StringVar(&Opts.Servers)
ncli.Flag("user", "Username or Token").Envar("NATS_USER").PlaceHolder("USER").StringVar(&Opts.Username)
Expand Down Expand Up @@ -236,5 +239,14 @@ func main() {
if err != nil {
logger.Error("failed to build rootfs", slog.Any("err", err))
}
case upgrade.FullCommand():
if updatable != "" {
_, err := UpgradeNex(ctx, logger, updatable)
if err != nil {
logger.Error("failed to upgrade nex", slog.Any("err", err))
}
} else {
logger.Info("no new version found")
}
}
}
50 changes: 0 additions & 50 deletions nex/update.go

This file was deleted.

132 changes: 132 additions & 0 deletions nex/upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package main

import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"os"
"path/filepath"
"runtime"
)

func versionCheck() (string, error) {
if VERSION == "development" {
return "", nil
}

res, err := http.Get("https://api.github.com/repos/synadia-io/nex/releases/latest")
if err != nil {
return "", err
}
defer res.Body.Close()

b, err := io.ReadAll(res.Body)
if err != nil {
return "", err
}

payload := make(map[string]interface{})
err = json.Unmarshal(b, &payload)
if err != nil {
return "", err
}

latestTag, ok := payload["tag_name"].(string)
if !ok {
return "", errors.New("error parsing tag_name")
}

if latestTag != VERSION {
fmt.Printf(`================================================================
🎉 There is a newer version [v%s] of the NEX CLI available 🎉
To update, run:
nex update
================================================================

`,
latestTag)
}

return latestTag, nil
}

func UpgradeNex(ctx context.Context, logger *slog.Logger, newVersion string) (string, error) {
nexPath, err := os.Executable()
if err != nil {
return "", err
}

f, err := os.Open(nexPath)
if err != nil {
return "", err
}

// copy binary backup
f_bak, err := os.Create(nexPath + ".bak")
if err != nil {
return "", err
}
defer os.Remove(nexPath + ".bak")
_, err = io.Copy(f_bak, f)
if err != nil {
return "", err
}
f_bak.Close()
f.Close()

restoreBackup := func() {
logger.Info("Restoring backup binary")
if err := os.Rename(nexPath+".bak", nexPath); err != nil {
logger.Error("Failed to restore backup binary", slog.Any("err", err))
}
}

_os := runtime.GOOS
arch := runtime.GOARCH

url := fmt.Sprintf("https://github.com/synadia-io/nex/releases/download/%s/nex_%s_%s_%s", newVersion, newVersion, _os, arch)
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("failed to download nex: %s", resp.Status)
}

nexBinary, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}

nex, err := os.Create(filepath.Join(filepath.Dir(nexPath), "nex"))
if err != nil {
restoreBackup()
return "", err
}

_, err = nex.Write(nexBinary)
if err != nil {
restoreBackup()
return "", err
}

h := sha256.New()
if _, err := io.Copy(h, nex); err != nil {
return "", err
}

shasum := hex.EncodeToString(h.Sum(nil))

logger.Debug("New binary downloaded", slog.String("sha256", shasum))
logger.Info("nex upgrade complete!", slog.String("new_version", newVersion))

return shasum, nil
}
56 changes: 56 additions & 0 deletions nex/upgrade_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package main

import (
"context"
"log/slog"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

shandler "github.com/jordan-rash/slog-handler"
)

func logger(t *testing.T) *slog.Logger {
t.Helper()
return slog.New(shandler.NewHandler())
}

func setEnvironment(t *testing.T) {
t.Helper()

tempPath := t.TempDir()
f, err := os.Create(filepath.Join(tempPath, "nex"))
if err != nil {
t.Fail()
}
f.Close()

err = os.Chmod(filepath.Join(tempPath, "nex"), 0775)
if err != nil {
t.Fail()
}

path := os.Getenv("PATH")
os.Setenv("PATH", tempPath+":"+path)
}

func TestUpdateNex(t *testing.T) {
setEnvironment(t)
log := logger(t)

testNexPath, _ := exec.LookPath("nex")
t.Log("nex path: " + testNexPath)
if !strings.HasPrefix(testNexPath, os.TempDir()) {
t.Log("bailing on update nex test so real env isnt affected")
t.SkipNow()
}

shasum, err := UpgradeNex(context.Background(), log, "0.2.1")
if err != nil {
t.Fatal(err)
}

t.Log(shasum)
}