mirror of
https://github.com/project-zot/zot.git
synced 2025-04-08 02:54:41 -05:00
feat(cve): graphql: paginate returned CVEs for a given image (#1136)
Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
This commit is contained in:
parent
08983a845a
commit
58ec62b3e4
11 changed files with 724 additions and 74 deletions
|
@ -18,8 +18,9 @@ import (
|
|||
type CveInfo interface {
|
||||
GetImageListForCVE(repo, cveID string) ([]common.TagInfo, error)
|
||||
GetImageListWithCVEFixed(repo, cveID string) ([]common.TagInfo, error)
|
||||
GetCVEListForImage(image string) (map[string]cvemodel.CVE, error)
|
||||
GetCVEListForImage(image string, pageinput PageInput) ([]cvemodel.CVE, PageInfo, error)
|
||||
GetCVESummaryForImage(image string) (ImageCVESummary, error)
|
||||
CompareSeverities(severity1, severity2 string) int
|
||||
UpdateDB() error
|
||||
}
|
||||
|
||||
|
@ -102,15 +103,11 @@ func (cveinfo BaseCveInfo) GetImageListForCVE(repo, cveID string) ([]common.TagI
|
|||
continue
|
||||
}
|
||||
|
||||
for id := range cveMap {
|
||||
if id == cveID {
|
||||
imgList = append(imgList, common.TagInfo{
|
||||
Name: tag,
|
||||
Digest: manifestDigest,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
if _, hasCVE := cveMap[cveID]; hasCVE {
|
||||
imgList = append(imgList, common.TagInfo{
|
||||
Name: tag,
|
||||
Digest: manifestDigest,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,15 +205,33 @@ func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]commo
|
|||
return fixedTags, nil
|
||||
}
|
||||
|
||||
func (cveinfo BaseCveInfo) GetCVEListForImage(image string) (map[string]cvemodel.CVE, error) {
|
||||
cveMap := make(map[string]cvemodel.CVE)
|
||||
|
||||
func (cveinfo BaseCveInfo) GetCVEListForImage(image string, pageInput PageInput) (
|
||||
[]cvemodel.CVE,
|
||||
PageInfo,
|
||||
error,
|
||||
) {
|
||||
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(image)
|
||||
if !isValidImage {
|
||||
return cveMap, err
|
||||
return []cvemodel.CVE{}, PageInfo{}, err
|
||||
}
|
||||
|
||||
return cveinfo.Scanner.ScanImage(image)
|
||||
cveMap, err := cveinfo.Scanner.ScanImage(image)
|
||||
if err != nil {
|
||||
return []cvemodel.CVE{}, PageInfo{}, err
|
||||
}
|
||||
|
||||
pageFinder, err := NewCvePageFinder(pageInput.Limit, pageInput.Offset, pageInput.SortBy, cveinfo)
|
||||
if err != nil {
|
||||
return []cvemodel.CVE{}, PageInfo{}, err
|
||||
}
|
||||
|
||||
for _, cve := range cveMap {
|
||||
pageFinder.Add(cve)
|
||||
}
|
||||
|
||||
cveList, pageInfo := pageFinder.Page()
|
||||
|
||||
return cveList, pageInfo, nil
|
||||
}
|
||||
|
||||
func (cveinfo BaseCveInfo) GetCVESummaryForImage(image string) (ImageCVESummary, error) {
|
||||
|
@ -260,3 +275,7 @@ func (cveinfo BaseCveInfo) GetCVESummaryForImage(image string) (ImageCVESummary,
|
|||
func (cveinfo BaseCveInfo) UpdateDB() error {
|
||||
return cveinfo.Scanner.UpdateDB()
|
||||
}
|
||||
|
||||
func (cveinfo BaseCveInfo) CompareSeverities(severity1, severity2 string) int {
|
||||
return cveinfo.Scanner.CompareSeverities(severity1, severity2)
|
||||
}
|
||||
|
|
|
@ -1216,58 +1216,75 @@ func TestCVEStruct(t *testing.T) {
|
|||
|
||||
t.Log("Test GetCVEListForImage")
|
||||
|
||||
pageInput := cveinfo.PageInput{
|
||||
SortBy: cveinfo.SeverityDsc,
|
||||
}
|
||||
|
||||
// Image is found
|
||||
cveMap, err := cveInfo.GetCVEListForImage("repo1:0.1.0")
|
||||
cveList, pageInfo, err := cveInfo.GetCVEListForImage("repo1:0.1.0", pageInput)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cveMap), ShouldEqual, 1)
|
||||
So(cveMap, ShouldContainKey, "CVE1")
|
||||
So(cveMap, ShouldNotContainKey, "CVE2")
|
||||
So(cveMap, ShouldNotContainKey, "CVE3")
|
||||
So(len(cveList), ShouldEqual, 1)
|
||||
So(cveList[0].ID, ShouldEqual, "CVE1")
|
||||
So(pageInfo.ItemCount, ShouldEqual, 1)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 1)
|
||||
|
||||
cveMap, err = cveInfo.GetCVEListForImage("repo1:1.0.0")
|
||||
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.0", pageInput)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cveMap), ShouldEqual, 3)
|
||||
So(cveMap, ShouldContainKey, "CVE1")
|
||||
So(cveMap, ShouldContainKey, "CVE2")
|
||||
So(cveMap, ShouldContainKey, "CVE3")
|
||||
So(len(cveList), ShouldEqual, 3)
|
||||
So(cveList[0].ID, ShouldEqual, "CVE2")
|
||||
So(cveList[1].ID, ShouldEqual, "CVE1")
|
||||
So(cveList[2].ID, ShouldEqual, "CVE3")
|
||||
So(pageInfo.ItemCount, ShouldEqual, 3)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 3)
|
||||
|
||||
cveMap, err = cveInfo.GetCVEListForImage("repo1:1.0.1")
|
||||
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.1", pageInput)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cveMap), ShouldEqual, 2)
|
||||
So(cveMap, ShouldContainKey, "CVE1")
|
||||
So(cveMap, ShouldNotContainKey, "CVE2")
|
||||
So(cveMap, ShouldContainKey, "CVE3")
|
||||
So(len(cveList), ShouldEqual, 2)
|
||||
So(cveList[0].ID, ShouldEqual, "CVE1")
|
||||
So(cveList[1].ID, ShouldEqual, "CVE3")
|
||||
So(pageInfo.ItemCount, ShouldEqual, 2)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 2)
|
||||
|
||||
cveMap, err = cveInfo.GetCVEListForImage("repo1:1.1.0")
|
||||
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.1.0", pageInput)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cveMap), ShouldEqual, 1)
|
||||
So(cveMap, ShouldNotContainKey, "CVE1")
|
||||
So(cveMap, ShouldNotContainKey, "CVE2")
|
||||
So(cveMap, ShouldContainKey, "CVE3")
|
||||
So(len(cveList), ShouldEqual, 1)
|
||||
So(cveList[0].ID, ShouldEqual, "CVE3")
|
||||
So(pageInfo.ItemCount, ShouldEqual, 1)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 1)
|
||||
|
||||
cveMap, err = cveInfo.GetCVEListForImage("repo6:1.0.0")
|
||||
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo6:1.0.0", pageInput)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cveMap), ShouldEqual, 0)
|
||||
So(len(cveList), ShouldEqual, 0)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 0)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 0)
|
||||
|
||||
// Image is not scannable
|
||||
cveMap, err = cveInfo.GetCVEListForImage("repo2:1.0.0")
|
||||
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo2:1.0.0", pageInput)
|
||||
So(err, ShouldEqual, zerr.ErrScanNotSupported)
|
||||
So(len(cveMap), ShouldEqual, 0)
|
||||
So(len(cveList), ShouldEqual, 0)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 0)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 0)
|
||||
|
||||
// Tag is not found
|
||||
cveMap, err = cveInfo.GetCVEListForImage("repo3:1.0.0")
|
||||
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo3:1.0.0", pageInput)
|
||||
So(err, ShouldEqual, zerr.ErrTagMetaNotFound)
|
||||
So(len(cveMap), ShouldEqual, 0)
|
||||
So(len(cveList), ShouldEqual, 0)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 0)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 0)
|
||||
|
||||
// Manifest is not found
|
||||
cveMap, err = cveInfo.GetCVEListForImage("repo5:nonexitent-manifest")
|
||||
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo5:nonexitent-manifest", pageInput)
|
||||
So(err, ShouldEqual, zerr.ErrManifestDataNotFound)
|
||||
So(len(cveMap), ShouldEqual, 0)
|
||||
So(len(cveList), ShouldEqual, 0)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 0)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 0)
|
||||
|
||||
// Repo is not found
|
||||
cveMap, err = cveInfo.GetCVEListForImage("repo100:1.0.0")
|
||||
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo100:1.0.0", pageInput)
|
||||
So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
|
||||
So(len(cveMap), ShouldEqual, 0)
|
||||
So(len(cveList), ShouldEqual, 0)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 0)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 0)
|
||||
|
||||
t.Log("Test GetImageListWithCVEFixed")
|
||||
|
||||
|
@ -1383,9 +1400,11 @@ func TestCVEStruct(t *testing.T) {
|
|||
So(cveSummary.Count, ShouldEqual, 0)
|
||||
So(cveSummary.MaxSeverity, ShouldEqual, "")
|
||||
|
||||
cveMap, err = cveInfo.GetCVEListForImage("repo1:0.1.0")
|
||||
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1:0.1.0", pageInput)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(cveMap, ShouldBeNil)
|
||||
So(cveList, ShouldBeEmpty)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 0)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 0)
|
||||
|
||||
tagList, err = cveInfo.GetImageListWithCVEFixed("repo1", "CVE1")
|
||||
// CVE is not considered fixed as scan is not possible
|
||||
|
|
144
pkg/extensions/search/cve/pagination.go
Normal file
144
pkg/extensions/search/cve/pagination.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
package cveinfo
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||
)
|
||||
|
||||
type SortCriteria string
|
||||
|
||||
const (
|
||||
AlphabeticAsc = SortCriteria("ALPHABETIC_ASC")
|
||||
AlphabeticDsc = SortCriteria("ALPHABETIC_DSC")
|
||||
SeverityDsc = SortCriteria("SEVERITY")
|
||||
)
|
||||
|
||||
func SortFunctions() map[SortCriteria]func(pageBuffer []cvemodel.CVE, cveInfo CveInfo) func(i, j int) bool {
|
||||
return map[SortCriteria]func(pageBuffer []cvemodel.CVE, cveInfo CveInfo) func(i, j int) bool{
|
||||
AlphabeticAsc: SortByAlphabeticAsc,
|
||||
AlphabeticDsc: SortByAlphabeticDsc,
|
||||
SeverityDsc: SortBySeverity,
|
||||
}
|
||||
}
|
||||
|
||||
func SortByAlphabeticAsc(pageBuffer []cvemodel.CVE, cveInfo CveInfo) func(i, j int) bool {
|
||||
return func(i, j int) bool {
|
||||
return pageBuffer[i].ID < pageBuffer[j].ID
|
||||
}
|
||||
}
|
||||
|
||||
func SortByAlphabeticDsc(pageBuffer []cvemodel.CVE, cveInfo CveInfo) func(i, j int) bool {
|
||||
return func(i, j int) bool {
|
||||
return pageBuffer[i].ID > pageBuffer[j].ID
|
||||
}
|
||||
}
|
||||
|
||||
func SortBySeverity(pageBuffer []cvemodel.CVE, cveInfo CveInfo) func(i, j int) bool {
|
||||
return func(i, j int) bool {
|
||||
return cveInfo.CompareSeverities(pageBuffer[i].Severity, pageBuffer[j].Severity) < 0
|
||||
}
|
||||
}
|
||||
|
||||
// PageFinder permits keeping a pool of objects using Add
|
||||
// and returning a specific page.
|
||||
type PageFinder interface {
|
||||
Add(cve cvemodel.CVE)
|
||||
Page() ([]cvemodel.CVE, PageInfo)
|
||||
Reset()
|
||||
}
|
||||
|
||||
// CvePageFinder implements PageFinder. It manages Cve objects and calculates the page
|
||||
// using the given limit, offset and sortBy option.
|
||||
type CvePageFinder struct {
|
||||
limit int
|
||||
offset int
|
||||
sortBy SortCriteria
|
||||
pageBuffer []cvemodel.CVE
|
||||
cveInfo CveInfo
|
||||
}
|
||||
|
||||
func NewCvePageFinder(limit, offset int, sortBy SortCriteria, cveInfo CveInfo) (*CvePageFinder, error) {
|
||||
if sortBy == "" {
|
||||
sortBy = SeverityDsc
|
||||
}
|
||||
|
||||
if limit < 0 {
|
||||
return nil, zerr.ErrLimitIsNegative
|
||||
}
|
||||
|
||||
if offset < 0 {
|
||||
return nil, zerr.ErrOffsetIsNegative
|
||||
}
|
||||
|
||||
if _, found := SortFunctions()[sortBy]; !found {
|
||||
return nil, errors.Wrapf(zerr.ErrSortCriteriaNotSupported, "sorting CVEs by '%s' is not supported", sortBy)
|
||||
}
|
||||
|
||||
return &CvePageFinder{
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
sortBy: sortBy,
|
||||
pageBuffer: make([]cvemodel.CVE, 0, limit),
|
||||
cveInfo: cveInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (bpt *CvePageFinder) Reset() {
|
||||
bpt.pageBuffer = []cvemodel.CVE{}
|
||||
}
|
||||
|
||||
func (bpt *CvePageFinder) Add(cve cvemodel.CVE) {
|
||||
bpt.pageBuffer = append(bpt.pageBuffer, cve)
|
||||
}
|
||||
|
||||
func (bpt *CvePageFinder) Page() ([]cvemodel.CVE, PageInfo) {
|
||||
if len(bpt.pageBuffer) == 0 {
|
||||
return []cvemodel.CVE{}, PageInfo{}
|
||||
}
|
||||
|
||||
pageInfo := &PageInfo{}
|
||||
|
||||
sort.Slice(bpt.pageBuffer, SortFunctions()[bpt.sortBy](bpt.pageBuffer, bpt.cveInfo))
|
||||
|
||||
// the offset and limit are calculated in terms of CVEs counted
|
||||
start := bpt.offset
|
||||
end := bpt.offset + bpt.limit
|
||||
|
||||
// we'll return an empty array when the offset is greater than the number of elements
|
||||
if start >= len(bpt.pageBuffer) {
|
||||
start = len(bpt.pageBuffer)
|
||||
end = start
|
||||
}
|
||||
|
||||
if end >= len(bpt.pageBuffer) {
|
||||
end = len(bpt.pageBuffer)
|
||||
}
|
||||
|
||||
cves := bpt.pageBuffer[start:end]
|
||||
|
||||
pageInfo.ItemCount = len(cves)
|
||||
|
||||
if start == 0 && end == 0 {
|
||||
cves = bpt.pageBuffer
|
||||
pageInfo.ItemCount = len(cves)
|
||||
}
|
||||
|
||||
pageInfo.TotalCount = len(bpt.pageBuffer)
|
||||
|
||||
return cves, *pageInfo
|
||||
}
|
||||
|
||||
type PageInfo struct {
|
||||
TotalCount int
|
||||
ItemCount int
|
||||
}
|
||||
|
||||
type PageInput struct {
|
||||
Limit int
|
||||
Offset int
|
||||
SortBy SortCriteria
|
||||
}
|
355
pkg/extensions/search/cve/pagination_test.go
Normal file
355
pkg/extensions/search/cve/pagination_test.go
Normal file
|
@ -0,0 +1,355 @@
|
|||
package cveinfo_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
bolt "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
)
|
||||
|
||||
func TestCVEPagination(t *testing.T) {
|
||||
Convey("CVE Pagination", t, func() {
|
||||
repoDB, err := bolt.NewBoltDBWrapper(bolt.DBParameters{
|
||||
RootDir: t.TempDir(),
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// Create repodb data for scannable image with vulnerabilities
|
||||
timeStamp11 := time.Date(2008, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
configBlob11, err := json.Marshal(ispec.Image{
|
||||
Created: &timeStamp11,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestBlob11, err := json.Marshal(ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Size: 0,
|
||||
Digest: godigest.FromBytes(configBlob11),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageLayerGzip,
|
||||
Size: 0,
|
||||
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
|
||||
},
|
||||
},
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
repoMeta11 := repodb.ManifestMetadata{
|
||||
ManifestBlob: manifestBlob11,
|
||||
ConfigBlob: configBlob11,
|
||||
}
|
||||
|
||||
digest11 := godigest.FromBytes(manifestBlob11)
|
||||
err = repoDB.SetManifestMeta("repo1", digest11, repoMeta11)
|
||||
So(err, ShouldBeNil)
|
||||
err = repoDB.SetRepoTag("repo1", "0.1.0", digest11, ispec.MediaTypeImageManifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
timeStamp12 := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
configBlob12, err := json.Marshal(ispec.Image{
|
||||
Created: &timeStamp12,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
manifestBlob12, err := json.Marshal(ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageConfig,
|
||||
Size: 0,
|
||||
Digest: godigest.FromBytes(configBlob12),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: ispec.MediaTypeImageLayerGzip,
|
||||
Size: 0,
|
||||
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
|
||||
},
|
||||
},
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
repoMeta12 := repodb.ManifestMetadata{
|
||||
ManifestBlob: manifestBlob12,
|
||||
ConfigBlob: configBlob12,
|
||||
}
|
||||
|
||||
digest12 := godigest.FromBytes(manifestBlob12)
|
||||
err = repoDB.SetManifestMeta("repo1", digest12, repoMeta12)
|
||||
So(err, ShouldBeNil)
|
||||
err = repoDB.SetRepoTag("repo1", "1.0.0", digest12, ispec.MediaTypeImageManifest)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// RepoDB loaded with initial data, mock the scanner
|
||||
severityToInt := map[string]int{
|
||||
"UNKNOWN": 0,
|
||||
"LOW": 1,
|
||||
"MEDIUM": 2,
|
||||
"HIGH": 3,
|
||||
"CRITICAL": 4,
|
||||
}
|
||||
|
||||
intToSeverity := make(map[int]string, len(severityToInt))
|
||||
for k, v := range severityToInt {
|
||||
intToSeverity[v] = k
|
||||
}
|
||||
|
||||
// Setup test CVE data in mock scanner
|
||||
scanner := mocks.CveScannerMock{
|
||||
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
||||
cveMap := map[string]cvemodel.CVE{}
|
||||
|
||||
if image == "repo1:0.1.0" {
|
||||
for i := 0; i < 5; i++ {
|
||||
cveMap[fmt.Sprintf("CVE%d", i)] = cvemodel.CVE{
|
||||
ID: fmt.Sprintf("CVE%d", i),
|
||||
Severity: intToSeverity[i%5],
|
||||
Title: fmt.Sprintf("Title for CVE%d", i),
|
||||
Description: fmt.Sprintf("Description for CVE%d", i),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if image == "repo1:1.0.0" {
|
||||
for i := 0; i < 30; i++ {
|
||||
cveMap[fmt.Sprintf("CVE%d", i)] = cvemodel.CVE{
|
||||
ID: fmt.Sprintf("CVE%d", i),
|
||||
Severity: intToSeverity[i%5],
|
||||
Title: fmt.Sprintf("Title for CVE%d", i),
|
||||
Description: fmt.Sprintf("Description for CVE%d", i),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// By default the image has no vulnerabilities
|
||||
return cveMap, nil
|
||||
},
|
||||
CompareSeveritiesFn: func(severity1, severity2 string) int {
|
||||
return severityToInt[severity2] - severityToInt[severity1]
|
||||
},
|
||||
}
|
||||
|
||||
log := log.NewLogger("debug", "")
|
||||
cveInfo := cveinfo.BaseCveInfo{Log: log, Scanner: scanner, RepoDB: repoDB}
|
||||
|
||||
Convey("create new paginator errors", func() {
|
||||
paginator, err := cveinfo.NewCvePageFinder(-1, 10, cveinfo.AlphabeticAsc, cveInfo)
|
||||
So(paginator, ShouldBeNil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
paginator, err = cveinfo.NewCvePageFinder(2, -1, cveinfo.AlphabeticAsc, cveInfo)
|
||||
So(paginator, ShouldBeNil)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
paginator, err = cveinfo.NewCvePageFinder(2, 1, "wrong sorting criteria", cveInfo)
|
||||
So(paginator, ShouldBeNil)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Reset", func() {
|
||||
paginator, err := cveinfo.NewCvePageFinder(1, 0, cveinfo.AlphabeticAsc, cveInfo)
|
||||
So(err, ShouldBeNil)
|
||||
So(paginator, ShouldNotBeNil)
|
||||
|
||||
paginator.Add(cvemodel.CVE{})
|
||||
paginator.Add(cvemodel.CVE{})
|
||||
paginator.Add(cvemodel.CVE{})
|
||||
|
||||
paginator.Reset()
|
||||
|
||||
result, _ := paginator.Page()
|
||||
So(result, ShouldBeEmpty)
|
||||
})
|
||||
|
||||
Convey("Page", func() {
|
||||
Convey("defaults", func() {
|
||||
// By default expect unlimitted results sorted by severity
|
||||
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 5)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 5)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 5)
|
||||
previousSeverity := 4
|
||||
for _, cve := range cves {
|
||||
So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity)
|
||||
previousSeverity = severityToInt[cve.Severity]
|
||||
}
|
||||
|
||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.0", cveinfo.PageInput{})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 30)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 30)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 30)
|
||||
previousSeverity = 4
|
||||
for _, cve := range cves {
|
||||
So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity)
|
||||
previousSeverity = severityToInt[cve.Severity]
|
||||
}
|
||||
})
|
||||
|
||||
Convey("no limit or offset", func() {
|
||||
cveIds := []string{}
|
||||
for i := 0; i < 30; i++ {
|
||||
cveIds = append(cveIds, fmt.Sprintf("CVE%d", i))
|
||||
}
|
||||
|
||||
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{SortBy: cveinfo.AlphabeticAsc})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 5)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 5)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 5)
|
||||
for i, cve := range cves {
|
||||
So(cve.ID, ShouldEqual, cveIds[i])
|
||||
}
|
||||
|
||||
sort.Strings(cveIds)
|
||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.0", cveinfo.PageInput{SortBy: cveinfo.AlphabeticAsc})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 30)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 30)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 30)
|
||||
for i, cve := range cves {
|
||||
So(cve.ID, ShouldEqual, cveIds[i])
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(cveIds)))
|
||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.0", cveinfo.PageInput{SortBy: cveinfo.AlphabeticDsc})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 30)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 30)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 30)
|
||||
for i, cve := range cves {
|
||||
So(cve.ID, ShouldEqual, cveIds[i])
|
||||
}
|
||||
|
||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.0", cveinfo.PageInput{SortBy: cveinfo.SeverityDsc})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 30)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 30)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 30)
|
||||
previousSeverity := 4
|
||||
for _, cve := range cves {
|
||||
So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity)
|
||||
previousSeverity = severityToInt[cve.Severity]
|
||||
}
|
||||
})
|
||||
|
||||
Convey("limit < len(cves)", func() {
|
||||
cveIds := []string{}
|
||||
for i := 0; i < 30; i++ {
|
||||
cveIds = append(cveIds, fmt.Sprintf("CVE%d", i))
|
||||
}
|
||||
|
||||
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{
|
||||
Limit: 3,
|
||||
Offset: 1,
|
||||
SortBy: cveinfo.AlphabeticAsc,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 3)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 3)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 5)
|
||||
So(cves[0].ID, ShouldEqual, "CVE1") // CVE0 is first ID and is not part of the page
|
||||
So(cves[1].ID, ShouldEqual, "CVE2")
|
||||
So(cves[2].ID, ShouldEqual, "CVE3")
|
||||
|
||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{
|
||||
Limit: 2,
|
||||
Offset: 1,
|
||||
SortBy: cveinfo.AlphabeticDsc,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 2)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 2)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 5)
|
||||
So(cves[0].ID, ShouldEqual, "CVE3")
|
||||
So(cves[1].ID, ShouldEqual, "CVE2")
|
||||
|
||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{
|
||||
Limit: 3,
|
||||
Offset: 1,
|
||||
SortBy: cveinfo.SeverityDsc,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 3)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 3)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 5)
|
||||
previousSeverity := 4
|
||||
for _, cve := range cves {
|
||||
So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity)
|
||||
previousSeverity = severityToInt[cve.Severity]
|
||||
}
|
||||
|
||||
sort.Strings(cveIds)
|
||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.0", cveinfo.PageInput{
|
||||
Limit: 5,
|
||||
Offset: 20,
|
||||
SortBy: cveinfo.AlphabeticAsc,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 5)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 5)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 30)
|
||||
for i, cve := range cves {
|
||||
So(cve.ID, ShouldEqual, cveIds[i+20])
|
||||
}
|
||||
})
|
||||
|
||||
Convey("limit > len(cves)", func() {
|
||||
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{
|
||||
Limit: 6,
|
||||
Offset: 3,
|
||||
SortBy: cveinfo.AlphabeticAsc,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 2)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 2)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 5)
|
||||
So(cves[0].ID, ShouldEqual, "CVE3")
|
||||
So(cves[1].ID, ShouldEqual, "CVE4")
|
||||
|
||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{
|
||||
Limit: 6,
|
||||
Offset: 3,
|
||||
SortBy: cveinfo.AlphabeticDsc,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 2)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 2)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 5)
|
||||
So(cves[0].ID, ShouldEqual, "CVE1")
|
||||
So(cves[1].ID, ShouldEqual, "CVE0")
|
||||
|
||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{
|
||||
Limit: 6,
|
||||
Offset: 3,
|
||||
SortBy: cveinfo.SeverityDsc,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 2)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 2)
|
||||
So(pageInfo.TotalCount, ShouldEqual, 5)
|
||||
previousSeverity := 4
|
||||
for _, cve := range cves {
|
||||
So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity)
|
||||
previousSeverity = severityToInt[cve.Severity]
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
|
@ -58,6 +58,7 @@ type ComplexityRoot struct {
|
|||
|
||||
CVEResultForImage struct {
|
||||
CVEList func(childComplexity int) int
|
||||
Page func(childComplexity int) int
|
||||
Tag func(childComplexity int) int
|
||||
}
|
||||
|
||||
|
@ -144,7 +145,7 @@ type ComplexityRoot struct {
|
|||
|
||||
Query struct {
|
||||
BaseImageList func(childComplexity int, image string) int
|
||||
CVEListForImage func(childComplexity int, image string) int
|
||||
CVEListForImage func(childComplexity int, image string, requestedPage *PageInput) int
|
||||
DerivedImageList func(childComplexity int, image string) int
|
||||
ExpandedRepoInfo func(childComplexity int, repo string) int
|
||||
GlobalSearch func(childComplexity int, query string, filter *Filter, requestedPage *PageInput) int
|
||||
|
@ -186,7 +187,7 @@ type ComplexityRoot struct {
|
|||
}
|
||||
|
||||
type QueryResolver interface {
|
||||
CVEListForImage(ctx context.Context, image string) (*CVEResultForImage, error)
|
||||
CVEListForImage(ctx context.Context, image string, requestedPage *PageInput) (*CVEResultForImage, error)
|
||||
ImageListForCve(ctx context.Context, id string, requestedPage *PageInput) ([]*ImageSummary, error)
|
||||
ImageListWithCVEFixed(ctx context.Context, id string, image string, requestedPage *PageInput) ([]*ImageSummary, error)
|
||||
ImageListForDigest(ctx context.Context, id string, requestedPage *PageInput) ([]*ImageSummary, error)
|
||||
|
@ -271,6 +272,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.CVEResultForImage.CVEList(childComplexity), true
|
||||
|
||||
case "CVEResultForImage.Page":
|
||||
if e.complexity.CVEResultForImage.Page == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.CVEResultForImage.Page(childComplexity), true
|
||||
|
||||
case "CVEResultForImage.Tag":
|
||||
if e.complexity.CVEResultForImage.Tag == nil {
|
||||
break
|
||||
|
@ -636,7 +644,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.CVEListForImage(childComplexity, args["image"].(string)), true
|
||||
return e.complexity.Query.CVEListForImage(childComplexity, args["image"].(string), args["requestedPage"].(*PageInput)), true
|
||||
|
||||
case "Query.DerivedImageList":
|
||||
if e.complexity.Query.DerivedImageList == nil {
|
||||
|
@ -947,6 +955,7 @@ Contains the tag of the image and a list of CVEs
|
|||
type CVEResultForImage {
|
||||
Tag: String
|
||||
CVEList: [CVE]
|
||||
Page: PageInfo
|
||||
}
|
||||
|
||||
"""
|
||||
|
@ -1103,6 +1112,7 @@ enum SortCriteria {
|
|||
UPDATE_TIME
|
||||
ALPHABETIC_ASC
|
||||
ALPHABETIC_DSC
|
||||
SEVERITY
|
||||
STARS
|
||||
DOWNLOADS
|
||||
}
|
||||
|
@ -1141,9 +1151,9 @@ input Filter {
|
|||
|
||||
type Query {
|
||||
"""
|
||||
Returns a CVE list for the image specified in the arugment
|
||||
Returns a CVE list for the image specified in the argument. Format image:tag
|
||||
"""
|
||||
CVEListForImage(image: String!): CVEResultForImage!
|
||||
CVEListForImage(image: String!, requestedPage: PageInput): CVEResultForImage!
|
||||
|
||||
"""
|
||||
Returns a list of images vulnerable to the CVE of the specified ID
|
||||
|
@ -1236,6 +1246,15 @@ func (ec *executionContext) field_Query_CVEListForImage_args(ctx context.Context
|
|||
}
|
||||
}
|
||||
args["image"] = arg0
|
||||
var arg1 *PageInput
|
||||
if tmp, ok := rawArgs["requestedPage"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("requestedPage"))
|
||||
arg1, err = ec.unmarshalOPageInput2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPageInput(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["requestedPage"] = arg1
|
||||
return args, nil
|
||||
}
|
||||
|
||||
|
@ -1912,6 +1931,53 @@ func (ec *executionContext) fieldContext_CVEResultForImage_CVEList(ctx context.C
|
|||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _CVEResultForImage_Page(ctx context.Context, field graphql.CollectedField, obj *CVEResultForImage) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_CVEResultForImage_Page(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Page, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*PageInfo)
|
||||
fc.Result = res
|
||||
return ec.marshalOPageInfo2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPageInfo(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_CVEResultForImage_Page(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "CVEResultForImage",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
switch field.Name {
|
||||
case "TotalCount":
|
||||
return ec.fieldContext_PageInfo_TotalCount(ctx, field)
|
||||
case "ItemCount":
|
||||
return ec.fieldContext_PageInfo_ItemCount(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name)
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _GlobalSearchResult_Page(ctx context.Context, field graphql.CollectedField, obj *GlobalSearchResult) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_GlobalSearchResult_Page(ctx, field)
|
||||
if err != nil {
|
||||
|
@ -4114,7 +4180,7 @@ func (ec *executionContext) _Query_CVEListForImage(ctx context.Context, field gr
|
|||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Query().CVEListForImage(rctx, fc.Args["image"].(string))
|
||||
return ec.resolvers.Query().CVEListForImage(rctx, fc.Args["image"].(string), fc.Args["requestedPage"].(*PageInput))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
|
@ -4143,6 +4209,8 @@ func (ec *executionContext) fieldContext_Query_CVEListForImage(ctx context.Conte
|
|||
return ec.fieldContext_CVEResultForImage_Tag(ctx, field)
|
||||
case "CVEList":
|
||||
return ec.fieldContext_CVEResultForImage_CVEList(ctx, field)
|
||||
case "Page":
|
||||
return ec.fieldContext_CVEResultForImage_Page(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type CVEResultForImage", field.Name)
|
||||
},
|
||||
|
@ -8038,6 +8106,10 @@ func (ec *executionContext) _CVEResultForImage(ctx context.Context, sel ast.Sele
|
|||
|
||||
out.Values[i] = ec._CVEResultForImage_CVEList(ctx, field, obj)
|
||||
|
||||
case "Page":
|
||||
|
||||
out.Values[i] = ec._CVEResultForImage_Page(ctx, field, obj)
|
||||
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
|
|
|
@ -25,8 +25,9 @@ type Cve struct {
|
|||
|
||||
// Contains the tag of the image and a list of CVEs
|
||||
type CVEResultForImage struct {
|
||||
Tag *string `json:"Tag"`
|
||||
CVEList []*Cve `json:"CVEList"`
|
||||
Tag *string `json:"Tag"`
|
||||
CVEList []*Cve `json:"CVEList"`
|
||||
Page *PageInfo `json:"Page"`
|
||||
}
|
||||
|
||||
type Filter struct {
|
||||
|
@ -167,6 +168,7 @@ const (
|
|||
SortCriteriaUpdateTime SortCriteria = "UPDATE_TIME"
|
||||
SortCriteriaAlphabeticAsc SortCriteria = "ALPHABETIC_ASC"
|
||||
SortCriteriaAlphabeticDsc SortCriteria = "ALPHABETIC_DSC"
|
||||
SortCriteriaSeverity SortCriteria = "SEVERITY"
|
||||
SortCriteriaStars SortCriteria = "STARS"
|
||||
SortCriteriaDownloads SortCriteria = "DOWNLOADS"
|
||||
)
|
||||
|
@ -176,13 +178,14 @@ var AllSortCriteria = []SortCriteria{
|
|||
SortCriteriaUpdateTime,
|
||||
SortCriteriaAlphabeticAsc,
|
||||
SortCriteriaAlphabeticDsc,
|
||||
SortCriteriaSeverity,
|
||||
SortCriteriaStars,
|
||||
SortCriteriaDownloads,
|
||||
}
|
||||
|
||||
func (e SortCriteria) IsValid() bool {
|
||||
switch e {
|
||||
case SortCriteriaRelevance, SortCriteriaUpdateTime, SortCriteriaAlphabeticAsc, SortCriteriaAlphabeticDsc, SortCriteriaStars, SortCriteriaDownloads:
|
||||
case SortCriteriaRelevance, SortCriteriaUpdateTime, SortCriteriaAlphabeticAsc, SortCriteriaAlphabeticDsc, SortCriteriaSeverity, SortCriteriaStars, SortCriteriaDownloads:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
|
|
@ -195,23 +195,36 @@ func getCVEListForImage(
|
|||
ctx context.Context, //nolint:unparam // may be used in the future to filter by permissions
|
||||
image string,
|
||||
cveInfo cveinfo.CveInfo,
|
||||
requestedPage *gql_generated.PageInput,
|
||||
log log.Logger, //nolint:unparam // may be used by devs for debugging
|
||||
) (*gql_generated.CVEResultForImage, error) {
|
||||
if requestedPage == nil {
|
||||
requestedPage = &gql_generated.PageInput{}
|
||||
}
|
||||
|
||||
pageInput := cveinfo.PageInput{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
SortBy: cveinfo.SortCriteria(
|
||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaSeverity),
|
||||
),
|
||||
}
|
||||
|
||||
_, copyImgTag := common.GetImageDirAndTag(image)
|
||||
|
||||
if copyImgTag == "" {
|
||||
return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided")
|
||||
}
|
||||
|
||||
cveidMap, err := cveInfo.GetCVEListForImage(image)
|
||||
cveList, pageInfo, err := cveInfo.GetCVEListForImage(image, pageInput)
|
||||
if err != nil {
|
||||
return &gql_generated.CVEResultForImage{}, err
|
||||
}
|
||||
|
||||
cveids := []*gql_generated.Cve{}
|
||||
|
||||
for id, cveDetail := range cveidMap {
|
||||
vulID := id
|
||||
for _, cveDetail := range cveList {
|
||||
vulID := cveDetail.ID
|
||||
desc := cveDetail.Description
|
||||
title := cveDetail.Title
|
||||
severity := cveDetail.Severity
|
||||
|
@ -241,7 +254,14 @@ func getCVEListForImage(
|
|||
)
|
||||
}
|
||||
|
||||
return &gql_generated.CVEResultForImage{Tag: ©ImgTag, CVEList: cveids}, nil
|
||||
return &gql_generated.CVEResultForImage{
|
||||
Tag: ©ImgTag,
|
||||
CVEList: cveids,
|
||||
Page: &gql_generated.PageInfo{
|
||||
TotalCount: pageInfo.TotalCount,
|
||||
ItemCount: pageInfo.ItemCount,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func FilterByTagInfo(tagsInfo []common.TagInfo) repodb.FilterFunc {
|
||||
|
|
|
@ -1020,7 +1020,7 @@ func TestImageList(t *testing.T) {
|
|||
},
|
||||
Signatures: map[string]repodb.ManifestSignatures{
|
||||
"digestTag1.0.1": {
|
||||
"cosgin": []repodb.SignatureInfo{
|
||||
"cosign": []repodb.SignatureInfo{
|
||||
{SignatureManifestDigest: "digestSignature1"},
|
||||
},
|
||||
},
|
||||
|
@ -1045,7 +1045,7 @@ func TestImageList(t *testing.T) {
|
|||
ConfigBlob: configBlob,
|
||||
DownloadCount: 0,
|
||||
Signatures: repodb.ManifestSignatures{
|
||||
"cosgin": []repodb.SignatureInfo{
|
||||
"cosign": []repodb.SignatureInfo{
|
||||
{SignatureManifestDigest: "digestSignature1"},
|
||||
},
|
||||
},
|
||||
|
@ -1733,12 +1733,15 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
|||
|
||||
Convey("Get CVE list for image ", t, func() {
|
||||
Convey("Unpaginated request to get all CVEs in an image", func() {
|
||||
// CVE pagination will be implemented later
|
||||
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
|
||||
pageInput := &gql_generated.PageInput{
|
||||
SortBy: &sortCriteria,
|
||||
}
|
||||
|
||||
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
|
||||
graphql.DefaultRecover)
|
||||
|
||||
cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, log)
|
||||
cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(*cveResult.Tag, ShouldEqual, "1.0.0")
|
||||
|
||||
|
@ -1749,7 +1752,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
|||
So(expectedCves, ShouldContain, *cve.ID)
|
||||
}
|
||||
|
||||
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.1", cveInfo, log)
|
||||
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.1", cveInfo, pageInput, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(*cveResult.Tag, ShouldEqual, "1.0.1")
|
||||
|
||||
|
@ -1760,7 +1763,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
|||
So(expectedCves, ShouldContain, *cve.ID)
|
||||
}
|
||||
|
||||
cveResult, err = getCVEListForImage(responseContext, "repo1:1.1.0", cveInfo, log)
|
||||
cveResult, err = getCVEListForImage(responseContext, "repo1:1.1.0", cveInfo, pageInput, log)
|
||||
So(err, ShouldBeNil)
|
||||
So(*cveResult.Tag, ShouldEqual, "1.1.0")
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ Contains the tag of the image and a list of CVEs
|
|||
type CVEResultForImage {
|
||||
Tag: String
|
||||
CVEList: [CVE]
|
||||
Page: PageInfo
|
||||
}
|
||||
|
||||
"""
|
||||
|
@ -162,6 +163,7 @@ enum SortCriteria {
|
|||
UPDATE_TIME
|
||||
ALPHABETIC_ASC
|
||||
ALPHABETIC_DSC
|
||||
SEVERITY
|
||||
STARS
|
||||
DOWNLOADS
|
||||
}
|
||||
|
@ -200,9 +202,9 @@ input Filter {
|
|||
|
||||
type Query {
|
||||
"""
|
||||
Returns a CVE list for the image specified in the arugment
|
||||
Returns a CVE list for the image specified in the argument. Format image:tag
|
||||
"""
|
||||
CVEListForImage(image: String!): CVEResultForImage!
|
||||
CVEListForImage(image: String!, requestedPage: PageInput): CVEResultForImage!
|
||||
|
||||
"""
|
||||
Returns a list of images vulnerable to the CVE of the specified ID
|
||||
|
|
|
@ -14,12 +14,12 @@ import (
|
|||
)
|
||||
|
||||
// CVEListForImage is the resolver for the CVEListForImage field.
|
||||
func (r *queryResolver) CVEListForImage(ctx context.Context, image string) (*gql_generated.CVEResultForImage, error) {
|
||||
func (r *queryResolver) CVEListForImage(ctx context.Context, image string, requestedPage *gql_generated.PageInput) (*gql_generated.CVEResultForImage, error) {
|
||||
if r.cveInfo == nil {
|
||||
return &gql_generated.CVEResultForImage{}, zerr.ErrCVESearchDisabled
|
||||
}
|
||||
|
||||
return getCVEListForImage(ctx, image, r.cveInfo, r.log)
|
||||
return getCVEListForImage(ctx, image, r.cveInfo, requestedPage, r.log)
|
||||
}
|
||||
|
||||
// ImageListForCve is the resolver for the ImageListForCVE field.
|
||||
|
|
|
@ -9,8 +9,9 @@ import (
|
|||
type CveInfoMock struct {
|
||||
GetImageListForCVEFn func(repo, cveID string) ([]common.TagInfo, error)
|
||||
GetImageListWithCVEFixedFn func(repo, cveID string) ([]common.TagInfo, error)
|
||||
GetCVEListForImageFn func(image string) (map[string]cvemodel.CVE, error)
|
||||
GetCVEListForImageFn func(image string, pageInput cveinfo.PageInput) ([]cvemodel.CVE, cveinfo.PageInfo, error)
|
||||
GetCVESummaryForImageFn func(image string) (cveinfo.ImageCVESummary, error)
|
||||
CompareSeveritiesFn func(severity1, severity2 string) int
|
||||
UpdateDBFn func() error
|
||||
}
|
||||
|
||||
|
@ -30,12 +31,16 @@ func (cveInfo CveInfoMock) GetImageListWithCVEFixed(repo, cveID string) ([]commo
|
|||
return []common.TagInfo{}, nil
|
||||
}
|
||||
|
||||
func (cveInfo CveInfoMock) GetCVEListForImage(image string) (map[string]cvemodel.CVE, error) {
|
||||
func (cveInfo CveInfoMock) GetCVEListForImage(image string, pageInput cveinfo.PageInput) (
|
||||
[]cvemodel.CVE,
|
||||
cveinfo.PageInfo,
|
||||
error,
|
||||
) {
|
||||
if cveInfo.GetCVEListForImageFn != nil {
|
||||
return cveInfo.GetCVEListForImageFn(image)
|
||||
return cveInfo.GetCVEListForImageFn(image, pageInput)
|
||||
}
|
||||
|
||||
return map[string]cvemodel.CVE{}, nil
|
||||
return []cvemodel.CVE{}, cveinfo.PageInfo{}, nil
|
||||
}
|
||||
|
||||
func (cveInfo CveInfoMock) GetCVESummaryForImage(image string) (cveinfo.ImageCVESummary, error) {
|
||||
|
@ -46,6 +51,14 @@ func (cveInfo CveInfoMock) GetCVESummaryForImage(image string) (cveinfo.ImageCVE
|
|||
return cveinfo.ImageCVESummary{}, nil
|
||||
}
|
||||
|
||||
func (cveInfo CveInfoMock) CompareSeverities(severity1, severity2 string) int {
|
||||
if cveInfo.CompareSeveritiesFn != nil {
|
||||
return cveInfo.CompareSeveritiesFn(severity1, severity2)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (cveInfo CveInfoMock) UpdateDB() error {
|
||||
if cveInfo.UpdateDBFn != nil {
|
||||
return cveInfo.UpdateDBFn()
|
||||
|
|
Loading…
Add table
Reference in a new issue