diff --git a/cli/scancommands.go b/cli/scancommands.go index a1bdf275..4e91bc29 100644 --- a/cli/scancommands.go +++ b/cli/scancommands.go @@ -1,6 +1,8 @@ package cli import ( + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/utils/usage" "os" "strings" @@ -328,7 +330,26 @@ func AuditCmd(c *components.Context) error { } } auditCmd.SetTechnologies(technologies) - return progressbar.ExecWithProgress(auditCmd) + err = progressbar.ExecWithProgress(auditCmd) + + // Reporting error if Xsc service is enabled + reportErrorIfExists(err, auditCmd) + return err +} + +func reportErrorIfExists(err error, auditCmd *audit.AuditCommand) { + if err == nil || !usage.ShouldReportUsage() { + return + } + var serverDetails *coreConfig.ServerDetails + serverDetails, innerError := auditCmd.ServerDetails() + if innerError != nil { + log.Debug(fmt.Sprintf("failed to get server details for error report: %q", innerError)) + return + } + if reportError := utils.ReportError(serverDetails, err, "cli"); reportError != nil { + log.Debug("failed to report error log:" + reportError.Error()) + } } func createAuditCmd(c *components.Context) (*audit.AuditCommand, error) { @@ -398,7 +419,11 @@ func AuditSpecificCmd(c *components.Context, technology coreutils.Technology) er } technologies := []string{string(technology)} auditCmd.SetTechnologies(technologies) - return progressbar.ExecWithProgress(auditCmd) + err = progressbar.ExecWithProgress(auditCmd) + + // Reporting error if Xsc service is enabled + reportErrorIfExists(err, auditCmd) + return err } func CurationCmd(c *components.Context) error { diff --git a/go.mod b/go.mod index 6885091e..5ef0a482 100644 --- a/go.mod +++ b/go.mod @@ -98,8 +98,8 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240404075604-3df49e9a9d64 +replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240408074156-13680c04f22e -replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240403100335-8292671b7cc4 +replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240409191434-4e96d77edd64 // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev diff --git a/go.sum b/go.sum index f23e834c..cf8ee9d7 100644 --- a/go.sum +++ b/go.sum @@ -102,10 +102,10 @@ github.com/jfrog/gofrog v1.6.3 h1:F7He0+75HcgCe6SGTSHLFCBDxiE2Ja0tekvvcktW6wc= github.com/jfrog/gofrog v1.6.3/go.mod h1:SZ1EPJUruxrVGndOzHd+LTiwWYKMlHqhKD+eu+v5Hqg= github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYLipdsOFMY= github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240404075604-3df49e9a9d64 h1:eCAqJ8hqJ6bqgmjswjpqhInJMG80MT5D2r465s/fXzg= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240404075604-3df49e9a9d64/go.mod h1:iQoYSsjLWF8x//rtQCwNPE2ycle2X2x6VFQM0LQE2n0= -github.com/jfrog/jfrog-client-go v1.28.1-0.20240403100335-8292671b7cc4 h1:A67yoFRYjRzg+xhLYhH0QN7b4/wggRa/lSQKSjzOwNQ= -github.com/jfrog/jfrog-client-go v1.28.1-0.20240403100335-8292671b7cc4/go.mod h1:tUyEmxznphh0nwAGo6xz9Sps7RRW/TBMxIJZteo+j2k= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240408074156-13680c04f22e h1:PjCzGWHyJqK4j1MP3osPDDAW6KBXMJlBypOxKtp/ZKo= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240408074156-13680c04f22e/go.mod h1:qXAP68g+DlyX2wk5znNbQdK2CcEHfOLOfYXPzdlnkxI= +github.com/jfrog/jfrog-client-go v1.28.1-0.20240409191434-4e96d77edd64 h1:q0GV0IdhYdTqEkNykRwNZP0qNEE8j9dWfY9uKovDPzM= +github.com/jfrog/jfrog-client-go v1.28.1-0.20240409191434-4e96d77edd64/go.mod h1:tUyEmxznphh0nwAGo6xz9Sps7RRW/TBMxIJZteo+j2k= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= diff --git a/utils/errorreport.go b/utils/errorreport.go new file mode 100644 index 00000000..31817086 --- /dev/null +++ b/utils/errorreport.go @@ -0,0 +1,25 @@ +package utils + +import ( + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/jfrog/jfrog-client-go/xsc/services" +) + +// Sends an error report when the Xsc service is enabled. +// Errors returned by this function typically do not disrupt the flow, as reporting errors is optional. +func ReportError(serverDetails *config.ServerDetails, errorToReport error, source string) error { + log.Debug("Sending an error report to JFrog analytics...") + xscManager, err := CreateXscServiceManager(serverDetails) + if err != nil { + return fmt.Errorf("failed to create an HTTP client: %s.\nReporting to JFrog analytics is skipped...", err.Error()) + } + + errorLog := &services.ExternalErrorLog{ + Log_level: "error", + Source: source, + Message: errorToReport.Error(), + } + return SendXscLogMessageIfEnabled(errorLog, xscManager) +} diff --git a/utils/errorreport_test.go b/utils/errorreport_test.go new file mode 100644 index 00000000..faa8f67b --- /dev/null +++ b/utils/errorreport_test.go @@ -0,0 +1,69 @@ +package utils + +import ( + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + clienttestutils "github.com/jfrog/jfrog-client-go/utils/tests" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +const ( + unsupportedXscVersionForErrorLogs = "1.6.0" + supportedXscVersionForErrorLogs = minXscVersionForErrorReport +) + +func TestReportLogErrorEventPossible(t *testing.T) { + restoreEnvVarFunc := clienttestutils.SetEnvWithCallbackAndAssert(t, coreutils.ReportUsage, "") + defer restoreEnvVarFunc() + + testCases := []struct { + serverCreationFunc func() (*httptest.Server, *config.ServerDetails) + expectedResponse bool + }{ + { + serverCreationFunc: func() (*httptest.Server, *config.ServerDetails) { + serverMock, serverDetails, _ := CreateXscRestsMockServer(t, func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/xsc/api/v1/system/version" { + w.WriteHeader(http.StatusNotFound) + _, innerError := w.Write([]byte("Xsc service is not enabled")) + if innerError != nil { + return + } + } + }) + return serverMock, serverDetails + }, + expectedResponse: false, + }, + { + serverCreationFunc: func() (*httptest.Server, *config.ServerDetails) { return xscServer(t, "") }, + expectedResponse: false, + }, + { + serverCreationFunc: func() (*httptest.Server, *config.ServerDetails) { + return xscServer(t, unsupportedXscVersionForErrorLogs) + }, + expectedResponse: false, + }, + { + serverCreationFunc: func() (*httptest.Server, *config.ServerDetails) { return xscServer(t, supportedXscVersionForErrorLogs) }, + expectedResponse: true, + }, + } + + for _, testcase := range testCases { + mockServer, serverDetails := testcase.serverCreationFunc() + xscManager, err := CreateXscServiceManager(serverDetails) + assert.NoError(t, err) + reportPossible := IsReportLogErrorEventPossible(xscManager) + if testcase.expectedResponse { + assert.True(t, reportPossible) + } else { + assert.False(t, reportPossible) + } + mockServer.Close() + } +} diff --git a/utils/xscmanager.go b/utils/xscmanager.go index 5eb5edf7..9111f000 100644 --- a/utils/xscmanager.go +++ b/utils/xscmanager.go @@ -1,11 +1,18 @@ package utils import ( + "fmt" "github.com/jfrog/jfrog-cli-core/v2/utils/config" - clientconfig "github.com/jfrog/jfrog-client-go/config" + clientutils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xsc" + "github.com/jfrog/jfrog-client-go/xsc/services" + + clientconfig "github.com/jfrog/jfrog-client-go/config" ) +const minXscVersionForErrorReport = "1.7.7" + func CreateXscServiceManager(serviceDetails *config.ServerDetails) (*xsc.XscServicesManager, error) { xscDetails, err := serviceDetails.CreateXscAuthConfig() if err != nil { @@ -19,3 +26,28 @@ func CreateXscServiceManager(serviceDetails *config.ServerDetails) (*xsc.XscServ } return xsc.New(serviceConfig) } + +func SendXscLogMessageIfEnabled(errorLog *services.ExternalErrorLog, xscManager *xsc.XscServicesManager) error { + if !IsReportLogErrorEventPossible(xscManager) { + return nil + } + return xscManager.SendXscLogErrorRequest(errorLog) +} + +// Determines if reporting the error is feasible. +func IsReportLogErrorEventPossible(xscManager *xsc.XscServicesManager) bool { + xscVersion, err := xscManager.GetVersion() + if err != nil { + log.Debug(fmt.Sprintf("failed to check availability of Xsc service:%s\nReporting to JFrog analytics is skipped...", err.Error())) + return false + } + if xscVersion == "" { + log.Debug("Xsc service is not available. Reporting to JFrog analytics is skipped...") + return false + } + if err = clientutils.ValidateMinimumVersion(clientutils.Xsc, xscVersion, minXscVersionForErrorReport); err != nil { + log.Debug(err.Error()) + return false + } + return true +} diff --git a/xsc_test.go b/xsc_test.go new file mode 100644 index 00000000..96974959 --- /dev/null +++ b/xsc_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "errors" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-security/tests" + "github.com/jfrog/jfrog-cli-security/utils" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestReportError(t *testing.T) { + serverDetails := &config.ServerDetails{ + Url: *tests.JfrogUrl, + ArtifactoryUrl: *tests.JfrogUrl + tests.ArtifactoryEndpoint, + XrayUrl: *tests.JfrogUrl + tests.XrayEndpoint, + AccessToken: *tests.JfrogAccessToken, + ServerId: tests.ServerId, + } + + // Prior to initiating the test, we verify whether Xsc is enabled for the customer. If not, the test is skipped. + xscManager, err := utils.CreateXscServiceManager(serverDetails) + assert.NoError(t, err) + + if !utils.IsReportLogErrorEventPossible(xscManager) { + t.Skip("Skipping test since Xsc server is not enabled or below minimal required version") + } + + errorToReport := errors.New("THIS IS NOT A REAL ERROR! This Error is posted as part of TestReportError test") + assert.NoError(t, utils.ReportError(serverDetails, errorToReport, "cli")) +}