Skip to content

Commit

Permalink
🐛 Fix CLI output on Windows (#216)
Browse files Browse the repository at this point in the history
Fixes #211

Signed-off-by: Christian Zunker <[email protected]>
  • Loading branch information
czunker authored Dec 2, 2022
1 parent 3d740e5 commit 0dcad44
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 107 deletions.
105 changes: 55 additions & 50 deletions cli/reporter/print_compact.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ func (r *defaultReporter) print() error {
func (r *defaultReporter) printSummary(orderedAssets []assetMrnName) {
summaryHeader := fmt.Sprintf("Summary (%d assets)", len(r.data.Assets))
summaryDivider := strings.Repeat("=", utf8.RuneCountInString(summaryHeader))
r.out.Write([]byte(termenv.String(summaryHeader + "\n" + summaryDivider + "\n").Foreground(r.Colors.Secondary).String()))
r.out.Write([]byte(termenv.String(summaryHeader + NewLineCharacter + summaryDivider + NewLineCharacter).Foreground(r.Colors.Secondary).String()))
for _, assetMrnName := range orderedAssets {
assetMrn := assetMrnName.Mrn
asset := r.data.Assets[assetMrn]
r.printAssetSummary(assetMrn, asset)
}

if r.isCompact {
r.out.Write([]byte("\nTo get more information, please run this scan with \"-o full\".\n"))
r.out.Write([]byte(NewLineCharacter + "To get more information, please run this scan with \"-o full\"." + NewLineCharacter))
}
}

Expand Down Expand Up @@ -129,7 +129,7 @@ func (r *defaultReporter) printAssetSummary(assetMrn string, asset *policy.Asset
target = assetMrn
}

r.out.Write([]byte(termenv.String(fmt.Sprintf("\nTarget: %s\n", target)).Foreground(r.Colors.Primary).String()))
r.out.Write([]byte(termenv.String(fmt.Sprintf("%sTarget: %s%s", NewLineCharacter, target, NewLineCharacter)).Foreground(r.Colors.Primary).String()))

report, ok := r.data.Reports[assetMrn]
if !ok {
Expand All @@ -144,7 +144,7 @@ func (r *defaultReporter) printAssetSummary(assetMrn string, asset *policy.Asset
target,
)))
}
r.out.Write([]byte{'\n'})
r.out.Write([]byte(NewLineCharacter))
return
}
if report == nil {
Expand All @@ -165,7 +165,7 @@ func (r *defaultReporter) printAssetSummary(assetMrn string, asset *policy.Asset
report.ComputeStats(resolved)

if report.Stats == nil || report.Stats.Total == 0 {
r.out.Write([]byte(fmt.Sprintf("Datapoints: %d\n", len(report.Data))))
r.out.Write([]byte(fmt.Sprintf("Datapoints: %d%s", len(report.Data), NewLineCharacter)))
} else {
passCnt := report.Stats.Passed.Total
passPct := float32(passCnt) / float32(report.Stats.Total) * 100
Expand All @@ -176,52 +176,52 @@ func (r *defaultReporter) printAssetSummary(assetMrn string, asset *policy.Asset
skipCnt := report.Stats.Skipped + report.Stats.Unknown
skipPct := float32(skipCnt) / float32(report.Stats.Total) * 100

r.out.Write([]byte(r.scoreColored(report.Score.Rating(), fmt.Sprintf("Score: %s\n", score))))
r.out.Write([]byte(r.scoreColored(report.Score.Rating(), fmt.Sprintf("Score: %s%s", score, NewLineCharacter))))
r.out.Write([]byte(
termenv.String(fmt.Sprintf("✓ Passed: %s%.0f%% (%d)\n",
addSpace(components.Hbar(15, passPct)), passPct, passCnt),
termenv.String(fmt.Sprintf("✓ Passed: %s%.0f%% (%d)%s",
addSpace(components.Hbar(15, passPct)), passPct, passCnt, NewLineCharacter),
).Foreground(r.Colors.Success).String()))
r.out.Write([]byte(
termenv.String("✕ Failed: ").Foreground(r.Colors.Critical).String() +
addSpace(failureHbar(report.Stats)) +
termenv.String(fmt.Sprintf("%.0f%% (%d)\n", failPct, failCnt)).Foreground(r.Colors.Critical).String(),
termenv.String(fmt.Sprintf("%.0f%% (%d)%s", failPct, failCnt, NewLineCharacter)).Foreground(r.Colors.Critical).String(),
))
r.out.Write([]byte(
termenv.String(fmt.Sprintf("! Errors: %s%.0f%% (%d)\n",
addSpace(components.Hbar(15, errPct)), errPct, errCnt),
termenv.String(fmt.Sprintf("! Errors: %s%.0f%% (%d)%s",
addSpace(components.Hbar(15, errPct)), errPct, errCnt, NewLineCharacter),
).Foreground(r.Colors.Error).String()))
r.out.Write([]byte(
termenv.String(fmt.Sprintf("» Skipped: %s%.0f%% (%d)\n",
addSpace(components.Hbar(15, skipPct)), skipPct, skipCnt),
termenv.String(fmt.Sprintf("» Skipped: %s%.0f%% (%d)%s",
addSpace(components.Hbar(15, skipPct)), skipPct, skipCnt, NewLineCharacter),
).Foreground(r.Colors.Disabled).String()))

}

r.out.Write([]byte("\nPolicies:\n"))
r.out.Write([]byte(NewLineCharacter + "Policies:" + NewLineCharacter))
scores := policyScores(report, r.bundle)
for i := range scores {
x := scores[i]
switch x.score.Type {
case policy.ScoreType_Error:
r.out.Write([]byte(termenv.String("E EE " + x.title).Foreground(r.Colors.Error).String()))
r.out.Write([]byte{'\n'})
r.out.Write([]byte(NewLineCharacter))
case policy.ScoreType_Unknown, policy.ScoreType_Unscored, policy.ScoreType_Skip:
r.out.Write([]byte(". .. " + x.title))
r.out.Write([]byte{'\n'})
r.out.Write([]byte(NewLineCharacter))
case policy.ScoreType_Result:
rating := x.score.Rating()
line := fmt.Sprintf(
"%s %3d %s\n",
rating.Letter(), x.score.Value, x.title,
"%s %3d %s%s",
rating.Letter(), x.score.Value, x.title, NewLineCharacter,
)
r.out.Write([]byte(r.scoreColored(rating, line)))
default:
r.out.Write([]byte("? .. " + x.title))
r.out.Write([]byte{'\n'})
r.out.Write([]byte(NewLineCharacter))
}
}
if len(scores) > 0 {
r.out.Write([]byte{'\n'})
r.out.Write([]byte(NewLineCharacter))
}

if !r.IsIncognito && report.Url != "" || asset.Url != "" {
Expand All @@ -231,12 +231,12 @@ func (r *defaultReporter) printAssetSummary(assetMrn string, asset *policy.Asset
url = asset.Url
}
r.out.Write([]byte(url))
r.out.Write([]byte{'\n'})
r.out.Write([]byte(NewLineCharacter))
}
}

func (r *defaultReporter) printAssetSections(orderedAssets []assetMrnName) {
r.out.Write([]byte{'\n', '\n'})
r.out.Write([]byte(NewLineCharacter + NewLineCharacter))
queries := r.bundle.QueryMap()

for _, assetMrnName := range orderedAssets {
Expand All @@ -259,9 +259,9 @@ func (r *defaultReporter) printAssetSections(orderedAssets []assetMrnName) {
assetString := fmt.Sprintf("Asset: %s", target)
assetDivider := strings.Repeat("=", utf8.RuneCountInString(assetString))
r.out.Write([]byte(termenv.String("Asset: ").Foreground(r.Colors.Secondary).String()))
r.out.Write([]byte(termenv.String(fmt.Sprintf("%s\n", target)).Foreground(r.Colors.Primary).String()))
r.out.Write([]byte(termenv.String(fmt.Sprintf("%s%s", target, NewLineCharacter)).Foreground(r.Colors.Primary).String()))
r.out.Write([]byte(termenv.String(assetDivider).Foreground(r.Colors.Secondary).String()))
r.out.Write([]byte{'\n'})
r.out.Write([]byte(NewLineCharacter))

resolved, ok := r.data.ResolvedPolicies[assetMrn]
if !ok {
Expand All @@ -270,12 +270,12 @@ func (r *defaultReporter) printAssetSections(orderedAssets []assetMrnName) {
}

r.printAssetQueries(resolved, report, queries, assetMrn, asset)
r.out.Write([]byte{'\n'})
r.out.Write([]byte(NewLineCharacter))
// TODO: we should re-use the report results
r.printVulns(resolved, report, report.RawResults())

}
r.out.Write([]byte{'\n'})
r.out.Write([]byte(NewLineCharacter))
}

// TODO: this should be done during the execution, as queries come in, not at the end!
Expand All @@ -295,16 +295,16 @@ func (r *defaultReporter) printAssetQueries(resolved *policy.ResolvedPolicy, rep
if r.isCompact {
result = stringx.MaxLines(10, result)
}
dataQueriesOutput += result + "\n"
dataQueriesOutput += result + NewLineCharacter
})

if len(dataQueriesOutput) > 0 {
r.out.Write([]byte("Data queries:\n"))
r.out.Write([]byte("Data queries:" + NewLineCharacter))
r.out.Write([]byte(dataQueriesOutput))
r.out.Write([]byte("\n"))
r.out.Write([]byte(NewLineCharacter))
}

r.out.Write([]byte("Controls:\n"))
r.out.Write([]byte("Controls:" + NewLineCharacter))
for id, score := range report.Scores {
_, ok := resolved.CollectorJob.ReportingQueries[id]
if !ok {
Expand Down Expand Up @@ -347,7 +347,7 @@ func (r *defaultReporter) printScore(title string, score *policy.Score, query *p
).Foreground(color).String()
}

return passfail + scoreIndicator + title + "\n"
return passfail + scoreIndicator + title + NewLineCharacter
}

func (r *defaultReporter) printControl(score *policy.Score, query *policy.Mquery, asset *policy.Asset, resolved *policy.ResolvedPolicy, report *policy.Report, results map[string]*llx.RawResult) {
Expand All @@ -360,47 +360,52 @@ func (r *defaultReporter) printControl(score *policy.Score, query *policy.Mquery
case policy.ScoreType_Error:
r.out.Write([]byte(termenv.String("! Error: ").Foreground(r.Colors.Error).String()))
r.out.Write([]byte(title))
r.out.Write([]byte{'\n'})
r.out.Write([]byte(NewLineCharacter))
if !r.isCompact {
r.out.Write([]byte(termenv.String(" Message: " + score.Message).Foreground(r.Colors.Error).String()))
r.out.Write([]byte{'\n'})
errorMessage := strings.ReplaceAll(score.Message, "\n", NewLineCharacter)
r.out.Write([]byte(termenv.String(" Message: " + errorMessage).Foreground(r.Colors.Error).String()))
r.out.Write([]byte(NewLineCharacter))
}
case policy.ScoreType_Unknown, policy.ScoreType_Unscored:
r.out.Write([]byte(termenv.String(". Unknown: ").Foreground(r.Colors.Disabled).String()))
r.out.Write([]byte(title))
r.out.Write([]byte{'\n'})
r.out.Write([]byte(NewLineCharacter))

case policy.ScoreType_Skip:
r.out.Write([]byte(termenv.String(". Skipped: ").Foreground(r.Colors.Disabled).String()))
r.out.Write([]byte(title))
r.out.Write([]byte{'\n'})
r.out.Write([]byte(NewLineCharacter))

case policy.ScoreType_Result:
r.out.Write([]byte(r.printScore(title, score, query)))

// additional information about the failed query
if !r.isCompact && score.Value != 100 {
r.out.Write([]byte(" Query:\n" + stringx.Indent(4, query.Query)))
r.out.Write([]byte{'\n'})
queryString := strings.ReplaceAll(stringx.Indent(4, query.Query), "\n", NewLineCharacter)
r.out.Write([]byte(" Query:" + NewLineCharacter + queryString))
r.out.Write([]byte(NewLineCharacter))

codeBundle := resolved.GetCodeBundle(query)
if codeBundle == nil {
r.out.Write([]byte(r.Reporter.Printer.Error("failed to find code bundle for query '" + query.Mrn + "' in bundle")))
} else {
r.out.Write([]byte(" Result:\n"))
r.out.Write([]byte(" Result:" + NewLineCharacter))
assessment := policy.Query2Assessment(codeBundle, report)
if assessment != nil {
r.out.Write([]byte(stringx.Indent(4, r.Printer.Assessment(codeBundle, assessment))))
assessmentString := stringx.Indent(4, r.Printer.Assessment(codeBundle, assessment))
assessmentString = strings.ReplaceAll(assessmentString, "\n", NewLineCharacter)
r.out.Write([]byte(assessmentString))
} else {
data := codeBundle.FilterResults(results)
result := r.Reporter.Printer.Results(codeBundle, data)
r.out.Write([]byte(stringx.Indent(4, result)))
result := stringx.Indent(4, r.Reporter.Printer.Results(codeBundle, data))
result = strings.ReplaceAll(result, "\n", NewLineCharacter)
r.out.Write([]byte(result))
}
}
r.out.Write([]byte{'\n'})
r.out.Write([]byte(NewLineCharacter))
}
default:
r.out.Write([]byte("unknown result for " + title + "\n"))
r.out.Write([]byte("unknown result for " + title + NewLineCharacter))
}
}

Expand All @@ -414,14 +419,14 @@ func (r *defaultReporter) printVulns(resolved *policy.ResolvedPolicy, report *po
return
}

r.out.Write([]byte(print.Primary("Vulnerabilities:\n")))
r.out.Write([]byte(print.Primary("Vulnerabilities:" + NewLineCharacter)))

if value == nil || value.Data == nil {
r.out.Write([]byte(print.Error("Could not find the vulnerability report.") + "\n"))
r.out.Write([]byte(print.Error("Could not find the vulnerability report.") + NewLineCharacter))
return
}
if value.Data.Error != nil {
r.out.Write([]byte(print.Error("Could not load the vulnerability report: "+value.Data.Error.Error()) + "\n"))
r.out.Write([]byte(print.Error("Could not load the vulnerability report: "+value.Data.Error.Error()) + NewLineCharacter))
return
}

Expand All @@ -439,7 +444,7 @@ func (r *defaultReporter) printVulns(resolved *policy.ResolvedPolicy, report *po
decoder, _ := mapstructure.NewDecoder(cfg)
err := decoder.Decode(rawData)
if err != nil {
r.out.Write([]byte(print.Error("could not decode advisory report\n\n")))
r.out.Write([]byte(print.Error("could not decode advisory report" + NewLineCharacter + NewLineCharacter)))
return
}

Expand All @@ -454,7 +459,7 @@ func (r *defaultReporter) printVulnList(report *mvd.VulnReport) {
title := "No advisories found"
state := "(passed)"
r.out.Write([]byte(termenv.String(string(indicatorChar), title, state).Foreground(color).String()))
r.out.Write([]byte("\n\n"))
r.out.Write([]byte(NewLineCharacter + NewLineCharacter))
return
}
r.out.Write([]byte(RenderVulnReport(report)))
Expand All @@ -475,5 +480,5 @@ func (r *defaultReporter) printVulnSummary(report *mvd.VulnReport) {
DataCompletion: 100,
}

r.out.Write([]byte(r.scoreColored(vulnScore.Rating(), fmt.Sprintf("Overall CVSS score: %.1f\n\n", cvss))))
r.out.Write([]byte(r.scoreColored(vulnScore.Rating(), fmt.Sprintf("Overall CVSS score: %.1f%s%s", cvss, NewLineCharacter, NewLineCharacter))))
}
3 changes: 3 additions & 0 deletions cli/reporter/print_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package reporter

const NewLineCharacter = "\n"
3 changes: 3 additions & 0 deletions cli/reporter/print_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package reporter

const NewLineCharacter = "\n"
13 changes: 7 additions & 6 deletions cli/reporter/print_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,16 @@ func (r *reportRenderer) print() error {

// print errors
if len(r.data.Errors) > 0 {
res.WriteString(r.printer.Primary("Scan Failures\n\n"))
res.WriteString(r.printer.Primary("Scan Failures" + NewLineCharacter + NewLineCharacter))

for name, errMsg := range r.data.Errors {
// TODO: most likely we need to fetch the asset name here
assetLine := termenv.String(fmt.Sprintf("■ Asset: %s\n", name)).
assetLine := termenv.String(fmt.Sprintf("■ Asset: %s%s", name, NewLineCharacter)).
Foreground(colors.DefaultColorTheme.Critical).String()
res.WriteString(assetLine)
errLine := termenv.String(stringx.Indent(2, fmt.Sprintf("Error: %s\n", errMsg))).
errLine := termenv.String(stringx.Indent(2, fmt.Sprintf("Error: %s%s", errMsg, NewLineCharacter))).
Foreground(colors.DefaultColorTheme.Critical).String()
errLine = strings.ReplaceAll(errLine, "\n", NewLineCharacter)
res.WriteString(errLine)
}
}
Expand All @@ -75,7 +76,7 @@ func (r *reportRenderer) print() error {
log.Error().Err(err).Send()
}
// we print the summary again to make it visible after the pager is closed of the page
fmt.Fprintln(r.out, "\n"+scanSummary)
fmt.Fprintln(r.out, NewLineCharacter+scanSummary)
} else {
fmt.Fprintln(r.out, res.String())
}
Expand Down Expand Up @@ -122,13 +123,13 @@ func (r *reportRenderer) assetSummary(assetObj *policy.Asset, score *policy.Scor
return res.String()
}

res.WriteString("\n")
res.WriteString(NewLineCharacter)

// header with asset name
res.WriteString(r.printer.H1(assetObj.Name))
res.WriteString(components.NewScoreCard().Render(score))

res.WriteString("\n")
res.WriteString(NewLineCharacter)
return res.String()
}

Expand Down
3 changes: 3 additions & 0 deletions cli/reporter/print_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package reporter

const NewLineCharacter = "\r\n"
Loading

0 comments on commit 0dcad44

Please sign in to comment.