0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00
zot/pkg/extensions/search/resolver_test.go
Andrei Aaron 7c78f80a96
feat(cve): implement CVE scanning as background tasks (#1833)
1. Move existing CVE DB download generator/task login under the cve package
2. Add a new CVE scanner task generator and task type to run in the background, as well as tests for it
3. Move the CVE cache in its own package
4. Add a CVE scanner methods to check if an entry is present in the cache, and to retreive the results
5. Modify the FilterTags MetaDB method to not exit on first error
This is needed in order to pass all tags to the generator,
instead of the generator stopping at the first set of invalid data
6. Integrate the new scanning task generator with the existing zot code.
7. Fix an issue where the CVE scan results for multiarch images was not cached
8. Rewrite some of the older CVE tests to use the new image-utils test package
9. Use the CVE scanner as attribute of the controller instead of CveInfo.
Remove functionality of CVE DB update from CveInfo, it is now responsible,
as the name states, only for providing CVE information.
10. The logic to get maximum severity and cve count for image sumaries now uses only the scanner cache.
11. Removed the GetCVESummaryForImage method from CveInfo as it was only used in tests

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
2023-09-22 11:49:17 -07:00

3686 lines
106 KiB
Go

//go:build search
package search //nolint
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"testing"
"time"
"github.com/99designs/gqlgen/graphql"
godigest "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.io/zot/pkg/common"
"zotregistry.io/zot/pkg/extensions/search/convert"
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
"zotregistry.io/zot/pkg/extensions/search/gql_generated"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta/boltdb"
mTypes "zotregistry.io/zot/pkg/meta/types"
reqCtx "zotregistry.io/zot/pkg/requestcontext"
"zotregistry.io/zot/pkg/storage"
"zotregistry.io/zot/pkg/test/mocks"
)
var ErrTestError = errors.New("TestError")
func TestGlobalSearch(t *testing.T) {
Convey("globalSearch", t, func() {
const query = "repo1"
Convey("MetaDB SearchRepos error", func() {
mockMetaDB := mocks.MetaDBMock{
SearchReposFn: func(ctx context.Context, searchText string,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return make([]mTypes.RepoMetadata, 0), make(map[string]mTypes.ManifestMetadata),
map[string]mTypes.IndexData{}, ErrTestError
},
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB, &gql_generated.Filter{},
&gql_generated.PageInput{}, mockCve, log.NewLogger("debug", ""))
So(err, ShouldNotBeNil)
So(images, ShouldBeEmpty)
So(layers, ShouldBeEmpty)
So(repos.Results, ShouldBeEmpty)
})
Convey("paginated fail", func() {
pageInput := &gql_generated.PageInput{
Limit: ref(-1),
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, _, _, err := globalSearch(responseContext, "repo", mocks.MetaDBMock{}, &gql_generated.Filter{},
pageInput, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
So(err, ShouldNotBeNil)
_, _, _, err = globalSearch(responseContext, "repo:tag", mocks.MetaDBMock{}, &gql_generated.Filter{},
pageInput, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
So(err, ShouldNotBeNil)
})
Convey("MetaDB SearchRepo is successful", func() {
mockMetaDB := mocks.MetaDBMock{
SearchReposFn: func(ctx context.Context, searchText string,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {
Digest: "digestTag1.0.1",
MediaType: ispec.MediaTypeImageManifest,
},
"1.0.2": {
Digest: "digestTag1.0.2",
MediaType: ispec.MediaTypeImageManifest,
},
},
Signatures: map[string]mTypes.ManifestSignatures{
"digestTag1.0.1": {
"cosign": []mTypes.SignatureInfo{
{SignatureManifestDigest: "testSignature", LayersInfo: []mTypes.LayerInfo{}},
},
},
},
Stars: 100,
},
}
createTime := time.Now()
configBlob1, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{
ispec.AnnotationVendor: "TestVendor1",
},
},
Created: &createTime,
})
So(err, ShouldBeNil)
configBlob2, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{
ispec.AnnotationVendor: "TestVendor2",
},
},
})
So(err, ShouldBeNil)
manifestBlob, err := json.Marshal(ispec.Manifest{})
So(err, ShouldBeNil)
manifestsMeta := map[string]mTypes.ManifestMetadata{
"digestTag1.0.1": {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob1,
},
"digestTag1.0.2": {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob2,
},
}
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
},
}
const query = "repo1"
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB,
&gql_generated.Filter{}, &pageInput, mockCve, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
So(images, ShouldBeEmpty)
So(layers, ShouldBeEmpty)
So(repos.Results, ShouldNotBeEmpty)
So(len(repos.Results[0].Vendors), ShouldEqual, 2)
})
Convey("MetaDB SearchRepo Bad manifest referenced", func() {
mockMetaDB := mocks.MetaDBMock{
SearchReposFn: func(ctx context.Context, searchText string,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {
Digest: "digestTag1.0.1",
MediaType: ispec.MediaTypeImageManifest,
},
},
Signatures: map[string]mTypes.ManifestSignatures{
"digestTag1.0.1": {
"cosign": []mTypes.SignatureInfo{
{SignatureManifestDigest: "testSignature", LayersInfo: []mTypes.LayerInfo{}},
},
},
},
Stars: 100,
},
}
configBlob, err := json.Marshal(ispec.Image{})
So(err, ShouldBeNil)
manifestsMeta := map[string]mTypes.ManifestMetadata{
"digestTag1.0.1": {
ManifestBlob: []byte("bad manifest blob"),
ConfigBlob: configBlob,
},
}
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
},
}
query := "repo1"
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB,
&gql_generated.Filter{}, &pageInput, mockCve, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
So(images, ShouldBeEmpty)
So(layers, ShouldBeEmpty)
So(repos, ShouldNotBeEmpty)
query = "repo1:1.0.1"
responseContext = graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
repos, images, layers, err = globalSearch(responseContext, query, mockMetaDB,
&gql_generated.Filter{}, &pageInput, mockCve, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
So(images, ShouldBeEmpty)
So(layers, ShouldBeEmpty)
So(repos.Results, ShouldBeEmpty)
})
Convey("MetaDB SearchRepo good manifest referenced and bad config blob", func() {
mockMetaDB := mocks.MetaDBMock{
SearchReposFn: func(ctx context.Context, searchText string,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {
Digest: "digestTag1.0.1",
MediaType: ispec.MediaTypeImageManifest,
},
},
Signatures: map[string]mTypes.ManifestSignatures{
"digestTag1.0.1": {
"cosign": []mTypes.SignatureInfo{
{SignatureManifestDigest: "testSignature", LayersInfo: []mTypes.LayerInfo{}},
},
},
},
Stars: 100,
},
}
manifestBlob, err := json.Marshal(ispec.Manifest{})
So(err, ShouldBeNil)
manifestsMeta := map[string]mTypes.ManifestMetadata{
"digestTag1.0.1": {
ManifestBlob: manifestBlob,
ConfigBlob: []byte("bad config blob"),
},
}
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
},
}
query := "repo1"
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
mockCve := mocks.CveInfoMock{}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB,
&gql_generated.Filter{}, &pageInput, mockCve, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
So(images, ShouldBeEmpty)
So(layers, ShouldBeEmpty)
So(repos.Results, ShouldNotBeEmpty)
query = "repo1:1.0.1"
responseContext = graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
repos, images, layers, err = globalSearch(responseContext, query, mockMetaDB,
&gql_generated.Filter{}, &pageInput, mockCve, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
So(images, ShouldBeEmpty)
So(layers, ShouldBeEmpty)
So(repos.Results, ShouldBeEmpty)
})
Convey("MetaDB SearchTags gives error", func() {
mockMetaDB := mocks.MetaDBMock{
SearchTagsFn: func(ctx context.Context, searchText string,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return make([]mTypes.RepoMetadata, 0), make(map[string]mTypes.ManifestMetadata),
map[string]mTypes.IndexData{}, ErrTestError
},
}
const query = "repo1:1.0.1"
mockCve := mocks.CveInfoMock{}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB, &gql_generated.Filter{},
&gql_generated.PageInput{}, mockCve, log.NewLogger("debug", ""))
So(err, ShouldNotBeNil)
So(images, ShouldBeEmpty)
So(layers, ShouldBeEmpty)
So(repos.Results, ShouldBeEmpty)
})
Convey("MetaDB SearchTags is successful", func() {
mockMetaDB := mocks.MetaDBMock{
SearchTagsFn: func(ctx context.Context, searchText string,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {
Digest: "digestTag1.0.1",
MediaType: ispec.MediaTypeImageManifest,
},
},
Signatures: map[string]mTypes.ManifestSignatures{
"digestTag1.0.1": {
"cosign": []mTypes.SignatureInfo{
{SignatureManifestDigest: "testSignature", LayersInfo: []mTypes.LayerInfo{}},
},
},
},
Stars: 100,
},
}
configBlob1, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{
ispec.AnnotationVendor: "TestVendor1",
},
},
})
So(err, ShouldBeNil)
configBlob2, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{
ispec.AnnotationVendor: "TestVendor2",
},
},
})
So(err, ShouldBeNil)
manifestBlob, err := json.Marshal(ispec.Manifest{})
So(err, ShouldBeNil)
manifestsMeta := map[string]mTypes.ManifestMetadata{
"digestTag1.0.1": {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob1,
},
"digestTag1.0.2": {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob2,
},
}
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
},
}
const query = "repo1:1.0.1"
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
mockCve := mocks.CveInfoMock{}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB,
&gql_generated.Filter{}, &pageInput, mockCve, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
So(images, ShouldNotBeEmpty)
So(layers, ShouldBeEmpty)
So(repos.Results, ShouldBeEmpty)
})
})
}
func TestRepoListWithNewestImage(t *testing.T) {
Convey("RepoListWithNewestImage", t, func() {
Convey("MetaDB SearchRepos error", func() {
mockMetaDB := mocks.MetaDBMock{
SearchReposFn: func(ctx context.Context, searchText string,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData, error,
) {
return make([]mTypes.RepoMetadata, 0), make(map[string]mTypes.ManifestMetadata),
map[string]mTypes.IndexData{}, ErrTestError
},
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaUpdateTime
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
repos, err := repoListWithNewestImage(responseContext, mockCve, log.NewLogger("debug", ""), &pageInput, mockMetaDB)
So(err, ShouldNotBeNil)
So(repos.Results, ShouldBeEmpty)
})
Convey("paginated fail", func() {
pageInput := &gql_generated.PageInput{
Limit: ref(-1),
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err := repoListWithNewestImage(responseContext, mocks.CveInfoMock{}, log.NewLogger("debug", ""),
pageInput, mocks.MetaDBMock{})
So(err, ShouldNotBeNil)
})
Convey("MetaDB SearchRepo bad manifest referenced", func() {
mockMetaDB := mocks.MetaDBMock{
SearchReposFn: func(ctx context.Context, searchText string,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {
Digest: "digestTag1.0.1",
MediaType: ispec.MediaTypeImageManifest,
},
},
Signatures: map[string]mTypes.ManifestSignatures{
"digestTag1.0.1": {
"cosign": []mTypes.SignatureInfo{
{SignatureManifestDigest: "testSignature", LayersInfo: []mTypes.LayerInfo{}},
},
},
},
Stars: 100,
},
{
Name: "repo2",
Tags: map[string]mTypes.Descriptor{
"1.0.2": {
Digest: "digestTag1.0.2",
MediaType: ispec.MediaTypeImageManifest,
},
},
Signatures: map[string]mTypes.ManifestSignatures{
"digestTag1.0.1": {
"cosign": []mTypes.SignatureInfo{
{SignatureManifestDigest: "testSignature", LayersInfo: []mTypes.LayerInfo{}},
},
},
},
Stars: 100,
},
}
configBlob1, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{},
},
})
So(err, ShouldBeNil)
manifestsMeta := map[string]mTypes.ManifestMetadata{
"digestTag1.0.1": {
ManifestBlob: []byte("bad manifest blob"),
ConfigBlob: configBlob1,
},
"digestTag1.0.2": {
ManifestBlob: []byte("bad manifest blob"),
ConfigBlob: configBlob1,
},
}
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
},
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaUpdateTime
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
repos, err := repoListWithNewestImage(responseContext, mockCve, log.NewLogger("debug", ""), &pageInput, mockMetaDB)
So(err, ShouldBeNil)
So(repos.Results, ShouldNotBeEmpty)
})
Convey("Working SearchRepo function", func() {
createTime := time.Now()
createTime2 := createTime.Add(time.Second)
mockMetaDB := mocks.MetaDBMock{
SearchReposFn: func(ctx context.Context, searchText string,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {
Digest: "digestTag1.0.1",
MediaType: ispec.MediaTypeImageManifest,
},
},
Signatures: map[string]mTypes.ManifestSignatures{
"digestTag1.0.1": {
"cosign": []mTypes.SignatureInfo{
{SignatureManifestDigest: "testSignature", LayersInfo: []mTypes.LayerInfo{}},
},
},
},
Stars: 100,
},
{
Name: "repo2",
Tags: map[string]mTypes.Descriptor{
"1.0.2": {
Digest: "digestTag1.0.2",
MediaType: ispec.MediaTypeImageManifest,
},
},
Signatures: map[string]mTypes.ManifestSignatures{
"digestTag1.0.1": {
"cosign": []mTypes.SignatureInfo{
{SignatureManifestDigest: "testSignature", LayersInfo: []mTypes.LayerInfo{}},
},
},
},
Stars: 100,
},
}
configBlob1, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{},
},
Created: &createTime,
})
So(err, ShouldBeNil)
configBlob2, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{},
},
Created: &createTime2,
})
So(err, ShouldBeNil)
manifestBlob, err := json.Marshal(ispec.Manifest{})
So(err, ShouldBeNil)
manifestsMeta := map[string]mTypes.ManifestMetadata{
"digestTag1.0.1": {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob1,
},
"digestTag1.0.2": {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob2,
},
}
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
},
}
Convey("MetaDB missing requestedPage", func() {
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
repos, err := repoListWithNewestImage(responseContext, mockCve, log.NewLogger("debug", ""), nil, mockMetaDB)
So(err, ShouldBeNil)
So(repos.Results, ShouldNotBeEmpty)
})
Convey("MetaDB SearchRepo is successful", func() {
limit := 2
offset := 0
sortCriteria := gql_generated.SortCriteriaUpdateTime
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
repos, err := repoListWithNewestImage(responseContext, mockCve,
log.NewLogger("debug", ""), &pageInput, mockMetaDB)
So(err, ShouldBeNil)
So(repos, ShouldNotBeEmpty)
So(len(repos.Results), ShouldEqual, 2)
So(*repos.Results[0].Name, ShouldEqual, "repo2")
So(*repos.Results[0].LastUpdated, ShouldEqual, createTime2)
})
})
})
}
func TestGetBookmarkedRepos(t *testing.T) {
Convey("getBookmarkedRepos", t, func() {
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err := getBookmarkedRepos(
responseContext,
mocks.CveInfoMock{},
log.NewLogger("debug", ""),
nil,
mocks.MetaDBMock{
GetBookmarkedReposFn: func(ctx context.Context) ([]string, error) {
return []string{}, ErrTestError
},
},
)
So(err, ShouldNotBeNil)
})
}
func TestGetStarredRepos(t *testing.T) {
Convey("getStarredRepos", t, func() {
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err := getStarredRepos(
responseContext,
mocks.CveInfoMock{},
log.NewLogger("debug", ""),
nil,
mocks.MetaDBMock{
GetStarredReposFn: func(ctx context.Context) ([]string, error) {
return []string{}, ErrTestError
},
},
)
So(err, ShouldNotBeNil)
})
}
func TestGetFilteredPaginatedRepos(t *testing.T) {
Convey("getFilteredPaginatedRepos FilterRepos fails", t, func() {
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err := getFilteredPaginatedRepos(
responseContext,
mocks.CveInfoMock{},
func(repoMeta mTypes.RepoMetadata) bool { return true },
log.NewLogger("debug", ""),
nil,
mocks.MetaDBMock{
FilterReposFn: func(ctx context.Context, filter mTypes.FilterRepoFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return []mTypes.RepoMetadata{}, map[string]mTypes.ManifestMetadata{}, map[string]mTypes.IndexData{},
ErrTestError
},
},
)
So(err, ShouldNotBeNil)
})
Convey("Paginated convert fails", t, func() {
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err := getFilteredPaginatedRepos(responseContext,
mocks.CveInfoMock{},
func(repoMeta mTypes.RepoMetadata) bool { return true },
log.NewLogger("debug", ""),
&gql_generated.PageInput{Limit: ref(-1)},
mocks.MetaDBMock{},
)
So(err, ShouldNotBeNil)
})
}
func TestImageListForDigest(t *testing.T) {
Convey("getImageList", t, func() {
Convey("no page requested, FilterTagsFn returns error", func() {
mockSearchDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context, filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return []mTypes.RepoMetadata{}, map[string]mTypes.ManifestMetadata{}, map[string]mTypes.IndexData{},
ErrTestError
},
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err := getImageListForDigest(responseContext, "invalid", mockSearchDB, mocks.CveInfoMock{}, nil)
So(err, ShouldNotBeNil)
})
Convey("Paginated convert fails", func() {
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err := getImageListForDigest(responseContext, "invalid", mocks.MetaDBMock{}, mocks.CveInfoMock{},
&gql_generated.PageInput{Limit: ref(-1)})
So(err, ShouldNotBeNil)
})
Convey("invalid manifest blob", func() {
mockSearchDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context, filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "test",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {Digest: "digestTag1.0.1", MediaType: ispec.MediaTypeImageManifest},
},
Stars: 100,
},
}
configBlob, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{},
},
})
So(err, ShouldBeNil)
manifestBlob := []byte("invalid")
manifestsMetaData := map[string]mTypes.ManifestMetadata{
"digestTag1.0.1": {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
DownloadCount: 0,
},
}
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
},
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
imageList, err := getImageListForDigest(responseContext, "test", mockSearchDB, mocks.CveInfoMock{}, nil)
So(err, ShouldBeNil)
So(imageList.Results, ShouldBeEmpty)
})
Convey("valid imageListForDigest returned for matching manifest digest", func() {
manifestBlob, err := json.Marshal(ispec.Manifest{})
So(err, ShouldBeNil)
manifestDigest := godigest.FromBytes(manifestBlob).String()
mockSearchDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context, filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "test",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {Digest: manifestDigest, MediaType: ispec.MediaTypeImageManifest},
},
Stars: 100,
},
}
configBlob, err := json.Marshal(ispec.ImageConfig{})
So(err, ShouldBeNil)
manifestsMetaData := map[string]mTypes.ManifestMetadata{
manifestDigest: {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
DownloadCount: 0,
},
}
matchedTags := repos[0].Tags
for tag, manifestDescriptor := range repos[0].Tags {
if !filterFunc(repos[0], manifestsMetaData[manifestDescriptor.Digest]) {
delete(matchedTags, tag)
delete(manifestsMetaData, manifestDescriptor.Digest)
continue
}
}
repos[0].Tags = matchedTags
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
},
}
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
imageSummaries, err := getImageListForDigest(responseContext, manifestDigest,
mockSearchDB, mocks.CveInfoMock{}, &pageInput)
So(err, ShouldBeNil)
So(len(imageSummaries.Results), ShouldEqual, 1)
imageSummaries, err = getImageListForDigest(responseContext, "invalid",
mockSearchDB, mocks.CveInfoMock{}, &pageInput)
So(err, ShouldBeNil)
So(len(imageSummaries.Results), ShouldEqual, 0)
})
Convey("valid imageListForDigest returned for matching config digest", func() {
manifestBlob, err := json.Marshal(ispec.Manifest{})
So(err, ShouldBeNil)
manifestDigest := godigest.FromBytes(manifestBlob).String()
configBlob, err := json.Marshal(ispec.Image{})
So(err, ShouldBeNil)
configDigest := godigest.FromBytes(configBlob)
mockSearchDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context, filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "test",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {Digest: manifestDigest, MediaType: ispec.MediaTypeImageManifest},
},
Stars: 100,
},
}
manifestBlob, err := json.Marshal(ispec.Manifest{
Config: ispec.Descriptor{
Digest: configDigest,
},
})
So(err, ShouldBeNil)
manifestsMetaData := map[string]mTypes.ManifestMetadata{
manifestDigest: {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
DownloadCount: 0,
},
}
matchedTags := repos[0].Tags
for tag, manifestDescriptor := range repos[0].Tags {
if !filterFunc(repos[0], manifestsMetaData[manifestDescriptor.Digest]) {
delete(matchedTags, tag)
delete(manifestsMetaData, manifestDescriptor.Digest)
continue
}
}
repos[0].Tags = matchedTags
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
},
}
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
imageSummaries, err := getImageListForDigest(responseContext, configDigest.String(),
mockSearchDB, mocks.CveInfoMock{}, &pageInput)
So(err, ShouldBeNil)
So(len(imageSummaries.Results), ShouldEqual, 1)
})
Convey("valid imageListForDigest returned for matching layer digest", func() {
manifestBlob, err := json.Marshal(ispec.Manifest{})
So(err, ShouldBeNil)
manifestDigest := godigest.FromBytes(manifestBlob).String()
configBlob, err := json.Marshal(ispec.Image{})
So(err, ShouldBeNil)
layerDigest := godigest.Digest("validDigest")
mockSearchDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context, filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "test",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {Digest: manifestDigest, MediaType: ispec.MediaTypeImageManifest},
},
Stars: 100,
},
}
manifestBlob, err := json.Marshal(ispec.Manifest{
Layers: []ispec.Descriptor{
{
Digest: layerDigest,
},
},
})
So(err, ShouldBeNil)
manifestsMetaData := map[string]mTypes.ManifestMetadata{
manifestDigest: {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
DownloadCount: 0,
},
}
matchedTags := repos[0].Tags
for tag, manifestDescriptor := range repos[0].Tags {
if !filterFunc(repos[0], manifestsMetaData[manifestDescriptor.Digest]) {
delete(matchedTags, tag)
delete(manifestsMetaData, manifestDescriptor.Digest)
continue
}
}
repos[0].Tags = matchedTags
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
},
}
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
imageSummaries, err := getImageListForDigest(responseContext, layerDigest.String(),
mockSearchDB, mocks.CveInfoMock{}, &pageInput)
So(err, ShouldBeNil)
So(len(imageSummaries.Results), ShouldEqual, 1)
})
Convey("valid imageListForDigest, multiple matching tags", func() {
manifestBlob, err := json.Marshal(ispec.Manifest{})
So(err, ShouldBeNil)
manifestDigest := godigest.FromBytes(manifestBlob).String()
configBlob, err := json.Marshal(ispec.Image{})
So(err, ShouldBeNil)
mockSearchDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context, filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "test",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {Digest: manifestDigest, MediaType: ispec.MediaTypeImageManifest},
"1.0.2": {Digest: manifestDigest, MediaType: ispec.MediaTypeImageManifest},
},
Stars: 100,
},
}
manifestsMetaData := map[string]mTypes.ManifestMetadata{
manifestDigest: {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
DownloadCount: 0,
},
}
for i, repo := range repos {
matchedTags := repo.Tags
for tag, manifestDescriptor := range repo.Tags {
if !filterFunc(repo, manifestsMetaData[manifestDescriptor.Digest]) {
delete(matchedTags, tag)
delete(manifestsMetaData, manifestDescriptor.Digest)
continue
}
}
repos[i].Tags = matchedTags
}
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
},
}
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
imageSummaries, err := getImageListForDigest(responseContext, manifestDigest,
mockSearchDB, mocks.CveInfoMock{}, &pageInput)
So(err, ShouldBeNil)
So(len(imageSummaries.Results), ShouldEqual, 1)
})
Convey("valid imageListForDigest, multiple matching tags limited by pageInput", func() {
manifestBlob, err := json.Marshal(ispec.Manifest{})
So(err, ShouldBeNil)
manifestDigest := godigest.FromBytes(manifestBlob).String()
configBlob, err := json.Marshal(ispec.Image{})
So(err, ShouldBeNil)
mockSearchDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context, filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData, error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "test",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {Digest: manifestDigest, MediaType: ispec.MediaTypeImageManifest},
"1.0.2": {Digest: manifestDigest, MediaType: ispec.MediaTypeImageManifest},
},
Stars: 100,
},
}
manifestsMetaData := map[string]mTypes.ManifestMetadata{
manifestDigest: {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
DownloadCount: 0,
},
}
for i, repo := range repos {
matchedTags := repo.Tags
for tag, manifestDescriptor := range repo.Tags {
if !filterFunc(repo, manifestsMetaData[manifestDescriptor.Digest]) {
delete(matchedTags, tag)
delete(manifestsMetaData, manifestDescriptor.Digest)
continue
}
}
repos[i].Tags = matchedTags
repos = append(repos, repo)
}
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
},
}
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
imageSummaries, err := getImageListForDigest(responseContext, manifestDigest,
mockSearchDB, mocks.CveInfoMock{}, &pageInput)
So(err, ShouldBeNil)
So(len(imageSummaries.Results), ShouldEqual, 1)
})
})
}
func TestGetImageSummary(t *testing.T) {
Convey("GetImageSummary", t, func() {
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
Convey("Media Type: ImageManifest", func() {
Convey("metaDB.GetManifestMeta fails", func() {
var (
metaDB = mocks.MetaDBMock{
GetManifestDataFn: func(manifestDigest godigest.Digest) (mTypes.ManifestData, error) {
return mTypes.ManifestData{}, ErrTestError
},
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {MediaType: ispec.MediaTypeImageManifest, Digest: "digest"},
},
}, nil
},
}
log = log.NewLogger("debug", "")
skip = convert.SkipQGLField{
Vulnerabilities: true,
}
)
_, err := getImageSummary(responseContext, "repo", "tag", nil, skip, metaDB, mocks.CveInfoMock{}, log)
So(err, ShouldNotBeNil)
})
Convey("0 len return", func() {
var (
metaDB = mocks.MetaDBMock{
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {MediaType: ispec.MediaTypeImageManifest, Digest: "digest"},
},
}, nil
},
}
log = log.NewLogger("debug", "")
skip = convert.SkipQGLField{
Vulnerabilities: true,
}
)
_, err := getImageSummary(responseContext, "repo", "tag", nil, skip, metaDB, mocks.CveInfoMock{}, log)
So(err, ShouldBeNil)
})
Convey("digest != nil && *digest != actual image digest", func() {
var (
metaDB = mocks.MetaDBMock{
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (mTypes.ManifestMetadata, error) {
return mTypes.ManifestMetadata{}, ErrTestError
},
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {MediaType: ispec.MediaTypeImageManifest, Digest: "digest"},
},
}, nil
},
}
log = log.NewLogger("debug", "")
digest = "wrongDigest"
skip = convert.SkipQGLField{
Vulnerabilities: true,
}
)
_, err := getImageSummary(responseContext, "repo", "tag", &digest, skip, metaDB, mocks.CveInfoMock{}, log)
So(err, ShouldNotBeNil)
})
})
Convey("Media Type: ImageIndex", func() {
Convey("metaDB.GetIndexData fails", func() {
var (
metaDB = mocks.MetaDBMock{
GetIndexDataFn: func(indexDigest godigest.Digest) (mTypes.IndexData, error) {
return mTypes.IndexData{}, ErrTestError
},
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {MediaType: ispec.MediaTypeImageIndex, Digest: "digest"},
},
}, nil
},
}
log = log.NewLogger("debug", "")
skip = convert.SkipQGLField{
Vulnerabilities: true,
}
)
_, err := getImageSummary(responseContext, "repo", "tag", nil, skip, metaDB, mocks.CveInfoMock{}, log)
So(err, ShouldNotBeNil)
})
Convey("json.Unmarshal(indexData.IndexBlob, &indexContent) fails", func() {
var (
metaDB = mocks.MetaDBMock{
GetIndexDataFn: func(indexDigest godigest.Digest) (mTypes.IndexData, error) {
return mTypes.IndexData{
IndexBlob: []byte("bad json"),
}, nil
},
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {MediaType: ispec.MediaTypeImageIndex, Digest: "digest"},
},
}, nil
},
}
log = log.NewLogger("debug", "")
skip = convert.SkipQGLField{
Vulnerabilities: true,
}
)
_, err := getImageSummary(responseContext, "repo", "tag", nil, skip, metaDB, mocks.CveInfoMock{}, log)
So(err, ShouldNotBeNil)
})
Convey("digest != nil", func() {
index := ispec.Index{
Manifests: []ispec.Descriptor{
{
Digest: "digest",
MediaType: ispec.MediaTypeImageManifest,
},
},
}
indexBlob, err := json.Marshal(index)
So(err, ShouldBeNil)
metaDB := mocks.MetaDBMock{
GetIndexDataFn: func(indexDigest godigest.Digest) (mTypes.IndexData, error) {
return mTypes.IndexData{
IndexBlob: indexBlob,
}, nil
},
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {MediaType: ispec.MediaTypeImageIndex, Digest: "digest"},
},
}, nil
},
}
log := log.NewLogger("debug", "")
goodDigest := "goodDigest"
Convey("digest not found", func() {
wrongDigest := "wrongDigest"
skip := convert.SkipQGLField{
Vulnerabilities: true,
}
_, err = getImageSummary(responseContext, "repo", "tag", &wrongDigest, skip, metaDB, mocks.CveInfoMock{}, log)
So(err, ShouldNotBeNil)
})
Convey("GetManifestData error", func() {
metaDB.GetManifestDataFn = func(manifestDigest godigest.Digest) (mTypes.ManifestData, error) {
return mTypes.ManifestData{}, ErrTestError
}
skip := convert.SkipQGLField{
Vulnerabilities: true,
}
_, err = getImageSummary(responseContext, "repo", "tag", &goodDigest, skip, metaDB, mocks.CveInfoMock{}, log)
So(err, ShouldNotBeNil)
})
})
})
Convey("Media Type: not supported", func() {
var (
metaDB = mocks.MetaDBMock{
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tag": {MediaType: "unknown", Digest: "digest"},
},
}, nil
},
}
log = log.NewLogger("debug", "")
skip = convert.SkipQGLField{
Vulnerabilities: true,
}
)
_, err := getImageSummary(responseContext, "repo", "tag", nil, skip, metaDB, mocks.CveInfoMock{}, log)
So(err, ShouldBeNil)
})
})
}
func TestFilterBaseImagesFn(t *testing.T) {
Convey("FilterBaseImages", t, func() {
filterFunc := filterBaseImages(&gql_generated.ImageSummary{})
ok := filterFunc(
mTypes.RepoMetadata{},
mTypes.ManifestMetadata{
ManifestBlob: []byte("bad json"),
},
)
So(ok, ShouldBeFalse)
})
}
func TestImageList(t *testing.T) {
Convey("getImageList", t, func() {
testLogger := log.NewLogger("debug", "")
Convey("no page requested, SearchRepoFn returns error", func() {
mockSearchDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context, filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return []mTypes.RepoMetadata{}, map[string]mTypes.ManifestMetadata{},
map[string]mTypes.IndexData{}, ErrTestError
},
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err := getImageList(responseContext, "test", mockSearchDB, mocks.CveInfoMock{}, nil, testLogger)
So(err, ShouldNotBeNil)
})
Convey("Paginated convert fails", func() {
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err := getImageList(responseContext, "test", mocks.MetaDBMock{}, mocks.CveInfoMock{},
&gql_generated.PageInput{Limit: ref(-1)}, log.NewLogger("debug", ""))
So(err, ShouldNotBeNil)
})
Convey("valid repoList returned", func() {
mockSearchDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context, filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "test",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {
Digest: "digestTag1.0.1",
MediaType: ispec.MediaTypeImageManifest,
},
},
Signatures: map[string]mTypes.ManifestSignatures{
"digestTag1.0.1": {
"cosign": []mTypes.SignatureInfo{
{SignatureManifestDigest: "testSignature", LayersInfo: []mTypes.LayerInfo{}},
},
},
},
Stars: 100,
},
}
configBlob, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{},
},
})
So(err, ShouldBeNil)
manifestBlob, err := json.Marshal(ispec.Manifest{})
So(err, ShouldBeNil)
manifestsMetaData := map[string]mTypes.ManifestMetadata{
"digestTag1.0.1": {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
DownloadCount: 0,
Signatures: mTypes.ManifestSignatures{
"cosign": []mTypes.SignatureInfo{
{SignatureManifestDigest: "digestSignature1"},
},
},
},
}
if !filterFunc(repos[0], manifestsMetaData["digestTag1.0.1"]) {
return []mTypes.RepoMetadata{}, map[string]mTypes.ManifestMetadata{},
map[string]mTypes.IndexData{}, nil
}
return repos, manifestsMetaData, map[string]mTypes.IndexData{}, nil
},
}
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
imageSummaries, err := getImageList(responseContext, "test", mockSearchDB,
mocks.CveInfoMock{}, &pageInput, testLogger)
So(err, ShouldBeNil)
So(len(imageSummaries.Results), ShouldEqual, 1)
imageSummaries, err = getImageList(responseContext, "invalid", mockSearchDB,
mocks.CveInfoMock{}, &pageInput, testLogger)
So(err, ShouldBeNil)
So(len(imageSummaries.Results), ShouldEqual, 0)
})
})
}
func TestGetReferrers(t *testing.T) {
Convey("getReferrers", t, func() {
referredDigest := godigest.FromString("t").String()
Convey("referredDigest is empty", func() {
testLogger := log.NewLogger("debug", "")
_, err := getReferrers(mocks.MetaDBMock{}, "test", "", nil, testLogger)
So(err, ShouldNotBeNil)
})
Convey("GetReferrers returns error", func() {
testLogger := log.NewLogger("debug", "")
mockedStore := mocks.MetaDBMock{
GetReferrersInfoFn: func(repo string, referredDigest godigest.Digest, artifactTypes []string,
) ([]mTypes.ReferrerInfo, error) {
return nil, ErrTestError
},
}
_, err := getReferrers(mockedStore, "test", referredDigest, nil, testLogger)
So(err, ShouldNotBeNil)
})
Convey("GetReferrers return index of descriptors", func() {
testLogger := log.NewLogger("debug", "")
referrerDescriptor := ispec.Descriptor{
MediaType: ispec.MediaTypeImageManifest,
ArtifactType: "com.artifact.test",
Size: 403,
Digest: godigest.FromString("test"),
Annotations: map[string]string{
"key": "value",
},
}
mockedStore := mocks.MetaDBMock{
GetReferrersInfoFn: func(repo string, referredDigest godigest.Digest, artifactTypes []string,
) ([]mTypes.ReferrerInfo, error) {
return []mTypes.ReferrerInfo{
{
Digest: referrerDescriptor.Digest.String(),
MediaType: referrerDescriptor.MediaType,
ArtifactType: referrerDescriptor.ArtifactType,
Size: int(referrerDescriptor.Size),
Annotations: referrerDescriptor.Annotations,
},
}, nil
},
}
referrers, err := getReferrers(mockedStore, "test", referredDigest, nil, testLogger)
So(err, ShouldBeNil)
So(*referrers[0].ArtifactType, ShouldEqual, referrerDescriptor.ArtifactType)
So(*referrers[0].MediaType, ShouldEqual, referrerDescriptor.MediaType)
So(*referrers[0].Size, ShouldEqual, referrerDescriptor.Size)
So(*referrers[0].Digest, ShouldEqual, referrerDescriptor.Digest.String())
So(*referrers[0].Annotations[0].Value, ShouldEqual, referrerDescriptor.Annotations["key"])
})
})
}
func TestQueryResolverErrors(t *testing.T) {
Convey("Errors", t, func() {
log := log.NewLogger("debug", "")
ctx := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
Convey("GlobalSearch error bad requested page", func() {
resolverConfig := NewResolver(
log,
storage.StoreController{},
mocks.MetaDBMock{},
mocks.CveInfoMock{},
)
resolver := queryResolver{
resolverConfig,
}
limit := -1
offset := 0
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
_, err := resolver.GlobalSearch(ctx, "some_string", &gql_generated.Filter{}, &pageInput)
So(err, ShouldNotBeNil)
limit = 0
offset = -1
pageInput = gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
_, err = resolver.GlobalSearch(ctx, "some_string", &gql_generated.Filter{}, &pageInput)
So(err, ShouldNotBeNil)
})
Convey("ImageListForCve error in GetMultipleRepoMeta", func() {
resolverConfig := NewResolver(
log,
storage.StoreController{
DefaultStore: mocks.MockedImageStore{},
},
mocks.MetaDBMock{
GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMetadata) bool,
) ([]mTypes.RepoMetadata, error) {
return []mTypes.RepoMetadata{}, ErrTestError
},
},
mocks.CveInfoMock{},
)
qr := queryResolver{
resolverConfig,
}
_, err := qr.ImageListForCve(ctx, "cve1", &gql_generated.Filter{}, &gql_generated.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("ImageListForCve error in FilterTags", func() {
resolverConfig := NewResolver(
log,
storage.StoreController{
DefaultStore: mocks.MockedImageStore{},
},
mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context,
filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return []mTypes.RepoMetadata{}, map[string]mTypes.ManifestMetadata{}, map[string]mTypes.IndexData{},
ErrTestError
},
},
mocks.CveInfoMock{},
)
qr := queryResolver{
resolverConfig,
}
_, err := qr.ImageListForCve(ctx, "cve1", &gql_generated.Filter{}, &gql_generated.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("ImageListWithCVEFixed error in FilterTags", func() {
resolverConfig := NewResolver(
log,
storage.StoreController{
DefaultStore: mocks.MockedImageStore{},
},
mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context,
filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return []mTypes.RepoMetadata{}, map[string]mTypes.ManifestMetadata{}, map[string]mTypes.IndexData{},
ErrTestError
},
},
mocks.CveInfoMock{},
)
qr := queryResolver{
resolverConfig,
}
_, err := qr.ImageListWithCVEFixed(ctx, "cve1", "image", &gql_generated.Filter{}, &gql_generated.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("RepoListWithNewestImage repoListWithNewestImage() errors mocked StoreController", func() {
resolverConfig := NewResolver(
log,
storage.StoreController{
DefaultStore: mocks.MockedImageStore{},
},
mocks.MetaDBMock{
SearchReposFn: func(ctx context.Context, searchText string,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return nil, nil, nil, ErrTestError
},
},
mocks.CveInfoMock{},
)
qr := queryResolver{
resolverConfig,
}
_, err := qr.RepoListWithNewestImage(ctx, &gql_generated.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("RepoListWithNewestImage repoListWithNewestImage() errors valid StoreController", func() {
resolverConfig := NewResolver(
log,
storage.StoreController{},
mocks.MetaDBMock{
SearchReposFn: func(ctx context.Context, searchText string,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return nil, nil, nil, ErrTestError
},
},
mocks.CveInfoMock{},
)
qr := queryResolver{
resolverConfig,
}
_, err := qr.RepoListWithNewestImage(ctx, &gql_generated.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("ImageList getImageList() errors", func() {
resolverConfig := NewResolver(
log,
storage.StoreController{},
mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context,
filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return []mTypes.RepoMetadata{}, map[string]mTypes.ManifestMetadata{}, map[string]mTypes.IndexData{},
ErrTestError
},
},
mocks.CveInfoMock{},
)
qr := queryResolver{
resolverConfig,
}
_, err := qr.ImageList(ctx, "repo", &gql_generated.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("DerivedImageList ExpandedRepoInfo() errors", func() {
resolverConfig := NewResolver(
log,
storage.StoreController{
DefaultStore: mocks.MockedImageStore{
GetRepositoriesFn: func() ([]string, error) {
return []string{"sub1/repo"}, nil
},
GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) {
return []byte("{}"), "digest", "str", nil
},
},
},
mocks.MetaDBMock{
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{}, ErrTestError
},
},
mocks.CveInfoMock{},
)
qr := queryResolver{
resolverConfig,
}
_, err := qr.DerivedImageList(ctx, "repo:tag", nil, &gql_generated.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("BaseImageList ExpandedRepoInfo() errors", func() {
resolverConfig := NewResolver(
log,
storage.StoreController{
DefaultStore: mocks.MockedImageStore{
GetRepositoriesFn: func() ([]string, error) {
return []string{"sub1/repo"}, nil
},
GetImageManifestFn: func(repo, reference string) ([]byte, godigest.Digest, string, error) {
return []byte("{}"), "digest", "str", nil
},
},
},
mocks.MetaDBMock{
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{}, ErrTestError
},
},
mocks.CveInfoMock{},
)
qr := queryResolver{
resolverConfig,
}
_, err := qr.BaseImageList(ctx, "repo:tag", nil, &gql_generated.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("DerivedImageList and BaseImage List FilterTags() errors", func() {
configBlob, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{},
},
})
So(err, ShouldBeNil)
manifest := ispec.Manifest{}
manifestBlob, err := json.Marshal(manifest)
So(err, ShouldBeNil)
manifestDigest := godigest.FromBytes(manifestBlob)
resolverConfig := NewResolver(
log,
storage.StoreController{},
mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context,
filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return []mTypes.RepoMetadata{}, map[string]mTypes.ManifestMetadata{}, map[string]mTypes.IndexData{},
ErrTestError
},
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Name: "repo",
Tags: map[string]mTypes.Descriptor{
"tag": {Digest: manifestDigest.String(), MediaType: ispec.MediaTypeImageManifest},
},
}, nil
},
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (mTypes.ManifestMetadata, error) {
return mTypes.ManifestMetadata{
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
}, nil
},
},
mocks.CveInfoMock{},
)
resolver := queryResolver{
resolverConfig,
}
_, err = resolver.DerivedImageList(ctx, "repo:tag", nil, &gql_generated.PageInput{})
So(err, ShouldNotBeNil)
_, err = resolver.BaseImageList(ctx, "repo:tag", nil, &gql_generated.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("GetReferrers error", func() {
resolverConfig := NewResolver(
log,
storage.StoreController{
DefaultStore: mocks.MockedImageStore{
GetReferrersFn: func(repo string, digest godigest.Digest, artifactTypes []string) (ispec.Index, error) {
return ispec.Index{}, ErrTestError
},
},
},
mocks.MetaDBMock{},
mocks.CveInfoMock{},
)
qr := queryResolver{
resolverConfig,
}
_, err := qr.Referrers(ctx, "repo", "", nil)
So(err, ShouldNotBeNil)
})
})
}
func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
params := boltdb.DBParameters{
RootDir: t.TempDir(),
}
LINUX := "linux"
AMD := "amd"
ARM := "arm64"
boltDriver, err := boltdb.GetBoltDriver(params)
if err != nil {
panic(err)
}
log := log.NewLogger("debug", "")
metaDB, err := boltdb.New(boltDriver, log)
if err != nil {
panic(err)
}
// Create metadb data for scannable image with vulnerabilities
// Create manifest metadata first
timeStamp1 := time.Date(2008, 1, 1, 12, 0, 0, 0, time.UTC)
configBlob1, err := json.Marshal(ispec.Image{
Created: &timeStamp1,
Platform: ispec.Platform{
Architecture: AMD,
OS: LINUX,
},
})
if err != nil {
panic(err)
}
manifestBlob1, err := json.Marshal(ispec.Manifest{
Config: ispec.Descriptor{
MediaType: ispec.MediaTypeImageConfig,
Size: 0,
Digest: godigest.FromBytes(configBlob1),
},
Layers: []ispec.Descriptor{
{
MediaType: ispec.MediaTypeImageLayerGzip,
Size: 0,
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
},
},
})
if err != nil {
panic(err)
}
repoMeta1 := mTypes.ManifestData{
ManifestBlob: manifestBlob1,
ConfigBlob: configBlob1,
}
digest1 := godigest.FromBytes(manifestBlob1)
err = metaDB.SetManifestData(digest1, repoMeta1)
if err != nil {
panic(err)
}
timeStamp2 := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
configBlob2, err := json.Marshal(ispec.Image{
Created: &timeStamp2,
Platform: ispec.Platform{
Architecture: AMD,
OS: LINUX,
},
})
if err != nil {
panic(err)
}
manifestBlob2, err := json.Marshal(ispec.Manifest{
Config: ispec.Descriptor{
MediaType: ispec.MediaTypeImageConfig,
Size: 0,
Digest: godigest.FromBytes(configBlob2),
},
Layers: []ispec.Descriptor{
{
MediaType: ispec.MediaTypeImageLayerGzip,
Size: 0,
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
},
},
})
if err != nil {
panic(err)
}
repoMeta2 := mTypes.ManifestData{
ManifestBlob: manifestBlob2,
ConfigBlob: configBlob2,
}
digest2 := godigest.FromBytes(manifestBlob2)
err = metaDB.SetManifestData(digest2, repoMeta2)
if err != nil {
panic(err)
}
timeStamp3 := time.Date(2010, 1, 1, 12, 0, 0, 0, time.UTC)
configBlob3, err := json.Marshal(ispec.Image{
Created: &timeStamp3,
Platform: ispec.Platform{
Architecture: ARM,
OS: LINUX,
},
})
if err != nil {
panic(err)
}
manifestBlob3, err := json.Marshal(ispec.Manifest{
Config: ispec.Descriptor{
MediaType: ispec.MediaTypeImageConfig,
Size: 0,
Digest: godigest.FromBytes(configBlob3),
},
Layers: []ispec.Descriptor{
{
MediaType: ispec.MediaTypeImageLayerGzip,
Size: 0,
Digest: godigest.NewDigestFromEncoded(godigest.SHA256, "digest"),
},
},
})
if err != nil {
panic(err)
}
repoMeta3 := mTypes.ManifestData{
ManifestBlob: manifestBlob3,
ConfigBlob: configBlob3,
}
digest3 := godigest.FromBytes(manifestBlob3)
err = metaDB.SetManifestData(digest3, repoMeta3)
if err != nil {
panic(err)
}
// Create the repo metadata using previously defined manifests
tagsMap := map[string]godigest.Digest{}
tagsMap["repo1:1.0.0"] = digest1
tagsMap["repo1:1.0.1"] = digest2
tagsMap["repo1:1.1.0"] = digest3
tagsMap["repo1:latest"] = digest3
tagsMap["repo2:2.0.0"] = digest1
tagsMap["repo2:2.0.1"] = digest2
tagsMap["repo2:2.1.0"] = digest3
tagsMap["repo2:latest"] = digest3
tagsMap["repo3:3.0.1"] = digest2
tagsMap["repo3:3.1.0"] = digest3
tagsMap["repo3:latest"] = digest3
for image, digest := range tagsMap {
repo, tag := common.GetImageDirAndTag(image)
err := metaDB.SetRepoReference(repo, tag, digest, ispec.MediaTypeImageManifest)
if err != nil {
panic(err)
}
}
getCveResults := func(digestStr string) map[string]cvemodel.CVE {
if digestStr == digest1.String() {
return map[string]cvemodel.CVE{
"CVE1": {
ID: "CVE1",
Severity: "HIGH",
Title: "Title CVE1",
Description: "Description CVE1",
},
"CVE2": {
ID: "CVE2",
Severity: "MEDIUM",
Title: "Title CVE2",
Description: "Description CVE2",
},
"CVE3": {
ID: "CVE3",
Severity: "LOW",
Title: "Title CVE3",
Description: "Description CVE3",
},
"CVE34": {
ID: "CVE34",
Severity: "LOW",
Title: "Title for CVE34",
Description: "Description CVE34",
},
}
}
if digestStr == digest2.String() {
return map[string]cvemodel.CVE{
"CVE2": {
ID: "CVE2",
Severity: "MEDIUM",
Title: "Title CVE2",
Description: "Description CVE2",
},
"CVE3": {
ID: "CVE3",
Severity: "LOW",
Title: "Title CVE3",
Description: "Description CVE3",
},
}
}
if digestStr == digest3.String() {
return map[string]cvemodel.CVE{
"CVE3": {
ID: "CVE3",
Severity: "LOW",
Title: "Title CVE3",
Description: "Description CVE3",
},
}
}
// By default the image has no vulnerabilities
return map[string]cvemodel.CVE{}
}
// MetaDB loaded with initial data, now mock the scanner
// Setup test CVE data in mock scanner
scanner := mocks.CveScannerMock{
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
digest, ok := tagsMap[image]
if !ok {
if !strings.Contains(image, "@") {
return map[string]cvemodel.CVE{}, nil
}
_, digestStr := common.GetImageDirAndDigest(image)
digest = godigest.Digest(digestStr)
}
return getCveResults(digest.String()), nil
},
GetCachedResultFn: func(digestStr string) map[string]cvemodel.CVE {
return getCveResults(digestStr)
},
IsResultCachedFn: func(digestStr string) bool {
return true
},
}
cveInfo := &cveinfo.BaseCveInfo{
Log: log,
Scanner: scanner,
MetaDB: metaDB,
}
Convey("Get CVE list for image ", t, func() {
Convey("Unpaginated request to get all CVEs in an image", func() {
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := &gql_generated.PageInput{
SortBy: &sortCriteria,
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
dig := godigest.FromString("dig")
repoWithDigestRef := fmt.Sprintf("repo@%s", dig)
_, err := getCVEListForImage(responseContext, repoWithDigestRef, cveInfo, pageInput, "", log)
So(err, ShouldBeNil)
cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.0.0")
expectedCves := []string{"CVE1", "CVE2", "CVE3", "CVE34"}
So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
for _, cve := range cveResult.CVEList {
So(expectedCves, ShouldContain, *cve.ID)
}
// test searching CVE by id in results
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "CVE3", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.0.0")
expectedCves = []string{"CVE3", "CVE34"}
So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
for _, cve := range cveResult.CVEList {
So(expectedCves, ShouldContain, *cve.ID)
}
// test searching CVE by id in results - no matches
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "CVE100", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.0.0")
So(len(cveResult.CVEList), ShouldEqual, 0)
// test searching CVE by id in results - partial name
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "VE3", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.0.0")
expectedCves = []string{"CVE3", "CVE34"}
So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
for _, cve := range cveResult.CVEList {
So(expectedCves, ShouldContain, *cve.ID)
}
// test searching CVE by title in results
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "Title CVE", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.0.0")
expectedCves = []string{"CVE1", "CVE2", "CVE3"}
So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
for _, cve := range cveResult.CVEList {
So(expectedCves, ShouldContain, *cve.ID)
}
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.1", cveInfo, pageInput, "", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.0.1")
expectedCves = []string{"CVE2", "CVE3"}
So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
for _, cve := range cveResult.CVEList {
So(expectedCves, ShouldContain, *cve.ID)
}
cveResult, err = getCVEListForImage(responseContext, "repo1:1.1.0", cveInfo, pageInput, "", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.1.0")
expectedCves = []string{"CVE3"}
So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
for _, cve := range cveResult.CVEList {
So(expectedCves, ShouldContain, *cve.ID)
}
})
Convey("paginated fail", func() {
pageInput := &gql_generated.PageInput{
Limit: ref(-1),
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err = getCVEListForImage(responseContext, "repo1:1.1.0", cveInfo, pageInput, "", log)
So(err, ShouldNotBeNil)
})
})
Convey("Get a list of images affected by a particular CVE ", t, func() {
Convey("Unpaginated request", func() {
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
images, err := getImageListForCVE(responseContext, "CVE1", cveInfo, nil, nil, metaDB, log)
So(err, ShouldBeNil)
expectedImages := []string{
"repo1:1.0.0",
"repo2:2.0.0",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, nil, nil, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:1.0.0", "repo1:1.0.1",
"repo2:2.0.0", "repo2:2.0.1",
"repo3:3.0.1",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, nil, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:1.0.0", "repo1:1.0.1", "repo1:1.1.0", "repo1:latest",
"repo2:2.0.0", "repo2:2.0.1", "repo2:2.1.0", "repo2:latest",
"repo3:3.0.1", "repo3:3.1.0", "repo3:latest",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
})
Convey("paginated fail", func() {
pageInput := &gql_generated.PageInput{
Limit: ref(-1),
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err = getImageListForCVE(responseContext, "repo1:1.1.0", cveInfo, &gql_generated.Filter{},
pageInput, mocks.MetaDBMock{}, log)
So(err, ShouldNotBeNil)
})
Convey("Paginated requests", func() {
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover,
)
pageInput := getPageInput(1, 0)
images, err := getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages := []string{
"repo1:1.0.0",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(1, 1)
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo2:2.0.0",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(1, 2)
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
So(len(images.Results), ShouldEqual, 0)
pageInput = getPageInput(1, 5)
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
So(len(images.Results), ShouldEqual, 0)
pageInput = getPageInput(2, 0)
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:1.0.0",
"repo2:2.0.0",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 0)
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:1.0.0",
"repo2:2.0.0",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 1)
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo2:2.0.0",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 2)
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
So(len(images.Results), ShouldEqual, 0)
pageInput = getPageInput(5, 5)
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
So(len(images.Results), ShouldEqual, 0)
pageInput = getPageInput(5, 0)
images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:1.0.0", "repo1:1.0.1",
"repo2:2.0.0", "repo2:2.0.1",
"repo3:3.0.1",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 3)
images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo2:2.0.1",
"repo3:3.0.1",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 0)
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:1.0.0", "repo1:1.0.1", "repo1:1.1.0", "repo1:latest",
"repo2:2.0.0",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 5)
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo2:2.0.1", "repo2:2.1.0", "repo2:latest",
"repo3:3.0.1", "repo3:3.1.0",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 10)
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo3:latest",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
amdFilter := &gql_generated.Filter{Arch: []*string{&AMD}}
pageInput = getPageInput(5, 0)
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, amdFilter, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:1.0.0", "repo1:1.0.1",
"repo2:2.0.0", "repo2:2.0.1",
"repo3:3.0.1",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(2, 2)
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, amdFilter, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo2:2.0.0", "repo2:2.0.1",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
})
})
Convey("Get a list of images where a particular CVE is fixed", t, func() {
Convey("Unpaginated request", func() {
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
images, err := getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, nil, metaDB, log)
So(err, ShouldBeNil)
expectedImages := []string{
"repo1:1.0.1", "repo1:1.1.0", "repo1:latest",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, nil, nil, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:1.1.0", "repo1:latest",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
images, err = getImageListWithCVEFixed(responseContext, "CVE3", "repo1", cveInfo, nil, nil, metaDB, log)
So(err, ShouldBeNil)
So(len(images.Results), ShouldEqual, 0)
})
Convey("paginated fail", func() {
pageInput := &gql_generated.PageInput{
Limit: ref(-1),
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err = getImageListWithCVEFixed(responseContext, "cve", "repo1:1.1.0", cveInfo, &gql_generated.Filter{},
pageInput, mocks.MetaDBMock{
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"1.1.0": {
Digest: godigest.FromString("str").String(),
MediaType: ispec.MediaTypeImageManifest,
},
},
}, nil
},
}, log)
So(err, ShouldNotBeNil)
})
Convey("Paginated requests", func() {
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover,
)
pageInput := getPageInput(1, 0)
images, err := getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages := []string{
"repo1:1.0.1",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(1, 1)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:1.1.0",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(1, 2)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:latest",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(1, 3)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
So(len(images.Results), ShouldEqual, 0)
pageInput = getPageInput(1, 10)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
So(len(images.Results), ShouldEqual, 0)
pageInput = getPageInput(2, 0)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:1.0.1", "repo1:1.1.0",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(2, 1)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:1.1.0", "repo1:latest",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(2, 2)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:latest",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 0)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:1.0.1", "repo1:1.1.0", "repo1:latest",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 0)
images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{
"repo1:1.1.0", "repo1:latest",
}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 2)
images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
So(len(images.Results), ShouldEqual, 0)
amdFilter := &gql_generated.Filter{Arch: []*string{&AMD}}
armFilter := &gql_generated.Filter{Arch: []*string{&ARM}}
pageInput = getPageInput(3, 0)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, amdFilter, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{"repo1:1.0.1"}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, armFilter, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{"repo1:1.1.0", "repo1:latest"}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(1, 1)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, armFilter, pageInput, metaDB, log)
So(err, ShouldBeNil)
expectedImages = []string{"repo1:latest"}
So(len(images.Results), ShouldEqual, len(expectedImages))
for _, image := range images.Results {
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
})
})
Convey("Errors for cve resolvers", t, func() {
_, err := getImageListForCVE(
context.Background(),
"id",
mocks.CveInfoMock{
GetImageListForCVEFn: func(repo, cveID string) ([]cvemodel.TagInfo, error) {
return []cvemodel.TagInfo{}, ErrTestError
},
},
nil,
nil,
mocks.MetaDBMock{
GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMetadata) bool,
) ([]mTypes.RepoMetadata, error) {
return []mTypes.RepoMetadata{{}}, nil
},
},
log,
)
So(err, ShouldNotBeNil)
})
}
func getPageInput(limit int, offset int) *gql_generated.PageInput {
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
return &gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
}
func TestDerivedImageList(t *testing.T) {
Convey("MetaDB FilterTags error", t, func() {
mockSearchDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context,
filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return make([]mTypes.RepoMetadata, 0), make(map[string]mTypes.ManifestMetadata),
make(map[string]mTypes.IndexData), ErrTestError
},
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{}, ErrTestError
},
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (mTypes.ManifestMetadata, error) {
return mTypes.ManifestMetadata{}, ErrTestError
},
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
images, err := derivedImageList(responseContext, "repo1:1.0.1", nil, mockSearchDB, &gql_generated.PageInput{},
mockCve, log.NewLogger("debug", ""))
So(err, ShouldNotBeNil)
So(images.Results, ShouldBeEmpty)
})
Convey("paginated fail", t, func() {
pageInput := &gql_generated.PageInput{
Limit: ref(-1),
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err := derivedImageList(responseContext, "repo1:1.0.1", nil,
mocks.MetaDBMock{
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"1.0.1": {
Digest: godigest.FromString("str").String(),
MediaType: ispec.MediaTypeImageManifest,
},
},
}, nil
},
},
pageInput,
mocks.CveInfoMock{}, log.NewLogger("debug", ""))
So(err, ShouldNotBeNil)
})
//nolint: dupl
Convey("MetaDB FilterTags no repo available", t, func() {
configBlob, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{},
},
})
So(err, ShouldBeNil)
manifest := ispec.Manifest{}
manifestBlob, err := json.Marshal(manifest)
So(err, ShouldBeNil)
manifestDigest := godigest.FromBytes(manifestBlob)
mockSearchDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context,
filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return []mTypes.RepoMetadata{}, map[string]mTypes.ManifestMetadata{}, map[string]mTypes.IndexData{},
nil
},
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {Digest: manifestDigest.String(), MediaType: ispec.MediaTypeImageManifest},
},
}, nil
},
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (mTypes.ManifestMetadata, error) {
return mTypes.ManifestMetadata{
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
}, nil
},
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
images, err := derivedImageList(responseContext, "repo1:1.0.1", nil, mockSearchDB, &gql_generated.PageInput{},
mockCve, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
So(images.Results, ShouldBeEmpty)
})
//nolint: dupl
Convey("derived image list working", t, func() {
configBlob, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{},
},
})
So(err, ShouldBeNil)
configDigest := godigest.FromBytes(configBlob)
layers := [][]byte{
{10, 11, 10, 11},
{11, 11, 11, 11},
{10, 10, 10, 11},
{13, 14, 15, 11},
}
manifestBlob, err := json.Marshal(ispec.Manifest{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
Config: ispec.Descriptor{
MediaType: "application/vnd.oci.image.config.v1+json",
Digest: configDigest,
Size: int64(len(configBlob)),
},
Layers: []ispec.Descriptor{
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[0]),
Size: int64(len(layers[0])),
},
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[1]),
Size: int64(len(layers[1])),
},
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[2]),
Size: int64(len(layers[2])),
},
},
})
So(err, ShouldBeNil)
derivedManifestBlob, err := json.Marshal(ispec.Manifest{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
Config: ispec.Descriptor{
MediaType: "application/vnd.oci.image.config.v1+json",
Digest: configDigest,
Size: int64(len(configBlob)),
},
Layers: []ispec.Descriptor{
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[0]),
Size: int64(len(layers[0])),
},
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[1]),
Size: int64(len(layers[1])),
},
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[2]),
Size: int64(len(layers[2])),
},
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[3]),
Size: int64(len(layers[3])),
},
},
})
So(err, ShouldBeNil)
manifestsMeta := map[string]mTypes.ManifestMetadata{
"digestTag1.0.1": {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
DownloadCount: 100,
Signatures: make(mTypes.ManifestSignatures),
},
"digestTag1.0.2": {
ManifestBlob: derivedManifestBlob,
ConfigBlob: configBlob,
DownloadCount: 100,
Signatures: make(mTypes.ManifestSignatures),
},
"digestTag1.0.3": {
ManifestBlob: derivedManifestBlob,
ConfigBlob: configBlob,
DownloadCount: 100,
Signatures: make(mTypes.ManifestSignatures),
},
}
manifestDigest := godigest.FromBytes(manifestBlob)
mockSearchDB := mocks.MetaDBMock{
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {Digest: manifestDigest.String(), MediaType: ispec.MediaTypeImageManifest},
},
}, nil
},
GetManifestDataFn: func(manifestDigest godigest.Digest) (mTypes.ManifestData, error) {
return mTypes.ManifestData{
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
}, nil
},
FilterTagsFn: func(ctx context.Context,
filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {Digest: "digestTag1.0.1", MediaType: ispec.MediaTypeImageManifest},
"1.0.2": {Digest: "digestTag1.0.2", MediaType: ispec.MediaTypeImageManifest},
"1.0.3": {Digest: "digestTag1.0.3", MediaType: ispec.MediaTypeImageManifest},
},
Stars: 100,
},
}
for i, repo := range repos {
matchedTags := repo.Tags
for tag, descriptor := range repo.Tags {
if !filterFunc(repo, manifestsMeta[descriptor.Digest]) {
delete(matchedTags, tag)
delete(manifestsMeta, descriptor.Digest)
continue
}
}
repos[i].Tags = matchedTags
}
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
},
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
Convey("valid derivedImageList, results not affected by pageInput", func() {
images, err := derivedImageList(responseContext, "repo1:1.0.1", nil, mockSearchDB, &gql_generated.PageInput{},
mockCve, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
So(images.Results, ShouldNotBeEmpty)
So(len(images.Results), ShouldEqual, 2)
})
Convey("valid derivedImageList, results affected by pageInput", func() {
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
images, err := derivedImageList(responseContext, "repo1:1.0.1", nil, mockSearchDB, &pageInput,
mockCve, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
So(images.Results, ShouldNotBeEmpty)
So(len(images.Results), ShouldEqual, limit)
})
})
}
func TestBaseImageList(t *testing.T) {
Convey("MetaDB FilterTags error", t, func() {
mockSearchDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context,
filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return []mTypes.RepoMetadata{}, map[string]mTypes.ManifestMetadata{}, map[string]mTypes.IndexData{},
ErrTestError
},
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{}, ErrTestError
},
GetManifestDataFn: func(manifestDigest godigest.Digest) (mTypes.ManifestData, error) {
return mTypes.ManifestData{}, ErrTestError
},
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockSearchDB, &gql_generated.PageInput{},
mockCve, log.NewLogger("debug", ""))
So(err, ShouldNotBeNil)
So(images.Results, ShouldBeEmpty)
})
Convey("paginated fail", t, func() {
pageInput := &gql_generated.PageInput{
Limit: ref(-1),
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err := baseImageList(responseContext, "repo1:1.0.2", nil,
mocks.MetaDBMock{
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"1.0.2": {
Digest: godigest.FromString("str").String(),
MediaType: ispec.MediaTypeImageManifest,
},
},
}, nil
},
},
pageInput, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
So(err, ShouldNotBeNil)
})
//nolint: dupl
Convey("MetaDB FilterTags no repo available", t, func() {
configBlob, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{},
},
})
So(err, ShouldBeNil)
manifest := ispec.Manifest{}
manifestBlob, err := json.Marshal(manifest)
So(err, ShouldBeNil)
manifestDigest := godigest.FromBytes(manifestBlob)
mockSearchDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context,
filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
return []mTypes.RepoMetadata{}, map[string]mTypes.ManifestMetadata{}, map[string]mTypes.IndexData{},
nil
},
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.2": {Digest: manifestDigest.String(), MediaType: ispec.MediaTypeImageManifest},
},
}, nil
},
GetManifestDataFn: func(manifestDigest godigest.Digest) (mTypes.ManifestData, error) {
return mTypes.ManifestData{
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
}, nil
},
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockSearchDB, &gql_generated.PageInput{},
mockCve, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
So(images.Results, ShouldBeEmpty)
})
//nolint: dupl
Convey("base image list working", t, func() {
configBlob, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{},
},
})
So(err, ShouldBeNil)
configDigest := godigest.FromBytes(configBlob)
layers := [][]byte{
{10, 11, 10, 11},
{11, 11, 11, 11},
{10, 10, 10, 11},
{13, 14, 15, 11},
}
manifestBlob, err := json.Marshal(ispec.Manifest{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
Config: ispec.Descriptor{
MediaType: "application/vnd.oci.image.config.v1+json",
Digest: configDigest,
Size: int64(len(configBlob)),
},
Layers: []ispec.Descriptor{
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[0]),
Size: int64(len(layers[0])),
},
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[1]),
Size: int64(len(layers[1])),
},
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[2]),
Size: int64(len(layers[2])),
},
},
})
So(err, ShouldBeNil)
derivedManifestBlob, err := json.Marshal(ispec.Manifest{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
Config: ispec.Descriptor{
MediaType: "application/vnd.oci.image.config.v1+json",
Digest: configDigest,
Size: int64(len(configBlob)),
},
Layers: []ispec.Descriptor{
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[0]),
Size: int64(len(layers[0])),
},
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[1]),
Size: int64(len(layers[1])),
},
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[2]),
Size: int64(len(layers[2])),
},
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[3]),
Size: int64(len(layers[3])),
},
},
})
So(err, ShouldBeNil)
manifestsMeta := map[string]mTypes.ManifestMetadata{
"digestTag1.0.1": {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
DownloadCount: 100,
Signatures: make(mTypes.ManifestSignatures),
},
"digestTag1.0.2": {
ManifestBlob: derivedManifestBlob,
ConfigBlob: configBlob,
DownloadCount: 100,
Signatures: make(mTypes.ManifestSignatures),
},
}
derivedManifestDigest := godigest.FromBytes(derivedManifestBlob)
mockSearchDB := mocks.MetaDBMock{
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.2": {Digest: derivedManifestDigest.String(), MediaType: ispec.MediaTypeImageManifest},
},
}, nil
},
GetManifestDataFn: func(manifestDigest godigest.Digest) (mTypes.ManifestData, error) {
return mTypes.ManifestData{
ManifestBlob: derivedManifestBlob,
ConfigBlob: configBlob,
}, nil
},
FilterTagsFn: func(ctx context.Context,
filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {Digest: "digestTag1.0.1", MediaType: ispec.MediaTypeImageManifest},
"1.0.3": {Digest: "digestTag1.0.1", MediaType: ispec.MediaTypeImageManifest},
"1.0.2": {Digest: "digestTag1.0.2", MediaType: ispec.MediaTypeImageManifest},
},
Stars: 100,
},
}
for i, repo := range repos {
matchedTags := repo.Tags
for tag, descriptor := range repo.Tags {
if !filterFunc(repo, manifestsMeta[descriptor.Digest]) {
delete(matchedTags, tag)
delete(manifestsMeta, descriptor.Digest)
continue
}
}
repos[i].Tags = matchedTags
}
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
},
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
Convey("valid baseImageList, results not affected by pageInput", func() {
images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockSearchDB,
&gql_generated.PageInput{}, mockCve, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
So(images.Results, ShouldNotBeEmpty)
So(len(images.Results), ShouldEqual, 2)
expectedTags := []string{"1.0.1", "1.0.3"}
So(expectedTags, ShouldContain, *images.Results[0].Tag)
So(expectedTags, ShouldContain, *images.Results[1].Tag)
})
Convey("valid baseImageList, results affected by pageInput", func() {
limit := 1
offset := 0
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
pageInput := gql_generated.PageInput{
Limit: &limit,
Offset: &offset,
SortBy: &sortCriteria,
}
images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockSearchDB,
&pageInput, mockCve, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
So(images.Results, ShouldNotBeEmpty)
So(len(images.Results), ShouldEqual, limit)
So(*images.Results[0].Tag, ShouldEqual, "1.0.1")
})
})
//nolint: dupl
Convey("filterTags working, no base image list found", t, func() {
configBlob, err := json.Marshal(ispec.Image{
Config: ispec.ImageConfig{
Labels: map[string]string{},
},
})
So(err, ShouldBeNil)
configDigest := godigest.FromBytes(configBlob)
layers := [][]byte{
{10, 11, 10, 11},
{11, 11, 11, 11},
{10, 10, 10, 11},
{13, 14, 15, 11},
}
manifestBlob, err := json.Marshal(ispec.Manifest{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
Config: ispec.Descriptor{
MediaType: "application/vnd.oci.image.config.v1+json",
Digest: configDigest,
Size: int64(len(configBlob)),
},
Layers: []ispec.Descriptor{
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[0]),
Size: int64(len(layers[0])),
},
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[1]),
Size: int64(len(layers[1])),
},
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[2]),
Size: int64(len(layers[2])),
},
},
})
So(err, ShouldBeNil)
derivedManifestBlob, err := json.Marshal(ispec.Manifest{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
Config: ispec.Descriptor{
MediaType: "application/vnd.oci.image.config.v1+json",
Digest: configDigest,
Size: int64(len(configBlob)),
},
Layers: []ispec.Descriptor{
{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layers[3]),
Size: int64(len(layers[3])),
},
},
})
So(err, ShouldBeNil)
manifestsMeta := map[string]mTypes.ManifestMetadata{
"digestTag1.0.1": {
ManifestBlob: manifestBlob,
ConfigBlob: configBlob,
DownloadCount: 100,
Signatures: make(mTypes.ManifestSignatures),
},
"digestTag1.0.2": {
ManifestBlob: derivedManifestBlob,
ConfigBlob: configBlob,
DownloadCount: 100,
Signatures: make(mTypes.ManifestSignatures),
},
}
derivedManifestDigest := godigest.FromBytes(derivedManifestBlob)
mockSearchDB := mocks.MetaDBMock{
GetRepoMetaFn: func(repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.2": {Digest: derivedManifestDigest.String(), MediaType: ispec.MediaTypeImageManifest},
},
}, nil
},
GetManifestDataFn: func(manifestDigest godigest.Digest) (mTypes.ManifestData, error) {
return mTypes.ManifestData{
ManifestBlob: derivedManifestBlob,
ConfigBlob: configBlob,
}, nil
},
FilterTagsFn: func(ctx context.Context,
filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMetadata, map[string]mTypes.ManifestMetadata, map[string]mTypes.IndexData,
error,
) {
repos := []mTypes.RepoMetadata{
{
Name: "repo1",
Tags: map[string]mTypes.Descriptor{
"1.0.1": {Digest: "digestTag1.0.1", MediaType: ispec.MediaTypeImageManifest},
"1.0.2": {Digest: "digestTag1.0.2", MediaType: ispec.MediaTypeImageManifest},
},
Stars: 100,
},
}
for i, repo := range repos {
matchedTags := repo.Tags
for tag, descriptor := range repo.Tags {
if !filterFunc(repo, manifestsMeta[descriptor.Digest]) {
delete(matchedTags, tag)
delete(manifestsMeta, descriptor.Digest)
continue
}
}
repos[i].Tags = matchedTags
}
return repos, manifestsMeta, map[string]mTypes.IndexData{}, nil
},
}
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
mockCve := mocks.CveInfoMock{}
images, err := baseImageList(responseContext, "repo1:1.0.2", nil, mockSearchDB, &gql_generated.PageInput{},
mockCve, log.NewLogger("debug", ""))
So(err, ShouldBeNil)
So(images.Results, ShouldBeEmpty)
})
}
func TestExpandedRepoInfo(t *testing.T) {
log := log.NewLogger("debug", "")
Convey("ExpandedRepoInfo Errors", t, func() {
responseContext := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
metaDB := mocks.MetaDBMock{
GetUserRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMetadata, error) {
return mTypes.RepoMetadata{
Tags: map[string]mTypes.Descriptor{
"tagManifest": {
Digest: "errorDigest",
MediaType: ispec.MediaTypeImageManifest,
},
"tagIndex": {
Digest: "digestIndex",
MediaType: ispec.MediaTypeImageIndex,
},
"tagGetIndexError": {
Digest: "errorIndexDigest",
MediaType: ispec.MediaTypeImageIndex,
},
"tagGoodIndexBadManifests": {
Digest: "goodIndexBadManifests",
MediaType: ispec.MediaTypeImageIndex,
},
"tagGoodIndex1GoodManifest": {
Digest: "goodIndexGoodManifest",
MediaType: ispec.MediaTypeImageIndex,
},
"tagGoodIndex2GoodManifest": {
Digest: "goodIndexGoodManifest",
MediaType: ispec.MediaTypeImageIndex,
},
},
}, nil
},
GetManifestDataFn: func(manifestDigest godigest.Digest) (mTypes.ManifestData, error) {
switch manifestDigest {
case "errorDigest":
return mTypes.ManifestData{}, ErrTestError
default:
return mTypes.ManifestData{
ManifestBlob: []byte("{}"),
ConfigBlob: []byte("{}"),
}, nil
}
},
GetIndexDataFn: func(indexDigest godigest.Digest) (mTypes.IndexData, error) {
goodIndexBadManifestsBlob, err := json.Marshal(ispec.Index{
Manifests: []ispec.Descriptor{
{
Digest: "errorDigest",
MediaType: ispec.MediaTypeImageManifest,
},
},
})
So(err, ShouldBeNil)
goodIndexGoodManifestBlob, err := json.Marshal(ispec.Index{
Manifests: []ispec.Descriptor{
{
Digest: "goodManifest",
MediaType: ispec.MediaTypeImageManifest,
},
},
})
So(err, ShouldBeNil)
switch indexDigest {
case "errorIndexDigest":
return mTypes.IndexData{}, ErrTestError
case "goodIndexBadManifests":
return mTypes.IndexData{
IndexBlob: goodIndexBadManifestsBlob,
}, nil
case "goodIndexGoodManifest":
return mTypes.IndexData{
IndexBlob: goodIndexGoodManifestBlob,
}, nil
default:
return mTypes.IndexData{}, nil
}
},
}
_, err := expandedRepoInfo(responseContext, "repo", metaDB, mocks.CveInfoMock{}, log)
So(err, ShouldBeNil)
})
Convey("Access error", t, func() {
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("user")
userAc.SetGlobPatterns("read", map[string]bool{
"repo": false,
})
ctx := userAc.DeriveContext(context.Background())
responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter,
graphql.DefaultRecover)
_, err := expandedRepoInfo(responseContext, "repo", mocks.MetaDBMock{}, mocks.CveInfoMock{}, log)
So(err, ShouldBeNil)
})
}
func TestFilterFunctions(t *testing.T) {
Convey("Filter Functions", t, func() {
Convey("FilterByDigest bad manifest blob", func() {
filterFunc := FilterByDigest("digest")
ok := filterFunc(
mTypes.RepoMetadata{},
mTypes.ManifestMetadata{
ManifestBlob: []byte("bad blob"),
},
)
So(ok, ShouldBeFalse)
})
Convey("filterDerivedImages bad manifest blob", func() {
filterFunc := filterDerivedImages(&gql_generated.ImageSummary{})
ok := filterFunc(
mTypes.RepoMetadata{},
mTypes.ManifestMetadata{
ManifestBlob: []byte("bad blob"),
},
)
So(ok, ShouldBeFalse)
})
Convey("FilterByTagInfo", func() {
fFunc := FilterByTagInfo([]cvemodel.TagInfo{
{
Descriptor: cvemodel.Descriptor{
MediaType: ispec.MediaTypeImageIndex,
},
Manifests: []cvemodel.DescriptorInfo{
{
Descriptor: cvemodel.Descriptor{
Digest: godigest.FromString("{}"),
},
},
},
},
})
ok := fFunc(mTypes.RepoMetadata{}, mTypes.ManifestMetadata{ManifestBlob: []byte("{}")})
So(ok, ShouldBeTrue)
})
})
}
func ref[T any](val T) *T {
ref := val
return &ref
}