0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00

feat(metadb): add support for querying for images by a blob digest (#2077)

Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
LaurentiuNiculae 2023-11-27 18:52:52 +02:00 committed by GitHub
parent 02a8ed7854
commit 0de2210686
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 273 additions and 80 deletions

View file

@ -160,6 +160,7 @@ var (
ErrBadHTTPStatusCode = errors.New("cli: the response doesn't contain the expected status code")
ErrFormatNotSupported = errors.New("cli: the given output format is not supported")
ErrAPINotSupported = errors.New("registry at the given address doesn't implement the correct API")
ErrInvalidSearchQuery = errors.New("invalid search query")
ErrFileAlreadyCancelled = errors.New("storageDriver: file already cancelled")
ErrFileAlreadyClosed = errors.New("storageDriver: file already closed")
ErrFileAlreadyCommitted = errors.New("storageDriver: file already committed")

View file

@ -76,7 +76,12 @@ func FilterByDigest(digest string) mTypes.FilterFunc {
// imageMeta will always contain 1 manifest
return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
lookupDigest := digest
contains := false
// Check in case of an index if the index digest matches the search digest
// For Manifests, this is equivalent to imageMeta.Manifests[0]
if imageMeta.Digest.String() == lookupDigest {
return true
}
manifest := imageMeta.Manifests[0]
@ -85,24 +90,24 @@ func FilterByDigest(digest string) mTypes.FilterFunc {
// Check the image manifest in index.json matches the search digest
// This is a blob with mediaType application/vnd.oci.image.manifest.v1+json
if strings.Contains(manifestDigest, lookupDigest) {
contains = true
return true
}
// Check the image config matches the search digest
// This is a blob with mediaType application/vnd.oci.image.config.v1+json
if strings.Contains(manifest.Manifest.Config.Digest.String(), lookupDigest) {
contains = true
return true
}
// Check to see if the individual layers in the oci image manifest match the digest
// These are blobs with mediaType application/vnd.oci.image.layer.v1.tar+gzip
for _, layer := range manifest.Manifest.Layers {
if strings.Contains(layer.Digest.String(), lookupDigest) {
contains = true
return true
}
}
return contains
return false
}
}
@ -628,18 +633,10 @@ func globalSearch(ctx context.Context, query string, metaDB mTypes.MetaDB, filte
}
}
if searchingForRepos(query) {
skip := convert.SkipQGLField{
Vulnerabilities: canSkipField(preloads, "Repos.NewestImage.Vulnerabilities"),
}
pageInput := pagination.PageInput{
Limit: deref(requestedPage.Limit, 0),
Offset: deref(requestedPage.Offset, 0),
SortBy: pagination.SortCriteria(
deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
),
}
switch getSearchTarget(query) {
case RepoTarget:
skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Repos.NewestImage.Vulnerabilities")}
pageInput := getPageInput(requestedPage)
repoMetaList, err := metaDB.SearchRepos(ctx, query)
if err != nil {
@ -667,18 +664,9 @@ func globalSearch(ctx context.Context, query string, metaDB mTypes.MetaDB, filte
}
paginatedRepos.Results = repos
} else { // search for images
skip := convert.SkipQGLField{
Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities"),
}
pageInput := pagination.PageInput{
Limit: deref(requestedPage.Limit, 0),
Offset: deref(requestedPage.Offset, 0),
SortBy: pagination.SortCriteria(
deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
),
}
case ImageTarget:
skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities")}
pageInput := getPageInput(requestedPage)
fullImageMetaList, err := metaDB.SearchTags(ctx, query)
if err != nil {
@ -697,11 +685,71 @@ func globalSearch(ctx context.Context, query string, metaDB mTypes.MetaDB, filte
TotalCount: pageInfo.TotalCount,
ItemCount: pageInfo.ItemCount,
}
case DigestTarget:
skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities")}
pageInput := getPageInput(requestedPage)
searchedDigest := query
fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByDigest(searchedDigest))
if err != nil {
return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
}
imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo,
localFilter, pageInput)
if err != nil {
return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
}
images = imageSummaries
paginatedRepos.Page = &gql_generated.PageInfo{
TotalCount: pageInfo.TotalCount,
ItemCount: pageInfo.ItemCount,
}
default:
return &paginatedRepos, images, layers, zerr.ErrInvalidSearchQuery
}
return &paginatedRepos, images, layers, nil
}
func getPageInput(requestedPage *gql_generated.PageInput) pagination.PageInput {
return pagination.PageInput{
Limit: deref(requestedPage.Limit, 0),
Offset: deref(requestedPage.Offset, 0),
SortBy: pagination.SortCriteria(
deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
),
}
}
type SearchTarget int
const (
RepoTarget = iota
ImageTarget
DigestTarget
InvalidTarget
)
func getSearchTarget(query string) SearchTarget {
if !strings.ContainsAny(query, ":@") {
return RepoTarget
}
if strings.HasPrefix(query, string(godigest.SHA256)+":") {
return DigestTarget
}
if before, _, found := strings.Cut(query, ":"); found && before != "" {
return ImageTarget
}
return InvalidTarget
}
func canSkipField(preloads map[string]bool, s string) bool {
fieldIsPresent := preloads[s]
@ -1100,10 +1148,6 @@ func deref[T any](pointer *T, defaultVal T) T {
return defaultVal
}
func searchingForRepos(query string) bool {
return !strings.Contains(query, ":")
}
func getImageList(ctx context.Context, repo string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo,
requestedPage *gql_generated.PageInput, log log.Logger, //nolint:unparam
) (*gql_generated.PaginatedImagesResult, error) {

View file

@ -88,6 +88,52 @@ func TestResolverGlobalSearch(t *testing.T) {
So(layers, ShouldBeEmpty)
So(repos.Results, ShouldBeEmpty)
})
Convey("Searching by digest", func() {
ctx := context.Background()
query := "sha256:aabb12341baf2"
mockMetaDB := mocks.MetaDBMock{
FilterTagsFn: func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc,
filterFunc mTypes.FilterFunc,
) ([]mTypes.FullImageMeta, error) {
return []mTypes.FullImageMeta{}, ErrTestError
},
}
responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover)
repos, images, layers, err := globalSearch(responseContext, query, mockMetaDB, &gql_generated.Filter{},
&gql_generated.PageInput{}, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
So(err, ShouldNotBeNil)
So(images, ShouldBeEmpty)
So(layers, ShouldBeEmpty)
So(repos.Results, ShouldBeEmpty)
})
Convey("Searching by digest with bad pagination", func() {
ctx := context.Background()
query := "sha256:aabb12341baf2"
responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover)
repos, images, layers, err := globalSearch(responseContext, query, mocks.MetaDBMock{}, &gql_generated.Filter{},
&gql_generated.PageInput{Limit: ref(-10)}, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
So(err, ShouldNotBeNil)
So(images, ShouldBeEmpty)
So(layers, ShouldBeEmpty)
So(repos.Results, ShouldBeEmpty)
})
Convey("Searching with a bad query", func() {
ctx := context.Background()
query := ":test"
responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover)
repos, images, layers, err := globalSearch(responseContext, query, mocks.MetaDBMock{}, &gql_generated.Filter{},
&gql_generated.PageInput{}, mocks.CveInfoMock{}, log.NewLogger("debug", ""))
So(err, ShouldNotBeNil)
So(images, ShouldBeEmpty)
So(layers, ShouldBeEmpty)
So(repos.Results, ShouldBeEmpty)
})
})
}
@ -647,7 +693,7 @@ func TestQueryResolverErrors(t *testing.T) {
}, mocks.CveInfoMock{})
resolver := queryResolver{resolverConfig}
_, err := resolver.GlobalSearch(ctx, "some_string", &gql_generated.Filter{}, getPageInput(1, 1))
_, err := resolver.GlobalSearch(ctx, "some_string", &gql_generated.Filter{}, getGQLPageInput(1, 1))
So(err, ShouldNotBeNil)
})
@ -1240,7 +1286,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
})
Convey("context done", func() {
pageInput := getPageInput(1, 0)
pageInput := getGQLPageInput(1, 0)
ctx, cancel := context.WithCancel(ctx)
cancel()
@ -1270,7 +1316,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
graphql.DefaultRecover,
)
pageInput := getPageInput(1, 0)
pageInput := getGQLPageInput(1, 0)
images, err := getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1284,7 +1330,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(1, 1)
pageInput = getGQLPageInput(1, 1)
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1298,19 +1344,19 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(1, 2)
pageInput = getGQLPageInput(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)
pageInput = getGQLPageInput(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)
pageInput = getGQLPageInput(2, 0)
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1325,7 +1371,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 0)
pageInput = getGQLPageInput(5, 0)
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1340,7 +1386,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 1)
pageInput = getGQLPageInput(5, 1)
images, err = getImageListForCVE(responseContext, "CVE1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1354,19 +1400,19 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 2)
pageInput = getGQLPageInput(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)
pageInput = getGQLPageInput(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)
pageInput = getGQLPageInput(5, 0)
images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1382,7 +1428,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 3)
pageInput = getGQLPageInput(5, 3)
images, err = getImageListForCVE(responseContext, "CVE2", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1397,7 +1443,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 0)
pageInput = getGQLPageInput(5, 0)
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1412,7 +1458,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 5)
pageInput = getGQLPageInput(5, 5)
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1427,7 +1473,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 10)
pageInput = getGQLPageInput(5, 10)
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1442,7 +1488,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
}
amdFilter := &gql_generated.Filter{Arch: []*string{&AMD}}
pageInput = getPageInput(5, 0)
pageInput = getGQLPageInput(5, 0)
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, amdFilter, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1458,7 +1504,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(2, 2)
pageInput = getGQLPageInput(2, 2)
images, err = getImageListForCVE(responseContext, "CVE3", cveInfo, amdFilter, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1548,7 +1594,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
graphql.DefaultRecover,
)
pageInput := getPageInput(1, 0)
pageInput := getGQLPageInput(1, 0)
images, err := getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1562,7 +1608,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(1, 1)
pageInput = getGQLPageInput(1, 1)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1576,7 +1622,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(1, 2)
pageInput = getGQLPageInput(1, 2)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1590,19 +1636,19 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(1, 3)
pageInput = getGQLPageInput(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)
pageInput = getGQLPageInput(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)
pageInput = getGQLPageInput(2, 0)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1616,7 +1662,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(2, 1)
pageInput = getGQLPageInput(2, 1)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1630,7 +1676,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(2, 2)
pageInput = getGQLPageInput(2, 2)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1644,7 +1690,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 0)
pageInput = getGQLPageInput(5, 0)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1658,7 +1704,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 0)
pageInput = getGQLPageInput(5, 0)
images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1672,7 +1718,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(5, 2)
pageInput = getGQLPageInput(5, 2)
images, err = getImageListWithCVEFixed(responseContext, "CVE2", "repo1", cveInfo, nil, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1681,7 +1727,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
amdFilter := &gql_generated.Filter{Arch: []*string{&AMD}}
armFilter := &gql_generated.Filter{Arch: []*string{&ARM}}
pageInput = getPageInput(3, 0)
pageInput = getGQLPageInput(3, 0)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, amdFilter, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -1703,7 +1749,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(fmt.Sprintf("%s:%s", *image.RepoName, *image.Tag), ShouldBeIn, expectedImages)
}
pageInput = getPageInput(1, 1)
pageInput = getGQLPageInput(1, 1)
images, err = getImageListWithCVEFixed(responseContext, "CVE1", "repo1", cveInfo, armFilter, pageInput, metaDB, log)
So(err, ShouldBeNil)
@ -2213,7 +2259,7 @@ func ref[T any](input T) *T {
return &ref
}
func getPageInput(limit int, offset int) *gql_generated.PageInput {
func getGQLPageInput(limit int, offset int) *gql_generated.PageInput {
sortCriteria := gql_generated.SortCriteriaAlphabeticAsc
return &gql_generated.PageInput{

View file

@ -3651,6 +3651,71 @@ func TestGlobalSearch(t *testing.T) {
So(actualImageSummary.Vulnerabilities.Count, ShouldEqual, 4)
So(actualImageSummary.Vulnerabilities.MaxSeverity, ShouldEqual, "CRITICAL")
})
Convey("global searching by digest", t, func() {
log := log.NewLogger("debug", "")
rootDir := t.TempDir()
port := GetFreePort()
baseURL := GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = rootDir
defaultVal := true
conf.Extensions = &extconf.ExtensionConfig{
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
}
conf.Extensions.Search.CVE = nil
ctlr := api.NewController(conf)
ctlrManager := NewControllerManager(ctlr)
storeCtlr := ociutils.GetDefaultStoreController(rootDir, log)
image1 := CreateRandomImage()
image2 := CreateRandomImage()
multiArch := CreateRandomMultiarch()
err := WriteImageToFileSystem(image1, "repo1", "tag1", storeCtlr)
So(err, ShouldBeNil)
err = WriteImageToFileSystem(image2, "repo1", "tag2", storeCtlr)
So(err, ShouldBeNil)
err = WriteMultiArchImageToFileSystem(multiArch, "repo1", "tag-multi", storeCtlr)
So(err, ShouldBeNil)
err = WriteImageToFileSystem(image2, "repo2", "tag2", storeCtlr)
So(err, ShouldBeNil)
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
// simple image
results := GlobalSearchGQL(image1.DigestStr(), baseURL).GlobalSearch
So(len(results.Images), ShouldEqual, 1)
So(results.Images[0].Digest, ShouldResemble, image1.DigestStr())
So(results.Images[0].RepoName, ShouldResemble, "repo1")
results = GlobalSearchGQL(image2.DigestStr(), baseURL).GlobalSearch
So(len(results.Images), ShouldEqual, 2)
repos := AccumulateField(results.Images,
func(is zcommon.ImageSummary) string { return is.RepoName })
So(repos, ShouldContain, "repo1")
So(repos, ShouldContain, "repo2")
// multiarch
results = GlobalSearchGQL(multiArch.DigestStr(), baseURL).GlobalSearch
So(len(results.Images), ShouldEqual, 1)
So(results.Images[0].Digest, ShouldResemble, multiArch.DigestStr())
So(len(results.Images[0].Manifests), ShouldEqual, len(multiArch.Images))
So(results.Images[0].RepoName, ShouldResemble, "repo1")
results = GlobalSearchGQL(multiArch.Images[0].DigestStr(), baseURL).GlobalSearch
So(len(results.Images), ShouldEqual, 1)
So(results.Images[0].Digest, ShouldResemble, multiArch.DigestStr())
So(len(results.Images[0].Manifests), ShouldEqual, 1)
So(results.Images[0].Manifests[0].Digest, ShouldResemble, multiArch.Images[0].DigestStr())
So(results.Images[0].RepoName, ShouldResemble, "repo1")
})
}
func TestCleaningFilteringParamsGlobalSearch(t *testing.T) {

View file

@ -639,16 +639,17 @@ func (bdw *BoltDB) FilterTags(ctx context.Context, filterRepoTag mTypes.FilterRe
case ispec.MediaTypeImageIndex:
indexDigest := descriptor.Digest
imageIndexData, err := getProtoImageMeta(imageMetaBuck, indexDigest)
protoImageIndexMeta, err := getProtoImageMeta(imageMetaBuck, indexDigest)
if err != nil {
viewError = errors.Join(viewError, err)
continue
}
imageIndexMeta := mConvert.GetImageMeta(protoImageIndexMeta)
matchedManifests := []*proto_go.ManifestMeta{}
for _, manifest := range imageIndexData.Index.Index.Manifests {
for _, manifest := range protoImageIndexMeta.Index.Index.Manifests {
manifestDigest := manifest.Digest
imageManifestData, err := getProtoImageMeta(imageMetaBuck, manifestDigest)
@ -659,16 +660,17 @@ func (bdw *BoltDB) FilterTags(ctx context.Context, filterRepoTag mTypes.FilterRe
}
imageMeta := mConvert.GetImageMeta(imageManifestData)
partialImageMeta := common.GetPartialImageMeta(imageIndexMeta, imageMeta)
if filterFunc(repoMeta, imageMeta) {
if filterFunc(repoMeta, partialImageMeta) {
matchedManifests = append(matchedManifests, imageManifestData.Manifests[0])
}
}
if len(matchedManifests) > 0 {
imageIndexData.Manifests = matchedManifests
protoImageIndexMeta.Manifests = matchedManifests
images = append(images, mConvert.GetFullImageMetaFromProto(tag, protoRepoMeta, imageIndexData))
images = append(images, mConvert.GetFullImageMetaFromProto(tag, protoRepoMeta, protoImageIndexMeta))
}
default:
bdw.Log.Error().Str("mediaType", descriptor.MediaType).Msg("Unsupported media type")

View file

@ -386,3 +386,35 @@ func GetAnnotationValue(annotations map[string]string, annotationKey, labelKey s
return value
}
func GetPartialImageMeta(imageIndexMeta mTypes.ImageMeta, imageMeta mTypes.ImageMeta) mTypes.ImageMeta {
partialImageMeta := imageIndexMeta
partialImageMeta.Manifests = imageMeta.Manifests
partialIndex := deref(imageIndexMeta.Index, ispec.Index{})
partialIndex.Manifests = getPartialManifestList(partialIndex.Manifests, imageMeta.Digest.String())
partialImageMeta.Index = &partialIndex
return partialImageMeta
}
func getPartialManifestList(descriptors []ispec.Descriptor, manifestDigest string) []ispec.Descriptor {
result := []ispec.Descriptor{}
for i := range descriptors {
if descriptors[i].Digest.String() == manifestDigest {
result = append(result, descriptors[i])
}
}
return result
}
func deref[T any](pointer *T, defaultVal T) T {
if pointer != nil {
return *pointer
}
return defaultVal
}

View file

@ -14,6 +14,10 @@ import (
var ErrTestError = errors.New("test error")
func TestUtils(t *testing.T) {
Convey("GetPartialImageMeta", t, func() {
So(func() { common.GetPartialImageMeta(mTypes.ImageMeta{}, mTypes.ImageMeta{}) }, ShouldNotPanic)
})
Convey("MatchesArtifactTypes", t, func() {
res := common.MatchesArtifactTypes("", nil)
So(res, ShouldBeTrue)

View file

@ -528,9 +528,9 @@ func GetImageMeta(dbImageMeta *proto_go.ImageMeta) mTypes.ImageMeta {
if dbImageMeta.MediaType == ispec.MediaTypeImageIndex {
manifests := make([]ispec.Descriptor, 0, len(dbImageMeta.Manifests))
for _, manifest := range dbImageMeta.Manifests {
for _, manifest := range deref(dbImageMeta.Index, proto_go.IndexMeta{}).Index.Manifests {
manifests = append(manifests, ispec.Descriptor{
MediaType: deref(manifest.Manifest.MediaType, ""),
MediaType: manifest.MediaType,
Digest: godigest.Digest(manifest.Digest),
Size: manifest.Size,
})

View file

@ -729,16 +729,17 @@ func (dwr *DynamoDB) FilterTags(ctx context.Context, filterRepoTag mTypes.Filter
case ispec.MediaTypeImageIndex:
indexDigest := descriptor.Digest
imageIndexData, err := dwr.GetProtoImageMeta(ctx, godigest.Digest(indexDigest))
protoImageIndexMeta, err := dwr.GetProtoImageMeta(ctx, godigest.Digest(indexDigest))
if err != nil {
viewError = errors.Join(viewError, err)
continue
}
imageIndexMeta := mConvert.GetImageMeta(protoImageIndexMeta)
matchedManifests := []*proto_go.ManifestMeta{}
for _, manifest := range imageIndexData.Index.Index.Manifests {
for _, manifest := range protoImageIndexMeta.Index.Index.Manifests {
manifestDigest := manifest.Digest
imageManifestData, err := dwr.GetProtoImageMeta(ctx, godigest.Digest(manifestDigest))
@ -749,16 +750,17 @@ func (dwr *DynamoDB) FilterTags(ctx context.Context, filterRepoTag mTypes.Filter
}
imageMeta := mConvert.GetImageMeta(imageManifestData)
partialImageMeta := common.GetPartialImageMeta(imageIndexMeta, imageMeta)
if filterFunc(repoMeta, imageMeta) {
if filterFunc(repoMeta, partialImageMeta) {
matchedManifests = append(matchedManifests, imageManifestData.Manifests[0])
}
}
if len(matchedManifests) > 0 {
imageIndexData.Manifests = matchedManifests
protoImageIndexMeta.Manifests = matchedManifests
images = append(images, mConvert.GetFullImageMetaFromProto(tag, protoRepoMeta, imageIndexData))
images = append(images, mConvert.GetFullImageMetaFromProto(tag, protoRepoMeta, protoImageIndexMeta))
}
default:
dwr.Log.Error().Str("mediaType", descriptor.MediaType).Msg("Unsupported media type")

View file

@ -57,9 +57,6 @@ type MetaDBMock struct {
SearchTagsFn func(ctx context.Context, searchText string) ([]mTypes.FullImageMeta, error)
FilterTagFn func(ctx context.Context, filterFunc mTypes.FilterFunc,
) ([]mTypes.RepoMeta, map[string]mTypes.ImageMeta, error)
GetImageMetaFn func(digest godigest.Digest) (mTypes.ImageMeta, error)
GetMultipleRepoMetaFn func(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool,