From 4b8a74c996457c39518aa5284bcbb76ba3b6b29f Mon Sep 17 00:00:00 2001 From: GDW1 Date: Thu, 29 Jun 2023 16:03:16 -0700 Subject: [PATCH 01/16] Filter and Print options for queries. example shown with fsoc solution list --- cmd/solution/list.go | 7 ++++--- cmdkit/fetch_and_print.go | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/cmd/solution/list.go b/cmd/solution/list.go index f49f46cf..265134b5 100644 --- a/cmd/solution/list.go +++ b/cmd/solution/list.go @@ -71,13 +71,14 @@ func getSolutionList(cmd *cobra.Command, args []string) { // get data and display solutionBaseURL := getSolutionListUrl() + var filters []string if subscribed { - solutionBaseURL += "?filter=" + url.QueryEscape("data.isSubscribed eq true") + filters = []string{"filter=" + url.QueryEscape("data.isSubscribed eq true")} } else if unsubscribed { - solutionBaseURL += "?filter=" + url.QueryEscape("data.isSubscribed ne true") + filters = []string{"filter=" + url.QueryEscape("data.isSubscribed ne true")} } println(solutionBaseURL) - cmdkit.FetchAndPrint(cmd, solutionBaseURL, &cmdkit.FetchAndPrintOptions{Headers: headers, IsCollection: true}) + cmdkit.FetchAndPrint(cmd, solutionBaseURL, &cmdkit.FetchAndPrintOptions{Headers: headers, IsCollection: true, Filters: filters}) } func getSolutionListUrl() string { diff --git a/cmdkit/fetch_and_print.go b/cmdkit/fetch_and_print.go index 82cb88f2..36dcc78b 100644 --- a/cmdkit/fetch_and_print.go +++ b/cmdkit/fetch_and_print.go @@ -16,6 +16,7 @@ package cmdkit import ( "reflect" + "strings" "github.com/apex/log" "github.com/spf13/cobra" @@ -30,6 +31,7 @@ type FetchAndPrintOptions struct { Body any // body to send with the request (nil for no body) ResponseType *reflect.Type // structure type to parse response into (for schema validation & fields) (nil for none) IsCollection bool // set to true for GET to request a collection that may be paginated (see platform/api/collection.go) + Filters []string } // FetchAndPrint consolidates the common sequence of fetching from the server and @@ -61,6 +63,19 @@ func FetchAndPrint(cmd *cobra.Command, path string, options *FetchAndPrintOption // fetch data var err error + + if options.Filters != nil { + // If there are filters, apply them to query path + numberOfFilters := len(strings.Split(path, "?")) + if numberOfFilters != 1 && numberOfFilters != 0 { + // Case 1: There is already a query in path append to the path + path += "&" + strings.Join(options.Filters, "&") + } else { + // Case 2: There is no query in path + path += "?" + strings.Join(options.Filters, "&") + } + } + if options != nil && options.IsCollection { if method != "GET" { log.Fatalf("bug: cannot request %q for a collection at %q, only GET is supported for collections", method, path) From 244053e0a98a0e206ef71c79a5c320615b2490e7 Mon Sep 17 00:00:00 2001 From: GDW1 Date: Fri, 7 Jul 2023 10:22:44 -0700 Subject: [PATCH 02/16] version checking draft --- cmd/root.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++-- go.mod | 4 +++ go.sum | 8 +++++ 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index a9147da8..51a27e25 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,16 +17,21 @@ package cmd import ( "context" + "errors" "fmt" - "os" - "path" - "github.com/apex/log" "github.com/apex/log/handlers/json" "github.com/apex/log/handlers/multi" + "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" + "github.com/tcnksm/go-latest" + "os" + "path" + "path/filepath" + "strings" + "time" "github.com/cisco-open/fsoc/cmd/config" "github.com/cisco-open/fsoc/cmd/version" @@ -69,6 +74,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, } @@ -215,6 +221,11 @@ func preExecHook(cmd *cobra.Command, args []string) { "existing": exists, }). Info("fsoc context") + lastCommand := viper.GetInt64("lastRun") + if time.Now().Unix()-lastCommand >= 86400 { + // Check for Update + checkForUpdate() + } } else { if bypass { log.Infof("Unable to read config file (%v), proceeding without a config", err) @@ -224,6 +235,74 @@ func preExecHook(cmd *cobra.Command, args []string) { } } +func checkForUpdate() { + githubTag := &latest.GithubTag{ + Owner: "cisco-open", + Repository: "fsoc", + } + + //println(version.GetVersion().Version) + currentVer := "0.1.0" //TODO: Get Ver here + + res, err := latest.Check(githubTag, currentVer) + if err != nil { + log.Fatalf(err.Error()) + } + if res.Outdated { + fmt.Printf("%s is not latest, you should upgrade to %s", currentVer, res.Current) + } + +} + +func postExecHook(cmd *cobra.Command, args []string) { + // Record Last Run + viper.Set("lastRun", time.Now().Unix()) + + // set up config file in viper + viper.SetConfigType("yaml") + if viper.ConfigFileUsed() == "" { + home, _ := os.UserHomeDir() + configFileLocation := strings.Replace("~/.fsoc", "~", home, 1) + viper.SetConfigFile(configFileLocation) + } + viper.SetConfigPermissions(0600) // o=rw + + // ensure file exists (viper fails to create it, likely a bug in viper) + ensureConfigFile() + + // update file contents + err := viper.WriteConfig() + if err != nil { + log.Fatalf("failed to write config file %q: %v", viper.ConfigFileUsed(), err) + } +} + +func ensureConfigFile() { + appFs := afero.NewOsFs() + + // finalize the path to use + var fileLoc = viper.ConfigFileUsed() + if strings.Contains(fileLoc[:2], "~/") { + homeDir, _ := os.UserHomeDir() + fileLoc = strings.Replace(fileLoc, "~", homeDir, 1) + } + configPath, _ := filepath.Abs(fileLoc) + + // try to open the file, create it if it doesn't exist + _, err := appFs.Open(configPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + _, err = appFs.Create(configPath) + if err != nil { + log.Fatalf("failed to create config file %q: %v", configPath, err) + } + viper.SetConfigFile(configPath) + } else { + log.Fatalf("failed to open config file %q: %v", configPath, err) + } + } +} + func bypassConfig(cmd *cobra.Command) bool { _, bypassConfig := cmd.Annotations[config.AnnotationForConfigBypass] return bypassConfig diff --git a/go.mod b/go.mod index 41ba9271..d9991344 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 + github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e github.com/xeipuuv/gojsonschema v1.2.0 go.opentelemetry.io/proto/otlp v0.20.0 go.pinniped.dev v0.24.0 @@ -32,7 +33,10 @@ require ( require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/go-github v17.0.0+incompatible // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index ae272ca5..de715fc5 100644 --- a/go.sum +++ b/go.sum @@ -127,6 +127,10 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -152,6 +156,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -270,6 +276,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e h1:IWllFTiDjjLIf2oeKxpIUmtiDV5sn71VgeQgg6vcE7k= +github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM= github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= From a92a4c85c53cad49bcfa03332d7ba7169514e349 Mon Sep 17 00:00:00 2001 From: GDW1 Date: Mon, 10 Jul 2023 14:16:16 -0700 Subject: [PATCH 03/16] Warn if upgrade is avilable every 24 hours --- cmd/root.go | 159 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 98 insertions(+), 61 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 51a27e25..7f395dd7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,26 +17,25 @@ package cmd import ( "context" - "errors" "fmt" "github.com/apex/log" "github.com/apex/log/handlers/json" "github.com/apex/log/handlers/multi" - "github.com/spf13/afero" + "github.com/cisco-open/fsoc/cmd/config" + "github.com/cisco-open/fsoc/cmd/version" + "github.com/cisco-open/fsoc/logfilter" + "github.com/cisco-open/fsoc/platform/api" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/tcnksm/go-latest" + "io/fs" + "net/http" "os" "path" "path/filepath" + "strconv" "strings" "time" - - "github.com/cisco-open/fsoc/cmd/config" - "github.com/cisco-open/fsoc/cmd/version" - "github.com/cisco-open/fsoc/logfilter" - "github.com/cisco-open/fsoc/platform/api" ) var cfgFile string @@ -221,11 +220,6 @@ func preExecHook(cmd *cobra.Command, args []string) { "existing": exists, }). Info("fsoc context") - lastCommand := viper.GetInt64("lastRun") - if time.Now().Unix()-lastCommand >= 86400 { - // Check for Update - checkForUpdate() - } } else { if bypass { log.Infof("Unable to read config file (%v), proceeding without a config", err) @@ -233,73 +227,116 @@ func preExecHook(cmd *cobra.Command, args []string) { log.Fatalf("fsoc is not configured, please use \"fsoc config set\" to configure an initial context") } } -} - -func checkForUpdate() { - githubTag := &latest.GithubTag{ - Owner: "cisco-open", - Repository: "fsoc", + if time.Now().Unix()-getRecentTimestamp() > 86000 { + checkForUpdate() } +} - //println(version.GetVersion().Version) - currentVer := "0.1.0" //TODO: Get Ver here - - res, err := latest.Check(githubTag, currentVer) +func getRecentTimestamp() int64 { + potentialFiles, err := findFiles(os.TempDir()) if err != nil { log.Fatalf(err.Error()) } - if res.Outdated { - fmt.Printf("%s is not latest, you should upgrade to %s", currentVer, res.Current) + var largestTimestamp int64 = 0 + for i := 0; i < len(potentialFiles); i++ { + stringSections := strings.Split(potentialFiles[i].Name(), "/") + desiredSections := strings.Split(stringSections[len(stringSections)-1], "fsoctimestamp") + newTimestamp, _ := strconv.ParseInt(desiredSections[0], 10, 64) + if largestTimestamp < newTimestamp { + largestTimestamp = newTimestamp + } + } + + for i := 0; i < len(potentialFiles); i++ { + err := os.Remove(potentialFiles[i].Name()) + if err != nil { + log.Fatalf(err.Error()) + } } + return largestTimestamp } -func postExecHook(cmd *cobra.Command, args []string) { - // Record Last Run - viper.Set("lastRun", time.Now().Unix()) +func findFiles(root string) ([]*os.File, error) { + var files []*os.File + err := filepath.WalkDir(root, func(pathh string, d fs.DirEntry, err error) error { + if !d.IsDir() { + if ok := strings.Contains(pathh, "fsoctimestamp"); ok && err == nil { + file, err := os.Open(pathh) + if err != nil { + log.Fatalf(err.Error()) + } + files = append(files, file) + } + } + return nil + }) + return files, err +} - // set up config file in viper - viper.SetConfigType("yaml") - if viper.ConfigFileUsed() == "" { - home, _ := os.UserHomeDir() - configFileLocation := strings.Replace("~/.fsoc", "~", home, 1) - viper.SetConfigFile(configFileLocation) +func checkForUpdate() { + newestVersionChan := make(chan string) + go func() { + err := getVersion(newestVersionChan) + if err != nil { + log.Fatalf(err.Error()) + } + }() + var newestVersion string + newestVersion = <-newestVersionChan + if compareVersion(newestVersion) { + log.Warnf("There is a newer version of FSOC available, please upgrade to version %s", newestVersion) } - viper.SetConfigPermissions(0600) // o=rw - - // ensure file exists (viper fails to create it, likely a bug in viper) - ensureConfigFile() +} - // update file contents - err := viper.WriteConfig() +func getVersion(ver chan string) error { + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + resp, err := client.Get("https://github.com/cisco-open/fsoc/releases/latest") if err != nil { - log.Fatalf("failed to write config file %q: %v", viper.ConfigFileUsed(), err) + return err } -} -func ensureConfigFile() { - appFs := afero.NewOsFs() + split := strings.Split(resp.Header.Get("Location"), "/") + ver <- split[len(split)-1] + return nil +} - // finalize the path to use - var fileLoc = viper.ConfigFileUsed() - if strings.Contains(fileLoc[:2], "~/") { - homeDir, _ := os.UserHomeDir() - fileLoc = strings.Replace(fileLoc, "~", homeDir, 1) +func compareVersion(newestVersion string) bool { // Returns if the parameter is a newer version than the installed one + currentVersion := version.GetVersion() + newestVersionMajor, newestVersionMinor, newestVersionPatch := parseVersion(newestVersion) + newerMajorVersion := uint(newestVersionMajor) > currentVersion.VersionMajor + newerMinorVersion := uint(newestVersionMinor) > currentVersion.VersionMinor + newerPatchVersion := uint(newestVersionPatch) > currentVersion.VersionPatch + if newerMajorVersion { + return true } - configPath, _ := filepath.Abs(fileLoc) + if newerMinorVersion { + return true + } + if newerPatchVersion { + return true + } + return false +} + +func parseVersion(version string) (uint64, uint64, uint64) { + version = version[1:] + versionSections := strings.Split(version, ".") + major, _ := strconv.ParseUint(versionSections[0], 10, 32) + minor, _ := strconv.ParseUint(versionSections[1], 10, 32) + patch, _ := strconv.ParseUint(versionSections[2], 10, 32) + return major, minor, patch +} - // try to open the file, create it if it doesn't exist - _, err := appFs.Open(configPath) +func postExecHook(cmd *cobra.Command, args []string) { + // Create File + _, err := os.CreateTemp(os.TempDir(), strconv.FormatInt(time.Now().Unix(), 10)+"fsoctimestamp") if err != nil { - if errors.Is(err, os.ErrNotExist) { - _, err = appFs.Create(configPath) - if err != nil { - log.Fatalf("failed to create config file %q: %v", configPath, err) - } - viper.SetConfigFile(configPath) - } else { - log.Fatalf("failed to open config file %q: %v", configPath, err) - } + log.Fatalf(err.Error()) } } From 1e59827629fee03058e6087e96852c57b5ccece8 Mon Sep 17 00:00:00 2001 From: GDW1 Date: Mon, 10 Jul 2023 14:17:58 -0700 Subject: [PATCH 04/16] Warn if upgrade is avilable every 24 hours --- cmd/root.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 7f395dd7..76db180e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -18,16 +18,6 @@ package cmd import ( "context" "fmt" - "github.com/apex/log" - "github.com/apex/log/handlers/json" - "github.com/apex/log/handlers/multi" - "github.com/cisco-open/fsoc/cmd/config" - "github.com/cisco-open/fsoc/cmd/version" - "github.com/cisco-open/fsoc/logfilter" - "github.com/cisco-open/fsoc/platform/api" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" "io/fs" "net/http" "os" @@ -36,6 +26,18 @@ import ( "strconv" "strings" "time" + + "github.com/apex/log" + "github.com/apex/log/handlers/json" + "github.com/apex/log/handlers/multi" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/cisco-open/fsoc/cmd/config" + "github.com/cisco-open/fsoc/cmd/version" + "github.com/cisco-open/fsoc/logfilter" + "github.com/cisco-open/fsoc/platform/api" ) var cfgFile string @@ -282,8 +284,7 @@ func checkForUpdate() { log.Fatalf(err.Error()) } }() - var newestVersion string - newestVersion = <-newestVersionChan + var newestVersion = <-newestVersionChan if compareVersion(newestVersion) { log.Warnf("There is a newer version of FSOC available, please upgrade to version %s", newestVersion) } From 97be983c88ce02fd32333953396c891ebcee72b3 Mon Sep 17 00:00:00 2001 From: GDW1 Date: Mon, 10 Jul 2023 14:24:57 -0700 Subject: [PATCH 05/16] Warn if upgrade is avilable every 24 hours --- cmd/solution/list.go | 7 +++---- cmdkit/fetch_and_print.go | 15 --------------- go.mod | 6 +----- go.sum | 10 +--------- 4 files changed, 5 insertions(+), 33 deletions(-) diff --git a/cmd/solution/list.go b/cmd/solution/list.go index 265134b5..33d9bf67 100644 --- a/cmd/solution/list.go +++ b/cmd/solution/list.go @@ -71,14 +71,13 @@ func getSolutionList(cmd *cobra.Command, args []string) { // get data and display solutionBaseURL := getSolutionListUrl() - var filters []string if subscribed { - filters = []string{"filter=" + url.QueryEscape("data.isSubscribed eq true")} + solutionBaseURL += "?filter=" + url.QueryEscape("data.isSubscribed eq true")} } else if unsubscribed { - filters = []string{"filter=" + url.QueryEscape("data.isSubscribed ne true")} + solutionBaseURL += "?filter=" + url.QueryEscape("data.isSubscribed ne true")} } println(solutionBaseURL) - cmdkit.FetchAndPrint(cmd, solutionBaseURL, &cmdkit.FetchAndPrintOptions{Headers: headers, IsCollection: true, Filters: filters}) + cmdkit.FetchAndPrint(cmd, solutionBaseURL, &cmdkit.FetchAndPrintOptions{Headers: headers, IsCollection: true}) } func getSolutionListUrl() string { diff --git a/cmdkit/fetch_and_print.go b/cmdkit/fetch_and_print.go index 36dcc78b..82cb88f2 100644 --- a/cmdkit/fetch_and_print.go +++ b/cmdkit/fetch_and_print.go @@ -16,7 +16,6 @@ package cmdkit import ( "reflect" - "strings" "github.com/apex/log" "github.com/spf13/cobra" @@ -31,7 +30,6 @@ type FetchAndPrintOptions struct { Body any // body to send with the request (nil for no body) ResponseType *reflect.Type // structure type to parse response into (for schema validation & fields) (nil for none) IsCollection bool // set to true for GET to request a collection that may be paginated (see platform/api/collection.go) - Filters []string } // FetchAndPrint consolidates the common sequence of fetching from the server and @@ -63,19 +61,6 @@ func FetchAndPrint(cmd *cobra.Command, path string, options *FetchAndPrintOption // fetch data var err error - - if options.Filters != nil { - // If there are filters, apply them to query path - numberOfFilters := len(strings.Split(path, "?")) - if numberOfFilters != 1 && numberOfFilters != 0 { - // Case 1: There is already a query in path append to the path - path += "&" + strings.Join(options.Filters, "&") - } else { - // Case 2: There is no query in path - path += "?" + strings.Join(options.Filters, "&") - } - } - if options != nil && options.IsCollection { if method != "GET" { log.Fatalf("bug: cannot request %q for a collection at %q, only GET is supported for collections", method, path) diff --git a/go.mod b/go.mod index d9991344..a3674c3a 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,6 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 - github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e github.com/xeipuuv/gojsonschema v1.2.0 go.opentelemetry.io/proto/otlp v0.20.0 go.pinniped.dev v0.24.0 @@ -33,10 +32,7 @@ require ( require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/google/go-github v17.0.0+incompatible // indirect - github.com/google/go-querystring v1.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -77,4 +73,4 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.30.0 gopkg.in/ini.v1 v1.67.0 // indirect -) +) \ No newline at end of file diff --git a/go.sum b/go.sum index de715fc5..6c4b9752 100644 --- a/go.sum +++ b/go.sum @@ -127,10 +127,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -156,8 +152,6 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -276,8 +270,6 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e h1:IWllFTiDjjLIf2oeKxpIUmtiDV5sn71VgeQgg6vcE7k= -github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM= github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= @@ -643,4 +635,4 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= \ No newline at end of file From 3ad4193e3319523426019e44cf4efb77311c50c0 Mon Sep 17 00:00:00 2001 From: GDW1 Date: Mon, 10 Jul 2023 14:26:09 -0700 Subject: [PATCH 06/16] Warn if upgrade is avilable every 24 hours --- cmd/solution/list.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/solution/list.go b/cmd/solution/list.go index 33d9bf67..265134b5 100644 --- a/cmd/solution/list.go +++ b/cmd/solution/list.go @@ -71,13 +71,14 @@ func getSolutionList(cmd *cobra.Command, args []string) { // get data and display solutionBaseURL := getSolutionListUrl() + var filters []string if subscribed { - solutionBaseURL += "?filter=" + url.QueryEscape("data.isSubscribed eq true")} + filters = []string{"filter=" + url.QueryEscape("data.isSubscribed eq true")} } else if unsubscribed { - solutionBaseURL += "?filter=" + url.QueryEscape("data.isSubscribed ne true")} + filters = []string{"filter=" + url.QueryEscape("data.isSubscribed ne true")} } println(solutionBaseURL) - cmdkit.FetchAndPrint(cmd, solutionBaseURL, &cmdkit.FetchAndPrintOptions{Headers: headers, IsCollection: true}) + cmdkit.FetchAndPrint(cmd, solutionBaseURL, &cmdkit.FetchAndPrintOptions{Headers: headers, IsCollection: true, Filters: filters}) } func getSolutionListUrl() string { From 05b413d54c4041d58474c362f28c4025a64b7a9c Mon Sep 17 00:00:00 2001 From: GDW1 Date: Mon, 10 Jul 2023 14:27:18 -0700 Subject: [PATCH 07/16] Warn if upgrade is avilable every 24 hours --- cmd/solution/list.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/solution/list.go b/cmd/solution/list.go index 265134b5..f49f46cf 100644 --- a/cmd/solution/list.go +++ b/cmd/solution/list.go @@ -71,14 +71,13 @@ func getSolutionList(cmd *cobra.Command, args []string) { // get data and display solutionBaseURL := getSolutionListUrl() - var filters []string if subscribed { - filters = []string{"filter=" + url.QueryEscape("data.isSubscribed eq true")} + solutionBaseURL += "?filter=" + url.QueryEscape("data.isSubscribed eq true") } else if unsubscribed { - filters = []string{"filter=" + url.QueryEscape("data.isSubscribed ne true")} + solutionBaseURL += "?filter=" + url.QueryEscape("data.isSubscribed ne true") } println(solutionBaseURL) - cmdkit.FetchAndPrint(cmd, solutionBaseURL, &cmdkit.FetchAndPrintOptions{Headers: headers, IsCollection: true, Filters: filters}) + cmdkit.FetchAndPrint(cmd, solutionBaseURL, &cmdkit.FetchAndPrintOptions{Headers: headers, IsCollection: true}) } func getSolutionListUrl() string { From 62a47b524a336154dffc8ea8b8b13ea5e48682f9 Mon Sep 17 00:00:00 2001 From: GDW1 Date: Mon, 10 Jul 2023 14:36:13 -0700 Subject: [PATCH 08/16] Warn if upgrade is avilable every 24 hours --- go.mod | 2 +- go.sum | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index a3674c3a..41ba9271 100644 --- a/go.mod +++ b/go.mod @@ -73,4 +73,4 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.30.0 gopkg.in/ini.v1 v1.67.0 // indirect -) \ No newline at end of file +) diff --git a/go.sum b/go.sum index 6c4b9752..ae272ca5 100644 --- a/go.sum +++ b/go.sum @@ -635,4 +635,4 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= \ No newline at end of file +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 248e809f25884b4fceef2fd3484022fe696c3f74 Mon Sep 17 00:00:00 2001 From: GDW1 Date: Wed, 12 Jul 2023 14:15:58 -0700 Subject: [PATCH 09/16] refactored the versioning and the where the timestamps are stored --- cmd/root.go | 125 ++++++----------------------------------- cmd/version/version.go | 44 +++++++++++++++ 2 files changed, 61 insertions(+), 108 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 76db180e..29886d68 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -18,13 +18,8 @@ package cmd import ( "context" "fmt" - "io/fs" - "net/http" "os" "path" - "path/filepath" - "strconv" - "strings" "time" "github.com/apex/log" @@ -49,6 +44,10 @@ const ( FSOC_PROFILE_ENVVAR = "FSOC_PROFILE" ) +const ( + secondInDay = time.Hour * 24 +) + // 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{ @@ -75,7 +74,6 @@ 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, } @@ -100,6 +98,7 @@ func init() { rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Enable detailed output") rootCmd.PersistentFlags().Bool("curl", false, "Log curl equivalent for platform API calls (implies --verbose)") rootCmd.PersistentFlags().String("log", path.Join(os.TempDir(), "fsoc.log"), "determines the location of the fsoc log file") + rootCmd.PersistentFlags().Bool("no-update-warning", false, "Removes daily warning for updates") rootCmd.SetOut(os.Stdout) rootCmd.SetErr(os.Stderr) rootCmd.SetIn(os.Stdin) @@ -229,116 +228,26 @@ func preExecHook(cmd *cobra.Command, args []string) { log.Fatalf("fsoc is not configured, please use \"fsoc config set\" to configure an initial context") } } - if time.Now().Unix()-getRecentTimestamp() > 86000 { - checkForUpdate() - } -} - -func getRecentTimestamp() int64 { - potentialFiles, err := findFiles(os.TempDir()) - if err != nil { - log.Fatalf(err.Error()) - } - var largestTimestamp int64 = 0 - for i := 0; i < len(potentialFiles); i++ { - stringSections := strings.Split(potentialFiles[i].Name(), "/") - desiredSections := strings.Split(stringSections[len(stringSections)-1], "fsoctimestamp") - newTimestamp, _ := strconv.ParseInt(desiredSections[0], 10, 64) - if largestTimestamp < newTimestamp { - largestTimestamp = newTimestamp - } - } - - for i := 0; i < len(potentialFiles); i++ { - err := os.Remove(potentialFiles[i].Name()) - if err != nil { - log.Fatalf(err.Error()) - } - } - - return largestTimestamp -} - -func findFiles(root string) ([]*os.File, error) { - var files []*os.File - err := filepath.WalkDir(root, func(pathh string, d fs.DirEntry, err error) error { - if !d.IsDir() { - if ok := strings.Contains(pathh, "fsoctimestamp"); ok && err == nil { - file, err := os.Open(pathh) - if err != nil { - log.Fatalf(err.Error()) - } - files = append(files, file) - } - } - return nil - }) - return files, err -} - -func checkForUpdate() { - newestVersionChan := make(chan string) - go func() { - err := getVersion(newestVersionChan) - if err != nil { - log.Fatalf(err.Error()) - } - }() - var newestVersion = <-newestVersionChan - if compareVersion(newestVersion) { - log.Warnf("There is a newer version of FSOC available, please upgrade to version %s", newestVersion) - } -} -func getVersion(ver chan string) error { - client := &http.Client{ - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, + // Do version checking + noUpdateWarning, _ := cmd.Flags().GetBool("no-update-warning") + if int(time.Now().Unix())-getRecentTimestamp() > int(secondInDay) && !noUpdateWarning { + version.CheckForUpdate() } - resp, err := client.Get("https://github.com/cisco-open/fsoc/releases/latest") + // Create new timestamp file + _ = os.Remove(os.TempDir() + "fsoc.timestamp") + file, err = os.Create(os.TempDir() + "fsoc.timestamp") if err != nil { - return err - } - - split := strings.Split(resp.Header.Get("Location"), "/") - ver <- split[len(split)-1] - return nil -} - -func compareVersion(newestVersion string) bool { // Returns if the parameter is a newer version than the installed one - currentVersion := version.GetVersion() - newestVersionMajor, newestVersionMinor, newestVersionPatch := parseVersion(newestVersion) - newerMajorVersion := uint(newestVersionMajor) > currentVersion.VersionMajor - newerMinorVersion := uint(newestVersionMinor) > currentVersion.VersionMinor - newerPatchVersion := uint(newestVersionPatch) > currentVersion.VersionPatch - if newerMajorVersion { - return true - } - if newerMinorVersion { - return true - } - if newerPatchVersion { - return true + log.Fatalf(err.Error()) } - return false -} - -func parseVersion(version string) (uint64, uint64, uint64) { - version = version[1:] - versionSections := strings.Split(version, ".") - major, _ := strconv.ParseUint(versionSections[0], 10, 32) - minor, _ := strconv.ParseUint(versionSections[1], 10, 32) - patch, _ := strconv.ParseUint(versionSections[2], 10, 32) - return major, minor, patch } -func postExecHook(cmd *cobra.Command, args []string) { - // Create File - _, err := os.CreateTemp(os.TempDir(), strconv.FormatInt(time.Now().Unix(), 10)+"fsoctimestamp") +func getRecentTimestamp() int { + fInfo, err := os.Stat(os.TempDir() + "fsoc.timestamp") if err != nil { - log.Fatalf(err.Error()) + return 0 } + return fInfo.ModTime().Second() } func bypassConfig(cmd *cobra.Command) bool { diff --git a/cmd/version/version.go b/cmd/version/version.go index 0502533c..64618bf4 100644 --- a/cmd/version/version.go +++ b/cmd/version/version.go @@ -16,6 +16,10 @@ package version import ( "fmt" + "github.com/Masterminds/semver/v3" + "github.com/apex/log" + "net/http" + "strings" "github.com/spf13/cobra" @@ -64,3 +68,43 @@ func displayVersion(cmd *cobra.Command) { Detail: true, }) } + +func GetLatestVersion() (string, error) { + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { // no redirect + return http.ErrUseLastResponse + }, + } + resp, err := client.Get("https://github.com/cisco-open/fsoc/releases/latest") + if err != nil { + return "", err + } + split := strings.Split(resp.Header.Get("Location"), "/") + if len(split) < 1 { + return "", fmt.Errorf("version request did not return a version") + } + return split[len(split)-1], nil +} + +func CheckForUpdate() { + log.Infof("Checking for newer version of FSOC") + newestVersion, err := GetLatestVersion() + log.Infof("Latest version available: %s", newestVersion) + if err != nil { + log.Fatalf(err.Error()) + } + currentVersion := GetVersion() + currentVersionSemVer := semver.New( + uint64(currentVersion.VersionMajor), + uint64(currentVersion.VersionMajor), + uint64(currentVersion.VersionMajor), + currentVersion.VersionMeta, "") + newestVersionSemVar := semver.MustParse(newestVersion) + newerVersionAvailable := currentVersionSemVer.Compare(newestVersionSemVar) == -1 + var debugFields = log.Fields{"newerVersionAvailable": newerVersionAvailable, "oldVersion": currentVersionSemVer.String(), "newVersion": newestVersionSemVar.String()} + if newerVersionAvailable { + log.WithFields(debugFields).Warnf("There is a newer version of FSOC available, please upgrade from version %s to version %s", currentVersionSemVer.String(), newestVersionSemVar.String()) + } else { + log.WithFields(debugFields) + } +} From 877dddc7079fa0bf4d028b5915d3bae9c20a153a Mon Sep 17 00:00:00 2001 From: GDW1 Date: Wed, 12 Jul 2023 14:53:48 -0700 Subject: [PATCH 10/16] Async checking --- cmd/root.go | 18 ++++++++++++++++-- cmd/version/version.go | 14 ++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 29886d68..ae81ef21 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -18,6 +18,7 @@ package cmd import ( "context" "fmt" + "github.com/Masterminds/semver/v3" "os" "path" "time" @@ -48,6 +49,9 @@ const ( secondInDay = time.Hour * 24 ) +var updateChannel chan *semver.Version +var updateChecked bool + // 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{ @@ -74,6 +78,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, } @@ -231,8 +236,10 @@ func preExecHook(cmd *cobra.Command, args []string) { // Do version checking noUpdateWarning, _ := cmd.Flags().GetBool("no-update-warning") - if int(time.Now().Unix())-getRecentTimestamp() > int(secondInDay) && !noUpdateWarning { - version.CheckForUpdate() + updateChecked := int(time.Now().Unix())-getRecentTimestamp() > int(secondInDay) && !noUpdateWarning + if updateChecked { + updateChannel = make(chan *semver.Version) + go version.CheckForUpdate(updateChannel) } // Create new timestamp file _ = os.Remove(os.TempDir() + "fsoc.timestamp") @@ -250,6 +257,13 @@ func getRecentTimestamp() int { return fInfo.ModTime().Second() } +func postExecHook(cmd *cobra.Command, args []string) { + if updateChecked { + var updateSemVar = <-updateChannel + version.CompareAndLogVersions(updateSemVar) + } +} + func bypassConfig(cmd *cobra.Command) bool { _, bypassConfig := cmd.Annotations[config.AnnotationForConfigBypass] return bypassConfig diff --git a/cmd/version/version.go b/cmd/version/version.go index 64618bf4..06794a85 100644 --- a/cmd/version/version.go +++ b/cmd/version/version.go @@ -86,25 +86,35 @@ func GetLatestVersion() (string, error) { return split[len(split)-1], nil } -func CheckForUpdate() { +func CheckForUpdate(versionChannel chan *semver.Version) { log.Infof("Checking for newer version of FSOC") newestVersion, err := GetLatestVersion() log.Infof("Latest version available: %s", newestVersion) if err != nil { log.Fatalf(err.Error()) } + newestVersionSemVar, err := semver.NewVersion(newestVersion) // This panics, so we need to be ready to recover + if err != nil { + log.Fatalf(err.Error()) + } + versionChannel <- newestVersionSemVar + return +} + +func CompareAndLogVersions(newestVersionSemVar *semver.Version) { currentVersion := GetVersion() currentVersionSemVer := semver.New( uint64(currentVersion.VersionMajor), uint64(currentVersion.VersionMajor), uint64(currentVersion.VersionMajor), currentVersion.VersionMeta, "") - newestVersionSemVar := semver.MustParse(newestVersion) newerVersionAvailable := currentVersionSemVer.Compare(newestVersionSemVar) == -1 var debugFields = log.Fields{"newerVersionAvailable": newerVersionAvailable, "oldVersion": currentVersionSemVer.String(), "newVersion": newestVersionSemVar.String()} + if newerVersionAvailable { log.WithFields(debugFields).Warnf("There is a newer version of FSOC available, please upgrade from version %s to version %s", currentVersionSemVer.String(), newestVersionSemVar.String()) } else { log.WithFields(debugFields) } + } From c998ba408c0fc2976bdfcd680817b7e63235636d Mon Sep 17 00:00:00 2001 From: GDW1 Date: Wed, 12 Jul 2023 14:56:26 -0700 Subject: [PATCH 11/16] make pre-commit --- cmd/root.go | 4 ++-- cmd/version/version.go | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index ae81ef21..877c0f57 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -18,11 +18,11 @@ package cmd import ( "context" "fmt" - "github.com/Masterminds/semver/v3" "os" "path" "time" + "github.com/Masterminds/semver/v3" "github.com/apex/log" "github.com/apex/log/handlers/json" "github.com/apex/log/handlers/multi" @@ -243,7 +243,7 @@ func preExecHook(cmd *cobra.Command, args []string) { } // Create new timestamp file _ = os.Remove(os.TempDir() + "fsoc.timestamp") - file, err = os.Create(os.TempDir() + "fsoc.timestamp") + _, err = os.Create(os.TempDir() + "fsoc.timestamp") if err != nil { log.Fatalf(err.Error()) } diff --git a/cmd/version/version.go b/cmd/version/version.go index 06794a85..c2b436f9 100644 --- a/cmd/version/version.go +++ b/cmd/version/version.go @@ -16,11 +16,11 @@ package version import ( "fmt" - "github.com/Masterminds/semver/v3" - "github.com/apex/log" "net/http" "strings" + "github.com/Masterminds/semver/v3" + "github.com/apex/log" "github.com/spf13/cobra" "github.com/cisco-open/fsoc/cmd/config" @@ -98,7 +98,6 @@ func CheckForUpdate(versionChannel chan *semver.Version) { log.Fatalf(err.Error()) } versionChannel <- newestVersionSemVar - return } func CompareAndLogVersions(newestVersionSemVar *semver.Version) { From 78722fcaab166f59234b3046dedfc2b85754fd04 Mon Sep 17 00:00:00 2001 From: GDW1 Date: Wed, 12 Jul 2023 15:03:28 -0700 Subject: [PATCH 12/16] make pre-commit --- cmd/root.go | 11 +++++------ cmd/version/version.go | 14 +++++++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 877c0f57..a3e7c07c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -50,7 +50,6 @@ const ( ) var updateChannel chan *semver.Version -var updateChecked bool // 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 @@ -237,9 +236,11 @@ func preExecHook(cmd *cobra.Command, args []string) { // Do version checking noUpdateWarning, _ := cmd.Flags().GetBool("no-update-warning") updateChecked := int(time.Now().Unix())-getRecentTimestamp() > int(secondInDay) && !noUpdateWarning + updateChannel = make(chan *semver.Version) if updateChecked { - updateChannel = make(chan *semver.Version) go version.CheckForUpdate(updateChannel) + } else { + go func() { updateChannel <- version.ConvertVerToSemVar(version.GetVersion()) }() } // Create new timestamp file _ = os.Remove(os.TempDir() + "fsoc.timestamp") @@ -258,10 +259,8 @@ func getRecentTimestamp() int { } func postExecHook(cmd *cobra.Command, args []string) { - if updateChecked { - var updateSemVar = <-updateChannel - version.CompareAndLogVersions(updateSemVar) - } + var updateSemVar = <-updateChannel + version.CompareAndLogVersions(updateSemVar) } func bypassConfig(cmd *cobra.Command) bool { diff --git a/cmd/version/version.go b/cmd/version/version.go index c2b436f9..11b0945d 100644 --- a/cmd/version/version.go +++ b/cmd/version/version.go @@ -102,11 +102,7 @@ func CheckForUpdate(versionChannel chan *semver.Version) { func CompareAndLogVersions(newestVersionSemVar *semver.Version) { currentVersion := GetVersion() - currentVersionSemVer := semver.New( - uint64(currentVersion.VersionMajor), - uint64(currentVersion.VersionMajor), - uint64(currentVersion.VersionMajor), - currentVersion.VersionMeta, "") + currentVersionSemVer := ConvertVerToSemVar(currentVersion) newerVersionAvailable := currentVersionSemVer.Compare(newestVersionSemVar) == -1 var debugFields = log.Fields{"newerVersionAvailable": newerVersionAvailable, "oldVersion": currentVersionSemVer.String(), "newVersion": newestVersionSemVar.String()} @@ -117,3 +113,11 @@ func CompareAndLogVersions(newestVersionSemVar *semver.Version) { } } + +func ConvertVerToSemVar(data VersionData) *semver.Version { + return semver.New( + uint64(data.VersionMajor), + uint64(data.VersionMajor), + uint64(data.VersionMajor), + data.VersionMeta, "") +} From 6e37c5d2b8e0f3dc3a9e543a9b08a9b477b2a81c Mon Sep 17 00:00:00 2001 From: GDW1 Date: Wed, 12 Jul 2023 15:06:14 -0700 Subject: [PATCH 13/16] make pre-commit --- cmd/root.go | 2 +- cmd/version/version.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index a3e7c07c..1eae395d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -246,7 +246,7 @@ func preExecHook(cmd *cobra.Command, args []string) { _ = os.Remove(os.TempDir() + "fsoc.timestamp") _, err = os.Create(os.TempDir() + "fsoc.timestamp") if err != nil { - log.Fatalf(err.Error()) + log.Errorf(err.Error()) } } diff --git a/cmd/version/version.go b/cmd/version/version.go index 11b0945d..52d60e4f 100644 --- a/cmd/version/version.go +++ b/cmd/version/version.go @@ -87,15 +87,16 @@ func GetLatestVersion() (string, error) { } func CheckForUpdate(versionChannel chan *semver.Version) { + defer func() { recover() }() log.Infof("Checking for newer version of FSOC") newestVersion, err := GetLatestVersion() log.Infof("Latest version available: %s", newestVersion) if err != nil { - log.Fatalf(err.Error()) + log.Warnf(err.Error()) } newestVersionSemVar, err := semver.NewVersion(newestVersion) // This panics, so we need to be ready to recover if err != nil { - log.Fatalf(err.Error()) + log.Warnf(err.Error()) } versionChannel <- newestVersionSemVar } From 36a3dda77a522307790e837ee89359adf696ebb7 Mon Sep 17 00:00:00 2001 From: GDW1 Date: Wed, 12 Jul 2023 15:16:06 -0700 Subject: [PATCH 14/16] make pre-commit --- cmd/version/version.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/version/version.go b/cmd/version/version.go index 52d60e4f..353e9914 100644 --- a/cmd/version/version.go +++ b/cmd/version/version.go @@ -87,7 +87,12 @@ func GetLatestVersion() (string, error) { } func CheckForUpdate(versionChannel chan *semver.Version) { - defer func() { recover() }() + defer func() { + r := recover() + if r != nil { + log.Warnf("Error occurred while checking version, continuing") + } + }() log.Infof("Checking for newer version of FSOC") newestVersion, err := GetLatestVersion() log.Infof("Latest version available: %s", newestVersion) From 126b7806d0701a200a8974563ac1444f8021a913 Mon Sep 17 00:00:00 2001 From: GDW1 Date: Sat, 15 Jul 2023 09:34:31 -0700 Subject: [PATCH 15/16] version code moved to difference files --- cmd/root.go | 42 +++++++++++++--------- cmd/version/version.go | 64 --------------------------------- cmd/version/version_check.go | 68 ++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 81 deletions(-) create mode 100644 cmd/version/version_check.go diff --git a/cmd/root.go b/cmd/root.go index 1eae395d..b297f7cc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "path" + "strconv" "time" "github.com/Masterminds/semver/v3" @@ -41,12 +42,14 @@ var cfgProfile string var outputFormat string const ( - FSOC_CONFIG_ENVVAR = "FSOC_CONFIG" - FSOC_PROFILE_ENVVAR = "FSOC_PROFILE" + FSOC_CONFIG_ENVVAR = "FSOC_CONFIG" + FSOC_PROFILE_ENVVAR = "FSOC_PROFILE" + FSOC_NO_VERSION_CHECK = "FSOC_NO_VERSION_CHECK" ) const ( - secondInDay = time.Hour * 24 + secondInDay = time.Hour * 24 + timestampFileName = "fsoc.timestamp" ) var updateChannel chan *semver.Version @@ -102,7 +105,7 @@ func init() { rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Enable detailed output") rootCmd.PersistentFlags().Bool("curl", false, "Log curl equivalent for platform API calls (implies --verbose)") rootCmd.PersistentFlags().String("log", path.Join(os.TempDir(), "fsoc.log"), "determines the location of the fsoc log file") - rootCmd.PersistentFlags().Bool("no-update-warning", false, "Removes daily warning for updates") + rootCmd.PersistentFlags().Bool("no-version-check", false, "Removes daily check for new versions of FSOC") rootCmd.SetOut(os.Stdout) rootCmd.SetErr(os.Stderr) rootCmd.SetIn(os.Stdin) @@ -234,33 +237,38 @@ func preExecHook(cmd *cobra.Command, args []string) { } // Do version checking - noUpdateWarning, _ := cmd.Flags().GetBool("no-update-warning") - updateChecked := int(time.Now().Unix())-getRecentTimestamp() > int(secondInDay) && !noUpdateWarning - updateChannel = make(chan *semver.Version) + 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 + updateChecked := int(time.Now().Unix())-getLastVersionCheckTime() > int(secondInDay) && !noVerCheck if updateChecked { + updateChannel = make(chan *semver.Version) go version.CheckForUpdate(updateChannel) - } else { - go func() { updateChannel <- version.ConvertVerToSemVar(version.GetVersion()) }() } // Create new timestamp file - _ = os.Remove(os.TempDir() + "fsoc.timestamp") - _, err = os.Create(os.TempDir() + "fsoc.timestamp") + _ = os.Remove(os.TempDir() + timestampFileName) + _, err = os.Create(os.TempDir() + timestampFileName) if err != nil { - log.Errorf(err.Error()) + log.Errorf("failed to create version check timestamp file: %w", err.Error()) } } -func getRecentTimestamp() int { - fInfo, err := os.Stat(os.TempDir() + "fsoc.timestamp") +func getLastVersionCheckTime() int { + fInfo, err := os.Stat(os.TempDir() + timestampFileName) if err != nil { return 0 } - return fInfo.ModTime().Second() + return int(fInfo.ModTime().Unix()) } func postExecHook(cmd *cobra.Command, args []string) { - var updateSemVar = <-updateChannel - version.CompareAndLogVersions(updateSemVar) + if updateChannel != nil { + var updateSemVar = <-updateChannel + version.CompareAndLogVersions(updateSemVar) + } } func bypassConfig(cmd *cobra.Command) bool { diff --git a/cmd/version/version.go b/cmd/version/version.go index 353e9914..15ab0496 100644 --- a/cmd/version/version.go +++ b/cmd/version/version.go @@ -16,11 +16,6 @@ package version import ( "fmt" - "net/http" - "strings" - - "github.com/Masterminds/semver/v3" - "github.com/apex/log" "github.com/spf13/cobra" "github.com/cisco-open/fsoc/cmd/config" @@ -68,62 +63,3 @@ func displayVersion(cmd *cobra.Command) { Detail: true, }) } - -func GetLatestVersion() (string, error) { - client := &http.Client{ - CheckRedirect: func(req *http.Request, via []*http.Request) error { // no redirect - return http.ErrUseLastResponse - }, - } - resp, err := client.Get("https://github.com/cisco-open/fsoc/releases/latest") - if err != nil { - return "", err - } - split := strings.Split(resp.Header.Get("Location"), "/") - if len(split) < 1 { - return "", fmt.Errorf("version request did not return a version") - } - return split[len(split)-1], nil -} - -func CheckForUpdate(versionChannel chan *semver.Version) { - defer func() { - r := recover() - if r != nil { - log.Warnf("Error occurred while checking version, continuing") - } - }() - log.Infof("Checking for newer version of FSOC") - newestVersion, err := GetLatestVersion() - log.Infof("Latest version available: %s", newestVersion) - if err != nil { - log.Warnf(err.Error()) - } - newestVersionSemVar, err := semver.NewVersion(newestVersion) // This panics, so we need to be ready to recover - if err != nil { - log.Warnf(err.Error()) - } - versionChannel <- newestVersionSemVar -} - -func CompareAndLogVersions(newestVersionSemVar *semver.Version) { - currentVersion := GetVersion() - currentVersionSemVer := ConvertVerToSemVar(currentVersion) - newerVersionAvailable := currentVersionSemVer.Compare(newestVersionSemVar) == -1 - var debugFields = log.Fields{"newerVersionAvailable": newerVersionAvailable, "oldVersion": currentVersionSemVer.String(), "newVersion": newestVersionSemVar.String()} - - if newerVersionAvailable { - log.WithFields(debugFields).Warnf("There is a newer version of FSOC available, please upgrade from version %s to version %s", currentVersionSemVer.String(), newestVersionSemVar.String()) - } else { - log.WithFields(debugFields) - } - -} - -func ConvertVerToSemVar(data VersionData) *semver.Version { - return semver.New( - uint64(data.VersionMajor), - uint64(data.VersionMajor), - uint64(data.VersionMajor), - data.VersionMeta, "") -} diff --git a/cmd/version/version_check.go b/cmd/version/version_check.go new file mode 100644 index 00000000..69499676 --- /dev/null +++ b/cmd/version/version_check.go @@ -0,0 +1,68 @@ +package version + +import ( + "fmt" + "github.com/Masterminds/semver/v3" + "github.com/apex/log" + "net/http" + "strings" +) + +const ( + versionLatestURL = "https://github.com/cisco-open/fsoc/releases/latest" +) + +func GetLatestVersion() (string, error) { + // Open HTTP client which will not follow redirect + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { // no redirect + return http.ErrUseLastResponse + }, + } + resp, err := client.Get(versionLatestURL) + if err != nil { + return "", err + } + // Redirected link should look like https://github.com/cisco-open/fsoc/releases/tag/{VERSION} + split := strings.Split(resp.Header.Get("Location"), "/") + if len(split) < 1 { + return "", fmt.Errorf("version request did not return a version") + } + return split[len(split)-1], nil +} + +func CheckForUpdate(versionChannel chan *semver.Version) { + log.Infof("Checking for newer version of FSOC") + newestVersion, err := GetLatestVersion() + log.WithField("Latest FSOC version", newestVersion).Infof("Latest fsoc version available: %s", newestVersion) + if err != nil { + log.Warnf(err.Error()) + } + newestVersionSemVar, err := semver.NewVersion(newestVersion) + if err != nil { + log.WithField("unparseable version", newestVersion).Warnf("Could not parse version string: %w", err.Error()) + } + versionChannel <- newestVersionSemVar +} + +func CompareAndLogVersions(newestVersionSemVar *semver.Version) { + currentVersion := GetVersion() + currentVersionSemVer := ConvertVerToSemVar(currentVersion) + newerVersionAvailable := currentVersionSemVer.Compare(newestVersionSemVar) == -1 + var debugFields = log.Fields{"IsLatestVersionDifferent": newerVersionAvailable, "CurrentVersion": currentVersionSemVer.String(), "LatestVersion": newestVersionSemVar.String()} + + if newerVersionAvailable { + log.WithFields(debugFields).Warnf("There is a newer version of FSOC available, please upgrade from version") + } else { + log.WithFields(debugFields) + } + +} + +func ConvertVerToSemVar(data VersionData) *semver.Version { + return semver.New( + uint64(data.VersionMajor), + uint64(data.VersionMajor), + uint64(data.VersionMajor), + data.VersionMeta, "") +} From 29c082fb92f746342dec178ed16131e1e31f3780 Mon Sep 17 00:00:00 2001 From: GDW1 Date: Sat, 15 Jul 2023 09:49:23 -0700 Subject: [PATCH 16/16] version code moved to difference files --- cmd/root.go | 2 +- cmd/version/version.go | 1 + cmd/version/version_check.go | 7 ++++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index b297f7cc..1e4e4aa0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -252,7 +252,7 @@ func preExecHook(cmd *cobra.Command, args []string) { _ = os.Remove(os.TempDir() + timestampFileName) _, err = os.Create(os.TempDir() + timestampFileName) if err != nil { - log.Errorf("failed to create version check timestamp file: %w", err.Error()) + log.Errorf("failed to create version check timestamp file: %v", err.Error()) } } diff --git a/cmd/version/version.go b/cmd/version/version.go index 15ab0496..0502533c 100644 --- a/cmd/version/version.go +++ b/cmd/version/version.go @@ -16,6 +16,7 @@ package version import ( "fmt" + "github.com/spf13/cobra" "github.com/cisco-open/fsoc/cmd/config" diff --git a/cmd/version/version_check.go b/cmd/version/version_check.go index 69499676..f63a4f2b 100644 --- a/cmd/version/version_check.go +++ b/cmd/version/version_check.go @@ -2,10 +2,11 @@ package version import ( "fmt" - "github.com/Masterminds/semver/v3" - "github.com/apex/log" "net/http" "strings" + + "github.com/Masterminds/semver/v3" + "github.com/apex/log" ) const ( @@ -40,7 +41,7 @@ func CheckForUpdate(versionChannel chan *semver.Version) { } newestVersionSemVar, err := semver.NewVersion(newestVersion) if err != nil { - log.WithField("unparseable version", newestVersion).Warnf("Could not parse version string: %w", err.Error()) + log.WithField("unparseable version", newestVersion).Warnf("Could not parse version string: %v", err.Error()) } versionChannel <- newestVersionSemVar }