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

Make version check run as best effort #197

Merged
Merged
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
119 changes: 92 additions & 27 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"strconv"
"time"

"github.com/Masterminds/semver/v3"
"github.com/apex/log"
"github.com/apex/log/handlers/json"
"github.com/apex/log/handlers/multi"
Expand All @@ -43,10 +44,12 @@ var outputFormat string
const FSOC_NO_VERSION_CHECK = "FSOC_NO_VERSION_CHECK"

const (
secondsInDay = 24 * 60 * 60
timestampFileName = "fsoc.timestamp"
versionCheckInterval = 24 * 60 * 60 // 1 day
versionFileName = "fsoc.latest-version"
)

var updateChannel chan *semver.Version

// rootCmd represents the base command when called without any subcommands
// TODO: replace github link "for more info" with Cisco DevNet link for fsoc once published
var rootCmd = &cobra.Command{
Expand Down Expand Up @@ -77,6 +80,7 @@ For more information, see https://github.com/cisco-open/fsoc

NOTE: fsoc is in alpha; breaking changes may occur`,
PersistentPreRun: preExecHook,
PersistentPostRun: postExecHook,
TraverseChildren: true,
DisableAutoGenTag: true,
}
Expand Down Expand Up @@ -223,38 +227,27 @@ func preExecHook(cmd *cobra.Command, args []string) {
}

// Do version checking
noVerCheck, _ := cmd.Flags().GetBool("no-version-check")
envNoVerCheck, err := strconv.ParseBool(os.Getenv(FSOC_NO_VERSION_CHECK))
if err != nil {
envNoVerCheck = false
}
noVerCheck = noVerCheck || envNoVerCheck
updateCheckNeeded := !noVerCheck && int(time.Now().Unix())-getLastVersionCheckTime() > secondsInDay
if updateCheckNeeded {
func() {
defer func() {
if r := recover(); r != nil {
log.Warnf("Failed to perform version check")
}
}()
var updateSemVar = version.CheckForUpdate()
version.CompareAndLogVersions(updateSemVar)
// Create new timestamp file (only if version was checked)
_ = os.Remove(getTimestampFilePath())
_, err := os.Create(getTimestampFilePath())
if err != nil {
log.Warnf("failed to create version check timestamp file: %v", err)
}
if versionCheckEnabled(cmd) && int(time.Now().Unix())-getLastVersionCheckTime() > versionCheckInterval {
updateChannel = make(chan *semver.Version)
go func() {
updateChannel <- version.CheckForUpdate()
}()
}
}

func getTimestampFilePath() string {
return os.TempDir() + "/" + timestampFileName
func postExecHook(cmd *cobra.Command, args []string) {
latestVersion := completeVersionCheck()
if versionCheckEnabled(cmd) {
reportNewVersionAvailable(latestVersion)
}
}

func getVersionFilePath() string {
return os.TempDir() + "/" + versionFileName
}

func getLastVersionCheckTime() int {
fInfo, err := os.Stat(getTimestampFilePath())
fInfo, err := os.Stat(getVersionFilePath())
if err != nil {
return 0 // makes it a really old file
}
Expand All @@ -270,3 +263,75 @@ func isCompletionCommand(cmd *cobra.Command) bool {
p := cmd.Parent()
return (p != nil && p.Name() == "completion")
}

func versionCheckEnabled(cmd *cobra.Command) bool {
noVerCheck, _ := cmd.Flags().GetBool("no-version-check")
if noVerCheck {
return false
}
envNoVerCheck, err := strconv.ParseBool(os.Getenv(FSOC_NO_VERSION_CHECK))
if err == nil && envNoVerCheck {
return false
}
return true
}

// Complete the version check if it was started, in a non-blocking way. Returns the latest version if we could get it
func completeVersionCheck() (latestVersion *semver.Version) {
if updateChannel == nil {
return nil // nothing to do
}

// See if we have a result, but don't block on it
select {
case latestVersion = <-updateChannel:
if latestVersion != nil {
// We got the latest version, store it in a file
f, err := os.Create(getVersionFilePath())
if err != nil {
log.Errorf("failed to create version file: %v", err)
} else {
_, err = f.WriteString(latestVersion.String())
if err != nil {
log.Errorf("failed to write to version file: %v", err)
}
f.Close()
}
}
default:
log.Infof("Did not finish checking for latest version in time, will try next time")
}

return latestVersion
}

func reportNewVersionAvailable(latestVersion *semver.Version) {
// If we did not do a successful version check, try to read from file, if it exists
if latestVersion == nil {
_, err := os.Stat(getVersionFilePath())
if err != nil {
return // no file, no version
}
f, err := os.Open(getVersionFilePath())
if err != nil {
log.Warnf("failed to open fsoc version file: %v", err)
} else {
defer f.Close()
// Read version from file
var versionString string
_, err = fmt.Fscanf(f, "%s", &versionString)
if err != nil {
log.Warnf("failed to read from fsoc version file: %v", err)
} else {
latestVersion, err = semver.NewVersion(versionString)
if err != nil {
log.Warnf("failed to parse fsoc version file: %v", err)
}
}
}
}
if latestVersion != nil {
version.CompareAndLogVersions(latestVersion)
}

}