Skip to content

Commit

Permalink
Add binary auto-updater
Browse files Browse the repository at this point in the history
  • Loading branch information
thedevsaddam committed Jan 25, 2022
1 parent 332f21b commit 6a069ac
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 0 deletions.
11 changes: 11 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package cmd

import (
"bytes"
"context"
"fmt"
"io/ioutil"
"log"
"os"
"text/template"

"github.com/spf13/cobra"
"github.com/thedevsaddam/docgen/collection"
"github.com/thedevsaddam/docgen/update"
)

const logo = `
Expand Down Expand Up @@ -38,6 +41,7 @@ var (
Use: "docgen",
Short: "Generate documentation from Postman JSON collection",
Long: logo,
Run: rootFunc,
}
)

Expand Down Expand Up @@ -84,6 +88,13 @@ func Execute() error {
return cmd.Execute()
}

func rootFunc(cmd *cobra.Command, args []string) {
err := update.SelfUpdate(context.Background(), BuildDate, Version)
if err != nil {
fmt.Println("Error: failed to update docgen:", err) //this error can be skipped
}
}

func handleErr(message string, err error) {
if err != nil {
log.Fatal(message, err)
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module github.com/thedevsaddam/docgen
go 1.12

require (
github.com/Masterminds/semver v1.5.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
Expand Down Expand Up @@ -231,6 +233,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
Expand Down
126 changes: 126 additions & 0 deletions update/github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package update

import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"os"
"path/filepath"
"time"

"github.com/kardianos/osext"
)

type (
// ReleaseInfo represents github latest release info
ReleaseInfo struct {
TagName string `json:"tag_name"`
Name string `json:"name"`
Body string `json:"body"`
Draft bool `json:"draft"`
Prerelease bool `json:"prerelease"`
CreatedAt time.Time `json:"created_at"`
PublishedAt time.Time `json:"published_at"`
Assets []Asset `json:"assets"`
}
Asset struct {
Name string `json:"name"`
Size int `json:"size"`
DownloadCount int `json:"download_count"`
BrowserDownloadURL string `json:"browser_download_url"`
}
)

func (r *ReleaseInfo) getDownloadURL(name string) string {
for _, a := range r.Assets {
if a.Name == name {
return a.BrowserDownloadURL
}
}
return ""
}

// fetchReleaseInfo return githublatest release info
func fetchReleaseInfo(ctx context.Context) (*ReleaseInfo, error) {
url := "https://github.com/gitapi/repos/thedevsaddam/docgen/releases/latest"
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, errors.New("update: failed to fetch latest release")
}

rf := ReleaseInfo{}
if err := json.NewDecoder(resp.Body).Decode(&rf); err != nil {
return nil, err
}
return &rf, nil
}

// updateBinary download the binary in current binary and replace the old one
func updateBinary(ctx context.Context, url string) error {
if url == "" {
return errors.New("update: empty download url")
}
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return errors.New("update: failed to fetch binary file")
}

dir, err := osext.ExecutableFolder()
if err != nil {
return err
}

fileName := filepath.Join(dir, filepath.Base(os.Args[0]))
tmpFile := fileName + ".tmp"

if err := os.Chmod(fileName, 0777); err != nil {
return err
}

if err := os.Rename(fileName, tmpFile); err != nil {
return err
}

f, err := os.Create(fileName)
if err != nil {
return err
}

if err := os.Chmod(fileName, 0777); err != nil {
return err
}

_, err = io.Copy(f, resp.Body)
if err != nil {
if err := os.Rename(tmpFile, fileName); err != nil {
return err
}
return err
}

return os.Remove(tmpFile)
}
77 changes: 77 additions & 0 deletions update/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package update

import (
"context"
"errors"
"fmt"
"runtime"
"time"

"github.com/Masterminds/semver"
)

const (
layoutISO = "2006-01-02"
updateInDays = 7
)

// SelfUpdate update the application to its latest version
// if the current release is 3days old and has a new update
func SelfUpdate(ctx context.Context, buildDate, version string) error {
if buildDate == "unknown" {
return errors.New("update: unable to update based on unkown build date/version")
}
currBinaryReleaseDate, err := time.Parse(layoutISO, buildDate)
if err != nil {
return fmt.Errorf("update: %v", err)
}
if (time.Since(currBinaryReleaseDate).Hours() / 24) <= updateInDays {
return nil
}

releaseInfo, err := fetchReleaseInfo(ctx)
if err != nil {
return fmt.Errorf("update: %v", err)
}

if releaseInfo.Draft || releaseInfo.Prerelease {
return nil
}

c, err := semver.NewConstraint(">" + version)
if err != nil {
return fmt.Errorf("update: %v", err)
}

v, err := semver.NewVersion(releaseInfo.TagName)
if err != nil {
return fmt.Errorf("update: %v", err)
}
// Check if the version meets the constraints. The a variable will be true.
if !c.Check(v) {
return nil
}

os := runtime.GOOS
arch := runtime.GOARCH

fmt.Println("Found newer version:", releaseInfo.TagName)
fmt.Printf("Updating from %s to %s\n", version, releaseInfo.Name)

name := fmt.Sprintf("%s_%s", os, arch)
if os == "windows" {
name = name + ".exe"
}

err = updateBinary(ctx, releaseInfo.getDownloadURL(name))
if err != nil {
return err
}

fmt.Println()
fmt.Println("Update includes:")
fmt.Print(releaseInfo.Body)
fmt.Println()

return err
}

0 comments on commit 6a069ac

Please sign in to comment.