Skip to content

Commit 1b6d22b

Browse files
committed
Add GC functionality to SQLite datastore
1 parent 7d92a57 commit 1b6d22b

File tree

2 files changed

+215
-8
lines changed

2 files changed

+215
-8
lines changed

cmd/cvetool/update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func update(c *cli.Context) error {
5858
Store: matcherStore,
5959
Locker: NewLocalLockSource(),
6060
DisableBackgroundUpdates: true,
61-
UpdateRetention: 3,
61+
UpdateRetention: 2,
6262
UpdateWorkers: 1,
6363
// We don't need matchers for update procedure
6464
MatcherNames: []string{},

datastore/sqlite_store.go

Lines changed: 214 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,14 @@ func formatStringArray(s []string) string {
705705
return strings.Join(s, "','")
706706
}
707707

708+
func formatStringArrayFromUUIDs(uuids []uuid.UUID) string {
709+
var s []string
710+
for _, uuid := range uuids {
711+
s = append(s, uuid.String())
712+
}
713+
return strings.Join(s, "','")
714+
}
715+
708716
func (ms *sqliteMatcherStore) GetEnrichment(ctx context.Context, kind string, tags []string) ([]driver.EnrichmentRecord, error) {
709717
var query = `
710718
WITH
@@ -765,6 +773,11 @@ func (ms *sqliteMatcherStore) GetEnrichment(ctx context.Context, kind string, ta
765773
if err := rows.Err(); err != nil {
766774
return nil, err
767775
}
776+
777+
if err := tx.Commit(); err != nil {
778+
return nil, fmt.Errorf("failed to commit tx: %v", err)
779+
}
780+
768781
return results, nil
769782
}
770783

@@ -828,7 +841,9 @@ func (ms *sqliteMatcherStore) GetUpdateOperations(ctx context.Context, kind driv
828841
q = queryVulnerability
829842
}
830843

831-
rows, err := tx.QueryContext(ctx, q, formatStringArray(updater))
844+
// FIXME: This is REALLY ugly!
845+
fmt_q := fmt.Sprintf(strings.Replace(q, "$1", "'%s'", 1), formatStringArray(updater))
846+
rows, err := tx.QueryContext(ctx, fmt_q)
832847
switch {
833848
case err == nil:
834849
case errors.Is(err, pgx.ErrNoRows):
@@ -855,6 +870,9 @@ func (ms *sqliteMatcherStore) GetUpdateOperations(ctx context.Context, kind driv
855870
if err := rows.Err(); err != nil {
856871
return nil, err
857872
}
873+
if err := tx.Commit(); err != nil {
874+
return nil, fmt.Errorf("failed to commit tx: %v", err)
875+
}
858876

859877
return out, nil
860878
}
@@ -889,8 +907,22 @@ func (ms *sqliteMatcherStore) GetUpdateDiff(ctx context.Context, prev uuid.UUID,
889907
//
890908
// The number of UpdateOperations deleted is returned.
891909
func (ms *sqliteMatcherStore) DeleteUpdateOperations(ctx context.Context, uuids ...uuid.UUID) (int64, error) {
892-
zlog.Warn(ctx).Msg("sqliteMatcherStore.DeleteUpdateOperations is not implemented!")
893-
return 0, nil
910+
const query = `DELETE FROM update_operation WHERE ref IN ($1);`
911+
ctx = zlog.ContextWithValues(ctx, "component", "internal/vulnstore/sqlite/deleteUpdateOperations")
912+
913+
if len(uuids) == 0 {
914+
return 0, nil
915+
}
916+
917+
// FIXME: This is REALLY ugly!
918+
fmt_q := fmt.Sprintf(strings.Replace(query, "$1", "'%s'", 1), formatStringArrayFromUUIDs(uuids))
919+
tag, err := ms.conn.ExecContext(ctx, fmt_q)
920+
if err != nil {
921+
zlog.Error(ctx).Err(err).Msg("ExecContext")
922+
return 0, fmt.Errorf("failed to delete: %w", err)
923+
}
924+
925+
return tag.RowsAffected()
894926
}
895927

896928
// GC will delete any update operations for an updater which exceeds the provided keep
@@ -901,8 +933,184 @@ func (ms *sqliteMatcherStore) DeleteUpdateOperations(ctx context.Context, uuids
901933
// The returned int64 value indicates the remaining number of update operations needing GC.
902934
// Running this method till the returned value is 0 accomplishes a full GC of the vulnstore.
903935
func (ms *sqliteMatcherStore) GC(ctx context.Context, count int) (int64, error) {
904-
zlog.Warn(ctx).Msg("sqliteMatcherStore.GC is not implemented!")
905-
return 0, nil
936+
ctx = zlog.ContextWithValues(ctx, "component", "datastore/sqlite/GC")
937+
938+
// obtain update operations which need deletin'
939+
ops, totalOps, err := eligibleUpdateOpts(ctx, ms.conn, count)
940+
if err != nil {
941+
return 0, err
942+
}
943+
944+
deletedOps, err := ms.DeleteUpdateOperations(ctx, ops...)
945+
if err != nil {
946+
zlog.Error(ctx).Err(err).Msg("DeleteUpdateOperations")
947+
return totalOps - deletedOps, err
948+
}
949+
950+
// get all updaters we know about.
951+
updaters, err := distinctUpdaters(ctx, ms.conn)
952+
if err != nil {
953+
return totalOps - deletedOps, err
954+
}
955+
956+
for kind, us := range updaters {
957+
var cleanup cleanupFunc
958+
switch kind {
959+
case driver.VulnerabilityKind:
960+
cleanup = vulnCleanup
961+
case driver.EnrichmentKind:
962+
cleanup = enrichmentCleanup
963+
default:
964+
zlog.Error(ctx).Str("kind", string(kind)).Msg("unknown updater kind; skipping cleanup")
965+
continue
966+
}
967+
for _, u := range us {
968+
err := cleanup(ctx, ms.conn, u)
969+
if err != nil {
970+
return totalOps - deletedOps, err
971+
}
972+
}
973+
}
974+
975+
return totalOps - deletedOps, nil
976+
}
977+
978+
// distinctUpdaters returns all updaters which have registered an update
979+
// operation.
980+
func distinctUpdaters(ctx context.Context, conn *sql.DB) (map[driver.UpdateKind][]string, error) {
981+
const (
982+
// will always contain at least two update operations
983+
selectUpdaters = `SELECT DISTINCT(updater), kind FROM update_operation;`
984+
)
985+
rows, err := conn.QueryContext(ctx, selectUpdaters)
986+
if err != nil {
987+
return nil, fmt.Errorf("error selecting distinct updaters: %v", err)
988+
}
989+
defer rows.Close()
990+
991+
updaters := make(map[driver.UpdateKind][]string)
992+
for rows.Next() {
993+
var (
994+
updater string
995+
kind driver.UpdateKind
996+
)
997+
err := rows.Scan(&updater, &kind)
998+
switch err {
999+
case nil:
1000+
// hop out
1001+
default:
1002+
return nil, fmt.Errorf("error scanning updater: %v", err)
1003+
}
1004+
updaters[kind] = append(updaters[kind], updater)
1005+
}
1006+
if rows.Err() != nil {
1007+
return nil, rows.Err()
1008+
}
1009+
1010+
return updaters, nil
1011+
}
1012+
1013+
// eligibleUpdateOpts returns a list of update operation refs which exceed the specified
1014+
// keep value.
1015+
func eligibleUpdateOpts(ctx context.Context, conn *sql.DB, keep int) ([]uuid.UUID, int64, error) {
1016+
const (
1017+
// this query will return rows of UUID arrays.
1018+
updateOps = `SELECT updater, json_group_array(ref ORDER BY date desc) FROM update_operation GROUP BY updater;`
1019+
)
1020+
1021+
// gather any update operations exceeding our keep value.
1022+
// keep+1 is used because PG's array slicing is inclusive,
1023+
// we want to grab all items once after our keep value.
1024+
m := []uuid.UUID{}
1025+
1026+
rows, err := conn.QueryContext(ctx, updateOps)
1027+
switch err {
1028+
case nil:
1029+
default:
1030+
return nil, 0, fmt.Errorf("error querying for update operations: %v", err)
1031+
}
1032+
1033+
defer rows.Close()
1034+
for rows.Next() {
1035+
var uuids_json string
1036+
var updater string
1037+
err := rows.Scan(&updater, &uuids_json)
1038+
if err != nil {
1039+
return nil, 0, fmt.Errorf("error scanning update operations: %w", err)
1040+
}
1041+
var uuids []uuid.UUID
1042+
json.Unmarshal([]byte(uuids_json), &uuids)
1043+
m = append(m, uuids[keep:]...)
1044+
}
1045+
if rows.Err() != nil {
1046+
return nil, 0, rows.Err()
1047+
}
1048+
1049+
return m, int64(len(m)), nil
1050+
}
1051+
1052+
type cleanupFunc func(context.Context, *sql.DB, string) error
1053+
1054+
func vulnCleanup(ctx context.Context, conn *sql.DB, updater string) error {
1055+
const (
1056+
deleteOrphanedVulns = `
1057+
DELETE FROM vuln
1058+
WHERE id IN (
1059+
SELECT v2.id
1060+
FROM vuln v2
1061+
LEFT JOIN uo_vuln uvl
1062+
ON v2.id = uvl.vuln
1063+
WHERE uvl.vuln IS NULL
1064+
AND v2.updater = $1
1065+
);
1066+
`
1067+
)
1068+
1069+
ctx = zlog.ContextWithValues(ctx, "updater", updater)
1070+
zlog.Debug(ctx).
1071+
Msg("starting vuln clean up")
1072+
res, err := conn.ExecContext(ctx, deleteOrphanedVulns, updater)
1073+
if err != nil {
1074+
return fmt.Errorf("failed while exec'ing vuln delete: %w", err)
1075+
}
1076+
rows, err := res.RowsAffected()
1077+
if err != nil {
1078+
return fmt.Errorf("failed while exec'ing vuln delete (affected): %w", err)
1079+
}
1080+
zlog.Debug(ctx).Int64("rows affected", rows).Msg("vulns deleted")
1081+
1082+
return nil
1083+
}
1084+
1085+
func enrichmentCleanup(ctx context.Context, conn *sql.DB, updater string) error {
1086+
const (
1087+
deleteOrphanedEnrichments = `
1088+
DELETE FROM enrichment
1089+
WHERE id IN (
1090+
SELECT e2.id
1091+
FROM enrichment e2
1092+
LEFT JOIN uo_enrich uen
1093+
ON e2.id = uen.enrich
1094+
WHERE uen.enrich IS NULL
1095+
AND e2.updater = $1
1096+
);
1097+
`
1098+
)
1099+
1100+
ctx = zlog.ContextWithValues(ctx, "updater", updater)
1101+
zlog.Debug(ctx).
1102+
Msg("starting enrichment clean up")
1103+
res, err := conn.ExecContext(ctx, deleteOrphanedEnrichments, updater)
1104+
if err != nil {
1105+
return fmt.Errorf("failed while exec'ing enrichment delete: %w", err)
1106+
}
1107+
rows, err := res.RowsAffected()
1108+
if err != nil {
1109+
return fmt.Errorf("failed while exec'ing enrichment delete (affected): %w", err)
1110+
}
1111+
zlog.Debug(ctx).Int64("rows affected", rows).Msg("enrichments deleted")
1112+
1113+
return nil
9061114
}
9071115

9081116
// RecordUpdaterStatus records that an updater is up to date with vulnerabilities at this time
@@ -1002,8 +1210,7 @@ func (ms *sqliteMatcherStore) RecordUpdaterSetStatus(ctx context.Context, update
10021210
WHERE updater_name LIKE $2 || '%';`
10031211
)
10041212

1005-
ctx = zlog.ContextWithValues(ctx,
1006-
"component", "internal/vulnstore/postgres/recordUpdaterSetStatus")
1213+
ctx = zlog.ContextWithValues(ctx, "component", "internal/vulnstore/sqlite/recordUpdaterSetStatus")
10071214

10081215
tx, err := ms.conn.BeginTx(ctx, nil)
10091216
if err != nil {

0 commit comments

Comments
 (0)