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 7c09b00
Show file tree
Hide file tree
Showing 5 changed files with 227 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
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ module github.com/thedevsaddam/docgen
go 1.12

require (
github.com/Masterminds/semver v1.5.0
github.com/briandowns/spinner v1.18.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
11 changes: 11 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 All @@ -64,6 +66,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/briandowns/spinner v1.18.0 h1:SJs0maNOs4FqhBwiJ3Gr7Z1D39/rukIVGQvpNZVHVcM=
github.com/briandowns/spinner v1.18.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
Expand Down Expand Up @@ -103,6 +107,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
Expand Down Expand Up @@ -231,6 +236,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 All @@ -243,15 +250,18 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
Expand Down Expand Up @@ -528,6 +538,7 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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://api.github.com/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)
}
76 changes: 76 additions & 0 deletions update/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package update

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

"github.com/Masterminds/semver"
"github.com/briandowns/spinner"
)

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)

s := spinner.New(spinner.CharSets[70], 100*time.Millisecond, spinner.WithHiddenCursor(true)) // code:39 is earth for the lib
s.Prefix = fmt.Sprintf("Updating from %s to %s ( ", version, releaseInfo.Name)
s.Suffix = ")"
s.Start()
name := fmt.Sprintf("%s_%s", os, arch)
if os == "windows" {
name = name + ".exe"
}
err = updateBinary(ctx, releaseInfo.getDownloadURL(name))
s.Stop()
fmt.Println()
fmt.Println("Update includes:")
fmt.Print(releaseInfo.Body)
fmt.Println()

return err
}

0 comments on commit 7c09b00

Please sign in to comment.