From 6aff7b0c4fd52fe9a4b5e4d5d4f756ac79540cbe Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 19 May 2025 11:01:50 +0600 Subject: [PATCH] refactor(db): change logic to detect wrong DB (#8864) --- integration/integration_test.go | 7 ++-- pkg/db/db.go | 18 +++++---- pkg/db/db_test.go | 72 +++++++++++++++++++++++++-------- 3 files changed, 71 insertions(+), 26 deletions(-) diff --git a/integration/integration_test.go b/integration/integration_test.go index d839cf619c..de236c9f05 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -60,9 +60,10 @@ func initDB(t *testing.T) string { defer dbtest.Close() err = metadata.NewClient(db.Dir(cacheDir)).Update(metadata.Metadata{ - Version: db.SchemaVersion, - NextUpdate: time.Now().Add(24 * time.Hour), - UpdatedAt: time.Now(), + Version: db.SchemaVersion, + NextUpdate: time.Now().Add(24 * time.Hour), + UpdatedAt: time.Now(), + DownloadedAt: time.Now(), }) require.NoError(t, err) diff --git a/pkg/db/db.go b/pkg/db/db.go index 2d617e0942..56680883f5 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -110,9 +110,18 @@ func (c *Client) NeedsUpdate(ctx context.Context, cliVersion string, skip bool) meta = metadata.Metadata{Version: db.SchemaVersion} } + // There are 2 cases when DownloadAt field is zero: + // - trivy-db was downloaded with `oras`. In this case user can use `--skip-db-update` (like for air-gapped) or re-download trivy-db. + // - trivy-db was corrupted while copying from tmp directory to cache directory. We should update this trivy-db. + // We can't detect these cases, so we will show warning for users who use oras + air-gapped. + if meta.DownloadedAt.IsZero() && !skip { + log.WarnContext(ctx, "Trivy DB may be corrupted and will be re-downloaded. If you manually downloaded DB - use the `--skip-db-update` flag to skip updating DB.") + return true, nil + } + if skip && noRequiredFiles { log.ErrorContext(ctx, "The first run cannot skip downloading DB") - return false, xerrors.New("--skip-update cannot be specified on the first run") + return false, xerrors.New("--skip-db-update cannot be specified on the first run") } if db.SchemaVersion < meta.Version { @@ -141,7 +150,7 @@ func (c *Client) NeedsUpdate(ctx context.Context, cliVersion string, skip bool) func (c *Client) validate(meta metadata.Metadata) error { if db.SchemaVersion != meta.Version { log.Error("The local DB has an old schema version which is not supported by the current version of Trivy CLI. DB needs to be updated.") - return xerrors.Errorf("--skip-update cannot be specified with the old DB schema. Local DB: %d, Expected: %d", + return xerrors.Errorf("--skip-db-update cannot be specified with the old DB schema. Local DB: %d, Expected: %d", meta.Version, db.SchemaVersion) } return nil @@ -163,11 +172,6 @@ func (c *Client) isNewDB(ctx context.Context, meta metadata.Metadata) bool { // Download downloads the DB file func (c *Client) Download(ctx context.Context, dst string, opt types.RegistryOptions) error { - // Remove the metadata file under the cache directory before downloading DB - if err := c.metadata.Delete(); err != nil { - log.DebugContext(ctx, "No metadata file") - } - if err := c.downloadDB(ctx, opt, dst); err != nil { return xerrors.Errorf("OCI artifact error: %w", err) } diff --git a/pkg/db/db_test.go b/pkg/db/db_test.go index 30d99f8943..dbfc7aae91 100644 --- a/pkg/db/db_test.go +++ b/pkg/db/db_test.go @@ -19,6 +19,8 @@ func TestClient_NeedsUpdate(t *testing.T) { timeNextUpdateDay1 := time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC) timeNextUpdateDay2 := time.Date(2019, 10, 2, 0, 0, 0, 0, time.UTC) + timeDownloadAt := time.Date(2019, 9, 30, 22, 30, 0, 0, time.UTC) + tests := []struct { name string skip bool @@ -57,11 +59,12 @@ func TestClient_NeedsUpdate(t *testing.T) { want: true, }, { - name: "happy path with --skip-update", + name: "happy path with --skip-db-update", dbFileExists: true, metadata: metadata.Metadata{ - Version: db.SchemaVersion, - NextUpdate: timeNextUpdateDay1, + Version: db.SchemaVersion, + NextUpdate: timeNextUpdateDay1, + DownloadedAt: timeDownloadAt, }, skip: true, want: false, @@ -70,8 +73,9 @@ func TestClient_NeedsUpdate(t *testing.T) { name: "skip downloading DB", dbFileExists: true, metadata: metadata.Metadata{ - Version: db.SchemaVersion, - NextUpdate: timeNextUpdateDay2, + Version: db.SchemaVersion, + NextUpdate: timeNextUpdateDay2, + DownloadedAt: timeDownloadAt, }, want: false, }, @@ -79,34 +83,36 @@ func TestClient_NeedsUpdate(t *testing.T) { name: "newer schema version", dbFileExists: true, metadata: metadata.Metadata{ - Version: db.SchemaVersion + 1, - NextUpdate: timeNextUpdateDay2, + Version: db.SchemaVersion + 1, + NextUpdate: timeNextUpdateDay2, + DownloadedAt: timeDownloadAt, }, wantErr: fmt.Sprintf("the version of DB schema doesn't match. Local DB: %d, Expected: %d", db.SchemaVersion+1, db.SchemaVersion), }, { - name: "--skip-update without trivy.db on the first run", + name: "--skip-db-update without trivy.db on the first run", dbFileExists: false, skip: true, - wantErr: "--skip-update cannot be specified on the first run", + wantErr: "--skip-db-update cannot be specified on the first run", }, { - name: "--skip-update without metadata.json on the first run", + name: "--skip-db-update without metadata.json on the first run", dbFileExists: true, metadata: metadata.Metadata{}, skip: true, - wantErr: "--skip-update cannot be specified on the first run", + wantErr: "--skip-db-update cannot be specified on the first run", }, { - name: "--skip-update with different schema version", + name: "--skip-db-update with different schema version", dbFileExists: true, metadata: metadata.Metadata{ - Version: 0, - NextUpdate: timeNextUpdateDay1, + Version: 0, + NextUpdate: timeNextUpdateDay1, + DownloadedAt: timeDownloadAt, }, skip: true, - wantErr: fmt.Sprintf("--skip-update cannot be specified with the old DB schema. Local DB: %d, Expected: %d", + wantErr: fmt.Sprintf("--skip-db-update cannot be specified with the old DB schema. Local DB: %d, Expected: %d", 0, db.SchemaVersion), }, { @@ -115,7 +121,7 @@ func TestClient_NeedsUpdate(t *testing.T) { metadata: metadata.Metadata{ Version: db.SchemaVersion, NextUpdate: timeNextUpdateDay1, - DownloadedAt: time.Date(2019, 9, 30, 22, 30, 0, 0, time.UTC), + DownloadedAt: timeDownloadAt, }, want: true, }, @@ -129,6 +135,40 @@ func TestClient_NeedsUpdate(t *testing.T) { }, want: false, }, + { + name: "DownloadedAt is zero, skip is false", + dbFileExists: true, + skip: false, + metadata: metadata.Metadata{ + Version: db.SchemaVersion, + DownloadedAt: time.Time{}, // zero time + NextUpdate: timeNextUpdateDay1, + }, + want: true, + }, + { + name: "DownloadedAt is zero, skip is true", + dbFileExists: true, + skip: true, + metadata: metadata.Metadata{ + Version: db.SchemaVersion, + DownloadedAt: time.Time{}, // zero time + NextUpdate: timeNextUpdateDay1, + }, + want: false, + }, + { + name: "DownloadedAt is zero, skip is true, old schema version", + dbFileExists: true, + skip: true, + metadata: metadata.Metadata{ + Version: 0, + DownloadedAt: time.Time{}, // zero time + NextUpdate: timeNextUpdateDay1, + }, + wantErr: "--skip-db-update cannot be specified with the old DB schema. Local DB: 0, Expected: 2", + want: false, + }, } for _, tt := range tests {