Skip to content

Commit 13c2f47

Browse files
authored
feat: Create SourceRepository interface for Go (#4731)
This is part of the Go rewrite of the Importer, but I thought I'd split up this PR to get some opinions on the database structure. Adding a `SourceRepository` abstraction, and the bindings for the Datastore object to allow the source repos to be read from Datastore. There's an abstraction layer to separate out the business logic (`internal/models`) from the Datastore API (`internal/database/datastore`) which should make migrating off of Datastore easier in the future. I've moved & aliased the existing datastore models in `osv/models` into `internal/database/datastore`, and will eventually try abstract away the datastore usage from the exporter & relations computation.
1 parent 9f2c9e9 commit 13c2f47

11 files changed

Lines changed: 791 additions & 69 deletions

File tree

go/osv/models/internal/validate/run_validate.sh renamed to go/internal/database/datastore/internal/validate/run_validate.sh

File renamed without changes.

go/osv/models/internal/validate/validate.go renamed to go/internal/database/datastore/internal/validate/validate.go

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import (
99
"time"
1010

1111
"cloud.google.com/go/datastore"
12-
"github.com/google/osv.dev/go/osv/models"
12+
db "github.com/google/osv.dev/go/internal/database/datastore"
13+
"github.com/google/osv.dev/go/internal/models"
1314
)
1415

1516
func main() {
@@ -30,65 +31,73 @@ func main() {
3031
func readRecords(ctx context.Context, client *datastore.Client) {
3132
fmt.Println("(Go) Getting Vulnerability")
3233
key := datastore.NameKey("Vulnerability", "CVE-123-456", nil)
33-
var vulnerability models.Vulnerability
34+
var vulnerability db.Vulnerability
3435
if err := client.Get(ctx, key, &vulnerability); err != nil {
3536
fmt.Printf("(Go) Failed getting Vulnerability: %v\n", err)
3637
os.Exit(1)
3738
}
3839

3940
fmt.Println("(Go) Getting AliasGroup")
4041
key = datastore.NameKey("AliasGroup", "1", nil)
41-
var aliasGroup models.AliasGroup
42+
var aliasGroup db.AliasGroup
4243
if err := client.Get(ctx, key, &aliasGroup); err != nil {
4344
fmt.Printf("(Go) Failed getting AliasGroup: %v\n", err)
4445
os.Exit(1)
4546
}
4647

4748
fmt.Println("(Go) Getting AliasAllowListEntry")
4849
key = datastore.NameKey("AliasAllowListEntry", "1", nil)
49-
var aliasAllowListEntry models.AliasAllowListEntry
50+
var aliasAllowListEntry db.AliasAllowListEntry
5051
if err := client.Get(ctx, key, &aliasAllowListEntry); err != nil {
5152
fmt.Printf("(Go) Failed getting AliasAllowListEntry: %v\n", err)
5253
os.Exit(1)
5354
}
5455

5556
fmt.Println("(Go) Getting AliasDenyListEntry")
5657
key = datastore.NameKey("AliasDenyListEntry", "1", nil)
57-
var aliasDenyListEntry models.AliasDenyListEntry
58+
var aliasDenyListEntry db.AliasDenyListEntry
5859
if err := client.Get(ctx, key, &aliasDenyListEntry); err != nil {
5960
fmt.Printf("(Go) Failed getting AliasDenyListEntry: %v\n", err)
6061
os.Exit(1)
6162
}
6263

6364
fmt.Println("(Go) Getting UpstreamGroup")
6465
key = datastore.NameKey("UpstreamGroup", "1", nil)
65-
var upstreamGroup models.UpstreamGroup
66+
var upstreamGroup db.UpstreamGroup
6667
if err := client.Get(ctx, key, &upstreamGroup); err != nil {
6768
fmt.Printf("(Go) Failed getting UpstreamGroup: %v\n", err)
6869
os.Exit(1)
6970
}
7071

7172
fmt.Println("(Go) Getting ListedVulnerability")
7273
key = datastore.NameKey("ListedVulnerability", "CVE-123-456", nil)
73-
var listedVulnerability models.ListedVulnerability
74+
var listedVulnerability db.ListedVulnerability
7475
if err := client.Get(ctx, key, &listedVulnerability); err != nil {
7576
fmt.Printf("(Go) Failed getting ListedVulnerability: %v\n", err)
7677
os.Exit(1)
7778
}
7879

7980
fmt.Println("(Go) Getting RelatedGroup")
8081
key = datastore.NameKey("RelatedGroup", "CVE-123-456", nil)
81-
var relatedGroup models.RelatedGroup
82+
var relatedGroup db.RelatedGroup
8283
if err := client.Get(ctx, key, &relatedGroup); err != nil {
8384
fmt.Printf("(Go) Failed getting RelatedGroup: %v\n", err)
8485
os.Exit(1)
8586
}
87+
88+
fmt.Println("(Go) Getting SourceRepository")
89+
key = datastore.NameKey("SourceRepository", "oss-fuzz", nil)
90+
var sourceRepo db.SourceRepository
91+
if err := client.Get(ctx, key, &sourceRepo); err != nil {
92+
fmt.Printf("(Go) Failed getting SourceRepository: %v\n", err)
93+
os.Exit(1)
94+
}
8695
}
8796

8897
func writeRecords(ctx context.Context, client *datastore.Client) {
8998
fmt.Println("(Go) Writing Vulnerability")
9099
key := datastore.NameKey("Vulnerability", "CVE-987-654", nil)
91-
vulnerability := models.Vulnerability{
100+
vulnerability := db.Vulnerability{
92101
SourceID: "test:path/to/CVE-987-654",
93102
Modified: time.Date(2025, time.December, 31, 23, 59, 59, 0, time.UTC),
94103
IsWithdrawn: false,
@@ -104,7 +113,7 @@ func writeRecords(ctx context.Context, client *datastore.Client) {
104113

105114
fmt.Println("(Go) Writing AliasGroup")
106115
key = datastore.NameKey("AliasGroup", "2", nil)
107-
aliasGroup := models.AliasGroup{
116+
aliasGroup := db.AliasGroup{
108117
VulnIDs: []string{"A-1", "B-1", "C-1"},
109118
Modified: time.Date(2025, time.January, 1, 1, 1, 1, 1, time.UTC),
110119
}
@@ -115,7 +124,7 @@ func writeRecords(ctx context.Context, client *datastore.Client) {
115124

116125
fmt.Println("(Go) Writing AliasAllowListEntry")
117126
key = datastore.NameKey("AliasAllowListEntry", "2", nil)
118-
aliasAllowListEntry := models.AliasAllowListEntry{
127+
aliasAllowListEntry := db.AliasAllowListEntry{
119128
VulnID: "IS-GOOD",
120129
}
121130
if _, err := client.Put(ctx, key, &aliasAllowListEntry); err != nil {
@@ -125,7 +134,7 @@ func writeRecords(ctx context.Context, client *datastore.Client) {
125134

126135
fmt.Println("(Go) Writing AliasDenyListEntry")
127136
key = datastore.NameKey("AliasDenyListEntry", "2", nil)
128-
aliasDenyListEntry := models.AliasDenyListEntry{
137+
aliasDenyListEntry := db.AliasDenyListEntry{
129138
VulnID: "IS-BAD",
130139
}
131140
if _, err := client.Put(ctx, key, &aliasDenyListEntry); err != nil {
@@ -135,7 +144,7 @@ func writeRecords(ctx context.Context, client *datastore.Client) {
135144

136145
fmt.Println("(Go) Writing UpstreamGroup")
137146
key = datastore.NameKey("UpstreamGroup", "2", nil)
138-
upstreamGroup := models.UpstreamGroup{
147+
upstreamGroup := db.UpstreamGroup{
139148
UpstreamIDs: []string{"U-1", "U-2"},
140149
Modified: time.Date(2025, time.January, 1, 1, 1, 1, 1, time.UTC),
141150
UpstreamHierarchy: []byte(`{"A": ["B"]}`),
@@ -147,13 +156,13 @@ func writeRecords(ctx context.Context, client *datastore.Client) {
147156

148157
fmt.Println("(Go) Writing ListedVulnerability")
149158
key = datastore.NameKey("ListedVulnerability", "CVE-987-654", nil)
150-
listedVulnerability := models.ListedVulnerability{
159+
listedVulnerability := db.ListedVulnerability{
151160
Published: time.Date(2025, time.December, 31, 23, 59, 59, 0, time.UTC),
152161
Ecosystems: []string{"Go", "PyPI"},
153162
Packages: []string{"stdlib", "requests"},
154163
Summary: "A vulnerability",
155164
IsFixed: true,
156-
Severities: []models.Severity{
165+
Severities: []db.Severity{
157166
{Type: "CVSS_V3", Score: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},
158167
},
159168
AutocompleteTags: []string{"cve-987-654", "stdlib", "requests"},
@@ -166,12 +175,46 @@ func writeRecords(ctx context.Context, client *datastore.Client) {
166175

167176
fmt.Println("(Go) Writing RelatedGroup")
168177
key = datastore.NameKey("RelatedGroup", "CVE-987-654", nil)
169-
relatedGroup := models.RelatedGroup{
178+
relatedGroup := db.RelatedGroup{
170179
RelatedIDs: []string{"R-1", "R-2"},
171180
Modified: time.Date(2025, time.January, 1, 1, 1, 1, 1, time.UTC),
172181
}
173182
if _, err := client.Put(ctx, key, &relatedGroup); err != nil {
174183
fmt.Printf("(Go) Failed writing RelatedGroup %v: %v\n", key, err)
175184
os.Exit(1)
176185
}
186+
187+
fmt.Println("(Go) Writing SourceRepository")
188+
key = datastore.NameKey("SourceRepository", "go-source", nil)
189+
lastUpdate := time.Date(2025, time.February, 1, 10, 0, 0, 0, time.UTC)
190+
goSourceRepo := db.SourceRepository{
191+
Type: models.SourceRepositoryTypeBucket,
192+
Name: "go-source",
193+
RepoURL: "https://example.com/go-source",
194+
RepoUsername: "user",
195+
RepoBranch: "master",
196+
RESTAPIURL: "http://localhost:8080/",
197+
Bucket: "osv-test-bucket",
198+
DirectoryPath: "osv",
199+
LastSyncedHash: "zyxwvutsrqponmlkjihgfedcba",
200+
LastUpdateDate: &lastUpdate,
201+
IgnorePatterns: []string{"ignore", "pattern"},
202+
Editable: false,
203+
Extension: ".yaml",
204+
KeyPath: "key",
205+
IgnoreGit: false,
206+
DetectCherrypicks: true,
207+
ConsiderAllBranches: true,
208+
VersionsFromRepo: true,
209+
IgnoreLastImportTime: true,
210+
IgnoreDeletionThreshold: true,
211+
Link: "https://example.com/go-source",
212+
HumanLink: "https://example.com/go-source/human",
213+
DBPrefix: []string{"GO-TEST", "GO-2-TEST"},
214+
StrictValidation: true,
215+
}
216+
if _, err := client.Put(ctx, key, &goSourceRepo); err != nil {
217+
fmt.Printf("(Go) Failed writing SourceRepository %v: %v\n", key, err)
218+
os.Exit(1)
219+
}
177220
}

go/osv/models/internal/validate/validate.py renamed to go/internal/database/datastore/internal/validate/validate.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import osv.tests
2222
from osv import Vulnerability, AliasGroup, AliasAllowListEntry, \
2323
AliasDenyListEntry, ListedVulnerability, Severity, UpstreamGroup, \
24-
RelatedGroup
24+
RelatedGroup, SourceRepository, SourceRepositoryType
2525

2626

2727
def main() -> int:
@@ -91,6 +91,36 @@ def main() -> int:
9191
modified=datetime.datetime(2025, 6, 7, 8, 9, 10, tzinfo=datetime.UTC),
9292
).put()
9393

94+
print('(Python) Putting SourceRepository')
95+
SourceRepository(
96+
id='oss-fuzz',
97+
type=SourceRepositoryType.GIT,
98+
name='oss-fuzz',
99+
repo_url='https://github.com/google/oss-fuzz',
100+
repo_username='user',
101+
repo_branch='master',
102+
rest_api_url='http://127.0.0.1/',
103+
bucket='bucket',
104+
directory_path='vulns',
105+
last_synced_hash='abcdef',
106+
last_update_date=datetime.datetime(
107+
2025, 1, 1, 12, 0, 0, tzinfo=datetime.UTC),
108+
ignore_patterns=['.*\\.md', 'test/.*'],
109+
editable=True,
110+
extension='.json',
111+
key_path='vulnerability',
112+
ignore_git=False,
113+
detect_cherrypicks=True,
114+
consider_all_branches=False,
115+
versions_from_repo=True,
116+
ignore_last_import_time=True,
117+
ignore_deletion_threshold=True,
118+
link='https://github.com/google/oss-fuzz/blob/master/',
119+
human_link='https://github.com/google/oss-fuzz/tree/master/',
120+
db_prefix=['OSS-FUZZ', 'OTHER'],
121+
strict_validation=True,
122+
).put()
123+
94124
# Run Go program to read the Python-created entities in Go.
95125
# And write Go entities.
96126
result = subprocess.run(['go', 'run', './validate.go'], check=False, cwd='.')
@@ -119,11 +149,15 @@ def main() -> int:
119149
print('(Python) Getting RelatedGroup')
120150
if RelatedGroup.get_by_id('CVE-987-654') is None:
121151
return 1
152+
print('(Python) Getting SourceRepository')
153+
if SourceRepository.get_by_id('go-source') is None:
154+
return 1
122155

123156
return 0
124157

125158

126159
if __name__ == '__main__':
127160
with osv.tests.datastore_emulator(), ndb.Client().context():
161+
print('Datastore emulator running')
128162
ret = main()
129163
sys.exit(ret)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package datastore contains definitions of Datastore entities for OSV.dev.
16+
package datastore
17+
18+
import (
19+
"time"
20+
21+
"cloud.google.com/go/datastore"
22+
"github.com/google/osv.dev/go/internal/models"
23+
)
24+
25+
type Vulnerability struct {
26+
Key *datastore.Key `datastore:"__key__"`
27+
SourceID string `datastore:"source_id"`
28+
Modified time.Time `datastore:"modified"`
29+
IsWithdrawn bool `datastore:"is_withdrawn"`
30+
ModifiedRaw time.Time `datastore:"modified_raw"`
31+
AliasRaw []string `datastore:"alias_raw"`
32+
RelatedRaw []string `datastore:"related_raw"`
33+
UpstreamRaw []string `datastore:"upstream_raw"`
34+
}
35+
36+
type AliasGroup struct {
37+
VulnIDs []string `datastore:"bug_ids"`
38+
Modified time.Time `datastore:"last_modified"`
39+
}
40+
41+
type UpstreamGroup struct {
42+
Key *datastore.Key `datastore:"__key__"`
43+
VulnID string `datastore:"db_id"`
44+
UpstreamIDs []string `datastore:"upstream_ids"`
45+
Modified time.Time `datastore:"last_modified"`
46+
UpstreamHierarchy []byte `datastore:"upstream_hierarchy,noindex"`
47+
}
48+
49+
type RelatedGroup struct {
50+
Key *datastore.Key `datastore:"__key__"`
51+
RelatedIDs []string `datastore:"related_ids"`
52+
Modified time.Time `datastore:"modified"`
53+
}
54+
55+
type AliasAllowListEntry struct {
56+
VulnID string `datastore:"bug_id"`
57+
}
58+
59+
type AliasDenyListEntry struct {
60+
VulnID string `datastore:"bug_id"`
61+
}
62+
63+
type Severity struct {
64+
Type string `datastore:"type"`
65+
Score string `datastore:"score"`
66+
}
67+
68+
type ListedVulnerability struct {
69+
Key *datastore.Key `datastore:"__key__"`
70+
Published time.Time `datastore:"published"`
71+
Ecosystems []string `datastore:"ecosystems"`
72+
Packages []string `datastore:"packages,noindex"`
73+
Summary string `datastore:"summary,noindex"`
74+
IsFixed bool `datastore:"is_fixed,noindex"`
75+
Severities []Severity `datastore:"severities"`
76+
AutocompleteTags []string `datastore:"autocomplete_tags"`
77+
SearchIndices []string `datastore:"search_indices"`
78+
}
79+
80+
type SourceRepository struct {
81+
Type models.SourceRepositoryType `datastore:"type"`
82+
Name string `datastore:"name"`
83+
RepoURL string `datastore:"repo_url"`
84+
RepoUsername string `datastore:"repo_username"`
85+
RepoBranch string `datastore:"repo_branch"`
86+
RESTAPIURL string `datastore:"rest_api_url"`
87+
Bucket string `datastore:"bucket"`
88+
DirectoryPath string `datastore:"directory_path"`
89+
LastSyncedHash string `datastore:"last_synced_hash"`
90+
LastUpdateDate *time.Time `datastore:"last_update_date"`
91+
IgnorePatterns []string `datastore:"ignore_patterns"`
92+
Editable bool `datastore:"editable"`
93+
Extension string `datastore:"extension"`
94+
KeyPath string `datastore:"key_path"`
95+
IgnoreGit bool `datastore:"ignore_git"`
96+
DetectCherrypicks bool `datastore:"detect_cherrypicks"`
97+
ConsiderAllBranches bool `datastore:"consider_all_branches"`
98+
VersionsFromRepo bool `datastore:"versions_from_repo"`
99+
IgnoreLastImportTime bool `datastore:"ignore_last_import_time"`
100+
IgnoreDeletionThreshold bool `datastore:"ignore_deletion_threshold"`
101+
Link string `datastore:"link"`
102+
HumanLink string `datastore:"human_link"`
103+
DBPrefix []string `datastore:"db_prefix"`
104+
StrictValidation bool `datastore:"strict_validation"`
105+
}

0 commit comments

Comments
 (0)