-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #142 from denopink/denopink/check-for-latest-version
Check for latest version of itself
- Loading branch information
Showing
6 changed files
with
222 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,4 +22,8 @@ target | |
.scntest | ||
|
||
# macOS | ||
.DS_Store | ||
.DS_Store | ||
|
||
# visual code | ||
.vscode/ | ||
*.code-workspace |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,48 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"os" | ||
"time" | ||
|
||
"github.com/suborbital/subo/subo/release" | ||
"github.com/suborbital/subo/subo/util" | ||
) | ||
|
||
const checkVersionTimeout = 500 * time.Millisecond | ||
|
||
func main() { | ||
rootCmd := rootCommand() | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
done := checkVersion(ctx) | ||
|
||
rootCmd := rootCommand() | ||
if err := rootCmd.Execute(); err != nil { | ||
os.Exit(-1) | ||
} | ||
|
||
select { | ||
case <-done: | ||
case <-time.After(checkVersionTimeout): | ||
util.LogFail("failed to CheckForLatestVersion due to timeout") | ||
} | ||
} | ||
|
||
func checkVersion(ctx context.Context) chan bool { | ||
done := make(chan bool) | ||
|
||
go func() { | ||
if version, err := release.CheckForLatestVersion(); err != nil { | ||
util.LogFail(err.Error()) | ||
} else if version != "" { | ||
util.LogInfo(version) | ||
} | ||
select { | ||
case <-ctx.Done(): | ||
default: | ||
done <- true | ||
} | ||
}() | ||
|
||
return done | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package release | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/gob" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"time" | ||
|
||
"github.com/google/go-github/v41/github" | ||
"github.com/hashicorp/go-version" | ||
"github.com/pkg/errors" | ||
"github.com/suborbital/subo/subo/util" | ||
) | ||
|
||
const lastCheckedFilename = "subo_last_checked" | ||
const latestReleaseFilename = "subo_latest_release" | ||
|
||
func getTimestampCache() (time.Time, error) { | ||
cachePath, err := util.CacheDir() | ||
if err != nil { | ||
return time.Time{}, errors.Wrap(err, "failed to CacheDir") | ||
} | ||
|
||
cachedTimestamp := time.Time{} | ||
filePath := filepath.Join(cachePath, lastCheckedFilename) | ||
if _, err = os.Stat(filePath); os.IsNotExist(err) { | ||
} else if err != nil { | ||
return time.Time{}, errors.Wrap(err, "failed to Stat") | ||
} else { | ||
data, err := ioutil.ReadFile(filePath) | ||
if err != nil { | ||
return time.Time{}, errors.Wrap(err, "failed to ReadFile") | ||
} | ||
|
||
cachedTimestamp, err = time.Parse(time.RFC3339, string(data)) | ||
if err != nil { | ||
return time.Time{}, errors.Wrap(err, "failed to parse cached timestamp") | ||
} | ||
} | ||
return cachedTimestamp, nil | ||
} | ||
|
||
func cacheTimestamp(timestamp time.Time) error { | ||
cachePath, err := util.CacheDir() | ||
if err != nil { | ||
return errors.Wrap(err, "failed to CacheDir") | ||
} | ||
|
||
filePath := filepath.Join(cachePath, lastCheckedFilename) | ||
data := []byte(timestamp.Format(time.RFC3339)) | ||
if err := ioutil.WriteFile(filePath, data, os.ModePerm); err != nil { | ||
return errors.Wrap(err, "failed to WriteFile") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func getLatestReleaseCache() (*github.RepositoryRelease, error) { | ||
if cachedTimestamp, err := getTimestampCache(); err != nil { | ||
return nil, errors.Wrap(err, "failed to getTimestampCache") | ||
} else if currentTimestamp := time.Now().UTC(); cachedTimestamp.IsZero() || currentTimestamp.After(cachedTimestamp.Add(time.Hour)) { | ||
// check if 1 hour has passed since the last version check, and update the cached timestamp and latest release if so | ||
if err := cacheTimestamp(currentTimestamp); err != nil { | ||
return nil, errors.Wrap(err, "failed to cacheTimestamp") | ||
} | ||
|
||
return nil, nil | ||
} | ||
|
||
cachePath, err := util.CacheDir() | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to CacheDir") | ||
} | ||
|
||
var latestRepoRelease *github.RepositoryRelease | ||
filepath := filepath.Join(cachePath, latestReleaseFilename) | ||
if _, err = os.Stat(filepath); os.IsNotExist(err) { | ||
return nil, nil | ||
} else if err != nil { | ||
return nil, errors.Wrap(err, "faild to Stat") | ||
} else { | ||
data, err := ioutil.ReadFile(filepath) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to ReadFile") | ||
} | ||
|
||
buffer := bytes.Buffer{} | ||
buffer.Write(data) | ||
decoder := gob.NewDecoder(&buffer) | ||
err = decoder.Decode(&latestRepoRelease) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to Decode cached RepositoryRelease") | ||
} | ||
} | ||
|
||
return latestRepoRelease, nil | ||
} | ||
|
||
func cacheLatestRelease(latestRepoRelease *github.RepositoryRelease) error { | ||
cachePath, err := util.CacheDir() | ||
if err != nil { | ||
return errors.Wrap(err, "failed to CacheDir") | ||
} | ||
|
||
buffer := bytes.Buffer{} | ||
encoder := gob.NewEncoder(&buffer) | ||
if err = encoder.Encode(latestRepoRelease); err != nil { | ||
return errors.Wrap(err, "failed to Encode RepositoryRelease") | ||
} else if err := ioutil.WriteFile(filepath.Join(cachePath, latestReleaseFilename), buffer.Bytes(), os.ModePerm); err != nil { | ||
return errors.Wrap(err, "failed to WriteFile") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func getLatestVersion() (*version.Version, error) { | ||
latestRepoRelease, err := getLatestReleaseCache() | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to getTimestampCache") | ||
} else if latestRepoRelease == nil { | ||
latestRepoRelease, _, err = github.NewClient(nil).Repositories.GetLatestRelease(context.Background(), "suborbital", "subo") | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to fetch latest subo release") | ||
} else if err = cacheLatestRelease(latestRepoRelease); err != nil { | ||
return nil, errors.Wrap(err, "failed to cacheLatestRelease") | ||
} | ||
} | ||
|
||
latestVersion, err := version.NewVersion(*latestRepoRelease.TagName) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to parse latest subo version") | ||
} | ||
|
||
return latestVersion, nil | ||
} | ||
|
||
// CheckForLatestVersion returns an error if SuboDotVersion does not match the latest GitHub release or if the check fails | ||
func CheckForLatestVersion() (string, error) { | ||
if latestCmdVersion, err := getLatestVersion(); err != nil { | ||
return "", errors.Wrap(err, "failed to getLatestVersion") | ||
} else if cmdVersion, err := version.NewVersion(SuboDotVersion); err != nil { | ||
return "", errors.Wrap(err, "failed to parse current subo version") | ||
} else if cmdVersion.LessThan(latestCmdVersion) { | ||
return fmt.Sprintf("An upgrade for subo is available: %s → %s\n", cmdVersion, latestCmdVersion), nil | ||
} | ||
|
||
return "", nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package util | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
// CacheDir returns the cache directory and creates it if it doesn't exist | ||
func CacheDir() (string, error) { | ||
targetPath := filepath.Join(os.TempDir(), "suborbital", "subo") | ||
|
||
if _, err := os.Stat(targetPath); os.IsNotExist(err) { | ||
if err := os.MkdirAll(targetPath, os.ModePerm); err != nil { | ||
return "", errors.Wrap(err, "failed to MkdirAll") | ||
} | ||
} | ||
return targetPath, nil | ||
} |