diff --git a/artifactory/utils/commandsummary/buildinfosummary.go b/artifactory/utils/commandsummary/buildinfosummary.go
index 8071b7694..040a28099 100644
--- a/artifactory/utils/commandsummary/buildinfosummary.go
+++ b/artifactory/utils/commandsummary/buildinfosummary.go
@@ -129,14 +129,17 @@ func (bis *BuildInfoSummary) generateModulesMarkdown(modules ...buildInfo.Module
if len(subModules) == 0 {
continue
}
- if !scannableModuleType[subModules[0].Type] {
+ // Check if the artifacts inside the module contains evidences
+ evidenceExists := checkEvidence(subModules)
+
+ if !scannableModuleType[subModules[0].Type] && !evidenceExists {
tree, err := bis.generateModuleArtifactTree(rootModuleID, subModules)
if err != nil {
return "", err
}
modulesMarkdown.WriteString(tree)
} else {
- view, err := bis.generateModuleTableView(rootModuleID, subModules)
+ view, err := bis.generateModuleTableView(rootModuleID, subModules, evidenceExists)
if err != nil {
return "", err
}
@@ -146,6 +149,19 @@ func (bis *BuildInfoSummary) generateModulesMarkdown(modules ...buildInfo.Module
return modulesMarkdown.String(), nil
}
+func checkEvidence(modules []buildInfo.Module) bool {
+ // TODO this has to be changed to SHA, as name can repeat
+ for _, module := range modules {
+ for _, artifact := range module.Artifacts {
+ _, exists := StaticMarkdownConfig.artifactsEvidencesMapping[artifact.Name]
+ if exists {
+ return true
+ }
+ }
+ }
+ return false
+}
+
func (bis *BuildInfoSummary) generateModuleArtifactTree(rootModuleID string, nestedModules []buildInfo.Module) (string, error) {
if len(nestedModules) == 0 {
return "", nil
@@ -170,17 +186,17 @@ func (bis *BuildInfoSummary) generateModuleArtifactTree(rootModuleID string, nes
return markdownBuilder.String(), nil
}
-func (bis *BuildInfoSummary) generateModuleTableView(rootModuleID string, subModules []buildInfo.Module) (string, error) {
+func (bis *BuildInfoSummary) generateModuleTableView(rootModuleID string, subModules []buildInfo.Module, evidenceExists bool) (string, error) {
var markdownBuilder strings.Builder
markdownBuilder.WriteString(generateModuleHeader(rootModuleID))
- markdownBuilder.WriteString(generateModuleTableHeader())
+ markdownBuilder.WriteString(generateModuleTableHeader(evidenceExists))
isMultiModule := len(subModules) > 1
nestedModuleMarkdownTree, err := bis.generateTableModuleMarkdown(subModules, rootModuleID, isMultiModule)
if err != nil {
return "", err
}
scanResult := getScanResults(extractDockerImageTag(subModules))
- markdownBuilder.WriteString(generateTableRow(nestedModuleMarkdownTree, scanResult))
+ markdownBuilder.WriteString(generateTableRow(nestedModuleMarkdownTree, scanResult, evidenceExists))
return markdownBuilder.String(), nil
}
@@ -351,14 +367,39 @@ func generateModuleHeader(parentModuleID string) string {
return fmt.Sprintf("\n\n**%s**\n\n", parentModuleID)
}
-func generateModuleTableHeader() string {
+func generateModuleTableHeader(evidenceExists bool) string {
+ if evidenceExists {
+ return "\n\n| Artifacts | Evidence created | Security Violations | Security Issues |\n|:------------|:---------------------|:------------------|:------------------|\n"
+ }
return "\n\n| Artifacts | Security Violations | Security Issues |\n|:------------|:---------------------|:------------------|\n"
}
-func generateTableRow(nestedModuleMarkdownTree string, scanResult ScanResult) string {
+func generateTableRow(nestedModuleMarkdownTree string, scanResult ScanResult, evidenceExists bool) string {
+ if evidenceExists {
+ return fmt.Sprintf(" %s | %s | %s | %s |\n", fitInsideMarkdownTable(nestedModuleMarkdownTree), getEvidenceLinkFromModule(nestedModuleMarkdownTree), appendSpacesToTableColumn(scanResult.GetViolations()), appendSpacesToTableColumn(scanResult.GetVulnerabilities()))
+ }
return fmt.Sprintf(" %s | %s | %s |\n", fitInsideMarkdownTable(nestedModuleMarkdownTree), appendSpacesToTableColumn(scanResult.GetViolations()), appendSpacesToTableColumn(scanResult.GetVulnerabilities()))
}
+func getEvidenceLinkFromModule(moduleTree string) string {
+ for _, artifact := range StaticMarkdownConfig.artifactsEvidencesMapping {
+ if strings.Contains(moduleTree, artifact.Name) {
+ evidenceUrl, err := GenerateArtifactEvidenceUrl(path.Join(artifact.OriginalDeploymentRepo, artifact.Path))
+ if err != nil {
+ log.Warn(err)
+ }
+ if StaticMarkdownConfig.IsExtendedSummary() {
+ return fmt.Sprintf("[Build-signature](%s)", evidenceUrl)
+ }
+ // TODO add evidence support link
+ return fmt.Sprintf("🔎 [Enable evidence support](%s)", "somelink")
+ }
+ }
+ // TODO handle no evidence
+ return fmt.Sprintf("[Learn more about evidence](%s)", "someLink")
+
+}
+
func fitInsideMarkdownTable(str string) string {
return strings.ReplaceAll(str, "\n", "
")
}
diff --git a/artifactory/utils/commandsummary/buildinfosummary_test.go b/artifactory/utils/commandsummary/buildinfosummary_test.go
index a533cde2a..915648f0d 100644
--- a/artifactory/utils/commandsummary/buildinfosummary_test.go
+++ b/artifactory/utils/commandsummary/buildinfosummary_test.go
@@ -14,12 +14,13 @@ import (
)
const (
- buildInfoTable = "build-info-table.md"
- dockerImageModule = "docker-image-module.md"
- genericModule = "generic-module.md"
- mavenModule = "maven-module.md"
- mavenNestedModule = "maven-nested-module.md"
- dockerMultiArchModule = "multiarch-docker-image.md"
+ buildInfoTable = "build-info-table.md"
+ dockerImageModule = "docker-image-module.md"
+ genericModule = "generic-module.md"
+ mavenModule = "maven-module.md"
+ mavenNestedModule = "maven-nested-module.md"
+ dockerMultiArchModule = "multiarch-docker-image.md"
+ dockerMultiArchModuleEvidence = "multiarch-docker-image-evidence.md"
)
type MockScanResult struct {
@@ -54,11 +55,21 @@ func prepareBuildInfoTest() (*BuildInfoSummary, func()) {
StaticMarkdownConfig.setPlatformMajorVersion(0)
StaticMarkdownConfig.setPlatformUrl("")
}
+ setWorkFlowEnvIfNeeded()
// Create build info instance
buildInfoSummary := &BuildInfoSummary{}
return buildInfoSummary, cleanup
}
+func setWorkFlowEnvIfNeeded() {
+ // Sets the GitHub workflow environment variable to allow testing locally
+ isGitHub := os.Getenv("GITHUB_ACTIONS")
+ if isGitHub == "" {
+ // This is the name of the GitHub action that executes the JFrog CLI Core Tests
+ _ = os.Setenv(githubWorkflowEnv, "JFrog CLI Core Tests")
+ }
+}
+
const buildUrl = "http://myJFrogPlatform/builds/buildName/123?gh_job_id=JFrog+CLI+Core+Tests&gh_section=buildInfo"
func TestBuildInfoTable(t *testing.T) {
@@ -489,6 +500,78 @@ func TestGroupModules(t *testing.T) {
}
}
+func TestModuleWithEvidence(t *testing.T) {
+ buildInfoSummary, cleanUp := prepareBuildInfoTest()
+ defer func() {
+ cleanUp()
+ }()
+ StaticMarkdownConfig.artifactsEvidencesMapping = make(map[string]buildinfo.Artifact)
+ StaticMarkdownConfig.artifactsEvidencesMapping["list.manifest.json"] = buildinfo.Artifact{
+ Path: "multiarch-image/sha256",
+ Name: "sha256",
+ }
+ var builds = []*buildinfo.BuildInfo{
+ {
+ Name: "dockerx",
+ Number: "1",
+ Started: "2024-08-12T11:11:50.198+0300",
+ Modules: []buildinfo.Module{
+ {
+ Properties: map[string]interface{}{
+ "docker.image.tag": "ecosysjfrog.jfrog.io/docker-local/multiarch-image:1",
+ },
+ Type: "docker",
+ Id: "multiarch-image:1",
+ Artifacts: []buildinfo.Artifact{
+ {
+ Type: "json",
+ Checksum: buildinfo.Checksum{
+ Sha1: "fa",
+ Sha256: "2217",
+ Md5: "ba0",
+ },
+ Name: "list.manifest.json",
+ Path: "multiarch-image/1/list.manifest.json",
+ OriginalDeploymentRepo: "docker-local",
+ },
+ },
+ },
+ {
+ Type: "docker",
+ Parent: "multiarch-image:1",
+ Id: "linux/amd64/multiarch-image:1",
+ Artifacts: []buildinfo.Artifact{
+ {
+ Checksum: buildinfo.Checksum{
+ Sha1: "32",
+ Sha256: "sha256:552c",
+ Md5: "f56",
+ },
+ Name: "manifest.json",
+ Path: "multiarch-image/sha256",
+ OriginalDeploymentRepo: "docker-local",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ t.Run("Extended Summary", func(t *testing.T) {
+ StaticMarkdownConfig.setExtendedSummary(true)
+ res, err := buildInfoSummary.buildInfoModules(builds)
+ assert.NoError(t, err)
+ testMarkdownOutput(t, getTestDataFile(t, dockerMultiArchModuleEvidence), res)
+ })
+ t.Run("Basic Summary", func(t *testing.T) {
+ StaticMarkdownConfig.setExtendedSummary(false)
+ res, err := buildInfoSummary.buildInfoModules(builds)
+ assert.NoError(t, err)
+ testMarkdownOutput(t, getTestDataFile(t, dockerMultiArchModuleEvidence), res)
+ })
+
+}
+
// Tests data files are location artifactory/commands/testdata/command_summary
func getTestDataFile(t *testing.T, fileName string) string {
var modulesPath string
diff --git a/artifactory/utils/commandsummary/commandsummary.go b/artifactory/utils/commandsummary/commandsummary.go
index f548168d9..e9f318b27 100644
--- a/artifactory/utils/commandsummary/commandsummary.go
+++ b/artifactory/utils/commandsummary/commandsummary.go
@@ -33,6 +33,7 @@ const (
BuildScan Index = "build-scans"
DockerScan Index = "docker-scans"
SarifReport Index = "sarif-reports"
+ Evidence Index = "evidence"
)
// List of allowed directories for searching indexed content
diff --git a/artifactory/utils/commandsummary/markdownConfig.go b/artifactory/utils/commandsummary/markdownConfig.go
index d2d8a129b..19baca545 100644
--- a/artifactory/utils/commandsummary/markdownConfig.go
+++ b/artifactory/utils/commandsummary/markdownConfig.go
@@ -3,6 +3,7 @@ package commandsummary
import (
"encoding/json"
"fmt"
+ buildInfo "github.com/jfrog/build-info-go/entities"
"net/http"
"net/url"
"strings"
@@ -23,6 +24,8 @@ type MarkdownConfig struct {
platformMajorVersion int
// Static mapping of scan results to be used in the summary
scanResultsMapping map[string]ScanResult
+ // Static mapping of artifacts evidences
+ artifactsEvidencesMapping map[string]buildInfo.Artifact
}
const extendedSummaryLandPage = "https://jfrog.com/help/access?xinfo:appid=csh-gen-gitbook"
@@ -60,6 +63,9 @@ func (mg *MarkdownConfig) GetExtendedSummaryLangPage() string {
func (mg *MarkdownConfig) SetScanResultsMapping(resultsMap map[string]ScanResult) {
mg.scanResultsMapping = resultsMap
}
+func (mg *MarkdownConfig) SetArtifactsEvidencesMapping(artifactsEvidencesMapping map[string]buildInfo.Artifact) {
+ mg.artifactsEvidencesMapping = artifactsEvidencesMapping
+}
// Initializes the command summary values that effect Markdown generation
func InitMarkdownGenerationValues(serverUrl string, platformMajorVersion int) (err error) {
diff --git a/artifactory/utils/commandsummary/utils.go b/artifactory/utils/commandsummary/utils.go
index f9e51b3ec..828111bc4 100644
--- a/artifactory/utils/commandsummary/utils.go
+++ b/artifactory/utils/commandsummary/utils.go
@@ -12,8 +12,10 @@ import (
const (
artifactory7UiFormat = "%sui/repos/tree/General/%s?clearFilter=true"
+ artifactory7UiEvidenceFormat = "%sui/repos/tree/Evidence/%s?clearFilter=true"
artifactory6UiFormat = "%sartifactory/webapp/#/artifacts/browse/tree/General/%s"
artifactoryDockerPackagesUiFormat = "%s/ui/packages/docker:%s/sha256__%s"
+ githubWorkflowEnv = "GITHUB_WORKFLOW"
)
func GenerateArtifactUrl(pathInRt string, section summarySection) (url string, err error) {
@@ -26,6 +28,17 @@ func GenerateArtifactUrl(pathInRt string, section summarySection) (url string, e
return
}
+func GenerateArtifactEvidenceUrl(pathInRt string) (url string, err error) {
+ if StaticMarkdownConfig.GetPlatformMajorVersion() == 6 {
+ // todo handle not supported
+ url = fmt.Sprintf(artifactory6UiFormat, StaticMarkdownConfig.GetPlatformUrl(), pathInRt)
+ } else {
+ url = fmt.Sprintf(artifactory7UiEvidenceFormat, StaticMarkdownConfig.GetPlatformUrl(), pathInRt)
+ }
+ url, err = addGitHubTrackingToUrl(url, artifactsSection)
+ return
+}
+
func WrapCollapsableMarkdown(title, markdown string, headerSize int) string {
return fmt.Sprintf("\n\n\n
\n\n%s\n\n
| 🔎 [Enable evidence support](somelink) | Not scanned | Not scanned | diff --git a/artifactory/utils/testdata/command_summaries/extended/multiarch-docker-image-evidence.md b/artifactory/utils/testdata/command_summaries/extended/multiarch-docker-image-evidence.md new file mode 100644 index 000000000..3c8294bef --- /dev/null +++ b/artifactory/utils/testdata/command_summaries/extended/multiarch-docker-image-evidence.md @@ -0,0 +1,13 @@ + + +linux/amd64/multiarch-image:1
📦 docker-local
└── 📁 multiarch-image
└── 📄 sha256
| [Build-signature](https://myplatform.com/ui/repos/tree/Evidence/multiarch-image/sha256?clearFilter=true&gh_job_id=JFrog+CLI+Core+Tests&gh_section=artifacts) | Not scanned | Not scanned |linux/amd64/multiarch-image:1 (🐸 View)
📦 docker-local
└── 📁 multiarch-image
└── sha256