Skip to content

Commit

Permalink
Add newlines in pull request comment table (#237)
Browse files Browse the repository at this point in the history
  • Loading branch information
omerzi authored Feb 1, 2023
1 parent 1d208f7 commit 8daba7e
Show file tree
Hide file tree
Showing 12 changed files with 391 additions and 104 deletions.
97 changes: 39 additions & 58 deletions commands/scanpullrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@ package commands
import (
"context"
"errors"
"fmt"
coreconfig "github.com/jfrog/jfrog-cli-core/v2/utils/config"
"os"
"os/exec"
"path/filepath"
"strings"

coreconfig "github.com/jfrog/jfrog-cli-core/v2/utils/config"

"github.com/jfrog/frogbot/commands/utils"
"github.com/jfrog/froggit-go/vcsclient"
Expand All @@ -28,8 +25,7 @@ const (
noGitHubEnvReviewersErr = "frogbot did not scan this PR, because the existing GitHub Environment named 'frogbot' doesn't have reviewers selected. Please refer to the Frogbot documentation for instructions on how to create the Environment"
)

type ScanPullRequestCmd struct {
}
type ScanPullRequestCmd struct{}

// Run ScanPullRequest method only works for single repository scan.
// Therefore, the first repository config represents the repository on which Frogbot runs, and it is the only one that matters.
Expand All @@ -55,49 +51,58 @@ func scanPullRequest(repoConfig *utils.FrogbotRepoConfig, client vcsclient.VcsCl
if len(repoConfig.Branches) == 0 {
return &utils.ErrMissingEnv{VariableName: utils.GitBaseBranchEnv}
}

// Audit PR code
vulnerabilitiesRows, err := auditPullRequest(repoConfig, client)
if err != nil {
return err
}

// Create pull request message
message := createPullRequestMessage(vulnerabilitiesRows, repoConfig.OutputWriter)

// Add comment to the pull request
if err = client.AddPullRequestComment(context.Background(), repoConfig.RepoOwner, repoConfig.RepoName, message, repoConfig.PullRequestID); err != nil {
return errors.New("couldn't add pull request comment: " + err.Error())
}

// Fail the Frogbot task, if a security issue is found and Frogbot isn't configured to avoid the failure.
if repoConfig.FailOnSecurityIssues != nil && *repoConfig.FailOnSecurityIssues && len(vulnerabilitiesRows) > 0 {
err = errors.New(securityIssueFoundErr)
}
return err
}

func auditPullRequest(repoConfig *utils.FrogbotRepoConfig, client vcsclient.VcsClient) ([]formats.VulnerabilityOrViolationRow, error) {
xrayScanParams := createXrayScanParams(repoConfig.Watches, repoConfig.JFrogProjectKey)
var vulnerabilitiesRows []formats.VulnerabilityOrViolationRow
for _, project := range repoConfig.Projects {
currentScan, isMultipleRoot, err := auditSource(xrayScanParams, project, &repoConfig.Server)
if err != nil {
return err
return nil, err
}
if repoConfig.IncludeAllVulnerabilities {
log.Info("Frogbot is configured to show all vulnerabilities")
allIssuesRows, err := createAllIssuesRows(currentScan, isMultipleRoot)
if err != nil {
return err
return nil, err
}
vulnerabilitiesRows = append(vulnerabilitiesRows, allIssuesRows...)
continue
}
// Audit target code
previousScan, isMultipleRoot, err := auditTarget(client, xrayScanParams, project, repoConfig.Branches[0], &repoConfig.Git, &repoConfig.Server)
if err != nil {
return err
return nil, err
}
newIssuesRows, err := createNewIssuesRows(previousScan, currentScan, isMultipleRoot)
if err != nil {
return err
return nil, err
}
vulnerabilitiesRows = append(vulnerabilitiesRows, newIssuesRows...)
}

log.Info("Xray scan completed")

// Frogbot adds a comment on the PR.
getTitleFunc, getSeverityTagFunc := getCommentFunctions(repoConfig.SimplifiedOutput)
message := createPullRequestMessage(vulnerabilitiesRows, getTitleFunc, getSeverityTagFunc)
err := client.AddPullRequestComment(context.Background(), repoConfig.RepoOwner, repoConfig.RepoName, message, repoConfig.PullRequestID)
if err != nil {
return errors.New("couldn't add pull request comment: " + err.Error())
}
// Fail the Frogbot task, if a security issue is found and Frogbot isn't configured to avoid the failure.
if repoConfig.FailOnSecurityIssues != nil && *repoConfig.FailOnSecurityIssues && len(vulnerabilitiesRows) > 0 {
err = errors.New(securityIssueFoundErr)
}
return err
return vulnerabilitiesRows, nil
}

// Verify that the 'frogbot' GitHub environment was properly configured on the repository
Expand Down Expand Up @@ -132,15 +137,6 @@ func verifyGitHubFrogbotEnvironment(client vcsclient.VcsClient, repoConfig *util
return nil
}

func getCommentFunctions(simplifiedOutput bool) (utils.GetTitleFunc, utils.GetSeverityTagFunc) {
if simplifiedOutput {
return utils.GetSimplifiedTitle, func(name utils.IconName) string {
return ""
}
}
return utils.GetBanner, utils.GetSeverityTag
}

// Create vulnerabilities rows. The rows should contain only the new issues added by this PR
func createNewIssuesRows(previousScan, currentScan []services.ScanResponse, isMultipleRoot bool) (vulnerabilitiesRows []formats.VulnerabilityOrViolationRow, err error) {
previousScanAggregatedResults := aggregateScanResults(previousScan)
Expand Down Expand Up @@ -337,33 +333,18 @@ func getUniqueID(vulnerability formats.VulnerabilityOrViolationRow) string {
return vulnerability.ImpactedDependencyName + vulnerability.ImpactedDependencyVersion + vulnerability.IssueId
}

func createPullRequestMessage(vulnerabilitiesRows []formats.VulnerabilityOrViolationRow, getBanner utils.GetTitleFunc, getSeverityTag utils.GetSeverityTagFunc) string {
func createPullRequestMessage(vulnerabilitiesRows []formats.VulnerabilityOrViolationRow, writer utils.OutputWriter) string {
if len(vulnerabilitiesRows) == 0 {
return getBanner(utils.NoVulnerabilityBannerSource) + utils.WhatIsFrogbotMd
return writer.NoVulnerabilitiesTitle()
}
tableContent := getTableContent(vulnerabilitiesRows, writer)
return writer.VulnerabiltiesTitle() + writer.TableHeader() + tableContent
}

func getTableContent(vulnerabilitiesRows []formats.VulnerabilityOrViolationRow, writer utils.OutputWriter) string {
var tableContent string
for _, vulnerability := range vulnerabilitiesRows {
var cve string
var directDependencies, directDependenciesVersions strings.Builder
if len(vulnerability.Components) > 0 {
for _, dependency := range vulnerability.Components {
directDependencies.WriteString(fmt.Sprintf("%s; ", dependency.Name))
directDependenciesVersions.WriteString(fmt.Sprintf("%s; ", dependency.Version))
}
}
if len(vulnerability.Cves) > 0 {
cve = vulnerability.Cves[0].Id
}
fixedVersionString := strings.Join(vulnerability.FixedVersions, " ")
tableContent += fmt.Sprintf("\n| %s%8s | %s | %s | %s | %s | %s | %s ",
getSeverityTag(utils.IconName(vulnerability.Severity)),
vulnerability.Severity,
strings.TrimSuffix(directDependencies.String(), "; "),
strings.TrimSuffix(directDependenciesVersions.String(), "; "),
vulnerability.ImpactedDependencyName,
vulnerability.ImpactedDependencyVersion,
fixedVersionString,
cve)
}
return getBanner(utils.VulnerabilitiesBannerSource) + utils.WhatIsFrogbotMd + utils.TableHeader + tableContent
tableContent += writer.TableRow(vulnerability)
}
return tableContent
}
4 changes: 2 additions & 2 deletions commands/scanpullrequest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ func TestGetNewVulnerabilitiesCaseNoNewVulnerabilities(t *testing.T) {

func TestCreatePullRequestMessageNoVulnerabilities(t *testing.T) {
vulnerabilities := []formats.VulnerabilityOrViolationRow{}
message := createPullRequestMessage(vulnerabilities, utils.GetBanner, utils.GetSeverityTag)
message := createPullRequestMessage(vulnerabilities, &utils.StandardOutput{})

expectedMessageByte, err := os.ReadFile(filepath.Join("testdata", "messages", "novulnerabilities.md"))
assert.NoError(t, err)
Expand Down Expand Up @@ -402,7 +402,7 @@ func TestCreatePullRequestMessage(t *testing.T) {
Cves: []formats.CveRow{{Id: "CVE-2022-26652"}},
},
}
message := createPullRequestMessage(vulnerabilities, utils.GetBanner, utils.GetSeverityTag)
message := createPullRequestMessage(vulnerabilities, &utils.StandardOutput{})

expectedMessage := "[![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/vulnerabilitiesBanner.png)](https://github.com/jfrog/frogbot#readme)\n\n[What is Frogbot?](https://github.com/jfrog/frogbot#readme)\n\n| SEVERITY | DIRECT DEPENDENCIES | DIRECT DEPENDENCIES VERSIONS | IMPACTED DEPENDENCY NAME | IMPACTED DEPENDENCY VERSION | FIXED VERSIONS | CVE\n:--: | -- | -- | -- | -- | :--: | --\n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/highSeverity.png)<br> High | github.com/nats-io/nats-streaming-server | v0.21.0 | github.com/nats-io/nats-streaming-server | v0.21.0 | [0.24.1] | CVE-2022-24450 \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/highSeverity.png)<br> High | github.com/mholt/archiver/v3 | v3.5.1 | github.com/mholt/archiver/v3 | v3.5.1 | | \n| ![](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/mediumSeverity.png)<br> Medium | github.com/nats-io/nats-streaming-server | v0.21.0 | github.com/nats-io/nats-streaming-server | v0.21.0 | [0.24.3] | CVE-2022-26652 "
assert.Equal(t, expectedMessage, message)
Expand Down
22 changes: 5 additions & 17 deletions commands/scanpullrequests.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"github.com/jfrog/froggit-go/vcsutils"
"sort"
"strings"

Expand Down Expand Up @@ -75,7 +74,7 @@ func shouldScanPullRequest(repo utils.FrogbotRepoConfig, client vcsclient.VcsCli
return true, nil
}
// if this is a Frogbot 'scan results' comment and not 're-scan' request comment, do not scan this pull request.
if isFrogbotResultComment(comment.Content, repo.SimplifiedOutput) {
if repo.OutputWriter.IsFrogbotResultComment(comment.Content) {
return false, nil
}
}
Expand All @@ -87,13 +86,6 @@ func isFrogbotRescanComment(comment string) bool {
return strings.Contains(strings.ToLower(strings.TrimSpace(comment)), utils.RescanRequestComment)
}

func isFrogbotResultComment(comment string, simplifiedOutput bool) bool {
if simplifiedOutput {
return strings.HasPrefix(comment, utils.GetSimplifiedTitle(utils.NoVulnerabilityBannerSource)) || strings.HasPrefix(comment, utils.GetSimplifiedTitle(utils.VulnerabilitiesBannerSource))
}
return strings.Contains(comment, utils.GetIconTag(utils.NoVulnerabilityBannerSource)) || strings.Contains(comment, utils.GetIconTag(utils.VulnerabilitiesBannerSource))
}

func downloadAndScanPullRequest(pr vcsclient.PullRequestInfo, repo utils.FrogbotRepoConfig, client vcsclient.VcsClient) error {
// Download the pull request source ("from") branch
params := utils.Params{Git: utils.Git{
Expand Down Expand Up @@ -150,15 +142,11 @@ func downloadAndScanPullRequest(pr vcsclient.PullRequestInfo, repo utils.Frogbot
JFrogProjectKey: repo.JFrogProjectKey,
},
}
var simplifiedOutput bool
// Bitbucket server requires a simple output without emojis + images
if repo.GitProvider.String() == vcsutils.BitbucketServer.String() {
simplifiedOutput = true
}

frogbotParams = &utils.FrogbotRepoConfig{
SimplifiedOutput: simplifiedOutput,
Server: repo.Server,
Params: params,
OutputWriter: utils.GetCompatibleOutputWriter(repo.GitProvider),
Server: repo.Server,
Params: params,
}
return scanPullRequest(frogbotParams, client)
}
20 changes: 10 additions & 10 deletions commands/scanpullrequests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

var gitParams = &utils.FrogbotRepoConfig{
SimplifiedOutput: true,
OutputWriter: &utils.SimplifiedOutput{},
Params: utils.Params{
Git: utils.Git{
RepoOwner: "repo-owner",
Expand Down Expand Up @@ -119,14 +119,14 @@ func TestScanAllPullRequestsMultiRepo(t *testing.T) {

configAggregator := utils.FrogbotConfigAggregator{
{
SimplifiedOutput: true,
Server: server,
Params: firstRepoParams,
OutputWriter: &utils.SimplifiedOutput{},
Server: server,
Params: firstRepoParams,
},
{
SimplifiedOutput: true,
Server: server,
Params: secondRepoParams,
OutputWriter: &utils.SimplifiedOutput{},
Server: server,
Params: secondRepoParams,
},
}
mockParams := []MockParams{
Expand Down Expand Up @@ -166,9 +166,9 @@ func TestScanAllPullRequests(t *testing.T) {
Git: gitParams.Git,
}
repoParams := &utils.FrogbotRepoConfig{
SimplifiedOutput: true,
Server: server,
Params: params,
OutputWriter: &utils.SimplifiedOutput{},
Server: server,
Params: params,
}
paramsAggregator := utils.FrogbotConfigAggregator{}
paramsAggregator = append(paramsAggregator, *repoParams)
Expand Down
11 changes: 3 additions & 8 deletions commands/utils/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ type IconName string
type ImageSource string
type vcsProvider string

// GetGetTitleFunc, a func to determine the title of Frogbot comment
type GetTitleFunc func(ImageSource) string

// GetGetTitleFunc, a func to determine the table's severity tag in the Frogbot comment
type GetSeverityTagFunc func(IconName) string

const (
baseResourceUrl = "https://raw.githubusercontent.com/jfrog/frogbot/master/resources/"

Expand Down Expand Up @@ -66,9 +60,10 @@ const (
GitApiEndpointEnv = "JF_GIT_API_ENDPOINT"

// Comment
TableHeader = "\n| SEVERITY | DIRECT DEPENDENCIES | DIRECT DEPENDENCIES VERSIONS | IMPACTED DEPENDENCY NAME | IMPACTED DEPENDENCY VERSION | FIXED VERSIONS | CVE\n" +
tableHeader = "\n| SEVERITY | DIRECT DEPENDENCIES | DIRECT DEPENDENCIES VERSIONS | IMPACTED DEPENDENCY NAME | IMPACTED DEPENDENCY VERSION | FIXED VERSIONS | CVE\n" +
":--: | -- | -- | -- | -- | :--: | --"
WhatIsFrogbotMd = "\n\n[What is Frogbot?](https://github.com/jfrog/frogbot#readme)\n"
simplifiedTableHeader = "\n| SEVERITY | DIRECT DEPENDENCIES | IMPACTED DEPENDENCY NAME | IMPACTED DEPENDENCY VERSION | FIXED VERSIONS | CVE\n" + ":--: | -- | -- | -- | :--: | --"
WhatIsFrogbotMd = "\n\n[What is Frogbot?](https://github.com/jfrog/frogbot#readme)\n"

// Product ID for usage reporting
productId = "frogbot"
Expand Down
1 change: 1 addition & 0 deletions commands/utils/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func (gm *GitManager) createBranchAndCheckout(branchName string, create bool) er
checkoutConfig := &git.CheckoutOptions{
Create: create,
Branch: getFullBranchName(branchName),
Force: true,
}
worktree, err := gm.repository.Worktree()
if err != nil {
Expand Down
14 changes: 7 additions & 7 deletions commands/utils/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ var frogbotConfigPath = filepath.Join(".frogbot", FrogbotConfigFile)
type FrogbotConfigAggregator []FrogbotRepoConfig

type FrogbotRepoConfig struct {
Server coreconfig.ServerDetails
SimplifiedOutput bool
Params `yaml:"params,omitempty"`
Params `yaml:"params,omitempty"`
OutputWriter
Server coreconfig.ServerDetails
}

type Params struct {
Expand Down Expand Up @@ -170,9 +170,9 @@ func NewConfigAggregator(configData *FrogbotConfigAggregator, gitParams Git, ser
}
config.Git = gitParams
newConfigAggregator = append(newConfigAggregator, FrogbotRepoConfig{
SimplifiedOutput: isSimplifiedOutput(gitParams.GitProvider),
Server: *server,
Params: config.Params,
OutputWriter: GetCompatibleOutputWriter(gitParams.GitProvider),
Server: *server,
Params: config.Params,
})
}
return newConfigAggregator, nil
Expand Down Expand Up @@ -393,7 +393,7 @@ func generateConfigAggregatorFromEnv(gitParams *Git, server *coreconfig.ServerDe
return nil, err
}
repo.Projects = append(repo.Projects, project)
repo.SimplifiedOutput = isSimplifiedOutput(gitParams.GitProvider)
repo.OutputWriter = GetCompatibleOutputWriter(gitParams.GitProvider)
return &FrogbotConfigAggregator{repo}, nil
}

Expand Down
45 changes: 45 additions & 0 deletions commands/utils/simplifiedoutput.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package utils

import (
"fmt"
"github.com/jfrog/jfrog-cli-core/v2/xray/formats"
"strings"
)

type SimplifiedOutput struct{}

func (smo *SimplifiedOutput) TableRow(vulnerability formats.VulnerabilityOrViolationRow) string {
var cveId string
if len(vulnerability.Cves) > 0 {
cveId = vulnerability.Cves[0].Id
}
var directDependencies strings.Builder
if len(vulnerability.Components) > 0 {
for _, dependency := range vulnerability.Components {
directDependencies.WriteString(fmt.Sprintf("%s:%s, ", dependency.Name, dependency.Version))
}
}
return fmt.Sprintf("\n| %s | %s | %s | %s | %s | %s |",
vulnerability.Severity,
strings.TrimSuffix(directDependencies.String(), ", "),
vulnerability.ImpactedDependencyName,
vulnerability.ImpactedDependencyVersion,
strings.Join(vulnerability.FixedVersions, " "),
cveId)
}

func (smo *SimplifiedOutput) NoVulnerabilitiesTitle() string {
return GetSimplifiedTitle(NoVulnerabilityBannerSource) + WhatIsFrogbotMd
}

func (smo *SimplifiedOutput) VulnerabiltiesTitle() string {
return GetSimplifiedTitle(VulnerabilitiesBannerSource) + WhatIsFrogbotMd
}

func (smo *SimplifiedOutput) TableHeader() string {
return simplifiedTableHeader
}

func (smo *SimplifiedOutput) IsFrogbotResultComment(comment string) bool {
return strings.HasPrefix(comment, GetSimplifiedTitle(NoVulnerabilityBannerSource)) || strings.HasPrefix(comment, GetSimplifiedTitle(VulnerabilitiesBannerSource))
}
Loading

0 comments on commit 8daba7e

Please sign in to comment.