mirror of
https://github.com/project-zot/zot.git
synced 2024-12-23 22:27:35 -05:00
e8e7c343ad
* feat(repodb): add pagination for ImageListForDigest and implement FilterTags ImageListForDigest can now return paginated results, directly from DB. It uses FilterTags, a new method to filter tags (obviously) based on the criteria provided in the filter function. Pagination of tags is now slightly different, it shows all results if no limit and offset are provided. Signed-off-by: Alex Stan <alexandrustan96@yahoo.ro> bug(tests): cli tests for digests expecting wrong size Signed-off-by: Andrei Aaron <aaaron@luxoft.com> (cherry picked from commit 369216df931a4053c18278a8d89f86d2e1e6a436) fix(repodb): do not include repo metadata in search results if no matching tags are identified Signed-off-by: Andrei Aaron <aaaron@luxoft.com> * fix(repodb): Fix an issue in FilterTags where repometa was not proceesed correctly The filter function was called only once per manifest digest. The function is supposed to also take into consideration repometa, but only the first repometa-manifestmeta pair was processed. Also increase code coverage. Signed-off-by: Andrei Aaron <aaaron@luxoft.com> Signed-off-by: Andrei Aaron <aaaron@luxoft.com> Co-authored-by: Alex Stan <alexandrustan96@yahoo.ro>
432 lines
12 KiB
Go
432 lines
12 KiB
Go
package search
|
|
|
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
|
// will be copied through when generating and any unknown code will be moved to the end.
|
|
// Code generated by github.com/99designs/gqlgen version v0.17.22
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
|
zerr "zotregistry.io/zot/errors"
|
|
"zotregistry.io/zot/pkg/extensions/search/common"
|
|
"zotregistry.io/zot/pkg/extensions/search/convert"
|
|
"zotregistry.io/zot/pkg/extensions/search/gql_generated"
|
|
)
|
|
|
|
// CVEListForImage is the resolver for the CVEListForImage field.
|
|
func (r *queryResolver) CVEListForImage(ctx context.Context, image string) (*gql_generated.CVEResultForImage, error) {
|
|
if r.cveInfo == nil {
|
|
return &gql_generated.CVEResultForImage{}, zerr.ErrCVESearchDisabled
|
|
}
|
|
|
|
_, copyImgTag := common.GetImageDirAndTag(image)
|
|
|
|
if copyImgTag == "" {
|
|
return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided")
|
|
}
|
|
|
|
cveidMap, err := r.cveInfo.GetCVEListForImage(image)
|
|
if err != nil {
|
|
return &gql_generated.CVEResultForImage{}, err
|
|
}
|
|
|
|
cveids := []*gql_generated.Cve{}
|
|
|
|
for id, cveDetail := range cveidMap {
|
|
vulID := id
|
|
desc := cveDetail.Description
|
|
title := cveDetail.Title
|
|
severity := cveDetail.Severity
|
|
|
|
pkgList := make([]*gql_generated.PackageInfo, 0)
|
|
|
|
for _, pkg := range cveDetail.PackageList {
|
|
pkg := pkg
|
|
|
|
pkgList = append(pkgList,
|
|
&gql_generated.PackageInfo{
|
|
Name: &pkg.Name,
|
|
InstalledVersion: &pkg.InstalledVersion,
|
|
FixedVersion: &pkg.FixedVersion,
|
|
},
|
|
)
|
|
}
|
|
|
|
cveids = append(cveids,
|
|
&gql_generated.Cve{
|
|
ID: &vulID,
|
|
Title: &title,
|
|
Description: &desc,
|
|
Severity: &severity,
|
|
PackageList: pkgList,
|
|
},
|
|
)
|
|
}
|
|
|
|
return &gql_generated.CVEResultForImage{Tag: ©ImgTag, CVEList: cveids}, nil
|
|
}
|
|
|
|
// ImageListForCve is the resolver for the ImageListForCVE field.
|
|
func (r *queryResolver) ImageListForCve(ctx context.Context, id string) ([]*gql_generated.ImageSummary, error) {
|
|
olu := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
|
affectedImages := []*gql_generated.ImageSummary{}
|
|
|
|
if r.cveInfo == nil {
|
|
return affectedImages, zerr.ErrCVESearchDisabled
|
|
}
|
|
|
|
r.log.Info().Msg("extracting repositories")
|
|
repoList, err := olu.GetRepositories()
|
|
if err != nil { //nolint: wsl
|
|
r.log.Error().Err(err).Msg("unable to search repositories")
|
|
|
|
return affectedImages, err
|
|
}
|
|
|
|
r.log.Info().Msg("scanning each repository")
|
|
|
|
for _, repo := range repoList {
|
|
r.log.Info().Str("repo", repo).Msg("extracting list of tags available in image repo")
|
|
|
|
imageListByCVE, err := r.cveInfo.GetImageListForCVE(repo, id)
|
|
if err != nil {
|
|
r.log.Error().Str("repo", repo).Str("CVE", id).Err(err).
|
|
Msg("error getting image list for CVE from repo")
|
|
|
|
return affectedImages, err
|
|
}
|
|
|
|
for _, imageByCVE := range imageListByCVE {
|
|
imageConfig, err := olu.GetImageConfigInfo(repo, imageByCVE.Digest)
|
|
if err != nil {
|
|
return affectedImages, err
|
|
}
|
|
|
|
isSigned := olu.CheckManifestSignature(repo, imageByCVE.Digest)
|
|
imageInfo := convert.BuildImageInfo(
|
|
repo, imageByCVE.Tag,
|
|
imageByCVE.Digest,
|
|
imageByCVE.Manifest,
|
|
imageConfig,
|
|
isSigned,
|
|
)
|
|
|
|
affectedImages = append(
|
|
affectedImages,
|
|
imageInfo,
|
|
)
|
|
}
|
|
}
|
|
|
|
return affectedImages, nil
|
|
}
|
|
|
|
// ImageListWithCVEFixed is the resolver for the ImageListWithCVEFixed field.
|
|
func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, id string, image string) ([]*gql_generated.ImageSummary, error) {
|
|
olu := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
|
|
|
unaffectedImages := []*gql_generated.ImageSummary{}
|
|
|
|
if r.cveInfo == nil {
|
|
return unaffectedImages, zerr.ErrCVESearchDisabled
|
|
}
|
|
|
|
tagsInfo, err := r.cveInfo.GetImageListWithCVEFixed(image, id)
|
|
if err != nil {
|
|
return unaffectedImages, err
|
|
}
|
|
|
|
for _, tag := range tagsInfo {
|
|
digest := tag.Digest
|
|
|
|
manifest, err := olu.GetImageBlobManifest(image, digest)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Str("repo", image).Str("digest", tag.Digest.String()).
|
|
Msg("extension api: error reading manifest")
|
|
|
|
return unaffectedImages, err
|
|
}
|
|
|
|
imageConfig, err := olu.GetImageConfigInfo(image, digest)
|
|
if err != nil {
|
|
return []*gql_generated.ImageSummary{}, err
|
|
}
|
|
|
|
isSigned := olu.CheckManifestSignature(image, digest)
|
|
imageInfo := convert.BuildImageInfo(image, tag.Name, digest, manifest, imageConfig, isSigned)
|
|
|
|
unaffectedImages = append(unaffectedImages, imageInfo)
|
|
}
|
|
|
|
return unaffectedImages, nil
|
|
}
|
|
|
|
// ImageListForDigest is the resolver for the ImageListForDigest field.
|
|
func (r *queryResolver) ImageListForDigest(ctx context.Context, id string, requestedPage *gql_generated.PageInput) ([]*gql_generated.ImageSummary, error) {
|
|
r.log.Info().Msg("extracting repositories")
|
|
|
|
imgResultForDigest, err := getImageListForDigest(ctx, id, r.repoDB, r.cveInfo, requestedPage)
|
|
|
|
return imgResultForDigest, err
|
|
}
|
|
|
|
// RepoListWithNewestImage is the resolver for the RepoListWithNewestImage field.
|
|
func (r *queryResolver) RepoListWithNewestImage(ctx context.Context, requestedPage *gql_generated.PageInput) ([]*gql_generated.RepoSummary, error) {
|
|
r.log.Info().Msg("extension api: finding image list")
|
|
|
|
reposSummary, err := repoListWithNewestImage(ctx, r.cveInfo, r.log, requestedPage, r.repoDB)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("unable to retrieve repo list")
|
|
|
|
return reposSummary, err
|
|
}
|
|
|
|
return reposSummary, nil
|
|
}
|
|
|
|
// ImageList is the resolver for the ImageList field.
|
|
func (r *queryResolver) ImageList(ctx context.Context, repo string) ([]*gql_generated.ImageSummary, error) {
|
|
r.log.Info().Msg("extension api: getting a list of all images")
|
|
|
|
imageList := make([]*gql_generated.ImageSummary, 0)
|
|
|
|
defaultStore := r.storeController.DefaultStore
|
|
|
|
dsImageList, err := r.getImageList(defaultStore, repo)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("extension api: error extracting default store image list")
|
|
|
|
return imageList, err
|
|
}
|
|
|
|
if len(dsImageList) != 0 {
|
|
imageList = append(imageList, dsImageList...)
|
|
}
|
|
|
|
subStore := r.storeController.SubStore
|
|
|
|
for _, store := range subStore {
|
|
ssImageList, err := r.getImageList(store, repo)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("extension api: error extracting substore image list")
|
|
|
|
return imageList, err
|
|
}
|
|
|
|
if len(ssImageList) != 0 {
|
|
imageList = append(imageList, ssImageList...)
|
|
}
|
|
}
|
|
|
|
return imageList, nil
|
|
}
|
|
|
|
// ExpandedRepoInfo is the resolver for the ExpandedRepoInfo field.
|
|
func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, repo string) (*gql_generated.RepoInfo, error) {
|
|
repoInfo, err := expandedRepoInfo(ctx, repo, r.repoDB, r.cveInfo, r.log)
|
|
|
|
return repoInfo, err
|
|
}
|
|
|
|
// GlobalSearch is the resolver for the GlobalSearch field.
|
|
func (r *queryResolver) GlobalSearch(ctx context.Context, query string, filter *gql_generated.Filter, requestedPage *gql_generated.PageInput) (*gql_generated.GlobalSearchResult, error) {
|
|
if err := validateGlobalSearchInput(query, filter, requestedPage); err != nil {
|
|
return &gql_generated.GlobalSearchResult{}, err
|
|
}
|
|
|
|
query = cleanQuery(query)
|
|
filter = cleanFilter(filter)
|
|
|
|
repos, images, layers, err := globalSearch(ctx, query, r.repoDB, filter, requestedPage, r.cveInfo, r.log)
|
|
|
|
return &gql_generated.GlobalSearchResult{
|
|
Images: images,
|
|
Repos: repos,
|
|
Layers: layers,
|
|
}, err
|
|
}
|
|
|
|
// DependencyListForImage is the resolver for the DependencyListForImage field.
|
|
func (r *queryResolver) DerivedImageList(ctx context.Context, image string) ([]*gql_generated.ImageSummary, error) {
|
|
layoutUtils := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
|
imageList := make([]*gql_generated.ImageSummary, 0)
|
|
|
|
repoList, err := layoutUtils.GetRepositories()
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("unable to get repositories list")
|
|
|
|
return nil, err
|
|
}
|
|
|
|
if len(repoList) == 0 {
|
|
r.log.Info().Msg("no repositories found")
|
|
|
|
return imageList, nil
|
|
}
|
|
|
|
imageDir, imageTag := common.GetImageDirAndTag(image)
|
|
|
|
if imageTag == "" {
|
|
return []*gql_generated.ImageSummary{}, gqlerror.Errorf("no reference provided")
|
|
}
|
|
|
|
imageManifest, _, err := layoutUtils.GetImageManifest(imageDir, imageTag)
|
|
if err != nil {
|
|
r.log.Info().Str("image", image).Msg("image not found")
|
|
|
|
return imageList, err
|
|
}
|
|
|
|
imageLayers := imageManifest.Layers
|
|
|
|
for _, repo := range repoList {
|
|
repoInfo, err := r.ExpandedRepoInfo(ctx, repo)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("unable to get image list")
|
|
|
|
return nil, err
|
|
}
|
|
|
|
imageSummaries := repoInfo.Images
|
|
|
|
// verify every image
|
|
for _, imageSummary := range imageSummaries {
|
|
if imageTag == *imageSummary.Tag && imageDir == repo {
|
|
continue
|
|
}
|
|
|
|
layers := imageSummary.Layers
|
|
|
|
sameLayer := 0
|
|
|
|
for _, l := range imageLayers {
|
|
for _, k := range layers {
|
|
if *k.Digest == l.Digest.String() {
|
|
sameLayer++
|
|
}
|
|
}
|
|
}
|
|
|
|
// if all layers are the same
|
|
if sameLayer == len(imageLayers) {
|
|
// add to returned list
|
|
imageList = append(imageList, imageSummary)
|
|
}
|
|
}
|
|
}
|
|
|
|
return imageList, nil
|
|
}
|
|
|
|
// BaseImageList is the resolver for the BaseImageList field.
|
|
func (r *queryResolver) BaseImageList(ctx context.Context, image string) ([]*gql_generated.ImageSummary, error) {
|
|
layoutUtils := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
|
imageList := make([]*gql_generated.ImageSummary, 0)
|
|
|
|
repoList, err := layoutUtils.GetRepositories()
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("unable to get repositories list")
|
|
|
|
return nil, err
|
|
}
|
|
|
|
if len(repoList) == 0 {
|
|
r.log.Info().Msg("no repositories found")
|
|
|
|
return imageList, nil
|
|
}
|
|
|
|
imageDir, imageTag := common.GetImageDirAndTag(image)
|
|
|
|
if imageTag == "" {
|
|
return []*gql_generated.ImageSummary{}, gqlerror.Errorf("no reference provided")
|
|
}
|
|
|
|
imageManifest, _, err := layoutUtils.GetImageManifest(imageDir, imageTag)
|
|
if err != nil {
|
|
r.log.Info().Str("image", image).Msg("image not found")
|
|
|
|
return imageList, err
|
|
}
|
|
|
|
imageLayers := imageManifest.Layers
|
|
|
|
// This logic may not scale well in the future as we need to read all the
|
|
// manifest files from the disk when the call is made, we should improve in a future PR
|
|
for _, repo := range repoList {
|
|
repoInfo, err := r.ExpandedRepoInfo(ctx, repo)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("unable to get image list")
|
|
|
|
return nil, err
|
|
}
|
|
|
|
imageSummaries := repoInfo.Images
|
|
|
|
var addImageToList bool
|
|
// verify every image
|
|
for _, imageSummary := range imageSummaries {
|
|
if imageTag == *imageSummary.Tag && imageDir == repo {
|
|
continue
|
|
}
|
|
|
|
addImageToList = true
|
|
layers := imageSummary.Layers
|
|
|
|
for _, l := range layers {
|
|
foundLayer := false
|
|
|
|
for _, k := range imageLayers {
|
|
if *l.Digest == k.Digest.String() {
|
|
foundLayer = true
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if !foundLayer {
|
|
addImageToList = false
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if addImageToList {
|
|
imageList = append(imageList, imageSummary)
|
|
}
|
|
}
|
|
}
|
|
|
|
return imageList, nil
|
|
}
|
|
|
|
// Image is the resolver for the Image field.
|
|
func (r *queryResolver) Image(ctx context.Context, image string) (*gql_generated.ImageSummary, error) {
|
|
repo, tag := common.GetImageDirAndTag(image)
|
|
|
|
if tag == "" {
|
|
return &gql_generated.ImageSummary{}, gqlerror.Errorf("no reference provided")
|
|
}
|
|
|
|
return getImageSummary(ctx, repo, tag, r.repoDB, r.cveInfo, r.log)
|
|
}
|
|
|
|
// Referrers is the resolver for the Referrers field.
|
|
func (r *queryResolver) Referrers(ctx context.Context, repo string, digest string, typeArg string) ([]*gql_generated.Referrer, error) {
|
|
store := r.storeController.GetImageStore(repo)
|
|
|
|
referrers, err := getReferrers(store, repo, digest, typeArg, r.log)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("unable to get referrers from default store")
|
|
|
|
return []*gql_generated.Referrer{}, err
|
|
}
|
|
|
|
return referrers, nil
|
|
}
|
|
|
|
// Query returns gql_generated.QueryResolver implementation.
|
|
func (r *Resolver) Query() gql_generated.QueryResolver { return &queryResolver{r} }
|
|
|
|
type queryResolver struct{ *Resolver }
|