Skip to content

Commit

Permalink
add affected manifest type, update plumbing of the type (#190)
Browse files Browse the repository at this point in the history
* add affected manifest type, update plumbing of the type

this commit introduces a new type to support the notifications
implementation along with tests and plumbing.

Signed-off-by: ldelossa <ldelossa@redhat.com>

* review changes

Signed-off-by: ldelossa <ldelossa@redhat.com>

* update comment
  • Loading branch information
Louis DeLosSantos committed Jun 8, 2020
1 parent 303b18b commit 90283d9
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 25 deletions.
56 changes: 56 additions & 0 deletions affectedmanifests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package claircore

import (
"sort"
"sync"
)

// AffectedManifests describes a set of manifests affected by
// a set of Vulnerabilities.
type AffectedManifests struct {
mu sync.Mutex
// map of vulnerabilities keyed by the vulnerability's ID
Vulnerabilities map[string]*Vulnerability `json:"vulnerabilities"`
// map associating a list of vulnerability ids keyed by the
// manifest hash they affect.
VulnerableManifests map[string][]string `json:"vulnerable_manifests"`
}

// NewAffectedManifests initializes a new AffectedManifests struct.
func NewAffectedManifests() AffectedManifests {
return AffectedManifests{
Vulnerabilities: make(map[string]*Vulnerability),
VulnerableManifests: make(map[string][]string),
}
}

// Add will add the provided Vulnerability and Manifest digest
// to the necessary maps.
//
// Add is safe to use by multiple goroutines.
func (a *AffectedManifests) Add(v *Vulnerability, digests ...Digest) {
a.mu.Lock()
a.Vulnerabilities[v.ID] = v
for _, d := range digests {
hash := d.String()
a.VulnerableManifests[hash] = append(a.VulnerableManifests[hash], v.ID)
}
a.mu.Unlock()
}

// Sort will sort each array in the VulnerableManifests map
// by Vulnerability.NormalizedSeverity in Desc order.
//
// Sort is safe to use by multiple goroutines.
func (a *AffectedManifests) Sort() {
a.mu.Lock()
for _, ids := range a.VulnerableManifests {
sort.Slice(ids, func(i, j int) bool {
id1, id2 := ids[i], ids[j]
v1, v2 := a.Vulnerabilities[id1], a.Vulnerabilities[id2]
// reverse this since we want descending sort
return v1.NormalizedSeverity > v2.NormalizedSeverity
})
}
a.mu.Unlock()
}
48 changes: 48 additions & 0 deletions affectedmanifests_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package claircore_test

import (
"testing"

"github.com/quay/claircore"
"github.com/quay/claircore/test"
)

// TestAffectedManifestsAddAndSort confirms adding to and sorting
// the AffectedManifests struct works correctly.
func TestAffectedManifestsAddAndSort(t *testing.T) {
vulns := test.GenUniqueVulnerabilities(2, "test-updater")
manifest := claircore.MustParseDigest(`sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef`)
affected := claircore.NewAffectedManifests()

// make vuln 1 higher severity, to test sorting
vulns[1].NormalizedSeverity = claircore.High

affected.Add(vulns[0], manifest)
affected.Add(vulns[1], manifest)

if len(affected.Vulnerabilities) != 2 {
t.Fatalf("got: %d, want: %d", len(affected.Vulnerabilities), 2)
}

if _, ok := affected.VulnerableManifests[manifest.String()]; !ok {
t.Fatalf("got: %v, want: %v", ok, true)
}

affected.Sort()

ids := affected.VulnerableManifests[manifest.String()]
if len(ids) != 2 {
t.Fatalf("got: %v, want: %v", len(ids), 2)
}

v1 := affected.Vulnerabilities[ids[0]]
v2 := affected.Vulnerabilities[ids[1]]

if v1.NormalizedSeverity != claircore.High {
t.Fatalf("got: %v, want: %v", v1.NormalizedSeverity, claircore.High)
}

if v2.NormalizedSeverity != claircore.Unknown {
t.Fatalf("got: %v, want: %v", v1.NormalizedSeverity, claircore.Unknown)
}
}
9 changes: 2 additions & 7 deletions libindex/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (h *HTTP) AffectedManifests(w http.ResponseWriter, r *http.Request) {
return
}

hashes, err := h.l.AffectedManifests(ctx, vulnerabilities.V)
affected, err := h.l.AffectedManifests(ctx, vulnerabilities.V)
if err != nil {
resp := &jsonerr.Response{
Code: "internal-server-error",
Expand All @@ -69,12 +69,7 @@ func (h *HTTP) AffectedManifests(w http.ResponseWriter, r *http.Request) {
return
}

json.NewEncoder(w).Encode(struct {
Manifests []claircore.Digest `json:"manifests"`
}{
Manifests: hashes,
})

json.NewEncoder(w).Encode(affected)
return
}

Expand Down
30 changes: 12 additions & 18 deletions libindex/libindex.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"math"
"net/http"
"sort"
"sync"

"github.com/jmoiron/sqlx"
"github.com/rs/zerolog"
Expand Down Expand Up @@ -187,24 +186,20 @@ func (l *Libindex) IndexReport(ctx context.Context, hash claircore.Digest) (*cla
}

// AffectedManifests retrieves a list of affected manifests when provided a list of vulnerabilities.
func (l *Libindex) AffectedManifests(ctx context.Context, vulns []claircore.Vulnerability) ([]claircore.Digest, error) {
func (l *Libindex) AffectedManifests(ctx context.Context, vulns []claircore.Vulnerability) (claircore.AffectedManifests, error) {
const (
maxGroupSize = 100
)
log := zerolog.Ctx(ctx).With().
Str("component", "libindex/Libindex.AffectedManifests").
Logger()

var manifests struct {
sync.Mutex
m []claircore.Digest
}

groupSize := int(math.Sqrt(float64(len(vulns))))
if groupSize > maxGroupSize {
groupSize = maxGroupSize
}

affected := claircore.NewAffectedManifests()
for i := 0; i < len(vulns); i += groupSize {
errGrp, eCTX := errgroup.WithContext(ctx)

Expand All @@ -215,28 +210,27 @@ func (l *Libindex) AffectedManifests(ctx context.Context, vulns []claircore.Vuln
}

for n := start; n < end; n++ {
vv := vulns[n]
log.Debug().Str("id", vv.ID).Msg("evaluating vulnerability")
errGrp.Go(func() error {
nn := n
do := func() error {
log.Debug().Str("id", vulns[nn].ID).Msg("evaluting vulnerability")
if eCTX.Err() != nil {
return eCTX.Err()
}
hashes, err := l.store.AffectedManifests(eCTX, vv)
hashes, err := l.store.AffectedManifests(eCTX, vulns[nn])
if err != nil {
return err
}
manifests.Lock()
manifests.m = append(manifests.m, hashes...)
manifests.Unlock()
affected.Add(&vulns[nn], hashes...)
return nil
})
}
errGrp.Go(do)
}

err := errGrp.Wait()
if err != nil {
return nil, fmt.Errorf("received error retreiving affected manifests: %v", err)
return affected, fmt.Errorf("received error retreiving affected manifests: %v", err)
}
}

return manifests.m, nil
affected.Sort()
return affected, nil
}

0 comments on commit 90283d9

Please sign in to comment.