Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Command Summary - Add Evidences #1323

Draft
wants to merge 4 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 48 additions & 7 deletions artifactory/utils/commandsummary/buildinfosummary.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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
}

Expand Down Expand Up @@ -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", "<br>")
}
Expand Down
95 changes: 89 additions & 6 deletions artifactory/utils/commandsummary/buildinfosummary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions artifactory/utils/commandsummary/commandsummary.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions artifactory/utils/commandsummary/markdownConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package commandsummary
import (
"encoding/json"
"fmt"
buildInfo "github.com/jfrog/build-info-go/entities"
"net/http"
"net/url"
"strings"
Expand All @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down
15 changes: 14 additions & 1 deletion artifactory/utils/commandsummary/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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<details open>\n\n<summary> <h%d> %s </h%d></summary><p></p>\n\n%s\n\n</details>\n\n\n", headerSize, title, headerSize, markdown)
}
Expand All @@ -52,7 +65,7 @@ const (
// addGitHubTrackingToUrl adds GitHub-related query parameters to a given URL if the GITHUB_WORKFLOW environment variable is set.
func addGitHubTrackingToUrl(urlStr string, section summarySection) (string, error) {
// Check if GITHUB_WORKFLOW environment variable is set
githubWorkflow := os.Getenv("GITHUB_WORKFLOW")
githubWorkflow := os.Getenv(githubWorkflowEnv)
if githubWorkflow == "" {
// Return the original URL if the variable is not set
return urlStr, nil
Expand Down
5 changes: 5 additions & 0 deletions artifactory/utils/commandsummary/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ const (
)

func TestGenerateArtifactUrl(t *testing.T) {
// Used to
_, cleanUp := prepareBuildInfoTest()
defer func() {
cleanUp()
}()
cases := []struct {
testName string
projectKey string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@


<h3>Published Modules</h3>



**multiarch-image:1**



| Artifacts | Evidence created | Security Violations | Security Issues |
| :------------ | :--------------------- | :------------------ | :------------------ |
| <a href="https://jfrog.com/help/access?xinfo:appid=csh-gen-gitbook">🐸 Enable the linkage to Artifactory</a><br><br><pre><details><summary>linux/amd64/multiarch-image:1</summary><br>📦 docker-local<br>└── 📁 multiarch-image<br> └── 📄 sha256<br><br></details></pre> | 🔎 [Enable evidence support](somelink) | Not scanned | Not scanned |
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@


<h3>Published Modules</h3>



**multiarch-image:1**



| Artifacts | Evidence created | Security Violations | Security Issues |
| :------------ | :--------------------- | :------------------ | :------------------ |
| <pre><details><summary>linux/amd64/multiarch-image:1 <a href=https://myplatform.com/ui/packages/docker:%2F%2Fmultiarch-image/sha256__sha256:552c>(🐸 View)</a></summary><br>📦 docker-local<br>└── 📁 multiarch-image<br> └── <a href='https://myplatform.com/ui/repos/tree/General/docker-local/multiarch-image/sha256?clearFilter=true&gh_job_id=JFrog+CLI+Core+Tests&gh_section=packages' target="_blank">sha256</a><br><br></details></pre> | [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 |
Loading