zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/extensions/search/resolver.go (about)

     1  package search
     2  
     3  // This file will not be regenerated automatically.
     4  //
     5  // It serves as dependency injection for your app, add any dependencies you require here.
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"sort"
    12  	"strings"
    13  
    14  	godigest "github.com/opencontainers/go-digest"
    15  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    16  	"github.com/vektah/gqlparser/v2/gqlerror"
    17  
    18  	zerr "zotregistry.io/zot/errors"
    19  	zcommon "zotregistry.io/zot/pkg/common"
    20  	"zotregistry.io/zot/pkg/extensions/search/convert"
    21  	cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
    22  	cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
    23  	"zotregistry.io/zot/pkg/extensions/search/gql_generated"
    24  	"zotregistry.io/zot/pkg/extensions/search/pagination"
    25  	"zotregistry.io/zot/pkg/log"
    26  	mTypes "zotregistry.io/zot/pkg/meta/types"
    27  	reqCtx "zotregistry.io/zot/pkg/requestcontext"
    28  	"zotregistry.io/zot/pkg/storage"
    29  )
    30  
    31  // THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.
    32  
    33  const (
    34  	querySizeLimit = 256
    35  )
    36  
    37  // Resolver ...
    38  type Resolver struct {
    39  	cveInfo         cveinfo.CveInfo
    40  	metaDB          mTypes.MetaDB
    41  	storeController storage.StoreController
    42  	log             log.Logger
    43  }
    44  
    45  // GetResolverConfig ...
    46  func GetResolverConfig(log log.Logger, storeController storage.StoreController,
    47  	metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo,
    48  ) gql_generated.Config {
    49  	resConfig := &Resolver{
    50  		cveInfo:         cveInfo,
    51  		metaDB:          metaDB,
    52  		storeController: storeController,
    53  		log:             log,
    54  	}
    55  
    56  	return gql_generated.Config{
    57  		Resolvers: resConfig, Directives: gql_generated.DirectiveRoot{},
    58  		Complexity: gql_generated.ComplexityRoot{},
    59  	}
    60  }
    61  
    62  func NewResolver(log log.Logger, storeController storage.StoreController,
    63  	metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo,
    64  ) *Resolver {
    65  	resolver := &Resolver{
    66  		cveInfo:         cveInfo,
    67  		metaDB:          metaDB,
    68  		storeController: storeController,
    69  		log:             log,
    70  	}
    71  
    72  	return resolver
    73  }
    74  
    75  func FilterByDigest(digest string) mTypes.FilterFunc {
    76  	// imageMeta will always contain 1 manifest
    77  	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
    78  		lookupDigest := digest
    79  		contains := false
    80  
    81  		manifest := imageMeta.Manifests[0]
    82  
    83  		manifestDigest := manifest.Digest.String()
    84  
    85  		// Check the image manifest in index.json matches the search digest
    86  		// This is a blob with mediaType application/vnd.oci.image.manifest.v1+json
    87  		if strings.Contains(manifestDigest, lookupDigest) {
    88  			contains = true
    89  		}
    90  
    91  		// Check the image config matches the search digest
    92  		// This is a blob with mediaType application/vnd.oci.image.config.v1+json
    93  		if strings.Contains(manifest.Manifest.Config.Digest.String(), lookupDigest) {
    94  			contains = true
    95  		}
    96  
    97  		// Check to see if the individual layers in the oci image manifest match the digest
    98  		// These are blobs with mediaType application/vnd.oci.image.layer.v1.tar+gzip
    99  		for _, layer := range manifest.Manifest.Layers {
   100  			if strings.Contains(layer.Digest.String(), lookupDigest) {
   101  				contains = true
   102  			}
   103  		}
   104  
   105  		return contains
   106  	}
   107  }
   108  
   109  func getImageListForDigest(ctx context.Context, digest string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo,
   110  	requestedPage *gql_generated.PageInput,
   111  ) (*gql_generated.PaginatedImagesResult, error) {
   112  	if requestedPage == nil {
   113  		requestedPage = &gql_generated.PageInput{}
   114  	}
   115  
   116  	skip := convert.SkipQGLField{
   117  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"),
   118  	}
   119  
   120  	pageInput := pagination.PageInput{
   121  		Limit:  deref(requestedPage.Limit, 0),
   122  		Offset: deref(requestedPage.Offset, 0),
   123  		SortBy: pagination.SortCriteria(
   124  			deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
   125  		),
   126  	}
   127  
   128  	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByDigest(digest))
   129  	if err != nil {
   130  		return &gql_generated.PaginatedImagesResult{}, err
   131  	}
   132  
   133  	imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip,
   134  		cveInfo, mTypes.Filter{}, pageInput)
   135  	if err != nil {
   136  		return &gql_generated.PaginatedImagesResult{}, err
   137  	}
   138  
   139  	return &gql_generated.PaginatedImagesResult{
   140  		Results: imageSummaries,
   141  		Page: &gql_generated.PageInfo{
   142  			TotalCount: pageInfo.TotalCount,
   143  			ItemCount:  pageInfo.ItemCount,
   144  		},
   145  	}, nil
   146  }
   147  
   148  func getImageSummary(ctx context.Context, repo, tag string, digest *string, skipCVE convert.SkipQGLField,
   149  	metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo, log log.Logger, //nolint:unparam
   150  ) (
   151  	*gql_generated.ImageSummary, error,
   152  ) {
   153  	repoMeta, err := metaDB.GetRepoMeta(ctx, repo)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  
   158  	manifestDescriptor, ok := repoMeta.Tags[tag]
   159  	if !ok {
   160  		return nil, gqlerror.Errorf("can't find image: %s:%s", repo, tag)
   161  	}
   162  
   163  	repoMeta.Tags = map[string]mTypes.Descriptor{tag: manifestDescriptor}
   164  
   165  	imageDigest := manifestDescriptor.Digest
   166  	if digest != nil {
   167  		imageDigest = *digest
   168  		repoMeta.Tags[tag] = mTypes.Descriptor{
   169  			Digest:    imageDigest,
   170  			MediaType: ispec.MediaTypeImageManifest,
   171  		}
   172  	}
   173  
   174  	imageMetaMap, err := metaDB.FilterImageMeta(ctx, []string{imageDigest})
   175  	if err != nil {
   176  		return &gql_generated.ImageSummary{}, err
   177  	}
   178  
   179  	imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, imageMetaMap, skipCVE, cveInfo)
   180  
   181  	if len(imageSummaries) == 0 {
   182  		return &gql_generated.ImageSummary{}, nil
   183  	}
   184  
   185  	return imageSummaries[0], nil
   186  }
   187  
   188  func getCVEListForImage(
   189  	ctx context.Context, //nolint:unparam // may be used in the future to filter by permissions
   190  	image string,
   191  	cveInfo cveinfo.CveInfo,
   192  	requestedPage *gql_generated.PageInput,
   193  	searchedCVE string,
   194  	log log.Logger, //nolint:unparam // may be used by devs for debugging
   195  ) (*gql_generated.CVEResultForImage, error) {
   196  	if requestedPage == nil {
   197  		requestedPage = &gql_generated.PageInput{}
   198  	}
   199  
   200  	pageInput := cvemodel.PageInput{
   201  		Limit:  deref(requestedPage.Limit, 0),
   202  		Offset: deref(requestedPage.Offset, 0),
   203  		SortBy: cvemodel.SortCriteria(
   204  			deref(requestedPage.SortBy, gql_generated.SortCriteriaSeverity),
   205  		),
   206  	}
   207  
   208  	repo, ref, _ := zcommon.GetImageDirAndReference(image)
   209  
   210  	if ref == "" {
   211  		return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided")
   212  	}
   213  
   214  	cveList, pageInfo, err := cveInfo.GetCVEListForImage(ctx, repo, ref, searchedCVE, pageInput)
   215  	if err != nil {
   216  		return &gql_generated.CVEResultForImage{}, err
   217  	}
   218  
   219  	cveids := []*gql_generated.Cve{}
   220  
   221  	for _, cveDetail := range cveList {
   222  		vulID := cveDetail.ID
   223  		desc := cveDetail.Description
   224  		title := cveDetail.Title
   225  		severity := cveDetail.Severity
   226  
   227  		pkgList := make([]*gql_generated.PackageInfo, 0)
   228  
   229  		for _, pkg := range cveDetail.PackageList {
   230  			pkg := pkg
   231  
   232  			pkgList = append(pkgList,
   233  				&gql_generated.PackageInfo{
   234  					Name:             &pkg.Name,
   235  					InstalledVersion: &pkg.InstalledVersion,
   236  					FixedVersion:     &pkg.FixedVersion,
   237  				},
   238  			)
   239  		}
   240  
   241  		cveids = append(cveids,
   242  			&gql_generated.Cve{
   243  				ID:          &vulID,
   244  				Title:       &title,
   245  				Description: &desc,
   246  				Severity:    &severity,
   247  				PackageList: pkgList,
   248  			},
   249  		)
   250  	}
   251  
   252  	return &gql_generated.CVEResultForImage{
   253  		Tag:     &ref,
   254  		CVEList: cveids,
   255  		Page: &gql_generated.PageInfo{
   256  			TotalCount: pageInfo.TotalCount,
   257  			ItemCount:  pageInfo.ItemCount,
   258  		},
   259  	}, nil
   260  }
   261  
   262  func FilterByTagInfo(tagsInfo []cvemodel.TagInfo) mTypes.FilterFunc {
   263  	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
   264  		manifestDigest := imageMeta.Manifests[0].Digest.String()
   265  
   266  		for _, tagInfo := range tagsInfo {
   267  			switch tagInfo.Descriptor.MediaType {
   268  			case ispec.MediaTypeImageManifest:
   269  				if tagInfo.Descriptor.Digest.String() == manifestDigest {
   270  					return true
   271  				}
   272  			case ispec.MediaTypeImageIndex:
   273  				for _, manifestDesc := range tagInfo.Manifests {
   274  					if manifestDesc.Digest.String() == manifestDigest {
   275  						return true
   276  					}
   277  				}
   278  			}
   279  		}
   280  
   281  		return false
   282  	}
   283  }
   284  
   285  func FilterByRepoAndTagInfo(repo string, tagsInfo []cvemodel.TagInfo) mTypes.FilterFunc {
   286  	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
   287  		if repoMeta.Name != repo {
   288  			return false
   289  		}
   290  
   291  		manifestDigest := imageMeta.Manifests[0].Digest.String()
   292  
   293  		for _, tagInfo := range tagsInfo {
   294  			switch tagInfo.Descriptor.MediaType {
   295  			case ispec.MediaTypeImageManifest:
   296  				if tagInfo.Descriptor.Digest.String() == manifestDigest {
   297  					return true
   298  				}
   299  			case ispec.MediaTypeImageIndex:
   300  				for _, manifestDesc := range tagInfo.Manifests {
   301  					if manifestDesc.Digest.String() == manifestDigest {
   302  						return true
   303  					}
   304  				}
   305  			}
   306  		}
   307  
   308  		return false
   309  	}
   310  }
   311  
   312  func getImageListForCVE(
   313  	ctx context.Context,
   314  	cveID string,
   315  	cveInfo cveinfo.CveInfo,
   316  	filter *gql_generated.Filter,
   317  	requestedPage *gql_generated.PageInput,
   318  	metaDB mTypes.MetaDB,
   319  	log log.Logger,
   320  ) (*gql_generated.PaginatedImagesResult, error) {
   321  	// Obtain all repos and tags
   322  	// Infinite page to make sure we scan all repos in advance, before filtering results
   323  	// The CVE scan logic is called from here, not in the actual filter,
   324  	// this is because we shouldn't keep the DB locked while we wait on scan results
   325  	reposMeta, err := metaDB.GetMultipleRepoMeta(ctx, func(repoMeta mTypes.RepoMeta) bool { return true })
   326  	if err != nil {
   327  		return &gql_generated.PaginatedImagesResult{}, err
   328  	}
   329  
   330  	affectedImages := []cvemodel.TagInfo{}
   331  
   332  	for _, repoMeta := range reposMeta {
   333  		repo := repoMeta.Name
   334  
   335  		log.Info().Str("repository", repo).Str("CVE", cveID).Msg("extracting list of tags affected by CVE")
   336  
   337  		tagsInfo, err := cveInfo.GetImageListForCVE(ctx, repo, cveID)
   338  		if err != nil {
   339  			log.Error().Str("repository", repo).Str("CVE", cveID).Err(err).
   340  				Msg("error getting image list for CVE from repo")
   341  
   342  			return &gql_generated.PaginatedImagesResult{}, err
   343  		}
   344  
   345  		affectedImages = append(affectedImages, tagsInfo...)
   346  	}
   347  
   348  	// We're not interested in other vulnerabilities
   349  	skip := convert.SkipQGLField{Vulnerabilities: true}
   350  
   351  	if requestedPage == nil {
   352  		requestedPage = &gql_generated.PageInput{}
   353  	}
   354  
   355  	localFilter := mTypes.Filter{}
   356  	if filter != nil {
   357  		localFilter = mTypes.Filter{
   358  			Os:            filter.Os,
   359  			Arch:          filter.Arch,
   360  			HasToBeSigned: filter.HasToBeSigned,
   361  			IsBookmarked:  filter.IsBookmarked,
   362  			IsStarred:     filter.IsStarred,
   363  		}
   364  	}
   365  
   366  	// Actual page requested by user
   367  	pageInput := pagination.PageInput{
   368  		Limit:  deref(requestedPage.Limit, 0),
   369  		Offset: deref(requestedPage.Offset, 0),
   370  		SortBy: pagination.SortCriteria(
   371  			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
   372  		),
   373  	}
   374  
   375  	// get all repos
   376  	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByTagInfo(affectedImages))
   377  	if err != nil {
   378  		return &gql_generated.PaginatedImagesResult{}, err
   379  	}
   380  
   381  	imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList,
   382  		skip, cveInfo, localFilter, pageInput)
   383  	if err != nil {
   384  		return &gql_generated.PaginatedImagesResult{}, err
   385  	}
   386  
   387  	return &gql_generated.PaginatedImagesResult{
   388  		Results: imageSummaries,
   389  		Page: &gql_generated.PageInfo{
   390  			TotalCount: pageInfo.TotalCount,
   391  			ItemCount:  pageInfo.ItemCount,
   392  		},
   393  	}, nil
   394  }
   395  
   396  func getImageListWithCVEFixed(
   397  	ctx context.Context,
   398  	cveID string,
   399  	repo string,
   400  	cveInfo cveinfo.CveInfo,
   401  	filter *gql_generated.Filter,
   402  	requestedPage *gql_generated.PageInput,
   403  	metaDB mTypes.MetaDB,
   404  	log log.Logger,
   405  ) (*gql_generated.PaginatedImagesResult, error) {
   406  	imageList := make([]*gql_generated.ImageSummary, 0)
   407  
   408  	log.Info().Str("repository", repo).Str("CVE", cveID).Msg("extracting list of tags where CVE is fixed")
   409  
   410  	tagsInfo, err := cveInfo.GetImageListWithCVEFixed(ctx, repo, cveID)
   411  	if err != nil {
   412  		log.Error().Str("repository", repo).Str("CVE", cveID).Err(err).
   413  			Msg("error getting image list with CVE fixed from repo")
   414  
   415  		return &gql_generated.PaginatedImagesResult{
   416  			Page:    &gql_generated.PageInfo{},
   417  			Results: imageList,
   418  		}, err
   419  	}
   420  
   421  	// We're not interested in other vulnerabilities
   422  	skip := convert.SkipQGLField{Vulnerabilities: true}
   423  
   424  	if requestedPage == nil {
   425  		requestedPage = &gql_generated.PageInput{}
   426  	}
   427  
   428  	localFilter := mTypes.Filter{}
   429  	if filter != nil {
   430  		localFilter = mTypes.Filter{
   431  			Os:            filter.Os,
   432  			Arch:          filter.Arch,
   433  			HasToBeSigned: filter.HasToBeSigned,
   434  			IsBookmarked:  filter.IsBookmarked,
   435  			IsStarred:     filter.IsStarred,
   436  		}
   437  	}
   438  
   439  	// Actual page requested by user
   440  	pageInput := pagination.PageInput{
   441  		Limit:  deref(requestedPage.Limit, 0),
   442  		Offset: deref(requestedPage.Offset, 0),
   443  		SortBy: pagination.SortCriteria(
   444  			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
   445  		),
   446  	}
   447  
   448  	// get all repos
   449  	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByRepoAndTagInfo(repo, tagsInfo))
   450  	if err != nil {
   451  		return &gql_generated.PaginatedImagesResult{}, err
   452  	}
   453  
   454  	imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList,
   455  		skip, cveInfo, localFilter, pageInput)
   456  	if err != nil {
   457  		return &gql_generated.PaginatedImagesResult{}, err
   458  	}
   459  
   460  	return &gql_generated.PaginatedImagesResult{
   461  		Results: imageSummaries,
   462  		Page: &gql_generated.PageInfo{
   463  			TotalCount: pageInfo.TotalCount,
   464  			ItemCount:  pageInfo.ItemCount,
   465  		},
   466  	}, nil
   467  }
   468  
   469  func repoListWithNewestImage(
   470  	ctx context.Context,
   471  	cveInfo cveinfo.CveInfo,
   472  	log log.Logger, //nolint:unparam // may be used by devs for debugging
   473  	requestedPage *gql_generated.PageInput,
   474  	metaDB mTypes.MetaDB,
   475  ) (*gql_generated.PaginatedReposResult, error) {
   476  	paginatedRepos := &gql_generated.PaginatedReposResult{}
   477  
   478  	if requestedPage == nil {
   479  		requestedPage = &gql_generated.PageInput{}
   480  	}
   481  
   482  	skip := convert.SkipQGLField{
   483  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Results.NewestImage.Vulnerabilities"),
   484  	}
   485  
   486  	pageInput := pagination.PageInput{
   487  		Limit:  deref(requestedPage.Limit, 0),
   488  		Offset: deref(requestedPage.Offset, 0),
   489  		SortBy: pagination.SortCriteria(
   490  			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
   491  		),
   492  	}
   493  
   494  	repoMetaList, err := metaDB.SearchRepos(ctx, "")
   495  	if err != nil {
   496  		return &gql_generated.PaginatedReposResult{}, err
   497  	}
   498  
   499  	imageMetaMap, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList))
   500  	if err != nil {
   501  		return &gql_generated.PaginatedReposResult{}, err
   502  	}
   503  
   504  	repos, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries(ctx, repoMetaList, imageMetaMap,
   505  		mTypes.Filter{}, pageInput, cveInfo, skip)
   506  	if err != nil {
   507  		return &gql_generated.PaginatedReposResult{}, err
   508  	}
   509  
   510  	paginatedRepos.Page = &gql_generated.PageInfo{
   511  		TotalCount: pageInfo.TotalCount,
   512  		ItemCount:  pageInfo.ItemCount,
   513  	}
   514  
   515  	paginatedRepos.Results = repos
   516  
   517  	return paginatedRepos, nil
   518  }
   519  
   520  func getBookmarkedRepos(
   521  	ctx context.Context,
   522  	cveInfo cveinfo.CveInfo,
   523  	log log.Logger, //nolint:unparam // may be used by devs for debugging
   524  	requestedPage *gql_generated.PageInput,
   525  	metaDB mTypes.MetaDB,
   526  ) (*gql_generated.PaginatedReposResult, error) {
   527  	bookmarkedRepos, err := metaDB.GetBookmarkedRepos(ctx)
   528  	if err != nil {
   529  		return &gql_generated.PaginatedReposResult{}, err
   530  	}
   531  
   532  	filterByName := func(repo string) bool {
   533  		return zcommon.Contains(bookmarkedRepos, repo)
   534  	}
   535  
   536  	return getFilteredPaginatedRepos(ctx, cveInfo, filterByName, log, requestedPage, metaDB)
   537  }
   538  
   539  func getStarredRepos(
   540  	ctx context.Context,
   541  	cveInfo cveinfo.CveInfo,
   542  	log log.Logger, //nolint:unparam // may be used by devs for debugging
   543  	requestedPage *gql_generated.PageInput,
   544  	metaDB mTypes.MetaDB,
   545  ) (*gql_generated.PaginatedReposResult, error) {
   546  	starredRepos, err := metaDB.GetStarredRepos(ctx)
   547  	if err != nil {
   548  		return &gql_generated.PaginatedReposResult{}, err
   549  	}
   550  
   551  	filterFn := func(repo string) bool {
   552  		return zcommon.Contains(starredRepos, repo)
   553  	}
   554  
   555  	return getFilteredPaginatedRepos(ctx, cveInfo, filterFn, log, requestedPage, metaDB)
   556  }
   557  
   558  func getFilteredPaginatedRepos(
   559  	ctx context.Context,
   560  	cveInfo cveinfo.CveInfo,
   561  	filterFn mTypes.FilterRepoNameFunc,
   562  	log log.Logger, //nolint:unparam // may be used by devs for debugging
   563  	requestedPage *gql_generated.PageInput,
   564  	metaDB mTypes.MetaDB,
   565  ) (*gql_generated.PaginatedReposResult, error) {
   566  	if requestedPage == nil {
   567  		requestedPage = &gql_generated.PageInput{}
   568  	}
   569  
   570  	skip := convert.SkipQGLField{
   571  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Results.NewestImage.Vulnerabilities"),
   572  	}
   573  
   574  	pageInput := pagination.PageInput{
   575  		Limit:  deref(requestedPage.Limit, 0),
   576  		Offset: deref(requestedPage.Offset, 0),
   577  		SortBy: pagination.SortCriteria(
   578  			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
   579  		),
   580  	}
   581  
   582  	repoMetaList, err := metaDB.FilterRepos(ctx, filterFn, mTypes.AcceptAllRepoMeta)
   583  	if err != nil {
   584  		return &gql_generated.PaginatedReposResult{}, err
   585  	}
   586  
   587  	latestImageMeta, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList))
   588  	if err != nil {
   589  		return &gql_generated.PaginatedReposResult{}, err
   590  	}
   591  
   592  	repos, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries(ctx, repoMetaList, latestImageMeta,
   593  		mTypes.Filter{}, pageInput, cveInfo, skip)
   594  	if err != nil {
   595  		return &gql_generated.PaginatedReposResult{}, err
   596  	}
   597  
   598  	return &gql_generated.PaginatedReposResult{
   599  		Results: repos,
   600  		Page: &gql_generated.PageInfo{
   601  			TotalCount: pageInfo.TotalCount,
   602  			ItemCount:  pageInfo.ItemCount,
   603  		},
   604  	}, nil
   605  }
   606  
   607  func globalSearch(ctx context.Context, query string, metaDB mTypes.MetaDB, filter *gql_generated.Filter,
   608  	requestedPage *gql_generated.PageInput, cveInfo cveinfo.CveInfo, log log.Logger, //nolint:unparam
   609  ) (*gql_generated.PaginatedReposResult, []*gql_generated.ImageSummary, []*gql_generated.LayerSummary, error,
   610  ) {
   611  	preloads := convert.GetPreloads(ctx)
   612  	paginatedRepos := gql_generated.PaginatedReposResult{}
   613  	images := []*gql_generated.ImageSummary{}
   614  	layers := []*gql_generated.LayerSummary{}
   615  
   616  	if requestedPage == nil {
   617  		requestedPage = &gql_generated.PageInput{}
   618  	}
   619  
   620  	localFilter := mTypes.Filter{}
   621  	if filter != nil {
   622  		localFilter = mTypes.Filter{
   623  			Os:            filter.Os,
   624  			Arch:          filter.Arch,
   625  			HasToBeSigned: filter.HasToBeSigned,
   626  			IsBookmarked:  filter.IsBookmarked,
   627  			IsStarred:     filter.IsStarred,
   628  		}
   629  	}
   630  
   631  	if searchingForRepos(query) {
   632  		skip := convert.SkipQGLField{
   633  			Vulnerabilities: canSkipField(preloads, "Repos.NewestImage.Vulnerabilities"),
   634  		}
   635  
   636  		pageInput := pagination.PageInput{
   637  			Limit:  deref(requestedPage.Limit, 0),
   638  			Offset: deref(requestedPage.Offset, 0),
   639  			SortBy: pagination.SortCriteria(
   640  				deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
   641  			),
   642  		}
   643  
   644  		repoMetaList, err := metaDB.SearchRepos(ctx, query)
   645  		if err != nil {
   646  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{},
   647  				[]*gql_generated.LayerSummary{}, err
   648  		}
   649  
   650  		imageMetaMap, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList))
   651  		if err != nil {
   652  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{},
   653  				[]*gql_generated.LayerSummary{}, err
   654  		}
   655  
   656  		repos, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries(ctx, repoMetaList, imageMetaMap, localFilter,
   657  			pageInput, cveInfo,
   658  			skip)
   659  		if err != nil {
   660  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{},
   661  				[]*gql_generated.LayerSummary{}, err
   662  		}
   663  
   664  		paginatedRepos.Page = &gql_generated.PageInfo{
   665  			TotalCount: pageInfo.TotalCount,
   666  			ItemCount:  pageInfo.ItemCount,
   667  		}
   668  
   669  		paginatedRepos.Results = repos
   670  	} else { // search for images
   671  		skip := convert.SkipQGLField{
   672  			Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities"),
   673  		}
   674  
   675  		pageInput := pagination.PageInput{
   676  			Limit:  deref(requestedPage.Limit, 0),
   677  			Offset: deref(requestedPage.Offset, 0),
   678  			SortBy: pagination.SortCriteria(
   679  				deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
   680  			),
   681  		}
   682  
   683  		fullImageMetaList, err := metaDB.SearchTags(ctx, query)
   684  		if err != nil {
   685  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
   686  		}
   687  
   688  		imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo,
   689  			localFilter, pageInput)
   690  		if err != nil {
   691  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
   692  		}
   693  
   694  		images = imageSummaries
   695  
   696  		paginatedRepos.Page = &gql_generated.PageInfo{
   697  			TotalCount: pageInfo.TotalCount,
   698  			ItemCount:  pageInfo.ItemCount,
   699  		}
   700  	}
   701  
   702  	return &paginatedRepos, images, layers, nil
   703  }
   704  
   705  func canSkipField(preloads map[string]bool, s string) bool {
   706  	fieldIsPresent := preloads[s]
   707  
   708  	return !fieldIsPresent
   709  }
   710  
   711  func derivedImageList(ctx context.Context, image string, digest *string, metaDB mTypes.MetaDB,
   712  	requestedPage *gql_generated.PageInput,
   713  	cveInfo cveinfo.CveInfo, log log.Logger,
   714  ) (*gql_generated.PaginatedImagesResult, error) {
   715  	if requestedPage == nil {
   716  		requestedPage = &gql_generated.PageInput{}
   717  	}
   718  
   719  	pageInput := pagination.PageInput{
   720  		Limit:  deref(requestedPage.Limit, 0),
   721  		Offset: deref(requestedPage.Offset, 0),
   722  		SortBy: pagination.SortCriteria(
   723  			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
   724  		),
   725  	}
   726  
   727  	skip := convert.SkipQGLField{
   728  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"),
   729  	}
   730  
   731  	imageRepo, imageTag := zcommon.GetImageDirAndTag(image)
   732  	if imageTag == "" {
   733  		return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided")
   734  	}
   735  
   736  	skipReferenceImage := convert.SkipQGLField{
   737  		Vulnerabilities: true,
   738  	}
   739  
   740  	searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, digest, skipReferenceImage, metaDB, cveInfo, log)
   741  	if err != nil {
   742  		if errors.Is(err, zerr.ErrRepoMetaNotFound) {
   743  			return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository: not found")
   744  		}
   745  
   746  		return &gql_generated.PaginatedImagesResult{}, err
   747  	}
   748  
   749  	// we need all available tags
   750  	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, filterDerivedImages(searchedImage))
   751  	if err != nil {
   752  		return &gql_generated.PaginatedImagesResult{}, err
   753  	}
   754  
   755  	derivedList, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo,
   756  		mTypes.Filter{}, pageInput)
   757  	if err != nil {
   758  		return &gql_generated.PaginatedImagesResult{}, err
   759  	}
   760  
   761  	return &gql_generated.PaginatedImagesResult{
   762  		Results: derivedList,
   763  		Page: &gql_generated.PageInfo{
   764  			TotalCount: pageInfo.TotalCount,
   765  			ItemCount:  pageInfo.ItemCount,
   766  		},
   767  	}, nil
   768  }
   769  
   770  func filterDerivedImages(image *gql_generated.ImageSummary) mTypes.FilterFunc {
   771  	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
   772  		var addImageToList bool
   773  
   774  		imageManifest := imageMeta.Manifests[0]
   775  
   776  		for i := range image.Manifests {
   777  			manifestDigest := imageManifest.Digest.String()
   778  			if manifestDigest == *image.Manifests[i].Digest {
   779  				return false
   780  			}
   781  			imageLayers := image.Manifests[i].Layers
   782  
   783  			addImageToList = false
   784  			layers := imageManifest.Manifest.Layers
   785  
   786  			sameLayer := 0
   787  
   788  			for _, l := range imageLayers {
   789  				for _, k := range layers {
   790  					if k.Digest.String() == *l.Digest {
   791  						sameLayer++
   792  					}
   793  				}
   794  			}
   795  
   796  			// if all layers are the same
   797  			if sameLayer == len(imageLayers) {
   798  				// it's a derived image
   799  				addImageToList = true
   800  			}
   801  
   802  			if addImageToList {
   803  				return true
   804  			}
   805  		}
   806  
   807  		return false
   808  	}
   809  }
   810  
   811  func baseImageList(ctx context.Context, image string, digest *string, metaDB mTypes.MetaDB,
   812  	requestedPage *gql_generated.PageInput,
   813  	cveInfo cveinfo.CveInfo, log log.Logger,
   814  ) (*gql_generated.PaginatedImagesResult, error) {
   815  	if requestedPage == nil {
   816  		requestedPage = &gql_generated.PageInput{}
   817  	}
   818  
   819  	pageInput := pagination.PageInput{
   820  		Limit:  deref(requestedPage.Limit, 0),
   821  		Offset: deref(requestedPage.Offset, 0),
   822  		SortBy: pagination.SortCriteria(
   823  			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
   824  		),
   825  	}
   826  
   827  	skip := convert.SkipQGLField{
   828  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"),
   829  	}
   830  
   831  	imageRepo, imageTag := zcommon.GetImageDirAndTag(image)
   832  
   833  	if imageTag == "" {
   834  		return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided")
   835  	}
   836  
   837  	skipReferenceImage := convert.SkipQGLField{
   838  		Vulnerabilities: true,
   839  	}
   840  
   841  	searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, digest, skipReferenceImage, metaDB, cveInfo, log)
   842  	if err != nil {
   843  		if errors.Is(err, zerr.ErrRepoMetaNotFound) {
   844  			return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository: not found")
   845  		}
   846  
   847  		return &gql_generated.PaginatedImagesResult{}, err
   848  	}
   849  
   850  	// we need all available tags
   851  	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, filterBaseImages(searchedImage))
   852  	if err != nil {
   853  		return &gql_generated.PaginatedImagesResult{}, err
   854  	}
   855  
   856  	baseList, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList,
   857  		skip, cveInfo, mTypes.Filter{}, pageInput)
   858  	if err != nil {
   859  		return &gql_generated.PaginatedImagesResult{}, err
   860  	}
   861  
   862  	return &gql_generated.PaginatedImagesResult{
   863  		Page: &gql_generated.PageInfo{
   864  			TotalCount: pageInfo.TotalCount,
   865  			ItemCount:  pageInfo.ItemCount,
   866  		},
   867  		Results: baseList,
   868  	}, nil
   869  }
   870  
   871  func filterBaseImages(image *gql_generated.ImageSummary) mTypes.FilterFunc {
   872  	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
   873  		var addImageToList bool
   874  
   875  		manifest := imageMeta.Manifests[0]
   876  
   877  		for i := range image.Manifests {
   878  			manifestDigest := manifest.Digest.String()
   879  			if manifestDigest == *image.Manifests[i].Digest {
   880  				return false
   881  			}
   882  
   883  			addImageToList = true
   884  
   885  			for _, l := range manifest.Manifest.Layers {
   886  				foundLayer := false
   887  
   888  				for _, k := range image.Manifests[i].Layers {
   889  					if l.Digest.String() == *k.Digest {
   890  						foundLayer = true
   891  
   892  						break
   893  					}
   894  				}
   895  
   896  				if !foundLayer {
   897  					addImageToList = false
   898  
   899  					break
   900  				}
   901  			}
   902  
   903  			if addImageToList {
   904  				return true
   905  			}
   906  		}
   907  
   908  		return false
   909  	}
   910  }
   911  
   912  func validateGlobalSearchInput(query string, filter *gql_generated.Filter,
   913  	requestedPage *gql_generated.PageInput,
   914  ) error {
   915  	if len(query) > querySizeLimit {
   916  		return fmt.Errorf("global-search: max string size limit exceeded for query parameter. max=%d current=%d %w",
   917  			querySizeLimit, len(query), zerr.ErrInvalidRequestParams)
   918  	}
   919  
   920  	err := checkFilter(filter)
   921  	if err != nil {
   922  		return err
   923  	}
   924  
   925  	err = checkRequestedPage(requestedPage)
   926  	if err != nil {
   927  		return err
   928  	}
   929  
   930  	return nil
   931  }
   932  
   933  func checkFilter(filter *gql_generated.Filter) error {
   934  	if filter == nil {
   935  		return nil
   936  	}
   937  
   938  	for _, arch := range filter.Arch {
   939  		if len(*arch) > querySizeLimit {
   940  			return fmt.Errorf("global-search: max string size limit exceeded for arch parameter. max=%d current=%d %w",
   941  				querySizeLimit, len(*arch), zerr.ErrInvalidRequestParams)
   942  		}
   943  	}
   944  
   945  	for _, osSys := range filter.Os {
   946  		if len(*osSys) > querySizeLimit {
   947  			return fmt.Errorf("global-search: max string size limit exceeded for os parameter. max=%d current=%d %w",
   948  				querySizeLimit, len(*osSys), zerr.ErrInvalidRequestParams)
   949  		}
   950  	}
   951  
   952  	return nil
   953  }
   954  
   955  func checkRequestedPage(requestedPage *gql_generated.PageInput) error {
   956  	if requestedPage == nil {
   957  		return nil
   958  	}
   959  
   960  	if requestedPage.Limit != nil && *requestedPage.Limit < 0 {
   961  		return fmt.Errorf("global-search: requested page limit parameter can't be negative %w",
   962  			zerr.ErrInvalidRequestParams)
   963  	}
   964  
   965  	if requestedPage.Offset != nil && *requestedPage.Offset < 0 {
   966  		return fmt.Errorf("global-search: requested page offset parameter can't be negative %w",
   967  			zerr.ErrInvalidRequestParams)
   968  	}
   969  
   970  	return nil
   971  }
   972  
   973  func cleanQuery(query string) string {
   974  	query = strings.TrimSpace(query)
   975  	query = strings.Trim(query, "/")
   976  	query = strings.ToLower(query)
   977  
   978  	return query
   979  }
   980  
   981  func cleanFilter(filter *gql_generated.Filter) *gql_generated.Filter {
   982  	if filter == nil {
   983  		return nil
   984  	}
   985  
   986  	if filter.Arch != nil {
   987  		for i := range filter.Arch {
   988  			*filter.Arch[i] = strings.ToLower(*filter.Arch[i])
   989  			*filter.Arch[i] = strings.TrimSpace(*filter.Arch[i])
   990  		}
   991  
   992  		filter.Arch = deleteEmptyElements(filter.Arch)
   993  	}
   994  
   995  	if filter.Os != nil {
   996  		for i := range filter.Os {
   997  			*filter.Os[i] = strings.ToLower(*filter.Os[i])
   998  			*filter.Os[i] = strings.TrimSpace(*filter.Os[i])
   999  		}
  1000  
  1001  		filter.Os = deleteEmptyElements(filter.Os)
  1002  	}
  1003  
  1004  	return filter
  1005  }
  1006  
  1007  func deleteEmptyElements(slice []*string) []*string {
  1008  	i := 0
  1009  	for i < len(slice) {
  1010  		if elementIsEmpty(*slice[i]) {
  1011  			slice = deleteElementAt(slice, i)
  1012  		} else {
  1013  			i++
  1014  		}
  1015  	}
  1016  
  1017  	return slice
  1018  }
  1019  
  1020  func elementIsEmpty(s string) bool {
  1021  	return s == ""
  1022  }
  1023  
  1024  func deleteElementAt(slice []*string, i int) []*string {
  1025  	slice[i] = slice[len(slice)-1]
  1026  	slice = slice[:len(slice)-1]
  1027  
  1028  	return slice
  1029  }
  1030  
  1031  func expandedRepoInfo(ctx context.Context, repo string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo, log log.Logger,
  1032  ) (*gql_generated.RepoInfo, error) {
  1033  	if ok, err := reqCtx.RepoIsUserAvailable(ctx, repo); !ok || err != nil {
  1034  		log.Info().Err(err).Str("repository", repo).Bool("availability", ok).Msg("resolver: repo user availability")
  1035  
  1036  		return &gql_generated.RepoInfo{}, nil //nolint:nilerr // don't give details to a potential attacker
  1037  	}
  1038  
  1039  	repoMeta, err := metaDB.GetRepoMeta(ctx, repo)
  1040  	if err != nil {
  1041  		log.Error().Err(err).Str("repository", repo).Msg("resolver: can't retrieve repoMeta for repo")
  1042  
  1043  		return &gql_generated.RepoInfo{}, err
  1044  	}
  1045  
  1046  	tagsDigests := []string{}
  1047  
  1048  	for i := range repoMeta.Tags {
  1049  		if i == "" {
  1050  			continue
  1051  		}
  1052  
  1053  		tagsDigests = append(tagsDigests, repoMeta.Tags[i].Digest)
  1054  	}
  1055  
  1056  	imageMetaMap, err := metaDB.FilterImageMeta(ctx, tagsDigests)
  1057  	if err != nil {
  1058  		log.Error().Err(err).Str("repository", repo).Msg("resolver: can't retrieve imageMeta for repo")
  1059  
  1060  		return &gql_generated.RepoInfo{}, err
  1061  	}
  1062  
  1063  	skip := convert.SkipQGLField{
  1064  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Summary.NewestImage.Vulnerabilities") &&
  1065  			canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"),
  1066  	}
  1067  
  1068  	repoSummary, imageSummaries := convert.RepoMeta2ExpandedRepoInfo(ctx, repoMeta, imageMetaMap,
  1069  		skip, cveInfo, log)
  1070  
  1071  	dateSortedImages := make(timeSlice, 0, len(imageSummaries))
  1072  	for _, imgSummary := range imageSummaries {
  1073  		dateSortedImages = append(dateSortedImages, imgSummary)
  1074  	}
  1075  
  1076  	sort.Sort(dateSortedImages)
  1077  
  1078  	return &gql_generated.RepoInfo{Summary: repoSummary, Images: dateSortedImages}, nil
  1079  }
  1080  
  1081  type timeSlice []*gql_generated.ImageSummary
  1082  
  1083  func (p timeSlice) Len() int {
  1084  	return len(p)
  1085  }
  1086  
  1087  func (p timeSlice) Less(i, j int) bool {
  1088  	return p[i].LastUpdated.After(*p[j].LastUpdated)
  1089  }
  1090  
  1091  func (p timeSlice) Swap(i, j int) {
  1092  	p[i], p[j] = p[j], p[i]
  1093  }
  1094  
  1095  func deref[T any](pointer *T, defaultVal T) T {
  1096  	if pointer != nil {
  1097  		return *pointer
  1098  	}
  1099  
  1100  	return defaultVal
  1101  }
  1102  
  1103  func searchingForRepos(query string) bool {
  1104  	return !strings.Contains(query, ":")
  1105  }
  1106  
  1107  func getImageList(ctx context.Context, repo string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo,
  1108  	requestedPage *gql_generated.PageInput, log log.Logger, //nolint:unparam
  1109  ) (*gql_generated.PaginatedImagesResult, error) {
  1110  	if requestedPage == nil {
  1111  		requestedPage = &gql_generated.PageInput{}
  1112  	}
  1113  
  1114  	skip := convert.SkipQGLField{
  1115  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"),
  1116  	}
  1117  
  1118  	pageInput := pagination.PageInput{
  1119  		Limit:  deref(requestedPage.Limit, 0),
  1120  		Offset: deref(requestedPage.Offset, 0),
  1121  		SortBy: pagination.SortCriteria(
  1122  			deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
  1123  		),
  1124  	}
  1125  
  1126  	var matchRepoName mTypes.FilterRepoTagFunc
  1127  
  1128  	if repo == "" {
  1129  		matchRepoName = mTypes.AcceptAllRepoTag
  1130  	} else {
  1131  		matchRepoName = func(repoName, tag string) bool { return repoName == repo }
  1132  	}
  1133  
  1134  	imageMeta, err := metaDB.FilterTags(ctx, matchRepoName, mTypes.AcceptAllImageMeta)
  1135  	if err != nil {
  1136  		return &gql_generated.PaginatedImagesResult{}, err
  1137  	}
  1138  
  1139  	imageList, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, imageMeta, skip,
  1140  		cveInfo, mTypes.Filter{}, pageInput)
  1141  	if err != nil {
  1142  		return &gql_generated.PaginatedImagesResult{}, err
  1143  	}
  1144  
  1145  	return &gql_generated.PaginatedImagesResult{
  1146  		Results: imageList,
  1147  		Page: &gql_generated.PageInfo{
  1148  			TotalCount: pageInfo.TotalCount,
  1149  			ItemCount:  pageInfo.ItemCount,
  1150  		},
  1151  	}, nil
  1152  }
  1153  
  1154  func getReferrers(metaDB mTypes.MetaDB, repo string, referredDigest string, artifactTypes []string,
  1155  	log log.Logger,
  1156  ) ([]*gql_generated.Referrer, error) {
  1157  	refDigest := godigest.Digest(referredDigest)
  1158  	if err := refDigest.Validate(); err != nil {
  1159  		log.Error().Err(err).Str("digest", referredDigest).Msg("graphql: bad referenced digest string from request")
  1160  
  1161  		return []*gql_generated.Referrer{}, fmt.Errorf("graphql: bad digest string from request '%s' %w",
  1162  			referredDigest, err)
  1163  	}
  1164  
  1165  	referrers, err := metaDB.GetReferrersInfo(repo, refDigest, artifactTypes)
  1166  	if err != nil {
  1167  		return nil, err
  1168  	}
  1169  
  1170  	results := make([]*gql_generated.Referrer, 0, len(referrers))
  1171  
  1172  	for _, referrer := range referrers {
  1173  		referrer := referrer
  1174  
  1175  		results = append(results, &gql_generated.Referrer{
  1176  			MediaType:    &referrer.MediaType,
  1177  			ArtifactType: &referrer.ArtifactType,
  1178  			Digest:       &referrer.Digest,
  1179  			Size:         &referrer.Size,
  1180  			Annotations:  convert.StringMap2Annotations(referrer.Annotations),
  1181  		})
  1182  	}
  1183  
  1184  	return results, nil
  1185  }