diff --git a/cli/cli.go b/cli/cli.go index 95f7abba..87183d2b 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -16,5 +16,12 @@ func GetJfrogCliSecurityApp() components.App { Commands: getXrayNameSpaceCommands(), Category: "Command Namespaces", }) + // TOOD: make namespace hidden? + app.Subcommands = append(app.Subcommands, components.Namespace{ + Name: "git", + Description: "Git integration commands.", + Commands: getGitNameSpaceCommands(), + Category: "Command Namespaces", + }) return app } diff --git a/cli/docs/flags.go b/cli/docs/flags.go index 32f3c12e..e78276ca 100644 --- a/cli/docs/flags.go +++ b/cli/docs/flags.go @@ -20,6 +20,7 @@ const ( DockerScan = "docker scan" Audit = "audit" CurationAudit = "curation-audit" + GitAudit = "git-audit" // TODO: Deprecated commands (remove at next CLI major version) AuditMvn = "audit-maven" @@ -81,6 +82,7 @@ const ( To = "to" Version = "version" Target = "target" + Source = "source" Stream = "stream" Periodic = "periodic" @@ -129,15 +131,11 @@ var commandFlags = map[string][]string{ DockerScan: { ServerId, Project, Watches, RepoPath, Licenses, OutputFormat, Fail, ExtendedTable, BypassArchiveLimits, MinSeverity, FixableOnly, }, - Audit: { - url, user, password, accessToken, ServerId, InsecureTls, Project, Watches, RepoPath, Licenses, OutputFormat, ExcludeTestDeps, - useWrapperAudit, DepType, RequirementsFile, Fail, ExtendedTable, WorkingDirs, ExclusionsAudit, Mvn, Gradle, Npm, - Pnpm, Yarn, Go, Nuget, Pip, Pipenv, Poetry, MinSeverity, FixableOnly, ThirdPartyContextualAnalysis, Threads, - Sca, Iac, Sast, Secrets, WithoutCA, - }, CurationAudit: { CurationOutput, WorkingDirs, Threads, RequirementsFile, }, + Audit: getAuditFlags(), + GitAudit: getAuditFlags(), // TODO: Deprecated commands (remove at next CLI major version) AuditMvn: { url, user, password, accessToken, ServerId, InsecureTls, Project, ExclusionsAudit, Watches, RepoPath, Licenses, OutputFormat, Fail, ExtendedTable, useWrapperAudit, @@ -159,6 +157,15 @@ var commandFlags = map[string][]string{ }, } +func getAuditFlags() []string { + return []string{ + url, user, password, accessToken, ServerId, InsecureTls, Project, Watches, RepoPath, Licenses, OutputFormat, ExcludeTestDeps, + useWrapperAudit, DepType, RequirementsFile, Fail, ExtendedTable, WorkingDirs, ExclusionsAudit, Mvn, Gradle, Npm, + Pnpm, Yarn, Go, Nuget, Pip, Pipenv, Poetry, MinSeverity, FixableOnly, ThirdPartyContextualAnalysis, Threads, + Sca, Iac, Sast, Secrets, WithoutCA, + } +} + // Security Flag keys mapped to their corresponding components.Flag definition. var flagsMap = map[string]components.Flag{ // Common commands flags diff --git a/cli/docs/git/audit/help.go b/cli/docs/git/audit/help.go new file mode 100644 index 00000000..d5c5b075 --- /dev/null +++ b/cli/docs/git/audit/help.go @@ -0,0 +1,14 @@ +package audit + +import "github.com/jfrog/jfrog-cli-core/v2/plugins/components" + +func GetDescription() string { + return "Audit a git repository. This command will compare the sourceCommit against the targetCommit and return the security vulnerabilities added by the sourceCommit against the targetCommit." +} + +func GetArguments() []components.Argument { + return []components.Argument{ + {Name: "sourceCommit", Description: "sourceCommit to compare against."}, + {Name: "targetCommit", Description: "targetCommit to compare against."}, + } +} \ No newline at end of file diff --git a/cli/gitcommands.go b/cli/gitcommands.go new file mode 100644 index 00000000..10012b80 --- /dev/null +++ b/cli/gitcommands.go @@ -0,0 +1,39 @@ +package cli + +import ( + "github.com/jfrog/jfrog-cli-core/v2/common/progressbar" + pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" + "github.com/jfrog/jfrog-cli-core/v2/plugins/components" + flags "github.com/jfrog/jfrog-cli-security/cli/docs" + auditDocs "github.com/jfrog/jfrog-cli-security/cli/docs/git/audit" + "github.com/jfrog/jfrog-cli-security/commands/git/audit" + "github.com/jfrog/jfrog-cli-security/utils" +) + +func getGitNameSpaceCommands() []components.Command { + return []components.Command{ + { + Name: "audit", + Aliases: []string{"gita"}, + Flags: flags.GetCommandFlags(flags.Audit), + Description: auditDocs.GetDescription(), + Arguments: auditDocs.GetArguments(), + Category: auditScanCategory, + Hidden: true, + Action: GitAuditCmd, + }, + } +} + +func GitAuditCmd(c *components.Context) error { + if len(c.Arguments) < 2 { + return pluginsCommon.WrongNumberOfArgumentsHandler(c) + } + auditCmd, err := CreateAuditCmd(c) + if err != nil { + return err + } + cmd := audit.NewGitAuditCommand(auditCmd) + cmd.SetSource(c.Arguments[0]).SetTarget(c.Arguments[1]) + return utils.ReportErrorIfExists(progressbar.ExecWithProgress(cmd), cmd.ServerDetails) +} \ No newline at end of file diff --git a/cli/scancommands.go b/cli/scancommands.go index 40e0855b..245823b3 100644 --- a/cli/scancommands.go +++ b/cli/scancommands.go @@ -448,11 +448,7 @@ func AuditSpecificCmd(c *components.Context, technology techutils.Technology) er } technologies := []string{string(technology)} auditCmd.SetTechnologies(technologies) - err = progressbar.ExecWithProgress(auditCmd) - - // Reporting error if Xsc service is enabled - reportErrorIfExists(err, auditCmd) - return err + return utils.ReportErrorIfExists(progressbar.ExecWithProgress(auditCmd), auditCmd.ServerDetails) } func CurationCmd(c *components.Context) error { diff --git a/commands/audit/audit.go b/commands/audit/audit.go index aaf2175c..fcdfd0ab 100644 --- a/commands/audit/audit.go +++ b/commands/audit/audit.go @@ -113,13 +113,17 @@ func (auditCmd *AuditCommand) CreateCommonGraphScanParams() *scangraph.CommonGra } func (auditCmd *AuditCommand) Run() (err error) { + _, err = auditCmd.RunAuditCommand(true) + return +} + +func (auditCmd *AuditCommand) RunAuditCommand(printResults bool) (auditResults *xrayutils.Results, err error) { // If no workingDirs were provided by the user, we apply a recursive scan on the root repository isRecursiveScan := len(auditCmd.workingDirs) == 0 workingDirs, err := coreutils.GetFullPathsWorkingDirs(auditCmd.workingDirs) if err != nil { return } - // Should be called before creating the audit params, so the params will contain XSC information. auditCmd.analyticsMetricsService.AddGeneralEvent(auditCmd.analyticsMetricsService.CreateGeneralEvent(xscservices.CliProduct, xscservices.CliEventType)) auditParams := NewAuditParams(). @@ -132,7 +136,7 @@ func (auditCmd *AuditCommand) Run() (err error) { SetThreads(auditCmd.Threads) auditParams.SetIsRecursiveScan(isRecursiveScan).SetExclusions(auditCmd.Exclusions()) - auditResults, err := RunAudit(auditParams) + auditResults, err = RunAudit(auditParams) if err != nil { return } @@ -142,29 +146,39 @@ func (auditCmd *AuditCommand) Run() (err error) { return } } - var messages []string - if !auditResults.ExtendedScanResults.EntitledForJas { - messages = []string{coreutils.PrintTitle("The ‘jf audit’ command also supports JFrog Advanced Security features, such as 'Contextual Analysis', 'Secret Detection', 'IaC Scan' and ‘SAST’.\nThis feature isn't enabled on your system. Read more - ") + coreutils.PrintLink("https://jfrog.com/xray/")} + if !printResults { + return } - if err = xrayutils.NewResultsWriter(auditResults). - SetIsMultipleRootProject(auditResults.IsMultipleProject()). - SetIncludeVulnerabilities(auditCmd.IncludeVulnerabilities). - SetIncludeLicenses(auditCmd.IncludeLicenses). - SetOutputFormat(auditCmd.OutputFormat()). - SetPrintExtendedTable(auditCmd.PrintExtendedTable). - SetExtraMessages(messages). - SetScanType(services.Dependency). - SetSubScansPreformed(auditCmd.ScansToPerform()). - PrintScanResults(); err != nil { + if err = auditCmd.PrintAuditResults(auditResults); err != nil { return } + err = auditCmd.GetResultsError(auditResults) + return +} - if auditResults.ScansErr != nil { - return auditResults.ScansErr +func (auditCmd *AuditCommand) PrintAuditResults(auditResults *xrayutils.Results) (err error) { + var messages []string + if !auditResults.ExtendedScanResults.EntitledForJas { + messages = []string{coreutils.PrintTitle("The ‘jf audit’ command also supports JFrog Advanced Security features, such as 'Contextual Analysis', 'Secret Detection', 'IaC Scan' and ‘SAST’.\nThis feature isn't enabled on your system. Read more - ") + coreutils.PrintLink("https://jfrog.com/xray/")} } + return xrayutils.NewResultsWriter(auditResults). + SetIsMultipleRootProject(auditResults.IsMultipleProject()). + SetIncludeVulnerabilities(auditCmd.IncludeVulnerabilities). + SetIncludeLicenses(auditCmd.IncludeLicenses). + SetOutputFormat(auditCmd.OutputFormat()). + SetPrintExtendedTable(auditCmd.PrintExtendedTable). + SetExtraMessages(messages). + SetScanType(services.Dependency). + SetSubScansPreformed(auditCmd.ScansToPerform()). + PrintScanResults() +} +func (auditCmd *AuditCommand) GetResultsError(results *xrayutils.Results) (err error) { + if results.ScansErr != nil { + return results.ScansErr + } // Only in case Xray's context was given (!auditCmd.IncludeVulnerabilities), and the user asked to fail the build accordingly, do so. - if auditCmd.Fail && !auditCmd.IncludeVulnerabilities && xrayutils.CheckIfFailBuild(auditResults.GetScaScansXrayResults()) { + if auditCmd.Fail && !auditCmd.IncludeVulnerabilities && xrayutils.CheckIfFailBuild(results.GetScaScansXrayResults()) { err = xrayutils.NewFailBuildError() } return diff --git a/commands/git/audit/auditgit.go b/commands/git/audit/auditgit.go new file mode 100644 index 00000000..77c3f140 --- /dev/null +++ b/commands/git/audit/auditgit.go @@ -0,0 +1,218 @@ +package audit + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + // "github.com/jfrog/jfrog-cli-core/artifactory/commands/utils" + "github.com/jfrog/jfrog-cli-security/commands/audit" + "github.com/jfrog/jfrog-cli-security/utils" + "github.com/jfrog/jfrog-cli-security/utils/techutils" + clientutils "github.com/jfrog/jfrog-client-go/utils" + "golang.org/x/exp/slices" + + // "golang.org/x/exp/slices" + + "github.com/jfrog/jfrog-client-go/utils/log" +) + +const ( + initialCommitHash = "0000000000000000000000000000000000000000" +) + +type GitAuditCommand struct { + Source string + Target string + audit.AuditCommand +} + +func NewGitAuditCommand(auditCmd *audit.AuditCommand) *GitAuditCommand { + return &GitAuditCommand{AuditCommand: *auditCmd} +} + +func (gaCmd *GitAuditCommand) SetSource(source string) *GitAuditCommand { + gaCmd.Source = source + return gaCmd +} + +func (gaCmd *GitAuditCommand) SetTarget(target string) *GitAuditCommand { + gaCmd.Target = target + return gaCmd +} + +func (gaCmd *GitAuditCommand) Run() (err error) { + log.Info(fmt.Sprintf("Calculating diff between source commit `%s` and target commit `%s`", gaCmd.Source, gaCmd.Target)) + wd, err := os.Getwd() + if err != nil { + return + } + gitManager := clientutils.NewGitManager(wd) + // Calculate diff + filesChanged, err := GetFileDiff(gitManager, gaCmd.Source, gaCmd.Target) + if err != nil { + return + } + if len(filesChanged) == 0 { + log.Info("No files changed between the commits") + return + } else { + log.Debug(fmt.Sprintf("Files changed: %v", filesChanged)) + } + // Get required scans by file changes + if requiredScans := getRequiredScans(gaCmd.ScansToPerform(), filesChanged); len(requiredScans) > 0 { + log.Info(fmt.Sprintf("Preforming the following scans: %v", requiredScans)) + gaCmd.AuditParams.SetScansToPerform(requiredScans) + } else { + log.Info("No scans required for the changed files") + return + } + // Change to target commit (TODO: at the end, return to the original commit or branch to avoid side effects) + if gitManager.GetRevision() != gaCmd.Target { + log.Info(fmt.Sprintf("Checking out to target commit `%s`", gaCmd.Target)) + if err = checkoutToCommit(gitManager, gaCmd.Target); err != nil { + return + } + } + // Run audit + results, err := gaCmd.RunAuditCommand(false) + if err != nil { + return + } + // Filter results by added lines + finalResults := filterResultsByFiles(results, filesChanged) + // Print results + if err = gaCmd.PrintAuditResults(finalResults); err != nil { + return + } + err = gaCmd.GetResultsError(finalResults) + return +} + +func checkoutToCommit(gitManager *clientutils.GitManager, commitHash string) (err error) { + stdOut, stdErr, err := gitManager.ExecGit("checkout", commitHash) + if stdErr != "" { + // log.Warn(fmt.Sprintf("Git checkout stderr:\n%s", stdErr)) + } + if err != nil { + return + } + if stdOut != "" { + // log.Debug(fmt.Sprintf("Git checkout stdout:\n%s", stdOut)) + } + return +} + +func getRequiredScans(requestedScans []utils.SubScanType, filesChanged []string) []utils.SubScanType { + // TODO: what if already provided? + requiredScans := []utils.SubScanType{} + tech := shouldRunScaScan(requestedScans, filesChanged) + shouldRunJas := shouldRunJasScan(filesChanged, tech) + // Calculate required scans + if tech != nil { + log.Debug(fmt.Sprintf("Technology `%s` is detected in the changed files", tech.ToFormal())) + requiredScans = append(requiredScans, utils.ScaScan) + if len(requestedScans) == 0 || slices.Contains(requestedScans, utils.ContextualAnalysisScan) { + requiredScans = append(requiredScans, utils.ContextualAnalysisScan) + } + } + if shouldRunJas { + requiredScans = append(requiredScans, utils.IacScan, utils.SastScan, utils.SecretsScan) + } else { + log.Debug("Files changed are only related to technology or not changed at all. Skipping IaC, SAST and Secrets scans...") + } + return requiredScans +} + +// Check if the files changed are related to the technology +func shouldRunScaScan(requestedScans []utils.SubScanType, filesChanged []string) *techutils.Technology { + if len(requestedScans) > 0 && !slices.Contains(requestedScans, utils.ScaScan){ + log.Debug("SCA scan is not requested. Skipping...") + return nil + } + for _, file := range filesChanged { + for _, tech := range techutils.GetAllTechnologiesList() { + isIndicator, err := tech.IsIndicator(file) + if err != nil { + log.Warn(fmt.Sprintf("Failed to check if file `%s` is an indicator for technology `%s`: %s", file, tech.ToFormal(), err.Error())) + continue + } + if tech.IsDescriptor(file) || isIndicator { + log.Debug(fmt.Sprintf("File `%s` is an indicator for technology `%s`", file, tech.ToFormal())) + return &tech + } + } + } + return nil +} + +// Check if the files changed are not related to the technology +func shouldRunJasScan(filesChanged []string, tech *techutils.Technology) bool { + if len(filesChanged) == 0 { + return false + } + if tech == nil { + return true + } + for _, file := range filesChanged { + isIndicator, err := tech.IsIndicator(file) + if err != nil { + log.Warn(fmt.Sprintf("Failed to check if file `%s` is an indicator for technology `%s`: %s", file, tech.ToFormal(), err.Error())) + continue + } + if !tech.IsDescriptor(file) && !isIndicator { + log.Debug(fmt.Sprintf("File `%s` is not an indicator for technology `%s`", file, tech.ToFormal())) + return true + } + } + return false +} + +func shouldRunScan(requiredScans []utils.SubScanType, requestedScanType utils.SubScanType) bool { + if len(requiredScans) == 0 { + return true + } + return slices.Contains(requiredScans, requestedScanType) +} + +func getAsFullPath(wd string, files ...string) ([]string) { + fullPaths := []string{} + for _, file := range files { + fullPaths = append(fullPaths, filepath.Join(wd,file)) + } + return fullPaths +} + +func filterResultsByFiles(results *utils.Results, changedFiles []string) *utils.Results { + // filteredResults := utils.NewAuditResults() + // Filter SCA results + // for _, scaResult := range results.ScaResults { + // // for _, xrayResults := range scaResult.XrayResults { + // // // if slices.Contains(changedFiles, xrayResults.FileName) { + // // // filteredResults.ScaResults = append(filteredResults.ScaResults, scaResult) + // // // break + // // // } + // // } + // } + return results +} + + +// move to GitManager in client-go + +// GetFileDiff returns the list of files changed between two commits. +// Source - can be a branch or a commit hash +// TODO: Target - can be a branch or a commit hash? (not sure) - need to check +func GetFileDiff(gitManager *clientutils.GitManager, source string, target string) (filesChanged []string, err error) { + stdout, stderr, err := gitManager.ExecGit("diff", "--name-only", source, target) + if stderr != "" { + log.Warn(fmt.Sprintf("Git diff stderr:\n%s", stderr)) + } + if err != nil { + return + } + // Split the output into lines and return as a slice of strings + changedFiles := strings.Split(strings.TrimSpace(stdout), "\n") + return changedFiles, nil +} \ No newline at end of file diff --git a/commands/git/audit/gitdiff.go b/commands/git/audit/gitdiff.go new file mode 100644 index 00000000..619c61f1 --- /dev/null +++ b/commands/git/audit/gitdiff.go @@ -0,0 +1,60 @@ +package audit + +import ( + "github.com/sourcegraph/go-diff/diff" +) + +type Edits struct { + FilesToEdit []FileEdit +} + +func (e *Edits) GetFilesToChange() []string { + return []string{} +} + +type FileEdit struct { + MetaDataEdit FileChange + // ContentEdits []ContentEdit +} + +const ( + NoEditOperation EditOperation = " " + EditOperationAdd EditOperation = "+" + EditOperationDelete EditOperation = "-" + + FileChangeAdd FileChange = "added" + FileChangeDelete FileChange = "deleted" + FileChangeModify FileChange = "modified" + + // If file was created the source (pre-change) is /dev/null + // If file was deleted the target (post-change) is /dev/null + FileNotExists = "/dev/null" + + // Default - could be diff depend on git config + Source = "a/" + Target = "b/" +) + +type EditOperation string +type FileChange string + +type FileMetaDataEdit struct { + Operation FileChange + Source string + // For rename operation + Target string +} + +// Content should be the output of git diff command (Unified format): https://git-scm.com/docs/git-diff +func ParseUnifiedDiffToEditOperations(unifiedDiffFormatContent string) (edits Edits, err error) { + fileDiff, err := diff.ParseMultiFileDiff([]byte(unifiedDiffFormatContent)) + if err != nil { + return + } + return ConvertFileDiffToEditOperations(fileDiff) +} + +// Get the edit operations to apply in order to transform the source to be the target based on the diff +func ConvertFileDiffToEditOperations(fd []*diff.FileDiff) (Edits, error) { + return Edits{}, nil +} diff --git a/go.mod b/go.mod index 866c4892..bd2fb38e 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/jfrog/jfrog-client-go v1.41.0 github.com/magiconair/properties v1.8.7 github.com/owenrumney/go-sarif/v2 v2.3.0 + github.com/sourcegraph/go-diff v0.7.0 github.com/stretchr/testify v1.9.0 golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 golang.org/x/sync v0.7.0 diff --git a/go.sum b/go.sum index e18474ab..ff6b086d 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,7 @@ github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.2/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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -183,11 +184,15 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= +github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= diff --git a/utils/analyticsmetrics.go b/utils/analyticsmetrics.go index b9b57beb..cd413b22 100644 --- a/utils/analyticsmetrics.go +++ b/utils/analyticsmetrics.go @@ -23,6 +23,22 @@ type AnalyticsMetricsService struct { finalizeEvent *xscservices.XscAnalyticsGeneralEventFinalize } +func ReportErrorIfExists(err error, getServerDetailsFunc func() (*config.ServerDetails, error)) error { + if err == nil || !usage.ShouldReportUsage() { + return err + } + var serverDetails *config.ServerDetails + serverDetails, innerError := getServerDetailsFunc() + if innerError != nil { + log.Debug(fmt.Sprintf("failed to get server details for error report: %q", innerError)) + return err + } + if reportError := ReportError(serverDetails, err, "cli"); reportError != nil { + log.Debug("failed to report error log:" + reportError.Error()) + } + return err +} + func NewAnalyticsMetricsService(serviceDetails *config.ServerDetails) *AnalyticsMetricsService { ams := AnalyticsMetricsService{} xscManager, err := CreateXscServiceManager(serviceDetails) diff --git a/utils/techutils/techutils.go b/utils/techutils/techutils.go index 8d86abd6..3c66df9c 100644 --- a/utils/techutils/techutils.go +++ b/utils/techutils/techutils.go @@ -232,7 +232,7 @@ func (tech Technology) GetPackageInstallationCommand() string { return technologiesData[tech].packageInstallationCommand } -func (tech Technology) isDescriptor(path string) bool { +func (tech Technology) IsDescriptor(path string) bool { for _, descriptor := range technologiesData[tech].packageDescriptors { if strings.HasSuffix(path, descriptor) { return true @@ -241,7 +241,7 @@ func (tech Technology) isDescriptor(path string) bool { return false } -func (tech Technology) isIndicator(path string) (bool, error) { +func (tech Technology) IsIndicator(path string) (bool, error) { for _, suffix := range technologiesData[tech].indicators { if strings.HasSuffix(path, suffix) { return checkPotentialIndicator(path, technologiesData[tech].validators[suffix]) @@ -314,12 +314,12 @@ func mapFilesToRelevantWorkingDirectories(files []string, requestedDescriptors m for tech, techData := range technologiesData { // Check if the working directory contains indicators/descriptors for the technology - indicator, e := tech.isIndicator(path) + indicator, e := tech.IsIndicator(path) if e != nil { err = errors.Join(err, fmt.Errorf("failed to check if %s is an indicator of %s: %w", path, tech, e)) continue } - relevant := indicator || tech.isDescriptor(path) || isRequestedDescriptor(path, requestedDescriptors[tech]) + relevant := indicator || tech.IsDescriptor(path) || isRequestedDescriptor(path, requestedDescriptors[tech]) if relevant { if _, exist := workingDirectoryToIndicatorsSet[directory]; !exist { workingDirectoryToIndicatorsSet[directory] = datastructures.MakeSet[string]() @@ -411,10 +411,10 @@ func getTechInformationFromWorkingDir(tech Technology, workingDirectoryToIndicat } // Check if the working directory contains indicators/descriptors for the technology for _, path := range indicators { - if tech.isDescriptor(path) || isRequestedDescriptor(path, requestedDescriptors[tech]) { + if tech.IsDescriptor(path) || isRequestedDescriptor(path, requestedDescriptors[tech]) { descriptorsAtWd = append(descriptorsAtWd, path) } - if indicator, e := tech.isIndicator(path); e != nil { + if indicator, e := tech.IsIndicator(path); e != nil { err = errors.Join(err, fmt.Errorf("failed to check if %s is an indicator of %s: %w", path, tech, e)) continue } else if indicator || isRequestedDescriptor(path, requestedDescriptors[tech]) {