From 866b3e07ba0d40d0314b8789972e55ef1c07ab54 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 30 Sep 2024 14:59:00 +1300 Subject: [PATCH] fix(offline): report all ecosystems without local databases in one single line (#1279) This reduces the amount of noise generated when using `--offline` if a database is not available for a particular ecosystem. While it was suggested that we could have some form of general error cache, for now I've just optimized for this specific case as really it's the one we expect the most and (in part) I think the nature of Go makes the more generic improvement a bit too faffy vs the gain. Resolves #1005 --- cmd/osv-scanner/__snapshots__/main_test.snap | 42 ++++++++++++++++++++ cmd/osv-scanner/main_test.go | 29 ++++++++++++++ internal/local/check.go | 21 +++++++++- 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/cmd/osv-scanner/__snapshots__/main_test.snap b/cmd/osv-scanner/__snapshots__/main_test.snap index 9991b044ce..eac9877f42 100755 --- a/cmd/osv-scanner/__snapshots__/main_test.snap +++ b/cmd/osv-scanner/__snapshots__/main_test.snap @@ -2091,6 +2091,48 @@ databases can only be downloaded when running in offline mode --- +[TestRun_LocalDatabases_AlwaysOffline/#00 - 1] +Scanning dir ./fixtures/locks-requirements +Scanned /fixtures/locks-requirements/my-requirements.txt file and found 1 package +Scanned /fixtures/locks-requirements/requirements-dev.txt file and found 1 package +Scanned /fixtures/locks-requirements/requirements.prod.txt file and found 1 package +Scanned /fixtures/locks-requirements/requirements.txt file and found 3 packages +Scanned /fixtures/locks-requirements/the_requirements_for_test.txt file and found 1 package +Scanning dir ./fixtures/locks-many +Scanned /fixtures/locks-many/Gemfile.lock file and found 1 package +Scanned /fixtures/locks-many/alpine.cdx.xml as CycloneDX SBOM and found 14 packages +Scanned /fixtures/locks-many/composer.lock file and found 1 package +Scanned /fixtures/locks-many/package-lock.json file and found 1 package +Scanned /fixtures/locks-many/yarn.lock file and found 1 package + +--- + +[TestRun_LocalDatabases_AlwaysOffline/#00 - 2] +could not find local databases for ecosystems: Alpine, Packagist, PyPI, RubyGems, npm + +--- + +[TestRun_LocalDatabases_AlwaysOffline/#00 - 3] +Scanning dir ./fixtures/locks-requirements +Scanned /fixtures/locks-requirements/my-requirements.txt file and found 1 package +Scanned /fixtures/locks-requirements/requirements-dev.txt file and found 1 package +Scanned /fixtures/locks-requirements/requirements.prod.txt file and found 1 package +Scanned /fixtures/locks-requirements/requirements.txt file and found 3 packages +Scanned /fixtures/locks-requirements/the_requirements_for_test.txt file and found 1 package +Scanning dir ./fixtures/locks-many +Scanned /fixtures/locks-many/Gemfile.lock file and found 1 package +Scanned /fixtures/locks-many/alpine.cdx.xml as CycloneDX SBOM and found 14 packages +Scanned /fixtures/locks-many/composer.lock file and found 1 package +Scanned /fixtures/locks-many/package-lock.json file and found 1 package +Scanned /fixtures/locks-many/yarn.lock file and found 1 package + +--- + +[TestRun_LocalDatabases_AlwaysOffline/#00 - 4] +could not find local databases for ecosystems: Alpine, Packagist, PyPI, RubyGems, npm + +--- + [TestRun_LockfileWithExplicitParseAs/#00 - 1] --- diff --git a/cmd/osv-scanner/main_test.go b/cmd/osv-scanner/main_test.go index 2dd2290dbf..5a4e579854 100644 --- a/cmd/osv-scanner/main_test.go +++ b/cmd/osv-scanner/main_test.go @@ -641,6 +641,35 @@ func TestRun_LocalDatabases(t *testing.T) { } } +func TestRun_LocalDatabases_AlwaysOffline(t *testing.T) { + t.Parallel() + + tests := []cliTestCase{ + // a bunch of different lockfiles and ecosystem + { + name: "", + args: []string{"", "--config=./fixtures/osv-scanner-empty-config.toml", "--experimental-offline", "./fixtures/locks-requirements", "./fixtures/locks-many"}, + exit: 127, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + testDir := testutility.CreateTestDir(t) + old := tt.args + tt.args = []string{"", "--experimental-local-db-path", testDir} + tt.args = append(tt.args, old[1:]...) + + // run each test twice since they should provide the same output, + // and the second run should be fast as the db is already available + testCli(t, tt) + testCli(t, tt) + }) + } +} + func TestRun_Licenses(t *testing.T) { t.Parallel() tests := []cliTestCase{ diff --git a/internal/local/check.go b/internal/local/check.go index c1fd9d996d..b8c2b4e95c 100644 --- a/internal/local/check.go +++ b/internal/local/check.go @@ -5,6 +5,8 @@ import ( "fmt" "os" "path" + "slices" + "strings" "github.com/google/osv-scanner/pkg/lockfile" "github.com/google/osv-scanner/pkg/models" @@ -116,6 +118,9 @@ func MakeRequest(r reporter.Reporter, query osv.BatchedQuery, offline bool, loca return db, nil } + // slice to track ecosystems that did not have an offline database available + var missingDbs []string + for _, query := range query.Queries { pkg, err := toPackageDetails(query) @@ -143,8 +148,13 @@ func MakeRequest(r reporter.Reporter, query osv.BatchedQuery, offline bool, loca db, err := loadDBFromCache(pkg.Ecosystem) if err != nil { - // currently, this will actually only error if the PURL cannot be parses - r.Errorf("could not load db for %s ecosystem: %v\n", pkg.Ecosystem, err) + if errors.Is(err, ErrOfflineDatabaseNotFound) { + missingDbs = append(missingDbs, string(pkg.Ecosystem)) + } else { + // the most likely error at this point is that the PURL could not be parsed + r.Errorf("could not load db for %s ecosystem: %v\n", pkg.Ecosystem, err) + } + results = append(results, osv.Response{Vulns: []models.Vulnerability{}}) continue @@ -153,5 +163,12 @@ func MakeRequest(r reporter.Reporter, query osv.BatchedQuery, offline bool, loca results = append(results, osv.Response{Vulns: db.VulnerabilitiesAffectingPackage(pkg)}) } + if len(missingDbs) > 0 { + missingDbs = slices.Compact(missingDbs) + slices.Sort(missingDbs) + + r.Errorf("could not find local databases for ecosystems: %s\n", strings.Join(missingDbs, ", ")) + } + return &osv.HydratedBatchedResponse{Results: results}, nil }