Skip to content

Commit

Permalink
Check latest CLI version and print warning if such exists (#1929)
Browse files Browse the repository at this point in the history
  • Loading branch information
sverdlov93 authored May 15, 2023
1 parent 92a1b62 commit 97e26fc
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 39 deletions.
7 changes: 6 additions & 1 deletion docs/common/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ const (

JfrogCliEncryptionKey = ` JFROG_CLI_ENCRYPTION_KEY
If provided, encrypt the sensitive data stored in the config with the provided key. Must be exactly 32 characters.`

JfrogCliAvoidNewVersionWarning = ` JFROG_CLI_AVOID_NEW_VERSION_WARNING
[Default: false]
Set to true if you'd like to avoid checking the latest available JFrog CLI version and printing warning when it newer than the current one. `
)

var (
Expand Down Expand Up @@ -118,7 +122,8 @@ func GetGlobalEnvVars() string {
JfrogCliBuildUrl,
JfrogCliEnvExclude,
JfrogCliFailNoOp,
JfrogCliEncryptionKey)
JfrogCliEncryptionKey,
JfrogCliAvoidNewVersionWarning)
}

func CreateEnvVars(envVars ...string) string {
Expand Down
11 changes: 5 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/agnivade/levenshtein v1.1.1
github.com/buger/jsonparser v1.1.1
github.com/go-git/go-git/v5 v5.6.1
github.com/gocarina/gocsv v0.0.0-20230406101422-6445c2b15027
github.com/gocarina/gocsv v0.0.0-20230513223533-9ddd7fd60602
github.com/jfrog/build-info-go v1.9.3
github.com/jfrog/gofrog v1.3.0
github.com/jfrog/jfrog-cli-core/v2 v2.32.1
Expand All @@ -15,14 +15,13 @@ require (
github.com/mholt/archiver/v3 v3.5.1
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.8.2
github.com/testcontainers/testcontainers-go v0.20.0
github.com/testcontainers/testcontainers-go v0.20.1
github.com/urfave/cli v1.22.13
github.com/vbauerster/mpb/v7 v7.5.3
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/exp v0.0.0-20230418202329-0354be287a23
golang.org/x/term v0.7.0
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea
golang.org/x/term v0.8.0
gopkg.in/yaml.v2 v2.4.0

)

require (
Expand Down Expand Up @@ -110,7 +109,7 @@ require (
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
google.golang.org/grpc v1.52.0 // indirect
Expand Down
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/gocarina/gocsv v0.0.0-20230406101422-6445c2b15027 h1:LCGzZb4kMUUjMUzLxxqSJBwo9szUO0tK8cOxnEOT4Jc=
github.com/gocarina/gocsv v0.0.0-20230406101422-6445c2b15027/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/gocarina/gocsv v0.0.0-20230513223533-9ddd7fd60602 h1:HSpPf+lPYwzoJNup34uegmOQk5Qm83S+wpu8anTDJkg=
github.com/gocarina/gocsv v0.0.0-20230513223533-9ddd7fd60602/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
Expand Down Expand Up @@ -387,8 +387,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
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/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/testcontainers/testcontainers-go v0.20.0 h1:ASrcJee7vcWNw43yUgL2n8KA5IOywrF031GawlrkVkE=
github.com/testcontainers/testcontainers-go v0.20.0/go.mod h1:zb+NOlCQBkZ7RQp4QI+YMIHyO2CQ/qsXzNF5eLJ24SY=
github.com/testcontainers/testcontainers-go v0.20.1 h1:mK15UPJ8c5P+NsQKmkqzs/jMdJt6JMs5vlw2y4j92c0=
github.com/testcontainers/testcontainers-go v0.20.1/go.mod h1:zb+NOlCQBkZ7RQp4QI+YMIHyO2CQ/qsXzNF5eLJ24SY=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
Expand Down Expand Up @@ -452,8 +452,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230418202329-0354be287a23 h1:4NKENAGIctmZYLK9W+X1kDK8ObBFqOSCJM6WE7CvkJY=
golang.org/x/exp v0.0.0-20230418202329-0354be287a23/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
Expand Down Expand Up @@ -612,16 +612,16 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
2 changes: 1 addition & 1 deletion go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ func prepareGoProject(projectName, configDestDir string, t *testing.T, copyDirs
_, err = tests.ReplaceTemplateVariables(filepath.Join(configFileDir, "go.yaml"), filepath.Join(configDestDir, "projects"))
assert.NoError(t, err)
clientTestUtils.ChangeDirAndAssert(t, projectPath)
log.Info("Using Go project located at ", projectPath)
log.Info("Using Go project located at", projectPath)
return projectPath
}

Expand Down
18 changes: 16 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ func execMain() error {
cli.AppHelpTemplate = getAppHelpTemplate()
cli.SubcommandHelpTemplate = subcommandHelpTemplate
app.CommandNotFound = func(c *cli.Context, command string) {
fmt.Fprintf(c.App.Writer, "'"+c.App.Name+" "+command+"' is not a jf command. See --help\n")
_, err := fmt.Fprintf(c.App.Writer, "'"+c.App.Name+" "+command+"' is not a jf command. See --help\n")
if err != nil {
clientlog.Debug(err)
os.Exit(1)
}
if bestSimilarity := searchSimilarCmds(c.App.Commands, command); len(bestSimilarity) > 0 {
text := "The most similar "
if len(bestSimilarity) == 1 {
Expand All @@ -99,13 +103,23 @@ func execMain() error {
sort.Strings(bestSimilarity)
text += "commands are:\n\tjf " + strings.Join(bestSimilarity, "\n\tjf ")
}
fmt.Fprintln(c.App.Writer, text)
_, err = fmt.Fprintln(c.App.Writer, text)
if err != nil {
clientlog.Debug(err)
}
}
os.Exit(1)
}
app.Before = func(ctx *cli.Context) error {
clientlog.Debug("JFrog CLI version:", app.Version)
clientlog.Debug("OS/Arch:", runtime.GOOS+"/"+runtime.GOARCH)
warningMessage, err := cliutils.CheckNewCliVersionAvailable(app.Version)
if err != nil {
clientlog.Debug("failed while trying to check latest JFrog CLI version:", err.Error())
}
if warningMessage != "" {
clientlog.Warn(warningMessage)
}
return nil
}
err := app.Run(args)
Expand Down
10 changes: 5 additions & 5 deletions plugins/commands/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func getServerDetails() (string, config.ServerDetails, error) {
return rtDetails.ArtifactoryUrl, *rtDetails, nil
}

// Checks if the requested plugin exists in registry and does not exists locally.
// Checks if the requested plugin exists in registry and does not exist locally.
func shouldDownloadPlugin(pluginsDir, pluginName, downloadUrl string, httpDetails httputils.HttpClientDetails) (bool, error) {
exists, err := fileutils.IsDirExists(filepath.Join(pluginsDir, pluginName), false)
if err != nil {
Expand All @@ -133,13 +133,13 @@ func shouldDownloadPlugin(pluginsDir, pluginName, downloadUrl string, httpDetail
if err != nil {
return false, err
}
log.Debug("Fetching plugin details from: ", downloadUrl)
log.Debug("Fetching plugin details from:", downloadUrl)

details, resp, err := client.GetRemoteFileDetails(downloadUrl, httpDetails)
if err != nil {
return false, err
}
log.Debug("Artifactory response: ", resp.Status)
log.Debug("Artifactory response:", resp.Status)
err = errorutils.CheckResponseStatus(resp, http.StatusOK)
if err != nil {
return false, err
Expand Down Expand Up @@ -216,7 +216,7 @@ func downloadPluginExec(downloadUrl, pluginName, pluginsDir string, httpDetails
LocalFileName: exeName,
RelativePath: exeName,
}
log.Debug("Downloading plugin's executable from: ", downloadDetails.DownloadPath)
log.Debug("Downloading plugin's executable from:", downloadDetails.DownloadPath)
response, err := downloadFromArtifactory(downloadDetails, httpDetails, progressMgr)
if err != nil {
return
Expand All @@ -241,7 +241,7 @@ func downloadPluginsResources(downloadUrl, pluginName, pluginsDir string, httpDe
LocalFileName: coreutils.PluginsResourcesDirName + ".zip",
RelativePath: coreutils.PluginsResourcesDirName + ".zip",
}
log.Debug("Downloading plugin's resources from: ", downloadDetails.DownloadPath)
log.Debug("Downloading plugin's resources from:", downloadDetails.DownloadPath)
response, err := downloadFromArtifactory(downloadDetails, httpDetails, progressMgr)
if err != nil {
return
Expand Down
2 changes: 1 addition & 1 deletion plugins/commands/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func verifyUniqueVersion(pluginName, pluginVersion string, rtDetails *config.Ser
if err != nil {
return err
}
log.Debug("Artifactory response: ", resp.Status)
log.Debug("Artifactory response:", resp.Status)
if resp.StatusCode == http.StatusOK {
return errorutils.CheckErrorf("plugin version already exists on server")
}
Expand Down
6 changes: 2 additions & 4 deletions poetry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ import (
)

func TestPoetryInstall(t *testing.T) {
tests.SkipKnownFailingTest(t)

// Init poetry test.
initPoetryTest(t)

if coreutils.IsWindows() {
tests.SkipKnownFailingTest(t)
}

// Populate cli config with 'default' server.
oldHomeDir, newHomeDir := prepareHomeDir(t)
defer func() {
Expand Down
1 change: 0 additions & 1 deletion unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
)

const (
JfrogTestsHome = ".jfrogTest"
CliIntegrationTests = "github.com/jfrog/jfrog-cli"
)

Expand Down
14 changes: 9 additions & 5 deletions utils/cliutils/cli_consts.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package cliutils

import "time"

const (
// General CLI constants
CliVersion = "2.37.1"
Expand All @@ -23,15 +25,17 @@ const (
DownloadMaxSplitCount = 15

// Common
Retries = 3
RetryWaitMilliSecs = 0
Threads = 3
TokenExpiry = 3600
DefaultLicenseCount = 1
Retries = 3
RetryWaitMilliSecs = 0
Threads = 3
TokenExpiry = 3600
DefaultLicenseCount = 1
LatestCliVersionCheckInterval = time.Hour * 6

// Env
BuildUrl = "JFROG_CLI_BUILD_URL"
EnvExclude = "JFROG_CLI_ENV_EXCLUDE"
UserAgent = "JFROG_CLI_USER_AGENT"
JfrogCliAvoidDeprecationWarnings = "JFROG_CLI_AVOID_DEPRECATION_WARNINGS"
JfrogCliAvoidNewVersionWarning = "JFROG_CLI_AVOID_NEW_VERSION_WARNING"
)
74 changes: 74 additions & 0 deletions utils/cliutils/utils.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package cliutils

import (
"encoding/json"
"fmt"
"github.com/jfrog/gofrog/version"
"github.com/jfrog/jfrog-client-go/http/httpclient"
"github.com/jfrog/jfrog-client-go/utils/io/httputils"
"io"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"time"

corecontainercmds "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/container"
commandUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils"
Expand Down Expand Up @@ -36,6 +43,11 @@ const (
// Error modes (how should the application behave when the CheckError function is invoked):
type OnError string

type githubResponse struct {
TagName string `json:"tag_name,omitempty"`
URL string `json:"html_url"`
}

func init() {
// Initialize cli-core values.
cliUserAgent := os.Getenv(UserAgent)
Expand Down Expand Up @@ -762,3 +774,65 @@ func CleanupResult(result *commandUtils.Result, originError *error) {
}
}
}

// Checks if the requested plugin exists in registry and does not exist locally.
func CheckNewCliVersionAvailable(currentVersion string) (warningMessage string, err error) {
shouldCheck, err := shouldCheckLatestCliVersion()
if err != nil || !shouldCheck {
return
}
githubVersionInfo, err := getLatestCliVersionFromGithubAPI()
if err != nil {
return
}
latestVersion := strings.TrimPrefix(githubVersionInfo.TagName, "v")
if version.NewVersion(latestVersion).Compare(currentVersion) < 0 {
warningMessage = strings.Join([]string{
fmt.Sprintf("You are using JFrog CLI version %s, however version %s is available.", coreutils.PrintComment(currentVersion), coreutils.PrintTitle(latestVersion)),
fmt.Sprintf("To install the latest version, visit: %sgetcli", coreutils.JFrogComUrl),
"To see the release notes, visit: " + githubVersionInfo.URL,
fmt.Sprintf("To ignore this message you can use %s=TRUE", JfrogCliAvoidNewVersionWarning),
},
"\n")
}
return
}

func shouldCheckLatestCliVersion() (shouldCheck bool, err error) {
if strings.ToLower(os.Getenv(JfrogCliAvoidNewVersionWarning)) == "true" {
return
}
homeDir, err := coreutils.GetJfrogHomeDir()
if err != nil {
return
}
indicatorFile := path.Join(homeDir, "Latest_Cli_Version_Check_Indicator")
fileInfo, err := os.Stat(indicatorFile)
if err != nil && !os.IsNotExist(err) {
err = fmt.Errorf("couldn't get indicator file %s info: %s", indicatorFile, err.Error())
return
}
if err == nil && (time.Now().UnixMilli()-fileInfo.ModTime().UnixMilli()) < LatestCliVersionCheckInterval.Milliseconds() {
// Timestamp file exists and updated less than 6 hours ago, therefor no need to check version again
return
}
return true, os.WriteFile(indicatorFile, []byte{}, 0666)
}

func getLatestCliVersionFromGithubAPI() (githubVersionInfo githubResponse, err error) {
client, err := httpclient.ClientBuilder().Build()
if err != nil {
return
}
resp, body, _, err := client.SendGet("https://api.github.com/repos/jfrog/jfrog-cli/releases/latest", true, httputils.HttpClientDetails{HttpTimeout: time.Second * 2}, "")
if err != nil {
err = errors.New("couldn't get latest JFrog CLI latest version info from GitHub API: " + err.Error())
return
}
err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK)
if err != nil {
return
}
err = json.Unmarshal(body, &githubVersionInfo)
return
}
Loading

0 comments on commit 97e26fc

Please sign in to comment.