mirror of
https://github.com/project-zot/zot.git
synced 2024-12-30 22:34:13 -05:00
feat(search): search for a specific tag cross-repo (#2211)
Syntax to search for `<tag_name>` accross all repos is `:<tag_name>` Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
This commit is contained in:
parent
580df421bf
commit
a2b923b6fd
3 changed files with 202 additions and 3 deletions
|
@ -694,6 +694,30 @@ func globalSearch(ctx context.Context, query string, metaDB mTypes.MetaDB, filte
|
|||
|
||||
images = imageSummaries
|
||||
|
||||
paginatedRepos.Page = &gql_generated.PageInfo{
|
||||
TotalCount: pageInfo.TotalCount,
|
||||
ItemCount: pageInfo.ItemCount,
|
||||
}
|
||||
case TagTarget:
|
||||
skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities")}
|
||||
pageInput := getPageInput(requestedPage)
|
||||
|
||||
expectedTag := strings.TrimPrefix(query, `:`)
|
||||
matchTagName := func(repoName, actualTag string) bool { return strings.Contains(actualTag, expectedTag) }
|
||||
|
||||
fullImageMetaList, err := metaDB.FilterTags(ctx, matchTagName, mTypes.AcceptAllImageMeta)
|
||||
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,
|
||||
|
@ -745,6 +769,7 @@ const (
|
|||
ImageTarget
|
||||
DigestTarget
|
||||
InvalidTarget
|
||||
TagTarget
|
||||
)
|
||||
|
||||
func getSearchTarget(query string) SearchTarget {
|
||||
|
@ -756,8 +781,12 @@ func getSearchTarget(query string) SearchTarget {
|
|||
return DigestTarget
|
||||
}
|
||||
|
||||
if before, _, found := strings.Cut(query, ":"); found && before != "" {
|
||||
if before, after, found := strings.Cut(query, ":"); found {
|
||||
if before != "" {
|
||||
return ImageTarget
|
||||
} else if after != "" {
|
||||
return TagTarget
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidTarget
|
||||
|
|
|
@ -122,9 +122,42 @@ func TestResolverGlobalSearch(t *testing.T) {
|
|||
So(repos.Results, ShouldBeEmpty)
|
||||
})
|
||||
|
||||
Convey("Searching with a bad query", func() {
|
||||
Convey("Searching by tag returns a filter error", func() {
|
||||
ctx := context.Background()
|
||||
query := ":test"
|
||||
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 tag returns a pagination error", 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{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 := ":"
|
||||
|
||||
responseContext := graphql.WithResponseContext(ctx, graphql.DefaultErrorPresenter, graphql.DefaultRecover)
|
||||
repos, images, layers, err := globalSearch(responseContext, query, mocks.MetaDBMock{}, &gql_generated.Filter{},
|
||||
|
|
|
@ -3729,6 +3729,143 @@ func TestGlobalSearch(t *testing.T) {
|
|||
So(results.Images[0].Manifests[0].Digest, ShouldResemble, multiArch.Images[0].DigestStr())
|
||||
So(results.Images[0].RepoName, ShouldResemble, "repo1")
|
||||
})
|
||||
|
||||
Convey("global searching by tag cross repo", 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)
|
||||
|
||||
image11 := CreateRandomImage()
|
||||
image12 := CreateRandomImage()
|
||||
err := WriteImageToFileSystem(image11, "repo1", "tag1", storeCtlr)
|
||||
So(err, ShouldBeNil)
|
||||
err = WriteImageToFileSystem(image12, "repo1", "tag2", storeCtlr)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
image21 := CreateRandomImage()
|
||||
image22 := CreateRandomImage()
|
||||
multiArch2 := CreateRandomMultiarch()
|
||||
err = WriteImageToFileSystem(image21, "repo2", "tag1", storeCtlr)
|
||||
So(err, ShouldBeNil)
|
||||
err = WriteImageToFileSystem(image22, "repo2", "tag2", storeCtlr)
|
||||
So(err, ShouldBeNil)
|
||||
err = WriteMultiArchImageToFileSystem(multiArch2, "repo2", "tag-multi", storeCtlr)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
image31 := CreateRandomImage()
|
||||
image32 := CreateRandomImage()
|
||||
err = WriteImageToFileSystem(image31, "repo3", "tag1", storeCtlr)
|
||||
So(err, ShouldBeNil)
|
||||
err = WriteImageToFileSystem(image32, "repo3", "tag2", storeCtlr)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
image41 := CreateRandomImage()
|
||||
image42 := CreateRandomImage()
|
||||
multiArch4 := CreateRandomMultiarch()
|
||||
err = WriteImageToFileSystem(image41, "repo4", "tag1", storeCtlr)
|
||||
So(err, ShouldBeNil)
|
||||
err = WriteImageToFileSystem(image42, "repo4", "tag2", storeCtlr)
|
||||
So(err, ShouldBeNil)
|
||||
err = WriteMultiArchImageToFileSystem(multiArch4, "repo4", "tag-multi", storeCtlr)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
image51 := CreateRandomImage()
|
||||
err = WriteImageToFileSystem(image51, "repo5", "tag1", storeCtlr)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
multiArch62 := CreateRandomMultiarch()
|
||||
err = WriteMultiArchImageToFileSystem(multiArch62, "repo6", "tag2", storeCtlr)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
// Search for a specific tag cross-repo and return single arch images
|
||||
results := GlobalSearchGQL(":tag1", baseURL).GlobalSearch
|
||||
So(len(results.Images), ShouldEqual, 5)
|
||||
So(len(results.Repos), ShouldEqual, 0)
|
||||
|
||||
expectedRepos := []string{"repo1", "repo2", "repo3", "repo4", "repo5"}
|
||||
for _, image := range results.Images {
|
||||
So(image.Tag, ShouldEqual, "tag1")
|
||||
So(image.RepoName, ShouldBeIn, expectedRepos)
|
||||
So(len(image.Manifests), ShouldEqual, 1)
|
||||
}
|
||||
|
||||
// Search for a specific tag cross-repo and return multi arch images
|
||||
results = GlobalSearchGQL(":tag-multi", baseURL).GlobalSearch
|
||||
So(len(results.Images), ShouldEqual, 2)
|
||||
So(len(results.Repos), ShouldEqual, 0)
|
||||
|
||||
expectedRepos = []string{"repo2", "repo4"}
|
||||
for _, image := range results.Images {
|
||||
So(image.Tag, ShouldEqual, "tag-multi")
|
||||
So(image.RepoName, ShouldBeIn, expectedRepos)
|
||||
So(len(image.Manifests), ShouldEqual, 3)
|
||||
}
|
||||
|
||||
// Search for a specific tag cross-repo and return mixed single and multiarch images
|
||||
results = GlobalSearchGQL(":tag2", baseURL).GlobalSearch
|
||||
So(len(results.Images), ShouldEqual, 5)
|
||||
So(len(results.Repos), ShouldEqual, 0)
|
||||
|
||||
expectedRepos = []string{"repo1", "repo2", "repo3", "repo4", "repo6"}
|
||||
for _, image := range results.Images {
|
||||
So(image.Tag, ShouldEqual, "tag2")
|
||||
So(image.RepoName, ShouldBeIn, expectedRepos)
|
||||
if image.RepoName == "repo6" {
|
||||
So(len(image.Manifests), ShouldEqual, 3)
|
||||
} else {
|
||||
So(len(image.Manifests), ShouldEqual, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Search for multiple tags using a partial match cross-repo and return multiarch images
|
||||
results = GlobalSearchGQL(":multi", baseURL).GlobalSearch
|
||||
So(len(results.Images), ShouldEqual, 2)
|
||||
So(len(results.Repos), ShouldEqual, 0)
|
||||
|
||||
expectedRepos = []string{"repo2", "repo4"}
|
||||
for _, image := range results.Images {
|
||||
So(image.Tag, ShouldContainSubstring, "multi")
|
||||
So(image.RepoName, ShouldBeIn, expectedRepos)
|
||||
So(len(image.Manifests), ShouldEqual, 3)
|
||||
}
|
||||
|
||||
// Search for multiple tags using a partial match cross-repo and return mixed single and multiarch images
|
||||
results = GlobalSearchGQL(":tag", baseURL).GlobalSearch
|
||||
So(len(results.Images), ShouldEqual, 12)
|
||||
So(len(results.Repos), ShouldEqual, 0)
|
||||
|
||||
expectedRepos = []string{"repo1", "repo2", "repo3", "repo4", "repo5", "repo6"}
|
||||
for _, image := range results.Images {
|
||||
So(image.Tag, ShouldContainSubstring, "tag")
|
||||
So(image.RepoName, ShouldBeIn, expectedRepos)
|
||||
}
|
||||
|
||||
// Search for a specific tag cross-repo and return mixt single and multiarch images
|
||||
result := GlobalSearchGQL(":", baseURL)
|
||||
errors := result.Errors
|
||||
So(len(errors), ShouldEqual, 1)
|
||||
|
||||
results = result.GlobalSearch
|
||||
So(len(results.Images), ShouldEqual, 0)
|
||||
So(len(results.Repos), ShouldEqual, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCleaningFilteringParamsGlobalSearch(t *testing.T) {
|
||||
|
|
Loading…
Reference in a new issue