-
Notifications
You must be signed in to change notification settings - Fork 594
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
- Loading branch information
Showing
6 changed files
with
256 additions
and
3 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package v6 | ||
|
||
import ( | ||
"io" | ||
"path/filepath" | ||
) | ||
|
||
const ( | ||
VulnerabilityDBFileName = "vulnerability.db" | ||
|
||
// We follow SchemaVer semantics (see https://snowplow.io/blog/introducing-schemaver-for-semantic-versioning-of-schemas) | ||
|
||
// ModelVersion indicates how many breaking schema changes there have been (which will prevent interaction with any historical data) | ||
// note: this must ALWAYS be "6" in the context of this package. | ||
ModelVersion = 6 | ||
|
||
// Revision indicates how many changes have been introduced which **may** prevent interaction with some historical data | ||
Revision = 0 | ||
|
||
// Addition indicates how many changes have been introduced that are compatible with all historical data | ||
Addition = 0 | ||
) | ||
|
||
type ReadWriter interface { | ||
Reader | ||
Writer | ||
} | ||
|
||
type Reader interface { | ||
DBMetadataStoreReader | ||
} | ||
|
||
type Writer interface { | ||
DBMetadataStoreWriter | ||
io.Closer | ||
} | ||
|
||
type Config struct { | ||
DBDirPath string | ||
} | ||
|
||
func (c *Config) DBFilePath() string { | ||
return filepath.Join(c.DBDirPath, VulnerabilityDBFileName) | ||
} | ||
|
||
func NewReader(cfg Config) (Reader, error) { | ||
return newStore(cfg, false) | ||
} | ||
|
||
func NewWriter(cfg Config) (ReadWriter, error) { | ||
return newStore(cfg, true) | ||
} |
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,59 @@ | ||
package v6 | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
|
||
"gorm.io/gorm" | ||
|
||
"github.com/anchore/grype/internal/log" | ||
) | ||
|
||
type DBMetadataStoreWriter interface { | ||
SetDBMetadata() error | ||
} | ||
|
||
type DBMetadataStoreReader interface { | ||
GetDBMetadata() (*DBMetadata, error) | ||
} | ||
|
||
type dbMetadataStore struct { | ||
db *gorm.DB | ||
} | ||
|
||
func newDBMetadataStore(db *gorm.DB) *dbMetadataStore { | ||
return &dbMetadataStore{ | ||
db: db, | ||
} | ||
} | ||
|
||
func (s *dbMetadataStore) GetDBMetadata() (*DBMetadata, error) { | ||
log.Trace("fetching DB metadata record") | ||
|
||
var model DBMetadata | ||
|
||
result := s.db.First(&model) | ||
return &model, result.Error | ||
} | ||
|
||
func (s *dbMetadataStore) SetDBMetadata() error { | ||
log.Trace("writing DB metadata record") | ||
|
||
if err := s.db.Unscoped().Where("true").Delete(&DBMetadata{}).Error; err != nil { | ||
return fmt.Errorf("failed to delete existing DB metadata record: %w", err) | ||
} | ||
|
||
ts := time.Now().UTC() | ||
instance := &DBMetadata{ | ||
BuildTimestamp: &ts, | ||
Model: ModelVersion, | ||
Revision: Revision, | ||
Addition: Addition, | ||
} | ||
|
||
if err := s.db.Create(instance).Error; err != nil { | ||
return fmt.Errorf("failed to create DB metadata record: %w", err) | ||
} | ||
|
||
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,51 @@ | ||
package v6 | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"gorm.io/gorm" | ||
) | ||
|
||
func TestDbMetadataStore_empty(t *testing.T) { | ||
s := newDBMetadataStore(setupTestDB(t)) | ||
|
||
// attempt to fetch a non-existent record | ||
actualMetadata, err := s.GetDBMetadata() | ||
require.ErrorIs(t, err, gorm.ErrRecordNotFound) | ||
require.NotNil(t, actualMetadata) | ||
} | ||
|
||
func TestDbMetadataStore(t *testing.T) { | ||
s := newDBMetadataStore(setupTestDB(t)) | ||
|
||
require.NoError(t, s.SetDBMetadata()) | ||
|
||
// fetch the record | ||
actualMetadata, err := s.GetDBMetadata() | ||
require.NoError(t, err) | ||
require.NotNil(t, actualMetadata) | ||
|
||
assert.NotZero(t, *actualMetadata.BuildTimestamp) // a timestamp was set | ||
name, _ := actualMetadata.BuildTimestamp.Zone() | ||
assert.Equal(t, "UTC", name) // the timestamp is in UTC | ||
|
||
actualMetadata.BuildTimestamp = nil // value not under test | ||
|
||
assert.Equal(t, DBMetadata{ | ||
BuildTimestamp: nil, | ||
// expect the correct version info | ||
Model: ModelVersion, | ||
Revision: Revision, | ||
Addition: Addition, | ||
}, *actualMetadata) | ||
} | ||
|
||
func setupTestDB(t *testing.T) *gorm.DB { | ||
// note: empty path means in-memory db | ||
s, err := newStore(Config{}, true) | ||
require.NoError(t, err) | ||
|
||
return s.db | ||
} |
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,19 @@ | ||
package v6 | ||
|
||
import "time" | ||
|
||
func models() []any { | ||
return []any{ | ||
// non-domain info | ||
&DBMetadata{}, | ||
} | ||
} | ||
|
||
// non-domain info ////////////////////////////////////////////////////// | ||
|
||
type DBMetadata struct { | ||
BuildTimestamp *time.Time `gorm:"column:build_timestamp;not null"` | ||
Model int `gorm:"column:model;not null"` | ||
Revision int `gorm:"column:revision;not null"` | ||
Addition int `gorm:"column:addition;not null"` | ||
} |
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,61 @@ | ||
package v6 | ||
|
||
import ( | ||
"fmt" | ||
|
||
"gorm.io/gorm" | ||
|
||
"github.com/anchore/grype/grype/db/internal/gormadapter" | ||
"github.com/anchore/grype/internal/log" | ||
) | ||
|
||
type store struct { | ||
*dbMetadataStore | ||
db *gorm.DB | ||
config Config | ||
write bool | ||
} | ||
|
||
func newStore(cfg Config, write bool) (*store, error) { | ||
db, err := gormadapter.Open(cfg.DBFilePath(), write) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
db.Exec("PRAGMA foreign_keys = ON") | ||
|
||
if write { | ||
if err := migrate(db, models()...); err != nil { | ||
return nil, fmt.Errorf("unable to migrate: %w", err) | ||
} | ||
} | ||
|
||
return &store{ | ||
dbMetadataStore: newDBMetadataStore(db), | ||
db: db, | ||
config: cfg, | ||
write: write, | ||
}, nil | ||
} | ||
|
||
func migrate(db *gorm.DB, models ...any) error { | ||
if err := db.AutoMigrate(models...); err != nil { | ||
return fmt.Errorf("unable to migrate: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (s *store) Close() error { | ||
log.Debug("closing store") | ||
if !s.write { | ||
return nil | ||
} | ||
|
||
err := s.db.Exec("VACUUM").Error | ||
if err != nil { | ||
return fmt.Errorf("failed to vacuum: %w", err) | ||
} | ||
|
||
return nil | ||
} |