Skip to content

Commit

Permalink
Add support for verbosity levels (google#727)
Browse files Browse the repository at this point in the history
Inspired by what was discussed in google#698, this PR makes the necessary
(breaking) changes to the `reporter` package such that the `Reporter`
interface now provides methods for printing at certain levels (i.e.
ErrorLevel, WarnLevel, InfoLevel, and VerboseLevel). It also adds a
--verbosity option so that users/automated tools can take advantage of
this too.

---------

Co-authored-by: Rex P <[email protected]>
  • Loading branch information
kemzeb and another-rex authored Jan 9, 2024
1 parent f1412ee commit 4090b04
Show file tree
Hide file tree
Showing 23 changed files with 736 additions and 149 deletions.
18 changes: 9 additions & 9 deletions cmd/osv-reporter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ func run(args []string, stdout, stderr io.Writer) int {

cli.VersionPrinter = func(ctx *cli.Context) {
// Use the app Writer and ErrWriter since they will be the writers to keep parallel tests consistent
tableReporter = reporter.NewTableReporter(ctx.App.Writer, ctx.App.ErrWriter, false, 0)
tableReporter.PrintTextf("osv-scanner version: %s\ncommit: %s\nbuilt at: %s\n", ctx.App.Version, commit, date)
tableReporter = reporter.NewTableReporter(ctx.App.Writer, ctx.App.ErrWriter, reporter.InfoLevel, false, 0)
tableReporter.Infof("osv-scanner version: %s\ncommit: %s\nbuilt at: %s\n", ctx.App.Version, commit, date)
}

app := &cli.App{
Expand Down Expand Up @@ -92,7 +92,7 @@ func run(args []string, stdout, stderr io.Writer) int {
}
}

if tableReporter, err = reporter.New("table", stdout, stderr, termWidth); err != nil {
if tableReporter, err = reporter.New("table", stdout, stderr, reporter.InfoLevel, termWidth); err != nil {
return err
}

Expand Down Expand Up @@ -138,7 +138,7 @@ func run(args []string, stdout, stderr io.Writer) int {

if context.Bool("gh-annotations") {
var ghAnnotationsReporter reporter.Reporter
if ghAnnotationsReporter, err = reporter.New("gh-annotations", stdout, stderr, termWidth); err != nil {
if ghAnnotationsReporter, err = reporter.New("gh-annotations", stdout, stderr, reporter.InfoLevel, termWidth); err != nil {
return err
}

Expand All @@ -156,7 +156,7 @@ func run(args []string, stdout, stderr io.Writer) int {
}
termWidth = 0
var sarifReporter reporter.Reporter
if sarifReporter, err = reporter.New("sarif", stdout, stderr, termWidth); err != nil {
if sarifReporter, err = reporter.New("sarif", stdout, stderr, reporter.InfoLevel, termWidth); err != nil {
return err
}

Expand All @@ -179,23 +179,23 @@ func run(args []string, stdout, stderr io.Writer) int {

if err := app.Run(args); err != nil {
if tableReporter == nil {
tableReporter = reporter.NewTableReporter(stdout, stderr, false, 0)
tableReporter = reporter.NewTableReporter(stdout, stderr, reporter.InfoLevel, false, 0)
}
if errors.Is(err, osvscanner.VulnerabilitiesFoundErr) {
return 1
}

if errors.Is(err, osvscanner.NoPackagesFoundErr) {
tableReporter.PrintErrorf("No package sources found, --help for usage information.\n")
tableReporter.Errorf("No package sources found, --help for usage information.\n")
return 128
}

tableReporter.PrintErrorf("%v\n", err)
tableReporter.Errorf("%v\n", err)
}

// if we've been told to print an error, and not already exited with
// a specific error code, then exit with a generic non-zero code
if tableReporter != nil && tableReporter.HasPrintedError() {
if tableReporter != nil && tableReporter.HasErrored() {
return 127
}

Expand Down
26 changes: 18 additions & 8 deletions cmd/osv-scanner/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ func run(args []string, stdout, stderr io.Writer) int {

cli.VersionPrinter = func(ctx *cli.Context) {
// Use the app Writer and ErrWriter since they will be the writers to keep parallel tests consistent
r = reporter.NewTableReporter(ctx.App.Writer, ctx.App.ErrWriter, false, 0)
r.PrintTextf("osv-scanner version: %s\ncommit: %s\nbuilt at: %s\n", ctx.App.Version, commit, date)
r = reporter.NewTableReporter(ctx.App.Writer, ctx.App.ErrWriter, reporter.InfoLevel, false, 0)
r.Infof("osv-scanner version: %s\ncommit: %s\nbuilt at: %s\n", ctx.App.Version, commit, date)
}

osv.RequestUserAgent = "osv-scanner/" + version.OSVVersion
Expand Down Expand Up @@ -116,6 +116,11 @@ func run(args []string, stdout, stderr io.Writer) int {
Name: "no-call-analysis",
Usage: "disables call graph analysis",
},
&cli.StringFlag{
Name: "verbosity",
Usage: fmt.Sprintf("specify the level of information that should be provided during runtime; value can be: %s", strings.Join(reporter.VerbosityLevels(), ", ")),
Value: "info",
},
&cli.BoolFlag{
Name: "experimental-local-db",
Usage: "checks for vulnerabilities using local databases",
Expand Down Expand Up @@ -182,14 +187,19 @@ func run(args []string, stdout, stderr io.Writer) int {
}
}

if r, err = reporter.New(format, stdout, stderr, termWidth); err != nil {
verbosityLevel, err := reporter.ParseVerbosityLevel(context.String("verbosity"))
if err != nil {
return err
}

if r, err = reporter.New(format, stdout, stderr, verbosityLevel, termWidth); err != nil {
return err
}

var callAnalysisStates map[string]bool
if context.IsSet("experimental-call-analysis") {
callAnalysisStates = createCallAnalysisStates([]string{"all"}, context.StringSlice("no-call-analysis"))
r.PrintTextf("Warning: the experimental-call-analysis flag has been replaced. Please use the call-analysis and no-call-analysis flags instead.\n")
r.Infof("Warning: the experimental-call-analysis flag has been replaced. Please use the call-analysis and no-call-analysis flags instead.\n")
} else {
callAnalysisStates = createCallAnalysisStates(context.StringSlice("call-analysis"), context.StringSlice("no-call-analysis"))
}
Expand Down Expand Up @@ -234,21 +244,21 @@ func run(args []string, stdout, stderr io.Writer) int {

if err := app.Run(args); err != nil {
if r == nil {
r = reporter.NewTableReporter(stdout, stderr, false, 0)
r = reporter.NewTableReporter(stdout, stderr, reporter.InfoLevel, false, 0)
}
switch {
case errors.Is(err, osvscanner.VulnerabilitiesFoundErr):
return 1
case errors.Is(err, osvscanner.NoPackagesFoundErr):
r.PrintErrorf("No package sources found, --help for usage information.\n")
r.Errorf("No package sources found, --help for usage information.\n")
return 128
}
r.PrintErrorf("%v\n", err)
r.Errorf("%v\n", err)
}

// if we've been told to print an error, and not already exited with
// a specific error code, then exit with a generic non-zero code
if r != nil && r.HasPrintedError() {
if r != nil && r.HasErrored() {
return 127
}

Expand Down
29 changes: 29 additions & 0 deletions cmd/osv-scanner/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,35 @@ func TestRun(t *testing.T) {
`,
wantStderr: "",
},
{
name: "invalid --verbosity value",
args: []string{"", "--verbosity", "unknown", "./fixtures/locks-many/composer.lock"},
wantExitCode: 127,
wantStdout: "",
wantStderr: `
invalid verbosity level "unknown" - must be one of: error, warn, info, verbose
`,
},
{
name: "verbosity level = error",
args: []string{"", "--verbosity", "error", "--format", "table", "./fixtures/locks-many/composer.lock"},
wantExitCode: 0,
wantStdout: `
No issues found
`,
wantStderr: ``,
},
{
name: "verbosity level = info",
args: []string{"", "--verbosity", "info", "--format", "table", "./fixtures/locks-many/composer.lock"},
wantExitCode: 0,
wantStdout: `
Scanning dir ./fixtures/locks-many/composer.lock
Scanned <rootdir>/fixtures/locks-many/composer.lock file and found 1 package
No issues found
`,
wantStderr: ``,
},
}
for _, tt := range tests {
tt := tt
Expand Down
8 changes: 4 additions & 4 deletions internal/local/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func MakeRequest(r reporter.Reporter, query osv.BatchedQuery, offline bool, loca
return nil, err
}

r.PrintTextf("Loaded %s local db from %s\n", db.Name, db.StoredAt)
r.Infof("Loaded %s local db from %s\n", db.Name, db.StoredAt)

dbs[ecosystem] = db

Expand All @@ -120,7 +120,7 @@ func MakeRequest(r reporter.Reporter, query osv.BatchedQuery, offline bool, loca

if err != nil {
// currently, this will actually only error if the PURL cannot be parses
r.PrintErrorf("skipping %s as it is not a valid PURL: %v\n", query.Package.PURL, err)
r.Errorf("skipping %s as it is not a valid PURL: %v\n", query.Package.PURL, err)
results = append(results, osv.Response{Vulns: []models.Vulnerability{}})

continue
Expand All @@ -134,7 +134,7 @@ func MakeRequest(r reporter.Reporter, query osv.BatchedQuery, offline bool, loca

// Is a commit based query, skip local scanning
results = append(results, osv.Response{})
r.PrintTextf("Skipping commit scanning for: %s\n", pkg.Commit)
r.Infof("Skipping commit scanning for: %s\n", pkg.Commit)

continue
}
Expand All @@ -143,7 +143,7 @@ func MakeRequest(r reporter.Reporter, query osv.BatchedQuery, offline bool, loca

if err != nil {
// currently, this will actually only error if the PURL cannot be parses
r.PrintErrorf("could not load db for %s ecosystem: %v\n", pkg.Ecosystem, err)
r.Errorf("could not load db for %s ecosystem: %v\n", pkg.Ecosystem, err)
results = append(results, osv.Response{Vulns: []models.Vulnerability{}})

continue
Expand Down
4 changes: 2 additions & 2 deletions internal/sourceanalysis/go.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ func goAnalysis(r reporter.Reporter, pkgs []models.PackageVulns, source models.S
cmd := exec.Command("go", "version")
_, err := cmd.Output()
if err != nil {
r.PrintTextf("Skipping call analysis on Go code since Go is not installed.\n")
r.Infof("Skipping call analysis on Go code since Go is not installed.\n")
return
}

vulns, vulnsByID := vulnsFromAllPkgs(pkgs)
res, err := runGovulncheck(filepath.Dir(source.Path), vulns)
if err != nil {
// TODO: Better method to identify the type of error and give advice specific to the error
r.PrintErrorf(
r.Errorf(
"Failed to run code analysis (govulncheck) on '%s' because %s\n"+
"(the Go toolchain is required)\n", source.Path, err.Error(),
)
Expand Down
14 changes: 7 additions & 7 deletions internal/sourceanalysis/rust.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const (
func rustAnalysis(r reporter.Reporter, pkgs []models.PackageVulns, source models.SourceInfo) {
binaryPaths, err := rustBuildSource(r, source)
if err != nil {
r.PrintErrorf("failed to build cargo/rust project from source: %s\n", err)
r.Errorf("failed to build cargo/rust project from source: %s\n", err)
return
}

Expand All @@ -50,14 +50,14 @@ func rustAnalysis(r reporter.Reporter, pkgs []models.PackageVulns, source models
// Is a library, so need an extra step to extract the object binary file before passing to parseDWARFData
buf, err := extractRlibArchive(path)
if err != nil {
r.PrintErrorf("failed to analyse '%s': %s\n", path, err)
r.Errorf("failed to analyse '%s': %s\n", path, err)
continue
}
readAt = bytes.NewReader(buf.Bytes())
} else {
f, err := os.Open(path)
if err != nil {
r.PrintErrorf("failed to read binary '%s': %s\n", path, err)
r.Errorf("failed to read binary '%s': %s\n", path, err)
continue
}
// This is fine to defer til the end of the function as there's
Expand All @@ -68,7 +68,7 @@ func rustAnalysis(r reporter.Reporter, pkgs []models.PackageVulns, source models

calls, err := functionsFromDWARF(readAt)
if err != nil {
r.PrintErrorf("failed to analyse '%s': %s\n", path, err)
r.Errorf("failed to analyse '%s': %s\n", path, err)
continue
}

Expand Down Expand Up @@ -233,11 +233,11 @@ func rustBuildSource(r reporter.Reporter, source models.SourceInfo) ([]string, e
cmd.Stdout = &stdoutBuffer
cmd.Stderr = &stderrBuffer

r.PrintTextf("Begin building rust/cargo project\n")
r.Infof("Begin building rust/cargo project\n")

if err := cmd.Run(); err != nil {
r.PrintErrorf("cargo stdout:\n%s", stdoutBuffer.String())
r.PrintErrorf("cargo stderr:\n%s", stderrBuffer.String())
r.Errorf("cargo stdout:\n%s", stdoutBuffer.String())
r.Errorf("cargo stderr:\n%s", stderrBuffer.String())

return nil, fmt.Errorf("failed to run `%v`: %w", cmd.String(), err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (c *ConfigManager) Get(r reporter.Reporter, targetPath string) Config {

config, configErr := tryLoadConfig(configPath)
if configErr == nil {
r.PrintTextf("Loaded filter from: %s\n", config.LoadPath)
r.Infof("Loaded filter from: %s\n", config.LoadPath)
} else {
// If config doesn't exist, use the default config
config = c.DefaultConfig
Expand Down
Loading

0 comments on commit 4090b04

Please sign in to comment.