Skip to content

Commit

Permalink
modify store to be one getter-per-noun (#2297)
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
  • Loading branch information
wagoodman authored Dec 3, 2024
1 parent 87d6201 commit 7d4ef8c
Show file tree
Hide file tree
Showing 7 changed files with 394 additions and 198 deletions.
110 changes: 85 additions & 25 deletions grype/db/v6/affected_cpe_store.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
package v6

import (
"encoding/json"
"fmt"

"gorm.io/gorm"

"github.com/anchore/go-logger"
"github.com/anchore/grype/internal/log"
"github.com/anchore/syft/syft/cpe"
)

type AffectedCPEStoreWriter interface {
AddAffectedCPEs(packages ...*AffectedCPEHandle) error
}

type AffectedCPEStoreReader interface {
GetCPEsByProduct(packageName string, config *GetAffectedCPEOptions) ([]AffectedCPEHandle, error)
GetAffectedCPEs(cpe *cpe.Attributes, config *GetAffectedCPEOptions) ([]AffectedCPEHandle, error)
}

type GetAffectedCPEOptions struct {
PreloadCPE bool
PreloadBlob bool
PreloadCPE bool
PreloadVulnerability bool
PreloadBlob bool
Vulnerability *VulnerabilitySpecifier
}

type affectedCPEStore struct {
Expand Down Expand Up @@ -48,59 +51,116 @@ func (s *affectedCPEStore) AddAffectedCPEs(packages ...*AffectedCPEHandle) error
return nil
}

// GetCPEsByProduct retrieves a single AffectedCPEHandle by product name
func (s *affectedCPEStore) GetCPEsByProduct(packageName string, config *GetAffectedCPEOptions) ([]AffectedCPEHandle, error) {
// GetAffectedCPEs retrieves a single AffectedCPEHandle by one or more CPE fields (not including version and update fields, which are ignored)
func (s *affectedCPEStore) GetAffectedCPEs(cpe *cpe.Attributes, config *GetAffectedCPEOptions) ([]AffectedCPEHandle, error) {
if config == nil {
config = &GetAffectedCPEOptions{}
}

log.WithFields("product", packageName).Trace("fetching AffectedCPE record")
fields := make(logger.Fields)
if cpe == nil {
fields["cpe"] = "any"
} else {
fields["cpe"] = cpe.String()
}
log.WithFields(fields).Trace("fetching AffectedCPE record")

var pkgs []AffectedCPEHandle
query := s.db.
Joins("JOIN cpes ON cpes.id = affected_cpe_handles.cpe_id").
Where("cpes.product = ?", packageName)
query := s.handleCPE(s.db, cpe)

query = s.handleVulnerabilityOptions(query, config.Vulnerability)

query = s.handlePreload(query, *config)

var pkgs []AffectedCPEHandle
err := query.Find(&pkgs).Error
if err != nil {
return nil, fmt.Errorf("unable to fetch affected package record: %w", err)
}

if config.PreloadBlob {
for i := range pkgs {
err := s.attachBlob(&pkgs[i])
err := s.blobStore.attachBlobValue(&pkgs[i])
if err != nil {
return nil, fmt.Errorf("unable to attach blob %#v: %w", pkgs[i], err)
}
}
}

if config.PreloadVulnerability {
for i := range pkgs {
err := s.blobStore.attachBlobValue(pkgs[i].Vulnerability)
if err != nil {
return nil, fmt.Errorf("unable to attach vulnerability blob %#v: %w", pkgs[i], err)
}
}
}

return pkgs, nil
}

func (s *affectedCPEStore) handlePreload(query *gorm.DB, config GetAffectedCPEOptions) *gorm.DB {
if config.PreloadCPE {
query = query.Preload("CPE")
func (s *affectedCPEStore) handleCPE(query *gorm.DB, c *cpe.Attributes) *gorm.DB {
if c == nil {
return query
}
query = query.Joins("JOIN cpes ON cpes.id = affected_cpe_handles.cpe_id")

if c.Part != cpe.Any {
query = query.Where("cpes.part = ?", c.Part)
}

if c.Vendor != cpe.Any {
query = query.Where("cpes.vendor = ?", c.Vendor)
}

if c.Product != cpe.Any {
query = query.Where("cpes.product = ?", c.Product)
}

if c.Edition != cpe.Any {
query = query.Where("cpes.edition = ?", c.Edition)
}

if c.Language != cpe.Any {
query = query.Where("cpes.language = ?", c.Language)
}

if c.SWEdition != cpe.Any {
query = query.Where("cpes.sw_edition = ?", c.SWEdition)
}

if c.TargetSW != cpe.Any {
query = query.Where("cpes.target_sw = ?", c.TargetSW)
}

if c.TargetHW != cpe.Any {
query = query.Where("cpes.target_hw = ?", c.TargetHW)
}

if c.Other != cpe.Any {
query = query.Where("cpes.other = ?", c.Other)
}

return query
}

// attachBlob attaches the BlobValue to the AffectedCPEHandle
func (s *affectedCPEStore) attachBlob(cpe *AffectedCPEHandle) error {
var blobValue *AffectedPackageBlob
func (s *affectedCPEStore) handleVulnerabilityOptions(query *gorm.DB, config *VulnerabilitySpecifier) *gorm.DB {
if config == nil {
return query
}
if config.Name != "" {
query = query.Joins("JOIN vulnerability_handles ON affected_cpe_handles.vulnerability_id = vulnerability_handles.id").Where("vulnerability_handles.name = ?", config.Name)
}
return query
}

rawValue, err := s.blobStore.getBlobValue(cpe.BlobID)
if err != nil {
return fmt.Errorf("unable to fetch blob value for affected CPE: %w", err)
func (s *affectedCPEStore) handlePreload(query *gorm.DB, config GetAffectedCPEOptions) *gorm.DB {
if config.PreloadCPE {
query = query.Preload("CPE")
}

if err := json.Unmarshal([]byte(rawValue), &blobValue); err != nil {
return fmt.Errorf("unable to unmarshal blob value: %w", err)
if config.PreloadVulnerability {
query = query.Preload("Vulnerability")
}

cpe.BlobValue = blobValue
return nil
return query
}
58 changes: 38 additions & 20 deletions grype/db/v6/affected_cpe_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/anchore/syft/syft/cpe"
)

func TestAffectedCPEStore_AddAffectedCPEs(t *testing.T) {
Expand All @@ -14,8 +16,10 @@ func TestAffectedCPEStore_AddAffectedCPEs(t *testing.T) {
s := newAffectedCPEStore(db, bw)

cpe1 := &AffectedCPEHandle{
VulnerabilityID: 1,
CpeID: 1,
Vulnerability: &VulnerabilityHandle{ // vuln id = 1
Name: "CVE-2023-5678",
},
CpeID: 1,
CPE: &Cpe{
Part: "a",
Vendor: "vendor-1",
Expand All @@ -27,7 +31,7 @@ func TestAffectedCPEStore_AddAffectedCPEs(t *testing.T) {
},
}

cpe2 := testAffectedCPEHandle()
cpe2 := testAffectedCPEHandle() // vuln id = 2

err := s.AddAffectedCPEs(cpe1, cpe2)
require.NoError(t, err)
Expand All @@ -49,33 +53,33 @@ func TestAffectedCPEStore_AddAffectedCPEs(t *testing.T) {
assert.Nil(t, result2.BlobValue) // since we're not preloading any fields on the fetch
}

func TestAffectedCPEStore_GetCPEsByProduct(t *testing.T) {
func TestAffectedCPEStore_GetCPEs(t *testing.T) {
db := setupTestStore(t).db
bw := newBlobStore(db)
s := newAffectedCPEStore(db, bw)

cpe := testAffectedCPEHandle()
err := s.AddAffectedCPEs(cpe)
c := testAffectedCPEHandle()
err := s.AddAffectedCPEs(c)
require.NoError(t, err)

results, err := s.GetCPEsByProduct(cpe.CPE.Product, nil)
results, err := s.GetAffectedCPEs(cpeFromProduct(c.CPE.Product), nil)
require.NoError(t, err)

expected := []AffectedCPEHandle{*cpe}
expected := []AffectedCPEHandle{*c}
require.Len(t, results, len(expected))
result := results[0]
assert.Equal(t, cpe.CpeID, result.CpeID)
assert.Equal(t, cpe.ID, result.ID)
assert.Equal(t, cpe.BlobID, result.BlobID)
assert.Equal(t, c.CpeID, result.CpeID)
assert.Equal(t, c.ID, result.ID)
assert.Equal(t, c.BlobID, result.BlobID)
require.Nil(t, result.BlobValue) // since we're not preloading any fields on the fetch

// fetch again with blob & cpe preloaded
results, err = s.GetCPEsByProduct(cpe.CPE.Product, &GetAffectedCPEOptions{PreloadCPE: true, PreloadBlob: true})
results, err = s.GetAffectedCPEs(cpeFromProduct(c.CPE.Product), &GetAffectedCPEOptions{PreloadCPE: true, PreloadBlob: true, PreloadVulnerability: true})
require.NoError(t, err)
require.Len(t, results, len(expected))
result = results[0]
assert.NotNil(t, result.BlobValue)
if d := cmp.Diff(*cpe, result); d != "" {
if d := cmp.Diff(*c, result); d != "" {
t.Errorf("unexpected result (-want +got):\n%s", d)
}
}
Expand All @@ -86,8 +90,10 @@ func TestAffectedCPEStore_PreventDuplicateCPEs(t *testing.T) {
s := newAffectedCPEStore(db, bw)

cpe1 := &AffectedCPEHandle{
VulnerabilityID: 1,
CpeID: 1,
Vulnerability: &VulnerabilityHandle{ // vuln id = 1
Name: "CVE-2023-5678",
},
CpeID: 1,
CPE: &Cpe{
Part: "a",
Vendor: "vendor-1",
Expand All @@ -104,8 +110,10 @@ func TestAffectedCPEStore_PreventDuplicateCPEs(t *testing.T) {

// attempt to add a duplicate CPE with the same values
duplicateCPE := &AffectedCPEHandle{
VulnerabilityID: 2, // different VulnerabilityID for testing
CpeID: 2,
Vulnerability: &VulnerabilityHandle{ // vuln id = 2, different VulnerabilityID for testing...
Name: "CVE-2024-1234",
},
CpeID: 2,
CPE: &Cpe{
Part: "a", // same
Vendor: "vendor-1", // same
Expand All @@ -127,9 +135,10 @@ func TestAffectedCPEStore_PreventDuplicateCPEs(t *testing.T) {
require.NoError(t, err)
require.Len(t, existingCPEs, 1, "expected only one CPE to exist")

actualHandles, err := s.GetCPEsByProduct(cpe1.CPE.Product, &GetAffectedCPEOptions{
PreloadCPE: true,
PreloadBlob: true,
actualHandles, err := s.GetAffectedCPEs(cpeFromProduct(cpe1.CPE.Product), &GetAffectedCPEOptions{
PreloadCPE: true,
PreloadBlob: true,
PreloadVulnerability: true,
})
require.NoError(t, err)
expected := []AffectedCPEHandle{*cpe1, *duplicateCPE}
Expand All @@ -139,8 +148,17 @@ func TestAffectedCPEStore_PreventDuplicateCPEs(t *testing.T) {
}
}

func cpeFromProduct(product string) *cpe.Attributes {
return &cpe.Attributes{
Product: product,
}
}

func testAffectedCPEHandle() *AffectedCPEHandle {
return &AffectedCPEHandle{
Vulnerability: &VulnerabilityHandle{
Name: "CVE-2024-4321",
},
CPE: &Cpe{
Part: "application",
Vendor: "vendor",
Expand Down
Loading

0 comments on commit 7d4ef8c

Please sign in to comment.