Skip to content

Commit

Permalink
feat(code): support severity threshold (#155)
Browse files Browse the repository at this point in the history
Co-authored-by: Peter Schäfer <[email protected]>
  • Loading branch information
thisislawatts and PeterSchafer authored May 8, 2024
1 parent 91048e7 commit 67808fe
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 26 deletions.
22 changes: 19 additions & 3 deletions internal/presenters/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,15 @@ func RenderTip(str string) string {
return fmt.Sprintf("\n💡 Tip\n\n%s", str)
}

func RenderSummary(summary *json_schemas.TestSummary, orgName string, testPath string) (string, error) {
func FilterSeverityASC(original []string, severityMinLevel string) []string {
minLevelPointer := slices.Index(original, severityMinLevel)
if minLevelPointer >= 0 {
return original[minLevelPointer:]
}
return original
}

func RenderSummary(summary *json_schemas.TestSummary, orgName string, testPath string, severityMinLevel string) (string, error) {
var buff bytes.Buffer
var summaryTemplate = template.Must(template.New("summary").Parse(`Test Summary
Expand All @@ -120,11 +128,19 @@ func RenderSummary(summary *json_schemas.TestSummary, orgName string, testPath s
openIssueLabelledCount := ""
ignoredIssueLabelledCount := ""

slices.Reverse(summary.SeverityOrderAsc)
filteredSeverityASC := FilterSeverityASC(summary.SeverityOrderAsc, severityMinLevel)
reversedSlice := slices.Clone(summary.SeverityOrderAsc)
slices.Reverse(reversedSlice)

for _, severity := range summary.SeverityOrderAsc {
for _, severity := range reversedSlice {
satisfyMinLevel := slices.Contains(filteredSeverityASC, severity)
for _, result := range summary.Results {
if result.Severity == severity {
if !satisfyMinLevel {
openIssueLabelledCount += renderInSeverityColor(severity, fmt.Sprintf(" %d %s ", 0, strings.ToUpper(severity)))
ignoredIssueLabelledCount += renderInSeverityColor(severity, fmt.Sprintf(" %d %s ", 0, strings.ToUpper(severity)))
continue
}
totalIssueCount += result.Total
openIssueCount += result.Open
ignoredIssueCount += result.Ignored
Expand Down
46 changes: 35 additions & 11 deletions internal/presenters/presenter_sarif_results_pretty.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/snyk/code-client-go/sarif"

sarif_utils "github.com/snyk/go-application-framework/internal/utils/sarif"
)

Expand All @@ -24,11 +25,12 @@ type FindingProperty struct {
}

type Presenter struct {
ShowIgnored bool
ShowOpen bool
Input sarif.SarifDocument
OrgName string
TestPath string
ShowIgnored bool
ShowOpen bool
Input sarif.SarifDocument
OrgName string
TestPath string
SeverityMinLevel string
}

type PresenterOption func(*Presenter)
Expand Down Expand Up @@ -57,13 +59,20 @@ func WithTestPath(testPath string) PresenterOption {
}
}

func WithSeverityThershold(severityMinLevel string) PresenterOption {
return func(p *Presenter) {
p.SeverityMinLevel = severityMinLevel
}
}

func SarifTestResults(sarifDocument sarif.SarifDocument, options ...PresenterOption) *Presenter {
p := &Presenter{
ShowIgnored: false,
ShowOpen: true,
Input: sarifDocument,
OrgName: "",
TestPath: "",
ShowIgnored: false,
ShowOpen: true,
Input: sarifDocument,
OrgName: "",
TestPath: "",
SeverityMinLevel: "low",
}

for _, option := range options {
Expand All @@ -73,16 +82,31 @@ func SarifTestResults(sarifDocument sarif.SarifDocument, options ...PresenterOpt
return p
}

func FilterFindingsBySeverity(findings []Finding, minLevel string, severityOrder []string) []Finding {
var filteredFindings []Finding

filteredSeverityASC := FilterSeverityASC(severityOrder, minLevel)
for _, finding := range findings {
if slices.Contains(filteredSeverityASC, finding.Severity) {
filteredFindings = append(filteredFindings, finding)
}
}
return filteredFindings
}

func (p *Presenter) Render() (string, error) {
summaryData := sarif_utils.CreateCodeSummary(&p.Input)
findings :=
SortFindings(convertSarifToFindingsList(p.Input), summaryData.SeverityOrderAsc)
summaryOutput, err := RenderSummary(summaryData, p.OrgName, p.TestPath)
summaryOutput, err := RenderSummary(summaryData, p.OrgName, p.TestPath, p.SeverityMinLevel)

if err != nil {
return "", err
}

// Filter findings based on severity
findings = FilterFindingsBySeverity(findings, p.SeverityMinLevel, summaryData.SeverityOrderAsc)

str := strings.Join([]string{
"",
renderBold(fmt.Sprintf("Testing %s ...", p.TestPath)),
Expand Down
26 changes: 25 additions & 1 deletion internal/presenters/presenter_sarif_results_pretty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (
"github.com/gkampitakis/go-snaps/snaps"
"github.com/muesli/termenv"
"github.com/snyk/code-client-go/sarif"
"github.com/snyk/go-application-framework/internal/presenters"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/snyk/go-application-framework/internal/presenters"
)

func TestPresenterSarifResultsPretty_NoIssues(t *testing.T) {
Expand Down Expand Up @@ -187,3 +189,25 @@ func TestPresenterSarifResultsPretty_OnlyIgnored(t *testing.T) {

snaps.MatchSnapshot(t, result)
}

func TestFilterSeverityASC(t *testing.T) {
input := []string{"low", "medium", "high", "critical"}

t.Run("Threshold medium", func(t *testing.T) {
expected := []string{"medium", "high", "critical"}
actual := presenters.FilterSeverityASC(input, "medium")
assert.Equal(t, expected, actual)
})

t.Run("Threshold critical", func(t *testing.T) {
expected := []string{"critical"}
actual := presenters.FilterSeverityASC(input, "critical")
assert.Equal(t, expected, actual)
})

t.Run("Threshold unknown", func(t *testing.T) {
expected := input
actual := presenters.FilterSeverityASC(input, "unknown")
assert.Equal(t, expected, actual)
})
}
7 changes: 4 additions & 3 deletions pkg/configuration/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ const (
FF_CODE_CONSISTENT_IGNORES string = "internal_snyk_code_ignores_enabled"

// flags
FLAG_EXPERIMENTAL string = "experimental"
FLAG_INCLUDE_IGNORES string = "include-ignores"
FLAG_ONLY_IGNORES string = "only-ignores"
FLAG_EXPERIMENTAL string = "experimental"
FLAG_INCLUDE_IGNORES string = "include-ignores"
FLAG_ONLY_IGNORES string = "only-ignores"
FLAG_SEVERITY_THRESHOLD string = "severity-threshold"
)
51 changes: 49 additions & 2 deletions pkg/local_workflows/output_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import (
"strings"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/snyk/code-client-go/sarif"
"github.com/spf13/pflag"

"github.com/snyk/go-application-framework/internal/presenters"
iUtils "github.com/snyk/go-application-framework/internal/utils"
"github.com/snyk/go-application-framework/pkg/configuration"
"github.com/snyk/go-application-framework/pkg/local_workflows/content_type"
"github.com/snyk/go-application-framework/pkg/local_workflows/json_schemas"
"github.com/snyk/go-application-framework/pkg/workflow"
)

Expand All @@ -36,13 +38,51 @@ func InitOutputWorkflow(engine workflow.Engine) error {
outputConfig.String(OUTPUT_CONFIG_KEY_SARIF_FILE, "", "Write sarif output to file")
outputConfig.Bool(configuration.FLAG_INCLUDE_IGNORES, false, "Include ignored findings in the output")
outputConfig.Bool(configuration.FLAG_ONLY_IGNORES, false, "Hide open issues in the output")
outputConfig.String(configuration.FLAG_SEVERITY_THRESHOLD, "low", "Severity threshold for findings to be included in the output")

entry, err := engine.Register(WORKFLOWID_OUTPUT_WORKFLOW, workflow.ConfigurationOptionsFromFlagset(outputConfig), outputWorkflowEntryPointImpl)
entry.SetVisibility(false)

return err
}

func filterSummaryOutput(config configuration.Configuration, input workflow.Data) (workflow.Data, error) {
// Parse the summary data
summary := json_schemas.NewTestSummary("")
payload, ok := input.GetPayload().([]byte)
if !ok {
return nil, fmt.Errorf("invalid payload type: %T", input.GetPayload())
}
err := json.Unmarshal(payload, &summary)
if err != nil {
return input, err
}

minSeverity := config.GetString(configuration.FLAG_SEVERITY_THRESHOLD)
filteredSeverityOrderAsc := presenters.FilterSeverityASC(summary.SeverityOrderAsc, minSeverity)

// Filter out the results based on the configuration
var filteredResults []json_schemas.TestSummaryResult

for _, severity := range filteredSeverityOrderAsc {
for _, result := range summary.Results {
if severity == result.Severity {
filteredResults = append(filteredResults, result)
}
}
}

summary.Results = filteredResults

bytes, err := json.Marshal(summary)
if err != nil {
return input, err
}

workflowId := workflow.NewTypeIdentifier(WORKFLOWID_OUTPUT_WORKFLOW, "FilterTestSummary")
return workflow.NewData(workflowId, content_type.TEST_SUMMARY, bytes), nil
}

// outputWorkflowEntryPoint defines the output entry point
// the entry point is called by the engine when the workflow is invoked
func outputWorkflowEntryPoint(invocation workflow.InvocationContext, input []workflow.Data, outputDestination iUtils.OutputDestination) ([]workflow.Data, error) {
Expand All @@ -55,6 +95,12 @@ func outputWorkflowEntryPoint(invocation workflow.InvocationContext, input []wor
mimeType := input[i].GetContentType()

if strings.HasPrefix(mimeType, content_type.TEST_SUMMARY) {
outputSummary, err := filterSummaryOutput(config, input[i])
if err != nil {
log.Warn().Err(err).Msg("Failed to filter test summary output")
output = append(output, input[i])
}
output = append(output, outputSummary)
continue
}

Expand Down Expand Up @@ -118,7 +164,7 @@ func handleContentTypeJson(config configuration.Configuration, input []workflow.
// yes: use presenter
// no: print json to cmd
if showToHuman && input[i].GetContentType() == content_type.SARIF_JSON {
humanReadanbleSarifOutput(config, input, i, outputDestination, debugLogger, singleData)
humanReadableSarifOutput(config, input, i, outputDestination, debugLogger, singleData)
} else {
// if json data is processed but non of the json related output configuration is specified, default printJsonToCmd is enabled
if !printJsonToCmd && !writeToFile {
Expand Down Expand Up @@ -151,7 +197,7 @@ func jsonWriteToFile(debugLogger *zerolog.Logger, input []workflow.Data, i int,
return nil
}

func humanReadanbleSarifOutput(config configuration.Configuration, input []workflow.Data, i int, outputDestination iUtils.OutputDestination, debugLogger *zerolog.Logger, singleData []byte) {
func humanReadableSarifOutput(config configuration.Configuration, input []workflow.Data, i int, outputDestination iUtils.OutputDestination, debugLogger *zerolog.Logger, singleData []byte) {
includeOpenFindings := !config.GetBool(configuration.FLAG_ONLY_IGNORES)
includeIgnoredFindings := config.GetBool(configuration.FLAG_INCLUDE_IGNORES) || config.GetBool(configuration.FLAG_ONLY_IGNORES)

Expand All @@ -167,6 +213,7 @@ func humanReadanbleSarifOutput(config configuration.Configuration, input []workf
presenters.WithTestPath(input[i].GetContentLocation()),
presenters.WithIgnored(includeIgnoredFindings),
presenters.WithOpen(includeOpenFindings),
presenters.WithSeverityThershold(config.GetString(configuration.FLAG_SEVERITY_THRESHOLD)),
)

humanReadableResult, err := p.Render()
Expand Down
Loading

0 comments on commit 67808fe

Please sign in to comment.