From ebf20927e69f9e78995be422f9d1fa000b6f8213 Mon Sep 17 00:00:00 2001 From: Eyal Delarea Date: Wed, 4 Sep 2024 12:52:15 +0300 Subject: [PATCH 1/3] Update Summaries extended page link & Add logs (#1256) --- artifactory/utils/commandsummary/buildinfosummary.go | 3 +++ artifactory/utils/commandsummary/commandsummary.go | 2 +- artifactory/utils/commandsummary/markdownConfig.go | 2 +- .../utils/testdata/command_summaries/basic/build-info-table.md | 2 +- .../testdata/command_summaries/basic/docker-image-module.md | 2 +- .../utils/testdata/command_summaries/basic/generic-module.md | 2 +- .../utils/testdata/command_summaries/basic/maven-module.md | 2 +- .../testdata/command_summaries/basic/multiarch-docker-image.md | 2 +- 8 files changed, 10 insertions(+), 7 deletions(-) diff --git a/artifactory/utils/commandsummary/buildinfosummary.go b/artifactory/utils/commandsummary/buildinfosummary.go index 00fb71e36..b835b831d 100644 --- a/artifactory/utils/commandsummary/buildinfosummary.go +++ b/artifactory/utils/commandsummary/buildinfosummary.go @@ -5,6 +5,7 @@ import ( buildInfo "github.com/jfrog/build-info-go/entities" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/container" + "github.com/jfrog/jfrog-client-go/utils/log" "path" "strings" ) @@ -273,9 +274,11 @@ func fitInsideMarkdownTable(str string) string { } func getScanResults(scannedEntity string) (sc ScanResult) { + log.Debug("Getting scan results for: ", scannedEntity) if sc = StaticMarkdownConfig.scanResultsMapping[fileNameToSha1(scannedEntity)]; sc != nil { return sc } + log.Debug("No scan results found for: ", scannedEntity) return StaticMarkdownConfig.scanResultsMapping[NonScannedResult] } diff --git a/artifactory/utils/commandsummary/commandsummary.go b/artifactory/utils/commandsummary/commandsummary.go index 2dc17fd11..fe993ac9c 100644 --- a/artifactory/utils/commandsummary/commandsummary.go +++ b/artifactory/utils/commandsummary/commandsummary.go @@ -127,7 +127,7 @@ func (cs *CommandSummary) Record(data any) (err error) { // SummaryIndex: The name of the index under which the data will be stored. // Args: Additional arguments used to determine the file name. func (cs *CommandSummary) RecordWithIndex(data any, summaryIndex Index, args ...string) (err error) { - log.Debug("Recording data with index:", summaryIndex) + log.Debug("Recording data with index:", summaryIndex, "and args:", args) return cs.recordInternal(data, summaryIndex, args) } diff --git a/artifactory/utils/commandsummary/markdownConfig.go b/artifactory/utils/commandsummary/markdownConfig.go index 931c9be42..300a93d69 100644 --- a/artifactory/utils/commandsummary/markdownConfig.go +++ b/artifactory/utils/commandsummary/markdownConfig.go @@ -24,7 +24,7 @@ type MarkdownConfig struct { scanResultsMapping map[string]ScanResult } -const extendedSummaryLandPage = "https://jfrog.com/help/r/jfrog-and-github-integration-guide/jfrog-and-github-integration-features-matrix" +const extendedSummaryLandPage = "https://jfrog.com/help/access?xinfo:appid=csh-gen-gitbook" var StaticMarkdownConfig = MarkdownConfig{} diff --git a/artifactory/utils/testdata/command_summaries/basic/build-info-table.md b/artifactory/utils/testdata/command_summaries/basic/build-info-table.md index ebcd93e69..96c5e8f14 100644 --- a/artifactory/utils/testdata/command_summaries/basic/build-info-table.md +++ b/artifactory/utils/testdata/command_summaries/basic/build-info-table.md @@ -2,6 +2,6 @@ | Build Info | Security Violations | Security Issues | | :--------- | :------------ | :------------ | -| 🐸 Enable the linkage to Artifactory

buildName 123 | Not scanned | Not scanned | +| 🐸 Enable the linkage to Artifactory

buildName 123 | Not scanned | Not scanned | diff --git a/artifactory/utils/testdata/command_summaries/basic/docker-image-module.md b/artifactory/utils/testdata/command_summaries/basic/docker-image-module.md index e390980f5..237a69a68 100644 --- a/artifactory/utils/testdata/command_summaries/basic/docker-image-module.md +++ b/artifactory/utils/testdata/command_summaries/basic/docker-image-module.md @@ -10,4 +10,4 @@ | Artifacts | Security Violations | Security Issues | | :------------ | :--------------------- | :------------------ | -| 🐸 Enable the linkage to Artifactory

📦 docker-local
└── 📁 image2
└── 📁 sha256:552c
└── 📄 sha256__aae9

| Not scanned | Not scanned | +| 🐸 Enable the linkage to Artifactory

📦 docker-local
└── 📁 image2
└── 📁 sha256:552c
└── 📄 sha256__aae9

| Not scanned | Not scanned | diff --git a/artifactory/utils/testdata/command_summaries/basic/generic-module.md b/artifactory/utils/testdata/command_summaries/basic/generic-module.md index ff9608223..2605bdb56 100644 --- a/artifactory/utils/testdata/command_summaries/basic/generic-module.md +++ b/artifactory/utils/testdata/command_summaries/basic/generic-module.md @@ -10,4 +10,4 @@ | Artifacts | Security Violations | Security Issues | | :------------ | :--------------------- | :------------------ | -| 🐸 Enable the linkage to Artifactory

📦 generic-local
└── 📁 path
└── 📁 to
└── 📄 artifact2

| Not scanned | Not scanned | +| 🐸 Enable the linkage to Artifactory

📦 generic-local
└── 📁 path
└── 📁 to
└── 📄 artifact2

| Not scanned | Not scanned | diff --git a/artifactory/utils/testdata/command_summaries/basic/maven-module.md b/artifactory/utils/testdata/command_summaries/basic/maven-module.md index 0a560a380..9aee43395 100644 --- a/artifactory/utils/testdata/command_summaries/basic/maven-module.md +++ b/artifactory/utils/testdata/command_summaries/basic/maven-module.md @@ -10,4 +10,4 @@ | Artifacts | Security Violations | Security Issues | | :------------ | :--------------------- | :------------------ | -| 🐸 Enable the linkage to Artifactory

📦 libs-release
└── 📁 path
└── 📁 to
└── 📄 artifact1

| Not scanned | Not scanned | +| 🐸 Enable the linkage to Artifactory

📦 libs-release
└── 📁 path
└── 📁 to
└── 📄 artifact1

| Not scanned | Not scanned | diff --git a/artifactory/utils/testdata/command_summaries/basic/multiarch-docker-image.md b/artifactory/utils/testdata/command_summaries/basic/multiarch-docker-image.md index ccff385b5..b0ccd5841 100644 --- a/artifactory/utils/testdata/command_summaries/basic/multiarch-docker-image.md +++ b/artifactory/utils/testdata/command_summaries/basic/multiarch-docker-image.md @@ -10,4 +10,4 @@ | Artifacts | Security Violations | Security Issues | | :------------ | :--------------------- | :------------------ | -| 🐸 Enable the linkage to Artifactory

linux/amd64/multiarch-image:1
📦 docker-local
└── 📁 multiarch-image
├── 📁 sha256:552c
│ └── 📄 sha256
└── 📄 sha256

| Not scanned | Not scanned | +| 🐸 Enable the linkage to Artifactory

linux/amd64/multiarch-image:1
📦 docker-local
└── 📁 multiarch-image
├── 📁 sha256:552c
│ └── 📄 sha256
└── 📄 sha256

| Not scanned | Not scanned | From fbb08c420ad589392096d687ce13ed52a6dd95c8 Mon Sep 17 00:00:00 2001 From: Eyal Delarea Date: Wed, 4 Sep 2024 15:21:26 +0300 Subject: [PATCH 2/3] Command Summary - Separate Module Table View from Artifacts Tree (#1257) --- .../utils/commandsummary/buildinfosummary.go | 157 ++++++---- .../commandsummary/buildinfosummary_test.go | 267 ++++++++++++++++++ .../utils/commandsummary/commandsummary.go | 4 +- .../command_summaries/basic/generic-module.md | 12 +- .../command_summaries/basic/maven-module.md | 12 +- .../basic/maven-nested-module.md | 30 ++ .../extended/generic-module.md | 10 +- .../extended/maven-module.md | 10 +- .../extended/maven-nested-module.md | 28 ++ 9 files changed, 459 insertions(+), 71 deletions(-) create mode 100644 artifactory/utils/testdata/command_summaries/basic/maven-nested-module.md create mode 100644 artifactory/utils/testdata/command_summaries/extended/maven-nested-module.md diff --git a/artifactory/utils/commandsummary/buildinfosummary.go b/artifactory/utils/commandsummary/buildinfosummary.go index b835b831d..2a0fe9e08 100644 --- a/artifactory/utils/commandsummary/buildinfosummary.go +++ b/artifactory/utils/commandsummary/buildinfosummary.go @@ -12,12 +12,28 @@ import ( const ( basicSummaryUpgradeNotice = "🐸 Enable the linkage to Artifactory\n\n" - modulesTitle = "📦 Artifacts published to Artifactory by this workflow" + modulesTitle = "📦 Modules published to Artifactory by this workflow" minTableColumnLength = 400 markdownSpaceFiller = " " NonScannedResult = "non-scanned" ) +var ( + // Scanned modules are modules which can be scanned via CLI command + scannableModuleType = map[buildInfo.ModuleType]bool{ + buildInfo.Docker: true, + } + // Supported modules are modules that their build info contains the OriginalDeploymentRepo field. + supportedModuleTypes = map[buildInfo.ModuleType]bool{ + buildInfo.Maven: true, + buildInfo.Npm: true, + buildInfo.Go: true, + buildInfo.Generic: true, + buildInfo.Terraform: true, + buildInfo.Docker: true, + } +) + type BuildInfoSummary struct { CommandSummary } @@ -41,47 +57,44 @@ func (bis *BuildInfoSummary) GenerateMarkdownFromFiles(dataFilePaths []string) ( builds = append(builds, &publishBuildInfo) } if len(builds) == 0 { - return "", nil + return } - // Creates the build info table + buildInfoTableMarkdown := bis.buildInfoTable(builds) - // Creates the published modules publishedModulesMarkdown := bis.buildInfoModules(builds) if publishedModulesMarkdown != "" { publishedModulesMarkdown = WrapCollapsableMarkdown(modulesTitle, publishedModulesMarkdown, 2) } finalMarkdown = buildInfoTableMarkdown + publishedModulesMarkdown - // Wrap the content under a collapsible section - finalMarkdown = WrapCollapsableMarkdown(bis.GetSummaryTitle(), finalMarkdown, 3) - return + return WrapCollapsableMarkdown(bis.GetSummaryTitle(), finalMarkdown, 3), nil } +// Create a table with published builds and possible scan results. func (bis *BuildInfoSummary) buildInfoTable(builds []*buildInfo.BuildInfo) string { var tableBuilder strings.Builder - // Write table header tableBuilder.WriteString(getBuildInfoTableHeader()) - // Add rows for _, build := range builds { - appendBuildRow(&tableBuilder, build) + appendBuildInfoRow(&tableBuilder, build) } - // Add a new line after the table tableBuilder.WriteString("\n\n") return tableBuilder.String() } +// Generates a view for published modules within the build. +// Modules are displayed as tables if they are scannable via CLI command, +// otherwise, they are shown as an artifact tree. func (bis *BuildInfoSummary) buildInfoModules(builds []*buildInfo.BuildInfo) string { var markdownBuilder strings.Builder markdownBuilder.WriteString("\n\n

Published Modules

\n\n") var shouldGenerate bool for _, build := range builds { - if modulesMarkdown := bis.generateModulesMarkdown(build.Modules...); modulesMarkdown != "" { + supportedModules := filterModules(build.Modules...) + if modulesMarkdown := bis.generateModulesMarkdown(supportedModules...); modulesMarkdown != "" { markdownBuilder.WriteString(modulesMarkdown) shouldGenerate = true } } - - // If no supported module with artifacts was found, avoid generating the markdown. if !shouldGenerate { return "" } @@ -89,30 +102,64 @@ func (bis *BuildInfoSummary) buildInfoModules(builds []*buildInfo.BuildInfo) str } func (bis *BuildInfoSummary) generateModulesMarkdown(modules ...buildInfo.Module) string { - var parentModulesMarkdown strings.Builder - // Modules could have nested modules inside them - // Group them by their parent ID to allow tracing - // If a module has no parent, it is considered a parent module itself - parentToModulesMap := groupModulesByParent(modules) - if len(parentToModulesMap) == 0 { + var modulesMarkdown strings.Builder + // Modules could include nested modules inside of them + // Group the modules by their root module ID + // If a module has no root, it is considered as a root module itself. + groupedModuleMap := groupModules(modules) + if len(groupedModuleMap) == 0 { + return "" + } + for rootModuleID, subModules := range groupedModuleMap { + if len(subModules) == 0 { + continue + } + if !scannableModuleType[subModules[0].Type] { + modulesMarkdown.WriteString(bis.generateModuleArtifactTree(rootModuleID, subModules)) + } else { + modulesMarkdown.WriteString(bis.generateModuleTableView(rootModuleID, subModules)) + } + } + return modulesMarkdown.String() +} + +func (bis *BuildInfoSummary) generateModuleArtifactTree(rootModuleID string, nestedModules []buildInfo.Module) string { + if len(nestedModules) == 0 { return "" } - for parentModuleID, parentModules := range parentToModulesMap { - parentModulesMarkdown.WriteString(generateModuleHeader(parentModuleID)) - parentModulesMarkdown.WriteString(generateModuleTableHeader()) - isMultiModule := len(parentModules) > 1 - nestedModuleMarkdownTree := bis.generateNestedModuleMarkdownTree(parentModules, parentModuleID, isMultiModule) - scanResult := getScanResults(extractDockerImageTag(parentModules)) - parentModulesMarkdown.WriteString(generateTableRow(nestedModuleMarkdownTree, scanResult)) + var markdownBuilder strings.Builder + isMultiModule := len(nestedModules) > 1 + + markdownBuilder.WriteString(generateModuleHeader(rootModuleID)) + if !StaticMarkdownConfig.IsExtendedSummary() { + markdownBuilder.WriteString(fmt.Sprintf(basicSummaryUpgradeNotice, StaticMarkdownConfig.GetExtendedSummaryLangPage())) + } + for _, module := range nestedModules { + if isMultiModule && rootModuleID == module.Id { + continue + } + markdownBuilder.WriteString(fmt.Sprintf("\n\n
%s
\n\n", bis.generateModuleArtifactsTree(&module, isMultiModule))) } - return parentModulesMarkdown.String() + return markdownBuilder.String() +} + +func (bis *BuildInfoSummary) generateModuleTableView(rootModuleID string, subModules []buildInfo.Module) string { + var markdownBuilder strings.Builder + markdownBuilder.WriteString(generateModuleHeader(rootModuleID)) + markdownBuilder.WriteString(generateModuleTableHeader()) + isMultiModule := len(subModules) > 1 + nestedModuleMarkdownTree := bis.generateTableModuleMarkdown(subModules, rootModuleID, isMultiModule) + scanResult := getScanResults(extractDockerImageTag(subModules)) + markdownBuilder.WriteString(generateTableRow(nestedModuleMarkdownTree, scanResult)) + return markdownBuilder.String() } -func (bis *BuildInfoSummary) generateNestedModuleMarkdownTree(parentModules []buildInfo.Module, parentModuleID string, isMultiModule bool) string { +func (bis *BuildInfoSummary) generateTableModuleMarkdown(nestedModules []buildInfo.Module, parentModuleID string, isMultiModule bool) string { var nestedModuleMarkdownTree strings.Builder - if len(parentModules) == 0 { + if len(nestedModules) == 0 { return "" } + if !StaticMarkdownConfig.IsExtendedSummary() { nestedModuleMarkdownTree.WriteString("|") nestedModuleMarkdownTree.WriteString(fmt.Sprintf(basicSummaryUpgradeNotice, StaticMarkdownConfig.GetExtendedSummaryLangPage())) @@ -121,7 +168,7 @@ func (bis *BuildInfoSummary) generateNestedModuleMarkdownTree(parentModules []bu nestedModuleMarkdownTree.WriteString("|
")
 	}
 
-	for _, module := range parentModules {
+	for _, module := range nestedModules {
 		if isMultiModule && parentModuleID == module.Id {
 			continue
 		}
@@ -154,10 +201,9 @@ func (bis *BuildInfoSummary) createArtifactsTree(module *buildInfo.Module) strin
 	for _, artifact := range module.Artifacts {
 		var artifactUrlInArtifactory string
 		if StaticMarkdownConfig.IsExtendedSummary() {
-			artifactUrlInArtifactory = bis.generateArtifactUrl(artifact)
+			artifactUrlInArtifactory = generateArtifactUrl(artifact)
 		}
 		if artifact.OriginalDeploymentRepo == "" {
-			// Placeholder needed to build an artifact tree when repo is missing.
 			artifact.OriginalDeploymentRepo = " "
 		}
 		artifactTreePath := path.Join(artifact.OriginalDeploymentRepo, artifact.Path)
@@ -166,22 +212,20 @@ func (bis *BuildInfoSummary) createArtifactsTree(module *buildInfo.Module) strin
 	return artifactsTree.String()
 }
 
-func (bis *BuildInfoSummary) generateArtifactUrl(artifact buildInfo.Artifact) string {
+func generateArtifactUrl(artifact buildInfo.Artifact) string {
 	if strings.TrimSpace(artifact.OriginalDeploymentRepo) == "" {
 		return ""
 	}
 	return GenerateArtifactUrl(path.Join(artifact.OriginalDeploymentRepo, artifact.Path))
 }
 
-// groupModulesByParent groups modules that share the same parent ID into a map where the key is the parent ID and the value is a slice of those modules.
-func groupModulesByParent(modules []buildInfo.Module) map[string][]buildInfo.Module {
+func groupModules(modules []buildInfo.Module) map[string][]buildInfo.Module {
 	parentToModulesMap := make(map[string][]buildInfo.Module, len(modules))
 	for _, module := range modules {
-		if len(module.Artifacts) == 0 || !isSupportedModule(&module) {
+		if len(module.Artifacts) == 0 {
 			continue
 		}
 		parentID := module.Parent
-		// If the module has no parent, that means it is the parent module itself, so we can use its ID as the parent ID.
 		if parentID == "" {
 			parentID = module.Id
 		}
@@ -191,22 +235,17 @@ func groupModulesByParent(modules []buildInfo.Module) map[string][]buildInfo.Mod
 }
 
 func isSupportedModule(module *buildInfo.Module) bool {
-	switch module.Type {
-	case buildInfo.Maven, buildInfo.Npm, buildInfo.Go, buildInfo.Generic, buildInfo.Terraform:
-		return true
-	case buildInfo.Docker:
-		// Skip attestations that are added as a module for multi-arch docker builds
-		return !strings.HasPrefix(module.Id, container.AttestationsModuleIdPrefix)
-	default:
+	if !supportedModuleTypes[module.Type] {
 		return false
 	}
+	if module.Type == buildInfo.Docker {
+		return !strings.HasPrefix(module.Id, container.AttestationsModuleIdPrefix)
+	}
+	return true
 }
 
 func createDockerMultiArchTitle(module *buildInfo.Module) string {
-	// Extract the parent image name from the module ID (e.g., my-image:1.0 -> my-image)
 	parentImageName := strings.Split(module.Parent, ":")[0]
-
-	// Get the relevant SHA256
 	var sha256 string
 	for _, artifact := range module.Artifacts {
 		if artifact.Name == container.ManifestJsonFile {
@@ -214,9 +253,7 @@ func createDockerMultiArchTitle(module *buildInfo.Module) string {
 			break
 		}
 	}
-
 	if StaticMarkdownConfig.IsExtendedSummary() {
-		// Create a link to the Docker package in Artifactory UI
 		dockerModuleLink := fmt.Sprintf(artifactoryDockerPackagesUiFormat, strings.TrimSuffix(StaticMarkdownConfig.GetPlatformUrl(), "/"), "%2F%2F"+parentImageName, sha256)
 		return fmt.Sprintf("%s (🐸 View)", module.Id, dockerModuleLink)
 	}
@@ -238,15 +275,13 @@ func appendSpacesToTableColumn(str string) string {
 	return str
 }
 
-func appendBuildRow(tableBuilder *strings.Builder, build *buildInfo.BuildInfo) {
+func appendBuildInfoRow(tableBuilder *strings.Builder, build *buildInfo.BuildInfo) {
 	buildName := build.Name + " " + build.Number
 	buildScanResult := getScanResults(buildName)
 	if StaticMarkdownConfig.IsExtendedSummary() {
 		tableBuilder.WriteString(fmt.Sprintf("| [%s](%s) %s | %s | %s | \n", buildName, build.BuildUrl, appendSpacesToTableColumn(""), appendSpacesToTableColumn(buildScanResult.GetViolations()), appendSpacesToTableColumn(buildScanResult.GetVulnerabilities())))
 	} else {
-		// Get the URL to the extended summary page
 		upgradeMessage := fmt.Sprintf(basicSummaryUpgradeNotice, StaticMarkdownConfig.GetExtendedSummaryLangPage())
-		// Append to build name to fit inside the table
 		buildName = fmt.Sprintf(" %s %s", upgradeMessage, buildName)
 		tableBuilder.WriteString(fmt.Sprintf("| %s %s | %s | %s |\n", fitInsideMarkdownTable(buildName), appendSpacesToTableColumn(""), appendSpacesToTableColumn(buildScanResult.GetViolations()), appendSpacesToTableColumn(buildScanResult.GetVulnerabilities())))
 	}
@@ -268,7 +303,6 @@ func generateTableRow(nestedModuleMarkdownTree string, scanResult ScanResult) st
 	return fmt.Sprintf(" %s | %s | %s |\n", fitInsideMarkdownTable(nestedModuleMarkdownTree), appendSpacesToTableColumn(scanResult.GetViolations()), appendSpacesToTableColumn(scanResult.GetVulnerabilities()))
 }
 
-// To fit inside the Markdown table, replace new lines with 
func fitInsideMarkdownTable(str string) string { return strings.ReplaceAll(str, "\n", "
") } @@ -282,8 +316,6 @@ func getScanResults(scannedEntity string) (sc ScanResult) { return StaticMarkdownConfig.scanResultsMapping[NonScannedResult] } -// Extracts the docker image tag from a docker module -// Docker modules have in their first index metadata, which contains the docker image tag func extractDockerImageTag(modules []buildInfo.Module) string { if len(modules) == 0 || modules[0].Type != buildInfo.Docker { return "" @@ -291,7 +323,6 @@ func extractDockerImageTag(modules []buildInfo.Module) string { const tagKey = "docker.image.tag" properties := modules[0].Properties - // Handle both cases where the properties are a map[string]interface{} or map[string]string switch props := properties.(type) { case map[string]interface{}: if tag, found := props[tagKey]; found { @@ -307,3 +338,15 @@ func extractDockerImageTag(modules []buildInfo.Module) string { return "" } + +// Filter out unsupported modules, return empty list if no supported modules found. +func filterModules(modules ...buildInfo.Module) []buildInfo.Module { + supportedModules := make([]buildInfo.Module, 0, len(modules)) + for _, module := range modules { + if !isSupportedModule(&module) { + continue + } + supportedModules = append(supportedModules, module) + } + return supportedModules +} diff --git a/artifactory/utils/commandsummary/buildinfosummary_test.go b/artifactory/utils/commandsummary/buildinfosummary_test.go index a24bc1885..7b20f072e 100644 --- a/artifactory/utils/commandsummary/buildinfosummary_test.go +++ b/artifactory/utils/commandsummary/buildinfosummary_test.go @@ -1,6 +1,7 @@ package commandsummary import ( + buildInfo "github.com/jfrog/build-info-go/entities" buildinfo "github.com/jfrog/build-info-go/entities" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/stretchr/testify/assert" @@ -16,6 +17,7 @@ const ( dockerImageModule = "docker-image-module.md" genericModule = "generic-module.md" mavenModule = "maven-module.md" + mavenNestedModule = "maven-nested-module.md" dockerMultiArchModule = "multiarch-docker-image.md" ) @@ -121,6 +123,72 @@ func TestBuildInfoModulesMaven(t *testing.T) { }) } +func TestBuildInfoModulesMavenWithSubModules(t *testing.T) { + buildInfoSummary, cleanUp := prepareBuildInfoTest() + defer func() { + cleanUp() + }() + var builds = []*buildinfo.BuildInfo{ + { + Name: "buildName", + Number: "123", + Started: "2024-05-05T12:47:20.803+0300", + BuildUrl: "http://myJFrogPlatform/builds/buildName/123", + Modules: []buildinfo.Module{ + { + Id: "maven", + Type: buildinfo.Maven, + Artifacts: []buildinfo.Artifact{{ + Name: "artifact1", + Path: "path/to/artifact1", + OriginalDeploymentRepo: "libs-release", + }}, + Dependencies: []buildinfo.Dependency{{ + Id: "dep1", + }}, + }, + { + Id: "submodule1", + Parent: "maven", + Type: buildinfo.Maven, + Artifacts: []buildinfo.Artifact{{ + Name: "artifact2", + Path: "path/to/artifact2", + OriginalDeploymentRepo: "libs-release", + }}, + Dependencies: []buildinfo.Dependency{{ + Id: "dep2", + }}, + }, + { + Id: "submodule2", + Parent: "maven", + Type: buildinfo.Maven, + Artifacts: []buildinfo.Artifact{{ + Name: "artifact3", + Path: "path/to/artifact3", + OriginalDeploymentRepo: "libs-release", + }}, + Dependencies: []buildinfo.Dependency{{ + Id: "dep3", + }}, + }, + }, + }, + } + + t.Run("Extended Summary", func(t *testing.T) { + StaticMarkdownConfig.setExtendedSummary(true) + res := buildInfoSummary.buildInfoModules(builds) + testMarkdownOutput(t, getTestDataFile(t, mavenNestedModule), res) + }) + t.Run("Basic Summary", func(t *testing.T) { + StaticMarkdownConfig.setExtendedSummary(false) + res := buildInfoSummary.buildInfoModules(builds) + testMarkdownOutput(t, getTestDataFile(t, mavenNestedModule), res) + }) +} + func TestBuildInfoModulesGradle(t *testing.T) { buildInfoSummary, cleanUp := prepareBuildInfoTest() defer func() { @@ -323,6 +391,87 @@ func TestDockerMultiArchModule(t *testing.T) { } +func TestGroupModules(t *testing.T) { + tests := []struct { + name string + modules []buildInfo.Module + expected map[string][]buildInfo.Module + }{ + { + name: "Single module", + modules: []buildInfo.Module{ + {Id: "module1", Artifacts: []buildInfo.Artifact{{Name: "artifact1"}}}, + }, + expected: map[string][]buildInfo.Module{ + "module1": { + {Id: "module1", Artifacts: []buildInfo.Artifact{{Name: "artifact1"}}}, + }, + }, + }, + { + name: "Module with subModules", + modules: []buildInfo.Module{ + {Id: "module1", Parent: "root", Artifacts: []buildInfo.Artifact{{Name: "artifact1"}}}, + {Id: "module2", Parent: "root", Artifacts: []buildInfo.Artifact{{Name: "artifact2"}}}, + }, + expected: map[string][]buildInfo.Module{ + "root": { + {Id: "module1", Parent: "root", Artifacts: []buildInfo.Artifact{{Name: "artifact1"}}}, + {Id: "module2", Parent: "root", Artifacts: []buildInfo.Artifact{{Name: "artifact2"}}}, + }, + }, + }, + { + name: "Multiple Modules", + modules: []buildInfo.Module{ + {Id: "module1", Parent: "root1", Artifacts: []buildInfo.Artifact{{Name: "artifact1"}}}, + {Id: "module2", Parent: "root2", Artifacts: []buildInfo.Artifact{{Name: "artifact2"}}}, + }, + expected: map[string][]buildInfo.Module{ + "root1": { + {Id: "module1", Parent: "root1", Artifacts: []buildInfo.Artifact{{Name: "artifact1"}}}, + }, + "root2": { + {Id: "module2", Parent: "root2", Artifacts: []buildInfo.Artifact{{Name: "artifact2"}}}, + }, + }, + }, + { + name: "Multiple Modules with subModules", + modules: []buildInfo.Module{ + {Id: "module1", Parent: "root1", Artifacts: []buildInfo.Artifact{{Name: "artifact1"}}}, + {Id: "module2", Parent: "root1", Artifacts: []buildInfo.Artifact{{Name: "artifact1"}}}, + {Id: "module3", Parent: "root2", Artifacts: []buildInfo.Artifact{{Name: "artifact2"}}}, + {Id: "module4", Parent: "root2", Artifacts: []buildInfo.Artifact{{Name: "artifact2"}}}, + }, + expected: map[string][]buildInfo.Module{ + "root1": { + {Id: "module1", Parent: "root1", Artifacts: []buildInfo.Artifact{{Name: "artifact1"}}}, + {Id: "module2", Parent: "root1", Artifacts: []buildInfo.Artifact{{Name: "artifact1"}}}, + }, + "root2": { + {Id: "module3", Parent: "root2", Artifacts: []buildInfo.Artifact{{Name: "artifact2"}}}, + {Id: "module4", Parent: "root2", Artifacts: []buildInfo.Artifact{{Name: "artifact2"}}}, + }, + }, + }, + { + name: "Module with no artifacts", + modules: []buildInfo.Module{ + {Id: "module1", Parent: "root1"}, + }, + expected: map[string][]buildInfo.Module{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := groupModules(tt.modules) + assert.Equal(t, tt.expected, result) + }) + } +} + // Tests data files are location artifactory/commands/testdata/command_summary func getTestDataFile(t *testing.T, fileName string) string { var modulesPath string @@ -341,6 +490,124 @@ func getTestDataFile(t *testing.T, fileName string) string { return contentStr } +func TestIsSupportedModule(t *testing.T) { + tests := []struct { + name string + module buildInfo.Module + expected bool + }{ + { + name: "Supported Maven Module", + module: buildInfo.Module{ + Type: buildInfo.Maven, + }, + expected: true, + }, + { + name: "Supported Npm Module", + module: buildInfo.Module{ + Type: buildInfo.Npm, + }, + expected: true, + }, + { + name: "Unsupported Module Type", + module: buildInfo.Module{ + Type: buildInfo.ModuleType("unsupported"), + }, + expected: false, + }, + { + name: "Docker Module with Attestations Prefix", + module: buildInfo.Module{ + Type: buildInfo.Docker, + Id: "attestations-module", + }, + expected: false, + }, + { + name: "Docker Module without Attestations Prefix", + module: buildInfo.Module{ + Type: buildInfo.Docker, + Id: "docker-module", + }, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isSupportedModule(&tt.module) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestFilterModules(t *testing.T) { + tests := []struct { + name string + modules []buildInfo.Module + expected []buildInfo.Module + }{ + { + name: "All Supported Modules", + modules: []buildInfo.Module{ + {Type: buildInfo.Maven}, + {Type: buildInfo.Npm}, + {Type: buildInfo.Go}, + }, + expected: []buildInfo.Module{ + {Type: buildInfo.Maven}, + {Type: buildInfo.Npm}, + {Type: buildInfo.Go}, + }, + }, + { + name: "Mixed Supported and Unsupported Modules", + modules: []buildInfo.Module{ + {Type: buildInfo.Maven}, + {Type: buildInfo.ModuleType("unsupported")}, + {Type: buildInfo.Npm}, + }, + expected: []buildInfo.Module{ + {Type: buildInfo.Maven}, + {Type: buildInfo.Npm}, + }, + }, + { + name: "All Unsupported Modules", + modules: []buildInfo.Module{ + {Type: buildInfo.ModuleType("unsupported1")}, + {Type: buildInfo.ModuleType("unsupported2")}, + }, + expected: []buildInfo.Module{}, + }, + { + name: "Docker Module with Attestations Prefix", + modules: []buildInfo.Module{ + {Type: buildInfo.Docker, Id: "attestations-module"}, + }, + expected: []buildInfo.Module{}, + }, + { + name: "Docker Module without Attestations Prefix", + modules: []buildInfo.Module{ + {Type: buildInfo.Docker, Id: "docker-module"}, + }, + expected: []buildInfo.Module{ + {Type: buildInfo.Docker, Id: "docker-module"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := filterModules(tt.modules...) + assert.Equal(t, tt.expected, result) + }) + } +} + // Sometimes there are inconsistencies in the Markdown output, this function normalizes the output for comparison // This allows easy debugging when tests fails func normalizeMarkdown(md string) string { diff --git a/artifactory/utils/commandsummary/commandsummary.go b/artifactory/utils/commandsummary/commandsummary.go index fe993ac9c..0be8e5b14 100644 --- a/artifactory/utils/commandsummary/commandsummary.go +++ b/artifactory/utils/commandsummary/commandsummary.go @@ -165,11 +165,11 @@ func (cs *CommandSummary) recordInternal(data any, args ...interface{}) (err err } func (cs *CommandSummary) saveDataFile(filePath, fileName string, data any) (err error) { - bytes, err := convertDataToBytes(data) + dataAsBytes, err := convertDataToBytes(data) if err != nil { return errorutils.CheckError(err) } - return createAndWriteToFile(filePath, fileName, bytes) + return createAndWriteToFile(filePath, fileName, dataAsBytes) } func (cs *CommandSummary) saveMarkdownFile(markdown string) (err error) { diff --git a/artifactory/utils/testdata/command_summaries/basic/generic-module.md b/artifactory/utils/testdata/command_summaries/basic/generic-module.md index 2605bdb56..b2b9fc209 100644 --- a/artifactory/utils/testdata/command_summaries/basic/generic-module.md +++ b/artifactory/utils/testdata/command_summaries/basic/generic-module.md @@ -6,8 +6,14 @@ **generic** +🐸 Enable the linkage to Artifactory -| Artifacts | Security Violations | Security Issues | -| :------------ | :--------------------- | :------------------ | -| 🐸 Enable the linkage to Artifactory

📦 generic-local
└── 📁 path
└── 📁 to
└── 📄 artifact2

| Not scanned | Not scanned | + +
📦 generic-local
+└── 📁 path
+    └── 📁 to
+        └── 📄 artifact2
+
+
+ diff --git a/artifactory/utils/testdata/command_summaries/basic/maven-module.md b/artifactory/utils/testdata/command_summaries/basic/maven-module.md index 9aee43395..93d8d6b72 100644 --- a/artifactory/utils/testdata/command_summaries/basic/maven-module.md +++ b/artifactory/utils/testdata/command_summaries/basic/maven-module.md @@ -6,8 +6,14 @@ **maven** +🐸 Enable the linkage to Artifactory -| Artifacts | Security Violations | Security Issues | -| :------------ | :--------------------- | :------------------ | -| 🐸 Enable the linkage to Artifactory

📦 libs-release
└── 📁 path
└── 📁 to
└── 📄 artifact1

| Not scanned | Not scanned | + +
📦 libs-release
+└── 📁 path
+    └── 📁 to
+        └── 📄 artifact1
+
+
+ diff --git a/artifactory/utils/testdata/command_summaries/basic/maven-nested-module.md b/artifactory/utils/testdata/command_summaries/basic/maven-nested-module.md new file mode 100644 index 000000000..fb37d7b3f --- /dev/null +++ b/artifactory/utils/testdata/command_summaries/basic/maven-nested-module.md @@ -0,0 +1,30 @@ + + +

Published Modules

+ + + +**maven** + +🐸 Enable the linkage to Artifactory + + + +
submodule1 +📦 libs-release +└── 📁 path + └── 📁 to + └── 📄 artifact2 + +
+ + + +
submodule2 +📦 libs-release +└── 📁 path + └── 📁 to + └── 📄 artifact3 + +
+ diff --git a/artifactory/utils/testdata/command_summaries/extended/generic-module.md b/artifactory/utils/testdata/command_summaries/extended/generic-module.md index cd7af6979..af0bee52e 100644 --- a/artifactory/utils/testdata/command_summaries/extended/generic-module.md +++ b/artifactory/utils/testdata/command_summaries/extended/generic-module.md @@ -8,6 +8,10 @@ -| Artifacts | Security Violations | Security Issues | -| :------------ | :--------------------- | :------------------ | -|
📦 generic-local
└── 📁 path
└── 📁 to
└── artifact2

| Not scanned | Not scanned | +
📦 generic-local
+└── 📁 path
+    └── 📁 to
+        └── artifact2
+
+
+ diff --git a/artifactory/utils/testdata/command_summaries/extended/maven-module.md b/artifactory/utils/testdata/command_summaries/extended/maven-module.md index ab2d9a699..738da70cb 100644 --- a/artifactory/utils/testdata/command_summaries/extended/maven-module.md +++ b/artifactory/utils/testdata/command_summaries/extended/maven-module.md @@ -8,6 +8,10 @@ -| Artifacts | Security Violations | Security Issues | -| :------------ | :--------------------- | :------------------ | -|
📦 libs-release
└── 📁 path
└── 📁 to
└── artifact1

| Not scanned | Not scanned | +
📦 libs-release
+└── 📁 path
+    └── 📁 to
+        └── artifact1
+
+
+ diff --git a/artifactory/utils/testdata/command_summaries/extended/maven-nested-module.md b/artifactory/utils/testdata/command_summaries/extended/maven-nested-module.md new file mode 100644 index 000000000..59a105406 --- /dev/null +++ b/artifactory/utils/testdata/command_summaries/extended/maven-nested-module.md @@ -0,0 +1,28 @@ + + +

Published Modules

+ + + +**maven** + + + +
submodule1 +📦 libs-release +└── 📁 path + └── 📁 to + └── artifact2 + +
+ + + +
submodule2 +📦 libs-release +└── 📁 path + └── 📁 to + └── artifact3 + +
+ From 5e23772e319c812bdeaab0c2db9cfab5d661a621 Mon Sep 17 00:00:00 2001 From: Robi Nino Date: Wed, 4 Sep 2024 15:32:11 +0300 Subject: [PATCH 3/3] Make command summary util available (#1260) --- artifactory/utils/commandsummary/commandsummary.go | 12 ++++++++---- .../utils/commandsummary/commandsummary_test.go | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/artifactory/utils/commandsummary/commandsummary.go b/artifactory/utils/commandsummary/commandsummary.go index 0be8e5b14..f548168d9 100644 --- a/artifactory/utils/commandsummary/commandsummary.go +++ b/artifactory/utils/commandsummary/commandsummary.go @@ -132,9 +132,13 @@ func (cs *CommandSummary) RecordWithIndex(data any, summaryIndex Index, args ... } // Retrieve all the indexed data files in the current command directory. -func (cs *CommandSummary) GetIndexedDataFilesPaths() (indexedFilePathsMap IndexedFilesMap, err error) { +func GetIndexedDataFilesPaths() (indexedFilePathsMap IndexedFilesMap, err error) { basePath := filepath.Join(os.Getenv(coreutils.SummaryOutputDirPathEnv), OutputDirName) - return cs.getIndexedFileRecursively(basePath, true) + exists, err := fileutils.IsDirExists(basePath, false) + if err != nil || !exists { + return + } + return getIndexedFileRecursively(basePath, true) } func (cs *CommandSummary) GetDataFilesPaths() ([]string, error) { @@ -178,7 +182,7 @@ func (cs *CommandSummary) saveMarkdownFile(markdown string) (err error) { } // Retrieve all the indexed data files paths in the given directory -func (cs *CommandSummary) getIndexedFileRecursively(dirPath string, isRoot bool) (nestedFilesMap IndexedFilesMap, err error) { +func getIndexedFileRecursively(dirPath string, isRoot bool) (nestedFilesMap IndexedFilesMap, err error) { entries, err := os.ReadDir(dirPath) if err != nil { return nil, errorutils.CheckError(err) @@ -190,7 +194,7 @@ func (cs *CommandSummary) getIndexedFileRecursively(dirPath string, isRoot bool) // Check if the directory is in the allowedDirs list _, allowed := allowedDirs[entry.Name()] if isRoot || allowed { - subNestedFilesMap, err := cs.getIndexedFileRecursively(fullPath, false) + subNestedFilesMap, err := getIndexedFileRecursively(fullPath, false) if err != nil { return nil, err } diff --git a/artifactory/utils/commandsummary/commandsummary_test.go b/artifactory/utils/commandsummary/commandsummary_test.go index fd43cc36e..79e96a1ff 100644 --- a/artifactory/utils/commandsummary/commandsummary_test.go +++ b/artifactory/utils/commandsummary/commandsummary_test.go @@ -156,7 +156,7 @@ func TestIndexedRecord(t *testing.T) { assert.NoError(t, err) // Verify file has been saved - indexedFilesMap, err := cs.GetIndexedDataFilesPaths() + indexedFilesMap, err := GetIndexedDataFilesPaths() assert.NoError(t, err) // Verify nested files @@ -194,7 +194,7 @@ func TestSarifMultipleReports(t *testing.T) { err = cs.RecordWithIndex(tc.originalData, tc.summaryIndex) assert.NoError(t, err) // Verify file has been saved - indexedFilesMap, err := cs.GetIndexedDataFilesPaths() + indexedFilesMap, err := GetIndexedDataFilesPaths() assert.NoError(t, err) assert.Equal(t, 2, len(indexedFilesMap[SarifReport])) })