-
Notifications
You must be signed in to change notification settings - Fork 370
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
318 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package output | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"strings" | ||
"unicode" | ||
|
||
"github.com/fatih/color" | ||
"github.com/google/osv-scanner/pkg/models" | ||
) | ||
|
||
func PrintVerticalResults(vulnResult *models.VulnerabilityResults, outputWriter io.Writer) { | ||
for _, result := range vulnResult.Results { | ||
fmt.Fprintln(outputWriter, toString(result)) | ||
} | ||
} | ||
|
||
func countVulnerabilities(result models.PackageSource) int { | ||
count := 0 | ||
|
||
for _, pkg := range result.Packages { | ||
count += len(pkg.Vulnerabilities) | ||
} | ||
|
||
return count | ||
} | ||
|
||
// truncate ensures that the given string is shorter than the provided limit. | ||
// | ||
// If the string is longer than the limit, it's trimmed and suffixed with an ellipsis. | ||
// Ideally the string will be trimmed at the space that's closest to the limit to | ||
// preserve whole words; if a string has no spaces before the limit, it'll be forcefully truncated. | ||
func truncate(str string, limit int) string { | ||
count := 0 | ||
truncateAt := -1 | ||
|
||
for i, c := range str { | ||
if unicode.IsSpace(c) { | ||
truncateAt = i | ||
} | ||
|
||
count++ | ||
|
||
if count >= limit { | ||
// ideally we want to keep words whole when truncating, | ||
// but if we can't find a space just truncate at the limit | ||
if truncateAt == -1 { | ||
truncateAt = limit | ||
} | ||
|
||
return str[:truncateAt] + "..." | ||
} | ||
} | ||
|
||
return str | ||
} | ||
|
||
func describe(vulnerability models.Vulnerability) string { | ||
description := vulnerability.Summary | ||
|
||
if description == "" { | ||
description += truncate(vulnerability.Details, 80) | ||
} | ||
|
||
if description == "" { | ||
description += "(no details available)" | ||
} | ||
|
||
description += " (" + OSVBaseVulnerabilityURL + vulnerability.ID + ")" | ||
|
||
return description | ||
} | ||
|
||
func formatLineByLine(result models.PackageSource) string { | ||
lines := make([]string, 0, len(result.Packages)) | ||
|
||
for _, pkg := range result.Packages { | ||
if len(pkg.Vulnerabilities) == 0 { | ||
continue | ||
} | ||
|
||
lines = append(lines, fmt.Sprintf( | ||
" %s %s", | ||
color.YellowString("%s@%s", pkg.Package.Name, pkg.Package.Version), | ||
color.RedString("is affected by the following vulnerabilities:"), | ||
)) | ||
|
||
for _, vulnerability := range pkg.Vulnerabilities { | ||
lines = append(lines, fmt.Sprintf( | ||
" %s %s", | ||
color.CyanString("%s:", vulnerability.ID), | ||
describe(vulnerability), | ||
)) | ||
} | ||
} | ||
|
||
return strings.Join(lines, "\n") | ||
} | ||
|
||
func toString(result models.PackageSource) string { | ||
count := countVulnerabilities(result) // r.countKnownVulnerabilities() | ||
ignoreMsg := "" // r.describeIgnores() | ||
word := "known" | ||
|
||
out := "" | ||
out += fmt.Sprintf( | ||
"%s: found %s %s\n", | ||
color.MagentaString("%s", result.Source.Path), | ||
color.YellowString("%d", len(result.Packages)), | ||
Form(len(result.Packages), "package", "packages"), | ||
) | ||
|
||
if ignoreMsg != "" { | ||
word = "new" | ||
} | ||
|
||
if count == 0 { | ||
return out + fmt.Sprintf( | ||
" %s%s\n", | ||
color.GreenString("no %s vulnerabilities found", word), | ||
ignoreMsg, | ||
) | ||
} | ||
|
||
out += "\n" | ||
out += formatLineByLine(result) | ||
out += "\n" | ||
|
||
out += fmt.Sprintf("\n %s%s\n", | ||
color.RedString( | ||
"%d %s %s found in %s", | ||
count, | ||
word, | ||
Form(count, "vulnerability", "vulnerabilities"), | ||
result.Source.Path, | ||
), | ||
ignoreMsg, | ||
) | ||
|
||
return out | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package reporter | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
|
||
"github.com/google/osv-scanner/internal/output" | ||
"github.com/google/osv-scanner/pkg/models" | ||
) | ||
|
||
type VerticalReporter struct { | ||
hasErrored bool | ||
stdout io.Writer | ||
stderr io.Writer | ||
level VerbosityLevel | ||
markdown bool | ||
// 0 indicates not a terminal output | ||
terminalWidth int | ||
} | ||
|
||
func NewVerticalReporter(stdout io.Writer, stderr io.Writer, level VerbosityLevel, markdown bool, terminalWidth int) *VerticalReporter { | ||
return &VerticalReporter{ | ||
stdout: stdout, | ||
stderr: stderr, | ||
hasErrored: false, | ||
level: level, | ||
markdown: markdown, | ||
terminalWidth: terminalWidth, | ||
} | ||
} | ||
|
||
func (r *VerticalReporter) Errorf(format string, a ...any) { | ||
fmt.Fprintf(r.stderr, format, a...) | ||
r.hasErrored = true | ||
} | ||
|
||
func (r *VerticalReporter) HasErrored() bool { | ||
return r.hasErrored | ||
} | ||
|
||
func (r *VerticalReporter) Warnf(format string, a ...any) { | ||
if WarnLevel <= r.level { | ||
fmt.Fprintf(r.stdout, format, a...) | ||
} | ||
} | ||
|
||
func (r *VerticalReporter) Infof(format string, a ...any) { | ||
if InfoLevel <= r.level { | ||
fmt.Fprintf(r.stdout, format, a...) | ||
} | ||
} | ||
|
||
func (r *VerticalReporter) Verbosef(format string, a ...any) { | ||
if VerboseLevel <= r.level { | ||
fmt.Fprintf(r.stdout, format, a...) | ||
} | ||
} | ||
|
||
func (r *VerticalReporter) PrintResult(vulnResult *models.VulnerabilityResults) error { | ||
if len(vulnResult.Results) == 0 && !r.hasErrored { | ||
fmt.Fprintf(r.stdout, "No issues found\n") | ||
return nil | ||
} | ||
|
||
output.PrintVerticalResults(vulnResult, r.stdout) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package reporter_test | ||
|
||
import ( | ||
"bytes" | ||
"io" | ||
"testing" | ||
|
||
"github.com/google/osv-scanner/pkg/reporter" | ||
) | ||
|
||
func TestVerticalReporter_Errorf(t *testing.T) { | ||
t.Parallel() | ||
|
||
writer := &bytes.Buffer{} | ||
r := reporter.NewVerticalReporter(io.Discard, writer, reporter.ErrorLevel, false, 0) | ||
text := "hello world!" | ||
|
||
r.Errorf(text) | ||
|
||
if writer.String() != text { | ||
t.Error("Error level message should have been printed") | ||
} | ||
if !r.HasErrored() { | ||
t.Error("HasErrored() should have returned true") | ||
} | ||
} | ||
|
||
func TestVerticalReporter_Warnf(t *testing.T) { | ||
t.Parallel() | ||
|
||
text := "hello world!" | ||
tests := []struct { | ||
lvl reporter.VerbosityLevel | ||
expectedPrintout string | ||
}{ | ||
{lvl: reporter.WarnLevel, expectedPrintout: text}, | ||
{lvl: reporter.ErrorLevel, expectedPrintout: ""}, | ||
} | ||
|
||
for _, test := range tests { | ||
writer := &bytes.Buffer{} | ||
r := reporter.NewVerticalReporter(writer, io.Discard, test.lvl, false, 0) | ||
|
||
r.Warnf(text) | ||
|
||
if writer.String() != test.expectedPrintout { | ||
t.Errorf("expected \"%s\", got \"%s\"", test.expectedPrintout, writer.String()) | ||
} | ||
} | ||
} | ||
|
||
func TestVerticalReporter_Infof(t *testing.T) { | ||
t.Parallel() | ||
|
||
text := "hello world!" | ||
tests := []struct { | ||
lvl reporter.VerbosityLevel | ||
expectedPrintout string | ||
}{ | ||
{lvl: reporter.InfoLevel, expectedPrintout: text}, | ||
{lvl: reporter.WarnLevel, expectedPrintout: ""}, | ||
} | ||
|
||
for _, test := range tests { | ||
writer := &bytes.Buffer{} | ||
r := reporter.NewVerticalReporter(writer, io.Discard, test.lvl, false, 0) | ||
|
||
r.Infof(text) | ||
|
||
if writer.String() != test.expectedPrintout { | ||
t.Errorf("expected \"%s\", got \"%s\"", test.expectedPrintout, writer.String()) | ||
} | ||
} | ||
} | ||
|
||
func TestVerticalReporter_Verbosef(t *testing.T) { | ||
t.Parallel() | ||
|
||
text := "hello world!" | ||
tests := []struct { | ||
lvl reporter.VerbosityLevel | ||
expectedPrintout string | ||
}{ | ||
{lvl: reporter.VerboseLevel, expectedPrintout: text}, | ||
{lvl: reporter.InfoLevel, expectedPrintout: ""}, | ||
} | ||
|
||
for _, test := range tests { | ||
writer := &bytes.Buffer{} | ||
r := reporter.NewVerticalReporter(writer, io.Discard, test.lvl, false, 0) | ||
|
||
r.Verbosef(text) | ||
|
||
if writer.String() != test.expectedPrintout { | ||
t.Errorf("expected \"%s\", got \"%s\"", test.expectedPrintout, writer.String()) | ||
} | ||
} | ||
} |