Skip to content
This repository has been archived by the owner on Jun 5, 2020. It is now read-only.

Commit

Permalink
add (back) update command
Browse files Browse the repository at this point in the history
  • Loading branch information
verdverm committed May 14, 2020
1 parent 3fc92ae commit 644dca8
Show file tree
Hide file tree
Showing 5 changed files with 677 additions and 1 deletion.
336 changes: 336 additions & 0 deletions .hof/Cli/cmd/mvs/cmd/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,336 @@
package cmd

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"time"

"github.com/parnurzeal/gorequest"
"github.com/spf13/cobra"

"github.com/hofstadter-io/mvs/cmd/mvs/ga"
"github.com/hofstadter-io/mvs/cmd/mvs/verinfo"
)

var UpdateLong = `Print the build version for mvs`

var (
UpdateCheckFlag bool

UpdateStarted bool
UpdateErrored bool
UpdateChecked bool
UpdateAvailable *ProgramVersion
UpdateData []interface{}
)

func init() {
UpdateCmd.Flags().BoolVarP(&UpdateCheckFlag, "check", "", false, "set to only check for an update")
}

const updateMessage = `
Updates available. v%s -> %s (latest)
run 'mvs update' to get the latest.
`

// TODO, add a curl to the above? or os specific?

var UpdateCmd = &cobra.Command{

Use: "update",

Short: "update the dma tool",

Long: UpdateLong,

PreRun: func(cmd *cobra.Command, args []string) {
ga.SendGaEvent("update", "<omit>", 0)
},

Run: func(cmd *cobra.Command, args []string) {

latest, err := CheckUpdate(true)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}

// Semver Check?
cur := ProgramVersion{Version: "v" + verinfo.Version}
if latest.Version == cur.Version || cur.Version == "vLocal" {
return
} else {
if UpdateCheckFlag {
return
}
}

err = InstallLatest()
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
},
}

func init() {
RootCmd.AddCommand(UpdateCmd)

go CheckUpdate(false)
}

type ProgramVersion struct {
Version string
URL string
}

func CheckUpdate(manual bool) (ver ProgramVersion, err error) {
if !manual && os.Getenv("MVS_UPDATES_DISABLED") != "" {
return
}
UpdateStarted = true
cur := ProgramVersion{Version: "v" + verinfo.Version}

checkURL := "https://api.github.com/repos/hofstadter-io/mvs/releases/latest"

req := gorequest.New().Get(checkURL).
Query("current=" + cur.Version).
Query("manual=" + fmt.Sprint(manual))
resp, b, errs := req.EndBytes()
UpdateErrored = true

check := "http2: server sent GOAWAY and closed the connection"
if len(errs) != 0 && !strings.Contains(errs[0].Error(), check) {
// fmt.Println("errs:", errs)
return ver, errs[0]
}

if len(errs) != 0 || resp.StatusCode >= 500 {
return ver, fmt.Errorf("Internal Error: " + string(b))
}
if resp.StatusCode >= 400 {
if resp.StatusCode == 404 {
return ver, fmt.Errorf("No releases available :[")
}
return ver, fmt.Errorf("Bad Request: " + string(b))
}

UpdateErrored = false
// fmt.Println(string(b))

var gh map[string]interface{}
err = json.Unmarshal(b, &gh)
if err != nil {
return ver, err
}

nameI, ok := gh["name"]
if !ok {
return ver, fmt.Errorf("Internal Error: could not find version in update check response")
}
name, ok := nameI.(string)
if !ok {
return ver, fmt.Errorf("Internal Error: version is not a string in update check response")
}
ver.Version = name

if !manual {
UpdateChecked = true

// Semver Check?
if ver.Version != cur.Version && cur.Version != "vLocal" {
UpdateAvailable = &ver
}

return ver, nil
}

// This goes here and signals else where that we got the request back
UpdateChecked = true

// Semver Check?
if ver.Version != cur.Version && cur.Version != "vLocal" {
UpdateAvailable = &ver
aI, ok := gh["assets"]
if ok {
a, aok := aI.([]interface{})
if aok {
UpdateData = a
}
}
}

return ver, nil
}

