Skip to content

Commit

Permalink
add v6 provider store
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <[email protected]>
  • Loading branch information
wagoodman committed Nov 1, 2024
1 parent 7cf9696 commit 5a8ed20
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 0 deletions.
2 changes: 2 additions & 0 deletions grype/db/v6/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ type ReadWriter interface {

type Reader interface {
DBMetadataStoreReader
ProviderStoreReader
}

type Writer interface {
DBMetadataStoreWriter
ProviderStoreWriter
io.Closer
}

Expand Down
25 changes: 25 additions & 0 deletions grype/db/v6/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ func models() []any {
return []any{
// non-domain info
&DBMetadata{},

// data source info
&Provider{},
}
}

Expand All @@ -17,3 +20,25 @@ type DBMetadata struct {
Revision int `gorm:"column:revision;not null"`
Addition int `gorm:"column:addition;not null"`
}

// data source info //////////////////////////////////////////////////////

// Provider is the upstream data processor (usually Vunnel) that is responsible for vulnerability records. Each provider
// should be scoped to a specific vulnerability dataset, for instance, the "ubuntu" provider for all records from
// Canonicals' Ubuntu Security Notices (for all Ubuntu distro versions).
type Provider struct {
// Name of the Vunnel provider (or sub processor responsible for data records from a single specific source, e.g. "ubuntu")
ID string `gorm:"column:id;primaryKey"`

// Version of the Vunnel provider (or sub processor equivalent)
Version string `gorm:"column:version"`

// Processor is the name of the application that processed the data (e.g. "vunnel")
Processor string `gorm:"column:processor"`

// DateCaptured is the timestamp which the upstream data was pulled and processed
DateCaptured *time.Time `gorm:"column:date_captured"`

// InputDigest is a self describing hash (e.g. sha256:123... not 123...) of all data used by the provider to generate the vulnerability records
InputDigest string `gorm:"column:input_digest"`
}
64 changes: 64 additions & 0 deletions grype/db/v6/provider_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package v6

import (
"errors"
"fmt"
"github.com/anchore/grype/internal/log"
"gorm.io/gorm"
)

type ProviderStoreWriter interface {
AddProvider(p *Provider) error
}

type ProviderStoreReader interface {
GetProvider(name string) (*Provider, error)
}

type providerStore struct {
db *gorm.DB
}

func newProviderStore(db *gorm.DB) *providerStore {
return &providerStore{
db: db,
}
}

func (s *providerStore) AddProvider(p *Provider) error {
log.WithFields("name", p.ID).Trace("writing provider record")

var existingProvider Provider
result := s.db.Where("id = ? AND version = ?", p.ID, p.Version).First(&existingProvider)
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
return fmt.Errorf("failed to find provider (name=%q version=%q): %w", p.ID, p.Version, result.Error)
}

if result.Error == nil {
// overwrite the existing provider if found
existingProvider.Processor = p.Processor
existingProvider.DateCaptured = p.DateCaptured
existingProvider.InputDigest = p.InputDigest
} else {
// create a new provider record if not found
existingProvider = *p
}

if err := s.db.Save(&existingProvider).Error; err != nil {
return fmt.Errorf("failed to save provider (name=%q version=%q): %w", p.ID, p.Version, err)
}

return nil
}

func (s *providerStore) GetProvider(name string) (*Provider, error) {
log.WithFields("name", name).Trace("fetching provider record")

var provider Provider
result := s.db.Where("id = ?", name).First(&provider)
if result.Error != nil {
return nil, fmt.Errorf("failed to fetch provider (name=%q): %w", name, result.Error)
}

return &provider, nil
}
93 changes: 93 additions & 0 deletions grype/db/v6/provider_store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package v6

import (
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
"time"
)

func TestProviderStore(t *testing.T) {
now := time.Date(2021, 1, 1, 2, 3, 4, 5, time.UTC)
other := time.Date(2022, 2, 3, 4, 5, 6, 7, time.UTC)
tests := []struct {
name string
providers []Provider
wantErr require.ErrorAssertionFunc
}{
{
name: "add new provider",
providers: []Provider{
{
ID: "ubuntu",
Version: "1.0",
Processor: "vunnel",
DateCaptured: &now,
InputDigest: "sha256:abcd1234",
},
},
},
{
name: "add existing provider",
providers: []Provider{
{ // original
ID: "ubuntu",
Version: "1.0",
Processor: "vunnel",
DateCaptured: &now,
InputDigest: "sha256:abcd1234",
},
{ // overwrite...
ID: "ubuntu",
Version: "2.0",
Processor: "something-else",
DateCaptured: &other,
InputDigest: "sha256:cdef5678",
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := newProviderStore(setupTestDB(t))
if tt.wantErr == nil {
tt.wantErr = require.NoError
}
for i, p := range tt.providers {
isLast := i == len(tt.providers)-1
err := s.AddProvider(&p)
if !isLast {
require.NoError(t, err)
continue
}

tt.wantErr(t, err)
if err != nil {
continue
}

provider, err := s.GetProvider(p.ID)
tt.wantErr(t, err)
if err != nil {
assert.Nil(t, provider)
return
}

require.NoError(t, err)
require.NotNil(t, provider)
if d := cmp.Diff(p, *provider); d != "" {
t.Errorf("unexpected provider (-want +got): %s", d)
}
}
})
}
}

func TestProviderStore_GetProvider(t *testing.T) {
s := newProviderStore(setupTestDB(t))
p, err := s.GetProvider("fake")
require.Error(t, err)
assert.Nil(t, p)
}
2 changes: 2 additions & 0 deletions grype/db/v6/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

type store struct {
*dbMetadataStore
*providerStore
db *gorm.DB
config Config
write bool
Expand All @@ -30,6 +31,7 @@ func newStore(cfg Config, write bool) (*store, error) {

return &store{
dbMetadataStore: newDBMetadataStore(db),
providerStore: newProviderStore(db),
db: db,
config: cfg,
write: write,
Expand Down

0 comments on commit 5a8ed20

Please sign in to comment.