Skip to content

Commit 2002eaa

Browse files
committed
fixup! jsonblob: implement Load as documented
add testing and move some code around Signed-off-by: Brad Lugo <blugo@redhat.com>
1 parent 85ae351 commit 2002eaa

File tree

4 files changed

+182
-104
lines changed

4 files changed

+182
-104
lines changed

libvuln/jsonblob/jsonblob.go

Lines changed: 3 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,11 @@ func Load(ctx context.Context, r io.Reader) (*Store, error) {
5656
return nil, err
5757
}
5858

59-
// TODO(DO NOT MERGE): This implementation might be a bit naive. Currently,
59+
// TODO(DO NOT MERGE): ~~This implementation might be a bit naive. Currently,
6060
// it basically copies [OfflineImport]. We could probably do some custom
6161
// parsing such that it basically just decodes the json into the
62-
// appropriate [Store] fields.
62+
// appropriate [Store] fields.~~
63+
// Actually, this might be the way.
6364
// [OfflineImport]: https://github.com/quay/claircore/blob/126f688bb11220fb34708719be91952dc32ff7b1/libvuln/updates.go#L17-L74
6465
for l.Next() {
6566
if err := ctx.Err(); err != nil {
@@ -83,88 +84,6 @@ func Load(ctx context.Context, r io.Reader) (*Store, error) {
8384
return s, nil
8485
}
8586

86-
// NewLoader creates a new Loader from the provided [io.Reader].
87-
func NewLoader(r io.Reader) (*Loader, error) {
88-
l := Loader{
89-
dec: json.NewDecoder(r),
90-
cur: uuid.Nil,
91-
}
92-
return &l, nil
93-
}
94-
95-
// Loader is an iterator that returns a series of [Entry].
96-
//
97-
// Users should call [*Loader.Next] until it reports false, then check for
98-
// errors via [*Loader.Err].
99-
type Loader struct {
100-
err error
101-
e *Entry
102-
103-
dec *json.Decoder
104-
next *Entry
105-
de diskEntry
106-
cur uuid.UUID
107-
}
108-
109-
// Next reports whether there's an [Entry] to be processed.
110-
func (l *Loader) Next() bool {
111-
if l.err != nil {
112-
return false
113-
}
114-
115-
for l.err = l.dec.Decode(&l.de); l.err == nil; l.err = l.dec.Decode(&l.de) {
116-
id := l.de.Ref
117-
// If we just hit a new Entry, promote the current one.
118-
if id != l.cur {
119-
l.e = l.next
120-
l.next = &Entry{}
121-
l.next.Updater = l.de.Updater
122-
l.next.Fingerprint = l.de.Fingerprint
123-
l.next.Date = l.de.Date
124-
}
125-
switch l.de.Kind {
126-
case driver.VulnerabilityKind:
127-
vuln := claircore.Vulnerability{}
128-
if err := json.Unmarshal(l.de.Vuln.buf, &vuln); err != nil {
129-
l.err = err
130-
return false
131-
}
132-
l.next.Vuln = append(l.next.Vuln, &vuln)
133-
case driver.EnrichmentKind:
134-
en := driver.EnrichmentRecord{}
135-
if err := json.Unmarshal(l.de.Enrichment.buf, &en); err != nil {
136-
l.err = err
137-
return false
138-
}
139-
l.next.Enrichment = append(l.next.Enrichment, en)
140-
}
141-
// If this was an initial diskEntry, promote the ref.
142-
if id != l.cur {
143-
l.cur = id
144-
// If we have an Entry ready, report that.
145-
if l.e != nil {
146-
return true
147-
}
148-
}
149-
}
150-
l.e = l.next
151-
return true
152-
}
153-
154-
// Entry returns the latest loaded [Entry].
155-
func (l *Loader) Entry() *Entry {
156-
return l.e
157-
}
158-
159-
// Err is the latest encountered error.
160-
func (l *Loader) Err() error {
161-
// Don't report EOF as an error.
162-
if errors.Is(l.err, io.EOF) {
163-
return nil
164-
}
165-
return l.err
166-
}
167-
16887
// Store writes out the contents of the receiver to the provided [io.Writer].
16988
// It's the inverse of [Load].
17089
//

libvuln/jsonblob/jsonblob_test.go

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@ package jsonblob
33
import (
44
"bytes"
55
"context"
6-
"io"
7-
"testing"
8-
96
"github.com/google/go-cmp/cmp"
10-
"golang.org/x/sync/errgroup"
11-
127
"github.com/quay/claircore"
138
"github.com/quay/claircore/libvuln/driver"
9+
"golang.org/x/sync/errgroup"
10+
"io"
11+
"testing"
12+
1413
"github.com/quay/claircore/test"
1514
)
1615

@@ -69,24 +68,13 @@ func TestRoundtrip(t *testing.T) {
6968
eg, ctx := errgroup.WithContext(ctx)
7069
eg.Go(func() error { defer w.Close(); return a.Store(w) })
7170
eg.Go(func() error {
72-
l, err := NewLoader(ctx, io.TeeReader(r, &buf))
71+
s, err := Load(ctx, io.TeeReader(r, &buf))
7372
if err != nil {
7473
return err
7574
}
76-
for l.Next() {
77-
e := l.Entry()
78-
if e.Vuln != nil && e.Enrichment != nil {
79-
t.Error("expecting entry to have either vulnerability or enrichment, got both")
80-
}
81-
if e.Vuln != nil {
82-
got.V = append(got.V, l.Entry().Vuln...)
83-
}
84-
if e.Enrichment != nil {
85-
got.E = append(got.E, l.Entry().Enrichment...)
86-
}
87-
}
88-
if err := l.Err(); err != nil {
89-
return err
75+
for _, e := range s.Entries() {
76+
got.V = append(got.V, e.Vuln...)
77+
got.E = append(got.E, e.Enrichment...)
9078
}
9179
return nil
9280
})

libvuln/jsonblob/loader.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package jsonblob
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"github.com/google/uuid"
7+
"io"
8+
9+
"github.com/quay/claircore"
10+
"github.com/quay/claircore/libvuln/driver"
11+
)
12+
13+
// NewLoader creates a new Loader from the provided [io.Reader].
14+
func NewLoader(r io.Reader) (*Loader, error) {
15+
l := Loader{
16+
dec: json.NewDecoder(r),
17+
cur: uuid.Nil,
18+
}
19+
return &l, nil
20+
}
21+
22+
// Loader is an iterator that returns a series of [Entry].
23+
//
24+
// Users should call [*Loader.Next] until it reports false, then check for
25+
// errors via [*Loader.Err].
26+
type Loader struct {
27+
err error
28+
e *Entry
29+
30+
dec *json.Decoder
31+
next *Entry
32+
de diskEntry
33+
cur uuid.UUID
34+
}
35+
36+
// Next reports whether there's an [Entry] to be processed.
37+
func (l *Loader) Next() bool {
38+
if l.err != nil {
39+
return false
40+
}
41+
42+
for l.err = l.dec.Decode(&l.de); l.err == nil; l.err = l.dec.Decode(&l.de) {
43+
id := l.de.Ref
44+
// If we just hit a new Entry, promote the current one.
45+
if id != l.cur {
46+
l.e = l.next
47+
l.next = &Entry{}
48+
l.next.Updater = l.de.Updater
49+
l.next.Fingerprint = l.de.Fingerprint
50+
l.next.Date = l.de.Date
51+
}
52+
switch l.de.Kind {
53+
case driver.VulnerabilityKind:
54+
vuln := claircore.Vulnerability{}
55+
if err := json.Unmarshal(l.de.Vuln.buf, &vuln); err != nil {
56+
l.err = err
57+
return false
58+
}
59+
l.next.Vuln = append(l.next.Vuln, &vuln)
60+
case driver.EnrichmentKind:
61+
en := driver.EnrichmentRecord{}
62+
if err := json.Unmarshal(l.de.Enrichment.buf, &en); err != nil {
63+
l.err = err
64+
return false
65+
}
66+
l.next.Enrichment = append(l.next.Enrichment, en)
67+
}
68+
// If this was an initial diskEntry, promote the ref.
69+
if id != l.cur {
70+
l.cur = id
71+
// If we have an Entry ready, report that.
72+
if l.e != nil {
73+
return true
74+
}
75+
}
76+
}
77+
l.e = l.next
78+
return true
79+
}
80+
81+
// Entry returns the latest loaded [Entry].
82+
func (l *Loader) Entry() *Entry {
83+
return l.e
84+
}
85+
86+
// Err is the latest encountered error.
87+
func (l *Loader) Err() error {
88+
// Don't report EOF as an error.
89+
if errors.Is(l.err, io.EOF) {
90+
return nil
91+
}
92+
return l.err
93+
}

libvuln/jsonblob/loader_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package jsonblob
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"io"
7+
"testing"
8+
9+
"github.com/google/go-cmp/cmp"
10+
"golang.org/x/sync/errgroup"
11+
12+
"github.com/quay/claircore"
13+
"github.com/quay/claircore/libvuln/driver"
14+
"github.com/quay/claircore/test"
15+
)
16+
17+
func TestLoader(t *testing.T) {
18+
ctx := context.Background()
19+
a, err := New()
20+
if err != nil {
21+
t.Fatal(err)
22+
}
23+
24+
var want, got struct {
25+
V []*claircore.Vulnerability
26+
E []driver.EnrichmentRecord
27+
}
28+
29+
want.V = test.GenUniqueVulnerabilities(10, "test")
30+
ref, err := a.UpdateVulnerabilities(ctx, "test", "", want.V)
31+
if err != nil {
32+
t.Error(err)
33+
}
34+
t.Logf("ref: %v", ref)
35+
36+
want.E = test.GenEnrichments(15)
37+
ref, err = a.UpdateEnrichments(ctx, "test", "", want.E)
38+
if err != nil {
39+
t.Error(err)
40+
}
41+
t.Logf("ref: %v", ref)
42+
43+
var buf bytes.Buffer
44+
defer func() {
45+
t.Logf("wrote:\n%s", buf.String())
46+
}()
47+
r, w := io.Pipe()
48+
eg, ctx := errgroup.WithContext(ctx)
49+
eg.Go(func() error { defer w.Close(); return a.Store(w) })
50+
eg.Go(func() error {
51+
l, err := NewLoader(io.TeeReader(r, &buf))
52+
if err != nil {
53+
return err
54+
}
55+
for l.Next() {
56+
e := l.Entry()
57+
if e.Vuln != nil && e.Enrichment != nil {
58+
t.Error("expecting entry to have either vulnerability or enrichment, got both")
59+
}
60+
if e.Vuln != nil {
61+
got.V = append(got.V, l.Entry().Vuln...)
62+
}
63+
if e.Enrichment != nil {
64+
got.E = append(got.E, l.Entry().Enrichment...)
65+
}
66+
}
67+
if err := l.Err(); err != nil {
68+
return err
69+
}
70+
return nil
71+
})
72+
if err := eg.Wait(); err != nil {
73+
t.Error(err)
74+
}
75+
if !cmp.Equal(got, want) {
76+
t.Error(cmp.Diff(got, want))
77+
}
78+
}

0 commit comments

Comments
 (0)