func WaitPrintUpdateAvail() {
for i := 0; i < 20 && !UpdateStarted && !UpdateChecked && !UpdateErrored; i++ {
time.Sleep(50 * time.Millisecond)
}
PrintUpdateAvailable()
}

func PrintUpdateAvailable() {
if UpdateChecked && UpdateAvailable != nil {
fmt.Printf(updateMessage, verinfo.Version, UpdateAvailable.Version)
}
}

func InstallLatest() (err error) {
fmt.Printf("Installing mvs@%s\n", UpdateAvailable.Version)

if UpdateData == nil {
return fmt.Errorf("No update available")
}
/*
vers, err := json.MarshalIndent(UpdateData, "", " ")
if err == nil {
fmt.Println(string(vers))
}
*/

fmt.Println("OS/Arch", verinfo.BuildOS, verinfo.BuildArch)

url := ""
for _, Asset := range UpdateData {
asset := Asset.(map[string]interface{})
U := asset["browser_download_url"].(string)
u := strings.ToLower(U)

osOk, archOk := false, false

switch verinfo.BuildOS {
case "linux":
if strings.Contains(u, "linux") {
osOk = true
}

case "darwin":
if strings.Contains(u, "darwin") {
osOk = true
}

case "windows":
if strings.Contains(u, "windows") {
osOk = true
}
}

switch verinfo.BuildArch {
case "amd64":
if strings.Contains(u, "x86_64") {
archOk = true
}
case "arm64":
if strings.Contains(u, "arm64") {
archOk = true
}
case "arm":
if strings.Contains(u, "arm") && !strings.Contains(u, "arm64") {
archOk = true
}
}

if osOk && archOk {
url = u
break
}
}

fmt.Println("Download URL: ", url, "\n")

switch verinfo.BuildOS {
case "linux":
fallthrough
case "darwin":

return downloadAndInstall(url)

case "windows":
fmt.Println("Please downlaod and install manually from the link above.\n")
return nil
}

return nil
}

func downloadAndInstall(url string) error {
req := gorequest.New().Get(url)
resp, content, errs := req.EndBytes()

check := "http2: server sent GOAWAY and closed the connection"
if len(errs) != 0 && !strings.Contains(errs[0].Error(), check) {
fmt.Println("errs:", errs)
fmt.Println("resp:", resp)
return errs[0]
}

if len(errs) != 0 || resp.StatusCode >= 400 {
return fmt.Errorf("Error %v - %s", resp.StatusCode, string(content))
}

tmpfile, err := ioutil.TempFile("", "example")
if err != nil {
return err
}

// defer os.Remove(tmpfile.Name()) // clean up

if _, err := tmpfile.Write(content); err != nil {
return err
}
if err := tmpfile.Close(); err != nil {
return err
}

ex, err := os.Executable()
if err != nil {
return err
}

real, err := filepath.EvalSymlinks(ex)
if err != nil {
return err
}

// Sudo copy the file
cmd := exec.Command("/bin/sh", "-c",
fmt.Sprintf("export OWNER=$(ls -l %s | awk '{ print $3 \":\" $4 }') && sudo mv %s %s.backup && sudo cp %s %s && sudo chown $OWNER %s && sudo chmod 0755 %s && sudo rm %s.backup",
real, // get owner
real, real, // mv
tmpfile.Name(), real, // cp
real, // chown
real, // chmod
real, // rm
),
)

// prep stdin for password
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}

go func() {
defer stdin.Close()
io.WriteString(stdin, "values written to stdin are passed to cmd's standard input")
}()

stdoutStderr, err := cmd.CombinedOutput()
fmt.Printf("%s\n", stdoutStderr)
if err != nil {
return err
}

UpdateAvailable = nil
UpdateData = nil
return nil
}
2 changes: 2 additions & 0 deletions cli.cue
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ HofGenCli: gen.#HofGenerator & {
}
}

Updates: true

Telemetry: "UA-103579574-5"
TelemetryIdDir: "hof"

Expand Down
Loading

0 comments on commit 644dca8

Please sign in to comment.