From e0ea860f6289f5c0389ea1f7bd8763b9f6f00864 Mon Sep 17 00:00:00 2001 From: Eyal Delarea Date: Sun, 1 Sep 2024 11:32:56 +0300 Subject: [PATCH] Command Summary - New UI (#2671) --- general/summary/cli.go | 106 ++++++++++++++++++++------------ go.mod | 12 ++-- go.sum | 16 ++--- utils/cliutils/commandsflags.go | 2 +- 4 files changed, 83 insertions(+), 53 deletions(-) diff --git a/general/summary/cli.go b/general/summary/cli.go index 2280925d0..a3d282a0a 100644 --- a/general/summary/cli.go +++ b/general/summary/cli.go @@ -37,14 +37,13 @@ func (ms MarkdownSection) String() string { return string(ms) } -// Creates a summary of recorded CLI commands that were executed on the current machine. -// The summary is generated in Markdown format -// and saved in the directory stored in the JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR environment variable. +// GenerateSummaryMarkdown creates a summary of recorded CLI commands in Markdown format. func GenerateSummaryMarkdown(c *cli.Context) error { - if !ShouldGenerateSummary() { + if !shouldGenerateSummary() { return fmt.Errorf("unable to generate the command summary because the output directory is not specified."+ " Please ensure that the environment variable '%s' is set before running your commands to enable summary generation", coreutils.SummaryOutputDirPathEnv) } + // Get URL and Version to generate summary links serverUrl, majorVersion, err := extractServerUrlAndVersion(c) if err != nil { @@ -58,22 +57,25 @@ func GenerateSummaryMarkdown(c *cli.Context) error { // Invoke each section's markdown generation function for _, section := range markdownSections { if err := invokeSectionMarkdownGeneration(section); err != nil { - log.Warn("Failed to generate markdown for section %s: %v", section, err) + log.Warn("Failed to generate markdown for section:", section, err) } } // Combine all sections into a single Markdown file - finalMarkdown, err := combineMarkdownFiles() + finalMarkdown, err := mergeMarkdownFiles() if err != nil { return fmt.Errorf("error combining markdown files: %w", err) } + // Saves the final Markdown to the root directory of the command summaries return saveMarkdownToFileSystem(finalMarkdown) } -func combineMarkdownFiles() (string, error) { +// The CLI generates summaries in sections, with each section as a separate Markdown file. +// This function merges all sections into a single Markdown file and saves it in the root of the +// command summary output directory. +func mergeMarkdownFiles() (string, error) { var combinedMarkdown strings.Builder - // Read each section content and append it to the final Markdown for _, section := range markdownSections { sectionContent, err := getSectionMarkdownContent(section) if err != nil { @@ -86,45 +88,24 @@ func combineMarkdownFiles() (string, error) { return combinedMarkdown.String(), nil } -// Saves markdown content in the directory stored in the JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR environment variable. +// saveMarkdownToFileSystem saves markdown content in the specified directory. func saveMarkdownToFileSystem(finalMarkdown string) (err error) { if finalMarkdown == "" { return nil } filePath := filepath.Join(os.Getenv(coreutils.SummaryOutputDirPathEnv), JfrogCliSummaryDir, MarkdownFileName) file, err := os.Create(filePath) - defer func() { - err = file.Close() - }() if err != nil { return fmt.Errorf("error creating markdown file: %w", err) } + defer func() { + err = errors.Join(err, file.Close()) + }() // Write to file if _, err := file.WriteString(finalMarkdown); err != nil { return fmt.Errorf("error writing to markdown file: %w", err) } - return -} - -func wrapCollapsibleSection(section MarkdownSection, markdown string) (string, error) { - sectionTitle, err := getSectionTitle(section) - if err != nil { - return "", err - } - return fmt.Sprintf("\n\n\n
\n\n %s

\n\n %s \n\n
\n\n\n", sectionTitle, markdown), nil -} - -func getSectionTitle(section MarkdownSection) (string, error) { - switch section { - case Upload: - return "📁 Files uploaded to Artifactory by this workflow", nil - case BuildInfo: - return "📦 Artifacts published to Artifactory by this workflow", nil - case Security: - return "🔒 Security Summary", nil - default: - return "", fmt.Errorf("unknown section: %s", section) - } + return nil } func getSectionMarkdownContent(section MarkdownSection) (string, error) { @@ -140,9 +121,10 @@ func getSectionMarkdownContent(section MarkdownSection) (string, error) { if len(contentBytes) == 0 { return "", nil } - return wrapCollapsibleSection(section, string(contentBytes)) + return string(contentBytes), nil } +// Initiate the desired command summary implementation and invoke its Markdown generation. func invokeSectionMarkdownGeneration(section MarkdownSection) error { switch section { case Security: @@ -169,6 +151,9 @@ func generateBuildInfoMarkdown() error { if err != nil { return fmt.Errorf("error generating build-info markdown: %w", err) } + if err = mapScanResults(buildInfoSummary); err != nil { + return fmt.Errorf("error mapping scan results: %w", err) + } return buildInfoSummary.GenerateMarkdown() } @@ -184,7 +169,52 @@ func generateUploadMarkdown() error { return uploadSummary.GenerateMarkdown() } -// Upload summary should be generated only if the no build-info data exists +// mapScanResults maps the scan results saved during runtime into scan components. +func mapScanResults(commandSummary *commandsummary.CommandSummary) (err error) { + // Gets the saved scan results file paths. + indexedFiles, err := commandSummary.GetIndexedDataFilesPaths() + if err != nil { + return err + } + securityJobSummary := &securityUtils.SecurityJobSummary{} + // Init scan result map + scanResultsMap := make(map[string]commandsummary.ScanResult) + // Set default not scanned component view + scanResultsMap[commandsummary.NonScannedResult] = securityJobSummary.GetNonScannedResult() + commandsummary.StaticMarkdownConfig.SetScanResultsMapping(scanResultsMap) + // Process each scan result file by its type and append to map + for index, keyValue := range indexedFiles { + for scannedEntityName, scanResultDataFilePath := range keyValue { + scanResultsMap, err = processScan(index, scanResultDataFilePath, scannedEntityName, securityJobSummary, scanResultsMap) + if err != nil { + return + } + } + } + return +} + +// Each scan result should be processed according to its index. +// To generate custom view for each scan type. +func processScan(index commandsummary.Index, filePath string, scannedName string, sec *securityUtils.SecurityJobSummary, scanResultsMap map[string]commandsummary.ScanResult) (map[string]commandsummary.ScanResult, error) { + var res commandsummary.ScanResult + var err error + switch index { + case commandsummary.DockerScan: + res, err = sec.DockerScan([]string{filePath}) + case commandsummary.BuildScan: + res, err = sec.BuildScan([]string{filePath}) + case commandsummary.BinariesScan: + res, err = sec.BinaryScan([]string{filePath}) + } + scanResultsMap[scannedName] = res + if err != nil { + return nil, err + } + return scanResultsMap, nil +} + +// shouldGenerateUploadSummary checks if upload summary should be generated. func shouldGenerateUploadSummary() (bool, error) { buildInfoPath := filepath.Join(os.Getenv(coreutils.SummaryOutputDirPathEnv), JfrogCliSummaryDir, string(BuildInfo)) if _, err := os.Stat(buildInfoPath); os.IsNotExist(err) { @@ -225,7 +255,7 @@ func extractServerUrlAndVersion(c *cli.Context) (platformUrl string, platformMaj return } -// Summary should be generated only when the output directory is defined -func ShouldGenerateSummary() bool { +// shouldGenerateSummary checks if the summary should be generated. +func shouldGenerateSummary() bool { return os.Getenv(coreutils.SummaryOutputDirPathEnv) != "" } diff --git a/go.mod b/go.mod index 9b6657873..6ce594f19 100644 --- a/go.mod +++ b/go.mod @@ -19,10 +19,10 @@ require ( github.com/jfrog/build-info-go v1.9.35 github.com/jfrog/gofrog v1.7.5 github.com/jfrog/jfrog-cli-artifactory v0.1.6 - github.com/jfrog/jfrog-cli-core/v2 v2.55.5 + github.com/jfrog/jfrog-cli-core/v2 v2.55.6 github.com/jfrog/jfrog-cli-platform-services v1.3.0 - github.com/jfrog/jfrog-cli-security v1.7.1 - github.com/jfrog/jfrog-client-go v1.46.0 + github.com/jfrog/jfrog-cli-security v1.7.2 + github.com/jfrog/jfrog-client-go v1.46.1 github.com/jszwec/csvutil v1.10.0 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.33.0 @@ -171,12 +171,12 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240828060210-e7b37bdf5410 +// replace github.com/jfrog/jfrog-cli-core/v2 => github.com/eyaldelarea/jfrog-cli-core/v2 v2.0.0-20240829171158-7b0f89df2c0c -replace github.com/jfrog/jfrog-cli-security => github.com/attiasas/jfrog-cli-security v0.0.0-20240828061232-6cb16f5dd7ef +// replace github.com/jfrog/jfrog-cli-security => github.com/attiasas/jfrog-cli-security v0.0.0-20240829151632-3a7a90969eca // replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240806162439-01bb7dcd43fc -replace github.com/jfrog/build-info-go => github.com/asafambar/build-info-go v1.8.9-0.20240819133117-c3f52700927d +// replace github.com/jfrog/build-info-go => github.com/asafambar/build-info-go v1.8.9-0.20240819133117-c3f52700927d // replace github.com/jfrog/gofrog => github.com/jfrog/gofrog dev diff --git a/go.sum b/go.sum index 1c67ca23d..5e4b116dd 100644 --- a/go.sum +++ b/go.sum @@ -641,10 +641,6 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asafambar/build-info-go v1.8.9-0.20240819133117-c3f52700927d h1:GS9yDbl7Moer5ODbWu+DPcQtyCYlmRL4TAdt1l+b/Vo= -github.com/asafambar/build-info-go v1.8.9-0.20240819133117-c3f52700927d/go.mod h1:6mdtqjREK76bHNODXakqKR/+ksJ9dvfLS7H57BZtnLY= -github.com/attiasas/jfrog-cli-security v0.0.0-20240828061232-6cb16f5dd7ef h1:TE2MFFvWKw6SblR3yEGDftRJrIfhReix0zYMWexfFls= -github.com/attiasas/jfrog-cli-security v0.0.0-20240828061232-6cb16f5dd7ef/go.mod h1:VRMOAgJzP8JDABOJs5PU2Ph7SiutOK4NffASTdoPcq8= github.com/beevik/etree v1.4.0 h1:oz1UedHRepuY3p4N5OjE0nK1WLCqtzHf25bxplKOHLs= github.com/beevik/etree v1.4.0/go.mod h1:cyWiXwGoasx60gHvtnEh5x8+uIjUVnjWqBvEnhnqKDA= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -935,6 +931,8 @@ github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+ github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= +github.com/jfrog/build-info-go v1.9.35 h1:P53Ckbuin0GYrq0LWMY0GZSptJcQwiUyW6lqTbXKdcc= +github.com/jfrog/build-info-go v1.9.35/go.mod h1:6mdtqjREK76bHNODXakqKR/+ksJ9dvfLS7H57BZtnLY= github.com/jfrog/froggit-go v1.16.1 h1:FBIM1qevX/ag9unfmpGzfmZ36D8ulOJ+DPTSFUk3l5U= github.com/jfrog/froggit-go v1.16.1/go.mod h1:TEJSzgiV+3D/GVGE8Y6j46ut1jrBLD1FL6WdMdKwwCE= github.com/jfrog/gofrog v1.7.5 h1:dFgtEDefJdlq9cqTRoe09RLxS5Bxbe1Ev5+E6SmZHcg= @@ -943,12 +941,14 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-artifactory v0.1.6 h1:bMfJsrLQJw0dZp4nqUf1xOmtY0rpCatW/I5q88x+fhQ= github.com/jfrog/jfrog-cli-artifactory v0.1.6/go.mod h1:jbNb22ebtupcjdhrdGq0VBew2vWG6VUK04xxGNDfynE= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240828060210-e7b37bdf5410 h1:nClCz6HXEH94Bw86lRYb+omzOIzq2iJMnbjiKbaVxVk= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240828060210-e7b37bdf5410/go.mod h1:g+QR8lvFtu7uDbEgMcS0Vl7fwfp+1mtC2fFQRB8tv+0= +github.com/jfrog/jfrog-cli-core/v2 v2.55.6 h1:3tQuEdYgS2q7fkrrSG66OnO0S998FXGaY9BVsxSLst4= +github.com/jfrog/jfrog-cli-core/v2 v2.55.6/go.mod h1:DPO5BfWAeOByahFMMy+PcjmbPlcyoRy7Bf2C5sGKVi0= github.com/jfrog/jfrog-cli-platform-services v1.3.0 h1:IblSDZFBjL7WLRi37Ni2DmHrXJJ6ysSMxx7t41AvyDA= github.com/jfrog/jfrog-cli-platform-services v1.3.0/go.mod h1:Ky4SDXuMeaiNP/5zMT1YSzIuXG+cNYYOl8BaEA7Awbc= -github.com/jfrog/jfrog-client-go v1.46.0 h1:lyzJCpLN9NIp4raHw48D12+g42/Aa8SHxBbuNp4Yflk= -github.com/jfrog/jfrog-client-go v1.46.0/go.mod h1:UCu2JNBfMp9rypEmCL84DCooG79xWIHVadZQR3Ab+BQ= +github.com/jfrog/jfrog-cli-security v1.7.2 h1:Kvabj/6LhM+WEb6woIqqbv2VmIj69IFwz859Sys1Tgs= +github.com/jfrog/jfrog-cli-security v1.7.2/go.mod h1:4eztJ+gBb7Xtq/TtnOvIodBOMZutPIAZOuLxqHWXrOo= +github.com/jfrog/jfrog-client-go v1.46.1 h1:ExqOF8ClOG9LO3vbm6jTIwQHHhprbu8lxB2RrM6mMI0= +github.com/jfrog/jfrog-client-go v1.46.1/go.mod h1:UCu2JNBfMp9rypEmCL84DCooG79xWIHVadZQR3Ab+BQ= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jszwec/csvutil v1.10.0 h1:upMDUxhQKqZ5ZDCs/wy+8Kib8rZR8I8lOR34yJkdqhI= diff --git a/utils/cliutils/commandsflags.go b/utils/cliutils/commandsflags.go index f37b33bcb..2c656a237 100644 --- a/utils/cliutils/commandsflags.go +++ b/utils/cliutils/commandsflags.go @@ -1829,7 +1829,7 @@ var commandFlags = map[string][]string{ }, Docker: { buildName, buildNumber, module, Project, - serverId, skipLogin, threads, detailedSummary, watches, repoPath, licenses, xrOutput, fail, ExtendedTable, BypassArchiveLimits, MinSeverity, FixableOnly, + serverId, skipLogin, threads, detailedSummary, watches, repoPath, licenses, xrOutput, fail, ExtendedTable, BypassArchiveLimits, MinSeverity, FixableOnly, vuln, }, DockerPush: { buildName, buildNumber, module, Project,