zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/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.dev/zot/errors"
    19  	zcommon "zotregistry.dev/zot/pkg/common"
    20  	"zotregistry.dev/zot/pkg/extensions/search/convert"
    21  	cveinfo "zotregistry.dev/zot/pkg/extensions/search/cve"
    22  	cvemodel "zotregistry.dev/zot/pkg/extensions/search/cve/model"
    23  	"zotregistry.dev/zot/pkg/extensions/search/gql_generated"
    24  	"zotregistry.dev/zot/pkg/extensions/search/pagination"
    25  	"zotregistry.dev/zot/pkg/log"
    26  	mTypes "zotregistry.dev/zot/pkg/meta/types"
    27  	reqCtx "zotregistry.dev/zot/pkg/requestcontext"
    28  	"zotregistry.dev/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  
    80  		// Check in case of an index if the index digest matches the search digest
    81  		// For Manifests, this is equivalent to imageMeta.Manifests[0]
    82  		if imageMeta.Digest.String() == lookupDigest {
    83  			return true
    84  		}
    85  
    86  		manifest := imageMeta.Manifests[0]
    87  
    88  		manifestDigest := manifest.Digest.String()
    89  
    90  		// Check the image manifest in index.json matches the search digest
    91  		// This is a blob with mediaType application/vnd.oci.image.manifest.v1+json
    92  		if strings.Contains(manifestDigest, lookupDigest) {
    93  			return true
    94  		}
    95  
    96  		// Check the image config matches the search digest
    97  		// This is a blob with mediaType application/vnd.oci.image.config.v1+json
    98  		if strings.Contains(manifest.Manifest.Config.Digest.String(), lookupDigest) {
    99  			return true
   100  		}
   101  
   102  		// Check to see if the individual layers in the oci image manifest match the digest
   103  		// These are blobs with mediaType application/vnd.oci.image.layer.v1.tar+gzip
   104  		for _, layer := range manifest.Manifest.Layers {
   105  			if strings.Contains(layer.Digest.String(), lookupDigest) {
   106  				return true
   107  			}
   108  		}
   109  
   110  		return false
   111  	}
   112  }
   113  
   114  func getImageListForDigest(ctx context.Context, digest string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo,
   115  	requestedPage *gql_generated.PageInput,
   116  ) (*gql_generated.PaginatedImagesResult, error) {
   117  	if requestedPage == nil {
   118  		requestedPage = &gql_generated.PageInput{}
   119  	}
   120  
   121  	skip := convert.SkipQGLField{
   122  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"),
   123  	}
   124  
   125  	pageInput := pagination.PageInput{
   126  		Limit:  deref(requestedPage.Limit, 0),
   127  		Offset: deref(requestedPage.Offset, 0),
   128  		SortBy: pagination.SortCriteria(
   129  			deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
   130  		),
   131  	}
   132  
   133  	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByDigest(digest))
   134  	if err != nil {
   135  		return &gql_generated.PaginatedImagesResult{}, err
   136  	}
   137  
   138  	imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip,
   139  		cveInfo, mTypes.Filter{}, pageInput)
   140  	if err != nil {
   141  		return &gql_generated.PaginatedImagesResult{}, err
   142  	}
   143  
   144  	return &gql_generated.PaginatedImagesResult{
   145  		Results: imageSummaries,
   146  		Page: &gql_generated.PageInfo{
   147  			TotalCount: pageInfo.TotalCount,
   148  			ItemCount:  pageInfo.ItemCount,
   149  		},
   150  	}, nil
   151  }
   152  
   153  func getImageSummary(ctx context.Context, repo, tag string, digest *string, skipCVE convert.SkipQGLField,
   154  	metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo, log log.Logger, //nolint:unparam
   155  ) (
   156  	*gql_generated.ImageSummary, error,
   157  ) {
   158  	repoMeta, err := metaDB.GetRepoMeta(ctx, repo)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	manifestDescriptor, ok := repoMeta.Tags[tag]
   164  	if !ok {
   165  		return nil, gqlerror.Errorf("can't find image: %s:%s", repo, tag)
   166  	}
   167  
   168  	repoMeta.Tags = map[mTypes.Tag]mTypes.Descriptor{tag: manifestDescriptor}
   169  
   170  	imageDigest := manifestDescriptor.Digest
   171  	if digest != nil {
   172  		imageDigest = *digest
   173  		repoMeta.Tags[tag] = mTypes.Descriptor{
   174  			Digest:    imageDigest,
   175  			MediaType: ispec.MediaTypeImageManifest,
   176  		}
   177  	}
   178  
   179  	imageMetaMap, err := metaDB.FilterImageMeta(ctx, []string{imageDigest})
   180  	if err != nil {
   181  		return &gql_generated.ImageSummary{}, err
   182  	}
   183  
   184  	imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, imageMetaMap, skipCVE, cveInfo)
   185  
   186  	if len(imageSummaries) == 0 {
   187  		return &gql_generated.ImageSummary{}, nil
   188  	}
   189  
   190  	return imageSummaries[0], nil
   191  }
   192  
   193  func getCVEListForImage(
   194  	ctx context.Context, //nolint:unparam // may be used in the future to filter by permissions
   195  	image string,
   196  	cveInfo cveinfo.CveInfo,
   197  	requestedPage *gql_generated.PageInput,
   198  	searchedCVE string,
   199  	excludedCVE string,
   200  	severity string,
   201  	log log.Logger, //nolint:unparam // may be used by devs for debugging
   202  ) (*gql_generated.CVEResultForImage, error) {
   203  	if requestedPage == nil {
   204  		requestedPage = &gql_generated.PageInput{}
   205  	}
   206  
   207  	pageInput := cvemodel.PageInput{
   208  		Limit:  deref(requestedPage.Limit, 0),
   209  		Offset: deref(requestedPage.Offset, 0),
   210  		SortBy: cvemodel.SortCriteria(
   211  			deref(requestedPage.SortBy, gql_generated.SortCriteriaSeverity),
   212  		),
   213  	}
   214  
   215  	repo, ref, _ := zcommon.GetImageDirAndReference(image)
   216  
   217  	if ref == "" {
   218  		return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided")
   219  	}
   220  
   221  	cveList, imageCveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, repo, ref,
   222  		searchedCVE, excludedCVE, severity, pageInput)
   223  	if err != nil {
   224  		return &gql_generated.CVEResultForImage{}, err
   225  	}
   226  
   227  	cveids := []*gql_generated.Cve{}
   228  
   229  	for _, cveDetail := range cveList {
   230  		vulID := cveDetail.ID
   231  		desc := cveDetail.Description
   232  		title := cveDetail.Title
   233  		severity := cveDetail.Severity
   234  		referenceURL := cveDetail.Reference
   235  
   236  		pkgList := make([]*gql_generated.PackageInfo, 0)
   237  
   238  		for _, pkg := range cveDetail.PackageList {
   239  			pkg := pkg
   240  
   241  			pkgList = append(pkgList,
   242  				&gql_generated.PackageInfo{
   243  					Name:             &pkg.Name,
   244  					PackagePath:      &pkg.PackagePath,
   245  					InstalledVersion: &pkg.InstalledVersion,
   246  					FixedVersion:     &pkg.FixedVersion,
   247  				},
   248  			)
   249  		}
   250  
   251  		cveids = append(cveids,
   252  			&gql_generated.Cve{
   253  				ID:          &vulID,
   254  				Title:       &title,
   255  				Description: &desc,
   256  				Severity:    &severity,
   257  				Reference:   &referenceURL,
   258  				PackageList: pkgList,
   259  			},
   260  		)
   261  	}
   262  
   263  	return &gql_generated.CVEResultForImage{
   264  		Tag:     &ref,
   265  		CVEList: cveids,
   266  		Summary: &gql_generated.ImageVulnerabilitySummary{
   267  			MaxSeverity:   &imageCveSummary.MaxSeverity,
   268  			UnknownCount:  &imageCveSummary.UnknownCount,
   269  			LowCount:      &imageCveSummary.LowCount,
   270  			MediumCount:   &imageCveSummary.MediumCount,
   271  			HighCount:     &imageCveSummary.HighCount,
   272  			CriticalCount: &imageCveSummary.CriticalCount,
   273  			Count:         &imageCveSummary.Count,
   274  		},
   275  		Page: &gql_generated.PageInfo{
   276  			TotalCount: pageInfo.TotalCount,
   277  			ItemCount:  pageInfo.ItemCount,
   278  		},
   279  	}, nil
   280  }
   281  
   282  func getCVEDiffListForImages(
   283  	ctx context.Context, //nolint:unparam // may be used in the future to filter by permissions
   284  	minuend gql_generated.ImageInput,
   285  	subtrahend gql_generated.ImageInput,
   286  	metaDB mTypes.MetaDB,
   287  	cveInfo cveinfo.CveInfo,
   288  	requestedPage *gql_generated.PageInput,
   289  	searchedCVE string,
   290  	excludedCVE string,
   291  	log log.Logger, //nolint:unparam // may be used by devs for debugging
   292  ) (*gql_generated.CVEDiffResult, error) {
   293  	minuend, err := resolveImageData(ctx, minuend, metaDB)
   294  	if err != nil {
   295  		return nil, err
   296  	}
   297  
   298  	resultMinuend := getImageIdentifier(minuend)
   299  	resultSubtrahend := gql_generated.ImageIdentifier{}
   300  
   301  	if subtrahend.Repo != "" {
   302  		subtrahend, err = resolveImageData(ctx, subtrahend, metaDB)
   303  		if err != nil {
   304  			return nil, err
   305  		}
   306  		resultSubtrahend = getImageIdentifier(subtrahend)
   307  	} else {
   308  		// search for base images
   309  		// get minuend image meta
   310  		minuendSummary, err := metaDB.GetImageMeta(godigest.Digest(deref(minuend.Digest, "")))
   311  		if err != nil {
   312  			return &gql_generated.CVEDiffResult{}, err
   313  		}
   314  
   315  		// get the base images for the minuend
   316  		minuendBaseImages, err := metaDB.FilterTags(ctx, mTypes.AcceptOnlyRepo(minuend.Repo),
   317  			filterBaseImagesForMeta(minuendSummary))
   318  		if err != nil {
   319  			return &gql_generated.CVEDiffResult{}, err
   320  		}
   321  
   322  		// get the best base image as subtrahend
   323  		// get the one with most layers in common
   324  		imgLayers := map[string]struct{}{}
   325  
   326  		for _, layer := range minuendSummary.Manifests[0].Manifest.Layers {
   327  			imgLayers[layer.Digest.String()] = struct{}{}
   328  		}
   329  
   330  		bestMatchingScore := 0
   331  
   332  		for _, baseImage := range minuendBaseImages {
   333  			for _, baseManifest := range baseImage.Manifests {
   334  				currentMatchingScore := 0
   335  
   336  				for _, layer := range baseManifest.Manifest.Layers {
   337  					if _, ok := imgLayers[layer.Digest.String()]; ok {
   338  						currentMatchingScore++
   339  					}
   340  				}
   341  
   342  				if currentMatchingScore > bestMatchingScore {
   343  					bestMatchingScore = currentMatchingScore
   344  
   345  					resultSubtrahend = gql_generated.ImageIdentifier{
   346  						Repo:   baseImage.Repo,
   347  						Tag:    baseImage.Tag,
   348  						Digest: ref(baseImage.Manifests[0].Digest.String()),
   349  						Platform: &gql_generated.Platform{
   350  							Os:   ref(baseImage.Manifests[0].Config.OS),
   351  							Arch: ref(getArch(baseImage.Manifests[0].Config.Platform)),
   352  						},
   353  					}
   354  					subtrahend.Repo = baseImage.Repo
   355  					subtrahend.Tag = baseImage.Tag
   356  					subtrahend.Digest = ref(baseImage.Manifests[0].Digest.String())
   357  				}
   358  			}
   359  		}
   360  	}
   361  
   362  	minuendRepoRef := minuend.Repo + "@" + deref(minuend.Digest, "")
   363  	subtrahendRepoRef := subtrahend.Repo + "@" + deref(subtrahend.Digest, "")
   364  	page := dderef(requestedPage)
   365  
   366  	diffCVEs, diffSummary, _, err := cveInfo.GetCVEDiffListForImages(ctx, minuendRepoRef, subtrahendRepoRef, searchedCVE,
   367  		excludedCVE, cvemodel.PageInput{
   368  			Limit:  deref(page.Limit, 0),
   369  			Offset: deref(page.Offset, 0),
   370  			SortBy: cvemodel.SortCriteria(deref(page.SortBy, gql_generated.SortCriteriaSeverity)),
   371  		})
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  
   376  	cveids := []*gql_generated.Cve{}
   377  
   378  	for _, cveDetail := range diffCVEs {
   379  		vulID := cveDetail.ID
   380  		desc := cveDetail.Description
   381  		title := cveDetail.Title
   382  		severity := cveDetail.Severity
   383  		referenceURL := cveDetail.Reference
   384  
   385  		pkgList := make([]*gql_generated.PackageInfo, 0)
   386  
   387  		for _, pkg := range cveDetail.PackageList {
   388  			pkg := pkg
   389  
   390  			pkgList = append(pkgList,
   391  				&gql_generated.PackageInfo{
   392  					Name:             &pkg.Name,
   393  					InstalledVersion: &pkg.InstalledVersion,
   394  					FixedVersion:     &pkg.FixedVersion,
   395  				},
   396  			)
   397  		}
   398  
   399  		cveids = append(cveids,
   400  			&gql_generated.Cve{
   401  				ID:          &vulID,
   402  				Title:       &title,
   403  				Description: &desc,
   404  				Severity:    &severity,
   405  				Reference:   &referenceURL,
   406  				PackageList: pkgList,
   407  			},
   408  		)
   409  	}
   410  
   411  	return &gql_generated.CVEDiffResult{
   412  		Minuend:    &resultMinuend,
   413  		Subtrahend: &resultSubtrahend,
   414  		Summary: &gql_generated.ImageVulnerabilitySummary{
   415  			Count:         &diffSummary.Count,
   416  			UnknownCount:  &diffSummary.UnknownCount,
   417  			LowCount:      &diffSummary.LowCount,
   418  			MediumCount:   &diffSummary.MediumCount,
   419  			HighCount:     &diffSummary.HighCount,
   420  			CriticalCount: &diffSummary.CriticalCount,
   421  			MaxSeverity:   &diffSummary.MaxSeverity,
   422  		},
   423  		CVEList: cveids,
   424  		Page:    &gql_generated.PageInfo{},
   425  	}, nil
   426  }
   427  
   428  func getImageIdentifier(img gql_generated.ImageInput) gql_generated.ImageIdentifier {
   429  	return gql_generated.ImageIdentifier{
   430  		Repo:     img.Repo,
   431  		Tag:      img.Tag,
   432  		Digest:   img.Digest,
   433  		Platform: getIdentifierPlatform(img.Platform),
   434  	}
   435  }
   436  
   437  func getIdentifierPlatform(platform *gql_generated.PlatformInput) *gql_generated.Platform {
   438  	if platform == nil {
   439  		return nil
   440  	}
   441  
   442  	return &gql_generated.Platform{
   443  		Os:   platform.Os,
   444  		Arch: platform.Arch,
   445  	}
   446  }
   447  
   448  // rename idea: identify image from input.
   449  func resolveImageData(ctx context.Context, imageInput gql_generated.ImageInput, metaDB mTypes.MetaDB,
   450  ) (gql_generated.ImageInput, error) {
   451  	if imageInput.Repo == "" {
   452  		return gql_generated.ImageInput{}, zerr.ErrEmptyRepoName
   453  	}
   454  
   455  	if imageInput.Tag == "" {
   456  		return gql_generated.ImageInput{}, zerr.ErrEmptyTag
   457  	}
   458  
   459  	// try checking if the tag is a simple image first
   460  	repoMeta, err := metaDB.GetRepoMeta(ctx, imageInput.Repo)
   461  	if err != nil {
   462  		return gql_generated.ImageInput{}, err
   463  	}
   464  
   465  	descriptor, ok := repoMeta.Tags[imageInput.Tag]
   466  	if !ok {
   467  		return gql_generated.ImageInput{}, zerr.ErrImageNotFound
   468  	}
   469  
   470  	switch descriptor.MediaType {
   471  	case ispec.MediaTypeImageManifest:
   472  		imageInput.Digest = ref(descriptor.Digest)
   473  
   474  		return imageInput, nil
   475  	case ispec.MediaTypeImageIndex:
   476  		if dderef(imageInput.Digest) == "" && !isPlatformSpecified(imageInput.Platform) {
   477  			return gql_generated.ImageInput{},
   478  				fmt.Errorf("%w: platform or specific manifest digest needed", zerr.ErrAmbiguousInput)
   479  		}
   480  
   481  		imageMeta, err := metaDB.GetImageMeta(godigest.Digest(descriptor.Digest))
   482  		if err != nil {
   483  			return gql_generated.ImageInput{}, err
   484  		}
   485  
   486  		for _, manifest := range imageMeta.Manifests {
   487  			if manifest.Digest.String() == dderef(imageInput.Digest) ||
   488  				isMatchingPlatform(manifest.Config.Platform, dderef(imageInput.Platform)) {
   489  				imageInput.Digest = ref(manifest.Digest.String())
   490  				imageInput.Platform = &gql_generated.PlatformInput{
   491  					Os:   ref(manifest.Config.OS),
   492  					Arch: ref(getArch(manifest.Config.Platform)),
   493  				}
   494  
   495  				return imageInput, nil
   496  			}
   497  		}
   498  
   499  		return imageInput, zerr.ErrImageNotFound
   500  	}
   501  
   502  	return imageInput, nil
   503  }
   504  
   505  func isPlatformSpecified(platformInput *gql_generated.PlatformInput) bool {
   506  	if platformInput == nil {
   507  		return false
   508  	}
   509  
   510  	if dderef(platformInput.Os) == "" || dderef(platformInput.Arch) == "" {
   511  		return false
   512  	}
   513  
   514  	return true
   515  }
   516  
   517  func isMatchingPlatform(platform ispec.Platform, platformInput gql_generated.PlatformInput) bool {
   518  	if platform.OS != deref(platformInput.Os, "") {
   519  		return false
   520  	}
   521  
   522  	arch := getArch(platform)
   523  
   524  	return arch == deref(platformInput.Arch, "")
   525  }
   526  
   527  func getArch(platform ispec.Platform) string {
   528  	arch := platform.Architecture
   529  	if arch != "" && platform.Variant != "" {
   530  		arch += "/" + platform.Variant
   531  	}
   532  
   533  	return arch
   534  }
   535  
   536  func FilterByTagInfo(tagsInfo []cvemodel.TagInfo) mTypes.FilterFunc {
   537  	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
   538  		manifestDigest := imageMeta.Manifests[0].Digest.String()
   539  
   540  		for _, tagInfo := range tagsInfo {
   541  			switch tagInfo.Descriptor.MediaType {
   542  			case ispec.MediaTypeImageManifest:
   543  				if tagInfo.Descriptor.Digest.String() == manifestDigest {
   544  					return true
   545  				}
   546  			case ispec.MediaTypeImageIndex:
   547  				for _, manifestDesc := range tagInfo.Manifests {
   548  					if manifestDesc.Digest.String() == manifestDigest {
   549  						return true
   550  					}
   551  				}
   552  			}
   553  		}
   554  
   555  		return false
   556  	}
   557  }
   558  
   559  func FilterByRepoAndTagInfo(repo string, tagsInfo []cvemodel.TagInfo) mTypes.FilterFunc {
   560  	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
   561  		if repoMeta.Name != repo {
   562  			return false
   563  		}
   564  
   565  		manifestDigest := imageMeta.Manifests[0].Digest.String()
   566  
   567  		for _, tagInfo := range tagsInfo {
   568  			switch tagInfo.Descriptor.MediaType {
   569  			case ispec.MediaTypeImageManifest:
   570  				if tagInfo.Descriptor.Digest.String() == manifestDigest {
   571  					return true
   572  				}
   573  			case ispec.MediaTypeImageIndex:
   574  				for _, manifestDesc := range tagInfo.Manifests {
   575  					if manifestDesc.Digest.String() == manifestDigest {
   576  						return true
   577  					}
   578  				}
   579  			}
   580  		}
   581  
   582  		return false
   583  	}
   584  }
   585  
   586  func getImageListForCVE(
   587  	ctx context.Context,
   588  	cveID string,
   589  	cveInfo cveinfo.CveInfo,
   590  	filter *gql_generated.Filter,
   591  	requestedPage *gql_generated.PageInput,
   592  	metaDB mTypes.MetaDB,
   593  	log log.Logger,
   594  ) (*gql_generated.PaginatedImagesResult, error) {
   595  	// Obtain all repos and tags
   596  	// Infinite page to make sure we scan all repos in advance, before filtering results
   597  	// The CVE scan logic is called from here, not in the actual filter,
   598  	// this is because we shouldn't keep the DB locked while we wait on scan results
   599  	reposMeta, err := metaDB.GetMultipleRepoMeta(ctx, func(repoMeta mTypes.RepoMeta) bool { return true })
   600  	if err != nil {
   601  		return &gql_generated.PaginatedImagesResult{}, err
   602  	}
   603  
   604  	affectedImages := []cvemodel.TagInfo{}
   605  
   606  	for _, repoMeta := range reposMeta {
   607  		repo := repoMeta.Name
   608  
   609  		log.Info().Str("repository", repo).Str("CVE", cveID).Msg("extracting list of tags affected by this cve")
   610  
   611  		tagsInfo, err := cveInfo.GetImageListForCVE(ctx, repo, cveID)
   612  		if err != nil {
   613  			log.Error().Str("repository", repo).Str("CVE", cveID).Err(err).
   614  				Msg("failed to get image list for this cve from repository")
   615  
   616  			return &gql_generated.PaginatedImagesResult{}, err
   617  		}
   618  
   619  		affectedImages = append(affectedImages, tagsInfo...)
   620  	}
   621  
   622  	// We're not interested in other vulnerabilities
   623  	skip := convert.SkipQGLField{Vulnerabilities: true}
   624  
   625  	if requestedPage == nil {
   626  		requestedPage = &gql_generated.PageInput{}
   627  	}
   628  
   629  	localFilter := mTypes.Filter{}
   630  	if filter != nil {
   631  		localFilter = mTypes.Filter{
   632  			Os:            filter.Os,
   633  			Arch:          filter.Arch,
   634  			HasToBeSigned: filter.HasToBeSigned,
   635  			IsBookmarked:  filter.IsBookmarked,
   636  			IsStarred:     filter.IsStarred,
   637  		}
   638  	}
   639  
   640  	// Actual page requested by user
   641  	pageInput := pagination.PageInput{
   642  		Limit:  deref(requestedPage.Limit, 0),
   643  		Offset: deref(requestedPage.Offset, 0),
   644  		SortBy: pagination.SortCriteria(
   645  			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
   646  		),
   647  	}
   648  
   649  	// get all repos
   650  	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByTagInfo(affectedImages))
   651  	if err != nil {
   652  		return &gql_generated.PaginatedImagesResult{}, err
   653  	}
   654  
   655  	imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList,
   656  		skip, cveInfo, localFilter, pageInput)
   657  	if err != nil {
   658  		return &gql_generated.PaginatedImagesResult{}, err
   659  	}
   660  
   661  	return &gql_generated.PaginatedImagesResult{
   662  		Results: imageSummaries,
   663  		Page: &gql_generated.PageInfo{
   664  			TotalCount: pageInfo.TotalCount,
   665  			ItemCount:  pageInfo.ItemCount,
   666  		},
   667  	}, nil
   668  }
   669  
   670  func getImageListWithCVEFixed(
   671  	ctx context.Context,
   672  	cveID string,
   673  	repo string,
   674  	cveInfo cveinfo.CveInfo,
   675  	filter *gql_generated.Filter,
   676  	requestedPage *gql_generated.PageInput,
   677  	metaDB mTypes.MetaDB,
   678  	log log.Logger,
   679  ) (*gql_generated.PaginatedImagesResult, error) {
   680  	imageList := make([]*gql_generated.ImageSummary, 0)
   681  
   682  	log.Info().Str("repository", repo).Str("CVE", cveID).Msg("extracting list of tags where this cve is fixed")
   683  
   684  	tagsInfo, err := cveInfo.GetImageListWithCVEFixed(ctx, repo, cveID)
   685  	if err != nil {
   686  		log.Error().Str("repository", repo).Str("CVE", cveID).Err(err).
   687  			Msg("failed to get image list with this cve fixed from repository")
   688  
   689  		return &gql_generated.PaginatedImagesResult{
   690  			Page:    &gql_generated.PageInfo{},
   691  			Results: imageList,
   692  		}, err
   693  	}
   694  
   695  	// We're not interested in other vulnerabilities
   696  	skip := convert.SkipQGLField{Vulnerabilities: true}
   697  
   698  	if requestedPage == nil {
   699  		requestedPage = &gql_generated.PageInput{}
   700  	}
   701  
   702  	localFilter := mTypes.Filter{}
   703  	if filter != nil {
   704  		localFilter = mTypes.Filter{
   705  			Os:            filter.Os,
   706  			Arch:          filter.Arch,
   707  			HasToBeSigned: filter.HasToBeSigned,
   708  			IsBookmarked:  filter.IsBookmarked,
   709  			IsStarred:     filter.IsStarred,
   710  		}
   711  	}
   712  
   713  	// Actual page requested by user
   714  	pageInput := pagination.PageInput{
   715  		Limit:  deref(requestedPage.Limit, 0),
   716  		Offset: deref(requestedPage.Offset, 0),
   717  		SortBy: pagination.SortCriteria(
   718  			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
   719  		),
   720  	}
   721  
   722  	// get all repos
   723  	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByRepoAndTagInfo(repo, tagsInfo))
   724  	if err != nil {
   725  		return &gql_generated.PaginatedImagesResult{}, err
   726  	}
   727  
   728  	imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList,
   729  		skip, cveInfo, localFilter, pageInput)
   730  	if err != nil {
   731  		return &gql_generated.PaginatedImagesResult{}, err
   732  	}
   733  
   734  	return &gql_generated.PaginatedImagesResult{
   735  		Results: imageSummaries,
   736  		Page: &gql_generated.PageInfo{
   737  			TotalCount: pageInfo.TotalCount,
   738  			ItemCount:  pageInfo.ItemCount,
   739  		},
   740  	}, nil
   741  }
   742  
   743  func repoListWithNewestImage(
   744  	ctx context.Context,
   745  	cveInfo cveinfo.CveInfo,
   746  	log log.Logger, //nolint:unparam // may be used by devs for debugging
   747  	requestedPage *gql_generated.PageInput,
   748  	metaDB mTypes.MetaDB,
   749  ) (*gql_generated.PaginatedReposResult, error) {
   750  	paginatedRepos := &gql_generated.PaginatedReposResult{}
   751  
   752  	if requestedPage == nil {
   753  		requestedPage = &gql_generated.PageInput{}
   754  	}
   755  
   756  	skip := convert.SkipQGLField{
   757  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Results.NewestImage.Vulnerabilities"),
   758  	}
   759  
   760  	pageInput := pagination.PageInput{
   761  		Limit:  deref(requestedPage.Limit, 0),
   762  		Offset: deref(requestedPage.Offset, 0),
   763  		SortBy: pagination.SortCriteria(
   764  			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
   765  		),
   766  	}
   767  
   768  	repoMetaList, err := metaDB.SearchRepos(ctx, "")
   769  	if err != nil {
   770  		return &gql_generated.PaginatedReposResult{}, err
   771  	}
   772  
   773  	imageMetaMap, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList))
   774  	if err != nil {
   775  		return &gql_generated.PaginatedReposResult{}, err
   776  	}
   777  
   778  	repos, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries(ctx, repoMetaList, imageMetaMap,
   779  		mTypes.Filter{}, pageInput, cveInfo, skip)
   780  	if err != nil {
   781  		return &gql_generated.PaginatedReposResult{}, err
   782  	}
   783  
   784  	paginatedRepos.Page = &gql_generated.PageInfo{
   785  		TotalCount: pageInfo.TotalCount,
   786  		ItemCount:  pageInfo.ItemCount,
   787  	}
   788  
   789  	paginatedRepos.Results = repos
   790  
   791  	return paginatedRepos, nil
   792  }
   793  
   794  func getBookmarkedRepos(
   795  	ctx context.Context,
   796  	cveInfo cveinfo.CveInfo,
   797  	log log.Logger, //nolint:unparam // may be used by devs for debugging
   798  	requestedPage *gql_generated.PageInput,
   799  	metaDB mTypes.MetaDB,
   800  ) (*gql_generated.PaginatedReposResult, error) {
   801  	bookmarkedRepos, err := metaDB.GetBookmarkedRepos(ctx)
   802  	if err != nil {
   803  		return &gql_generated.PaginatedReposResult{}, err
   804  	}
   805  
   806  	filterByName := func(repo string) bool {
   807  		return zcommon.Contains(bookmarkedRepos, repo)
   808  	}
   809  
   810  	return getFilteredPaginatedRepos(ctx, cveInfo, filterByName, log, requestedPage, metaDB)
   811  }
   812  
   813  func getStarredRepos(
   814  	ctx context.Context,
   815  	cveInfo cveinfo.CveInfo,
   816  	log log.Logger, //nolint:unparam // may be used by devs for debugging
   817  	requestedPage *gql_generated.PageInput,
   818  	metaDB mTypes.MetaDB,
   819  ) (*gql_generated.PaginatedReposResult, error) {
   820  	starredRepos, err := metaDB.GetStarredRepos(ctx)
   821  	if err != nil {
   822  		return &gql_generated.PaginatedReposResult{}, err
   823  	}
   824  
   825  	filterFn := func(repo string) bool {
   826  		return zcommon.Contains(starredRepos, repo)
   827  	}
   828  
   829  	return getFilteredPaginatedRepos(ctx, cveInfo, filterFn, log, requestedPage, metaDB)
   830  }
   831  
   832  func getFilteredPaginatedRepos(
   833  	ctx context.Context,
   834  	cveInfo cveinfo.CveInfo,
   835  	filterFn mTypes.FilterRepoNameFunc,
   836  	log log.Logger, //nolint:unparam // may be used by devs for debugging
   837  	requestedPage *gql_generated.PageInput,
   838  	metaDB mTypes.MetaDB,
   839  ) (*gql_generated.PaginatedReposResult, error) {
   840  	if requestedPage == nil {
   841  		requestedPage = &gql_generated.PageInput{}
   842  	}
   843  
   844  	skip := convert.SkipQGLField{
   845  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Results.NewestImage.Vulnerabilities"),
   846  	}
   847  
   848  	pageInput := pagination.PageInput{
   849  		Limit:  deref(requestedPage.Limit, 0),
   850  		Offset: deref(requestedPage.Offset, 0),
   851  		SortBy: pagination.SortCriteria(
   852  			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
   853  		),
   854  	}
   855  
   856  	repoMetaList, err := metaDB.FilterRepos(ctx, filterFn, mTypes.AcceptAllRepoMeta)
   857  	if err != nil {
   858  		return &gql_generated.PaginatedReposResult{}, err
   859  	}
   860  
   861  	latestImageMeta, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList))
   862  	if err != nil {
   863  		return &gql_generated.PaginatedReposResult{}, err
   864  	}
   865  
   866  	repos, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries(ctx, repoMetaList, latestImageMeta,
   867  		mTypes.Filter{}, pageInput, cveInfo, skip)
   868  	if err != nil {
   869  		return &gql_generated.PaginatedReposResult{}, err
   870  	}
   871  
   872  	return &gql_generated.PaginatedReposResult{
   873  		Results: repos,
   874  		Page: &gql_generated.PageInfo{
   875  			TotalCount: pageInfo.TotalCount,
   876  			ItemCount:  pageInfo.ItemCount,
   877  		},
   878  	}, nil
   879  }
   880  
   881  func globalSearch(ctx context.Context, query string, metaDB mTypes.MetaDB, filter *gql_generated.Filter,
   882  	requestedPage *gql_generated.PageInput, cveInfo cveinfo.CveInfo, log log.Logger, //nolint:unparam
   883  ) (*gql_generated.PaginatedReposResult, []*gql_generated.ImageSummary, []*gql_generated.LayerSummary, error,
   884  ) {
   885  	preloads := convert.GetPreloads(ctx)
   886  	paginatedRepos := gql_generated.PaginatedReposResult{}
   887  	images := []*gql_generated.ImageSummary{}
   888  	layers := []*gql_generated.LayerSummary{}
   889  
   890  	if requestedPage == nil {
   891  		requestedPage = &gql_generated.PageInput{}
   892  	}
   893  
   894  	localFilter := mTypes.Filter{}
   895  	if filter != nil {
   896  		localFilter = mTypes.Filter{
   897  			Os:            filter.Os,
   898  			Arch:          filter.Arch,
   899  			HasToBeSigned: filter.HasToBeSigned,
   900  			IsBookmarked:  filter.IsBookmarked,
   901  			IsStarred:     filter.IsStarred,
   902  		}
   903  	}
   904  
   905  	switch getSearchTarget(query) {
   906  	case RepoTarget:
   907  		skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Repos.NewestImage.Vulnerabilities")}
   908  		pageInput := getPageInput(requestedPage)
   909  
   910  		repoMetaList, err := metaDB.SearchRepos(ctx, query)
   911  		if err != nil {
   912  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{},
   913  				[]*gql_generated.LayerSummary{}, err
   914  		}
   915  
   916  		imageMetaMap, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList))
   917  		if err != nil {
   918  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{},
   919  				[]*gql_generated.LayerSummary{}, err
   920  		}
   921  
   922  		repos, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries(ctx, repoMetaList, imageMetaMap, localFilter,
   923  			pageInput, cveInfo,
   924  			skip)
   925  		if err != nil {
   926  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{},
   927  				[]*gql_generated.LayerSummary{}, err
   928  		}
   929  
   930  		paginatedRepos.Page = &gql_generated.PageInfo{
   931  			TotalCount: pageInfo.TotalCount,
   932  			ItemCount:  pageInfo.ItemCount,
   933  		}
   934  
   935  		paginatedRepos.Results = repos
   936  	case ImageTarget:
   937  		skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities")}
   938  		pageInput := getPageInput(requestedPage)
   939  
   940  		fullImageMetaList, err := metaDB.SearchTags(ctx, query)
   941  		if err != nil {
   942  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
   943  		}
   944  
   945  		imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo,
   946  			localFilter, pageInput)
   947  		if err != nil {
   948  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
   949  		}
   950  
   951  		images = imageSummaries
   952  
   953  		paginatedRepos.Page = &gql_generated.PageInfo{
   954  			TotalCount: pageInfo.TotalCount,
   955  			ItemCount:  pageInfo.ItemCount,
   956  		}
   957  	case TagTarget:
   958  		skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities")}
   959  		pageInput := getPageInput(requestedPage)
   960  
   961  		expectedTag := strings.TrimPrefix(query, `:`)
   962  		matchTagName := func(repoName, actualTag string) bool { return strings.Contains(actualTag, expectedTag) }
   963  
   964  		fullImageMetaList, err := metaDB.FilterTags(ctx, matchTagName, mTypes.AcceptAllImageMeta)
   965  		if err != nil {
   966  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
   967  		}
   968  
   969  		imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo,
   970  			localFilter, pageInput)
   971  		if err != nil {
   972  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
   973  		}
   974  
   975  		images = imageSummaries
   976  
   977  		paginatedRepos.Page = &gql_generated.PageInfo{
   978  			TotalCount: pageInfo.TotalCount,
   979  			ItemCount:  pageInfo.ItemCount,
   980  		}
   981  	case DigestTarget:
   982  		skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities")}
   983  		pageInput := getPageInput(requestedPage)
   984  
   985  		searchedDigest := query
   986  
   987  		fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByDigest(searchedDigest))
   988  		if err != nil {
   989  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
   990  		}
   991  
   992  		imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo,
   993  			localFilter, pageInput)
   994  		if err != nil {
   995  			return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
   996  		}
   997  
   998  		images = imageSummaries
   999  
  1000  		paginatedRepos.Page = &gql_generated.PageInfo{
  1001  			TotalCount: pageInfo.TotalCount,
  1002  			ItemCount:  pageInfo.ItemCount,
  1003  		}
  1004  	default:
  1005  		return &paginatedRepos, images, layers, zerr.ErrInvalidSearchQuery
  1006  	}
  1007  
  1008  	return &paginatedRepos, images, layers, nil
  1009  }
  1010  
  1011  func getPageInput(requestedPage *gql_generated.PageInput) pagination.PageInput {
  1012  	return pagination.PageInput{
  1013  		Limit:  deref(requestedPage.Limit, 0),
  1014  		Offset: deref(requestedPage.Offset, 0),
  1015  		SortBy: pagination.SortCriteria(
  1016  			deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
  1017  		),
  1018  	}
  1019  }
  1020  
  1021  type SearchTarget int
  1022  
  1023  const (
  1024  	RepoTarget = iota
  1025  	ImageTarget
  1026  	DigestTarget
  1027  	InvalidTarget
  1028  	TagTarget
  1029  )
  1030  
  1031  func getSearchTarget(query string) SearchTarget {
  1032  	if !strings.ContainsAny(query, ":@") {
  1033  		return RepoTarget
  1034  	}
  1035  
  1036  	if strings.HasPrefix(query, string(godigest.SHA256)+":") {
  1037  		return DigestTarget
  1038  	}
  1039  
  1040  	if before, after, found := strings.Cut(query, ":"); found {
  1041  		if before != "" {
  1042  			return ImageTarget
  1043  		} else if after != "" {
  1044  			return TagTarget
  1045  		}
  1046  	}
  1047  
  1048  	return InvalidTarget
  1049  }
  1050  
  1051  func canSkipField(preloads map[string]bool, s string) bool {
  1052  	fieldIsPresent := preloads[s]
  1053  
  1054  	return !fieldIsPresent
  1055  }
  1056  
  1057  func derivedImageList(ctx context.Context, image string, digest *string, metaDB mTypes.MetaDB,
  1058  	requestedPage *gql_generated.PageInput,
  1059  	cveInfo cveinfo.CveInfo, log log.Logger,
  1060  ) (*gql_generated.PaginatedImagesResult, error) {
  1061  	if requestedPage == nil {
  1062  		requestedPage = &gql_generated.PageInput{}
  1063  	}
  1064  
  1065  	pageInput := pagination.PageInput{
  1066  		Limit:  deref(requestedPage.Limit, 0),
  1067  		Offset: deref(requestedPage.Offset, 0),
  1068  		SortBy: pagination.SortCriteria(
  1069  			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
  1070  		),
  1071  	}
  1072  
  1073  	skip := convert.SkipQGLField{
  1074  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"),
  1075  	}
  1076  
  1077  	imageRepo, imageTag := zcommon.GetImageDirAndTag(image)
  1078  	if imageTag == "" {
  1079  		return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided")
  1080  	}
  1081  
  1082  	skipReferenceImage := convert.SkipQGLField{
  1083  		Vulnerabilities: true,
  1084  	}
  1085  
  1086  	searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, digest, skipReferenceImage, metaDB, cveInfo, log)
  1087  	if err != nil {
  1088  		if errors.Is(err, zerr.ErrRepoMetaNotFound) {
  1089  			return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository not found")
  1090  		}
  1091  
  1092  		return &gql_generated.PaginatedImagesResult{}, err
  1093  	}
  1094  
  1095  	// we need all available tags
  1096  	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, filterDerivedImages(searchedImage))
  1097  	if err != nil {
  1098  		return &gql_generated.PaginatedImagesResult{}, err
  1099  	}
  1100  
  1101  	derivedList, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo,
  1102  		mTypes.Filter{}, pageInput)
  1103  	if err != nil {
  1104  		return &gql_generated.PaginatedImagesResult{}, err
  1105  	}
  1106  
  1107  	return &gql_generated.PaginatedImagesResult{
  1108  		Results: derivedList,
  1109  		Page: &gql_generated.PageInfo{
  1110  			TotalCount: pageInfo.TotalCount,
  1111  			ItemCount:  pageInfo.ItemCount,
  1112  		},
  1113  	}, nil
  1114  }
  1115  
  1116  func filterDerivedImages(image *gql_generated.ImageSummary) mTypes.FilterFunc {
  1117  	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
  1118  		var addImageToList bool
  1119  
  1120  		imageManifest := imageMeta.Manifests[0]
  1121  
  1122  		for i := range image.Manifests {
  1123  			manifestDigest := imageManifest.Digest.String()
  1124  			if manifestDigest == *image.Manifests[i].Digest {
  1125  				return false
  1126  			}
  1127  			imageLayers := image.Manifests[i].Layers
  1128  
  1129  			addImageToList = false
  1130  			layers := imageManifest.Manifest.Layers
  1131  
  1132  			sameLayer := 0
  1133  
  1134  			for _, l := range imageLayers {
  1135  				for _, k := range layers {
  1136  					if k.Digest.String() == *l.Digest {
  1137  						sameLayer++
  1138  					}
  1139  				}
  1140  			}
  1141  
  1142  			// if all layers are the same
  1143  			if sameLayer == len(imageLayers) {
  1144  				// it's a derived image
  1145  				addImageToList = true
  1146  			}
  1147  
  1148  			if addImageToList {
  1149  				return true
  1150  			}
  1151  		}
  1152  
  1153  		return false
  1154  	}
  1155  }
  1156  
  1157  func baseImageList(ctx context.Context, image string, digest *string, metaDB mTypes.MetaDB,
  1158  	requestedPage *gql_generated.PageInput,
  1159  	cveInfo cveinfo.CveInfo, log log.Logger,
  1160  ) (*gql_generated.PaginatedImagesResult, error) {
  1161  	if requestedPage == nil {
  1162  		requestedPage = &gql_generated.PageInput{}
  1163  	}
  1164  
  1165  	pageInput := pagination.PageInput{
  1166  		Limit:  deref(requestedPage.Limit, 0),
  1167  		Offset: deref(requestedPage.Offset, 0),
  1168  		SortBy: pagination.SortCriteria(
  1169  			deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
  1170  		),
  1171  	}
  1172  
  1173  	skip := convert.SkipQGLField{
  1174  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"),
  1175  	}
  1176  
  1177  	imageRepo, imageTag := zcommon.GetImageDirAndTag(image)
  1178  
  1179  	if imageTag == "" {
  1180  		return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided")
  1181  	}
  1182  
  1183  	skipReferenceImage := convert.SkipQGLField{
  1184  		Vulnerabilities: true,
  1185  	}
  1186  
  1187  	searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, digest, skipReferenceImage, metaDB, cveInfo, log)
  1188  	if err != nil {
  1189  		if errors.Is(err, zerr.ErrRepoMetaNotFound) {
  1190  			return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository not found")
  1191  		}
  1192  
  1193  		return &gql_generated.PaginatedImagesResult{}, err
  1194  	}
  1195  
  1196  	// we need all available tags
  1197  	fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, filterBaseImages(searchedImage))
  1198  	if err != nil {
  1199  		return &gql_generated.PaginatedImagesResult{}, err
  1200  	}
  1201  
  1202  	baseList, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList,
  1203  		skip, cveInfo, mTypes.Filter{}, pageInput)
  1204  	if err != nil {
  1205  		return &gql_generated.PaginatedImagesResult{}, err
  1206  	}
  1207  
  1208  	return &gql_generated.PaginatedImagesResult{
  1209  		Page: &gql_generated.PageInfo{
  1210  			TotalCount: pageInfo.TotalCount,
  1211  			ItemCount:  pageInfo.ItemCount,
  1212  		},
  1213  		Results: baseList,
  1214  	}, nil
  1215  }
  1216  
  1217  func filterBaseImages(image *gql_generated.ImageSummary) mTypes.FilterFunc {
  1218  	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
  1219  		var addImageToList bool
  1220  
  1221  		manifest := imageMeta.Manifests[0]
  1222  
  1223  		for i := range image.Manifests {
  1224  			manifestDigest := manifest.Digest.String()
  1225  			if manifestDigest == *image.Manifests[i].Digest {
  1226  				return false
  1227  			}
  1228  
  1229  			addImageToList = true
  1230  
  1231  			for _, l := range manifest.Manifest.Layers {
  1232  				foundLayer := false
  1233  
  1234  				for _, k := range image.Manifests[i].Layers {
  1235  					if l.Digest.String() == *k.Digest {
  1236  						foundLayer = true
  1237  
  1238  						break
  1239  					}
  1240  				}
  1241  
  1242  				if !foundLayer {
  1243  					addImageToList = false
  1244  
  1245  					break
  1246  				}
  1247  			}
  1248  
  1249  			if addImageToList {
  1250  				return true
  1251  			}
  1252  		}
  1253  
  1254  		return false
  1255  	}
  1256  }
  1257  
  1258  func filterBaseImagesForMeta(image mTypes.ImageMeta) mTypes.FilterFunc {
  1259  	return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool {
  1260  		var addImageToList bool
  1261  
  1262  		manifest := imageMeta.Manifests[0]
  1263  
  1264  		for i := range image.Manifests {
  1265  			manifestDigest := manifest.Digest.String()
  1266  			if manifestDigest == image.Manifests[i].Digest.String() {
  1267  				return false
  1268  			}
  1269  
  1270  			addImageToList = true
  1271  
  1272  			for _, l := range manifest.Manifest.Layers {
  1273  				foundLayer := false
  1274  
  1275  				for _, k := range image.Manifests[i].Manifest.Layers {
  1276  					if l.Digest.String() == k.Digest.String() {
  1277  						foundLayer = true
  1278  
  1279  						break
  1280  					}
  1281  				}
  1282  
  1283  				if !foundLayer {
  1284  					addImageToList = false
  1285  
  1286  					break
  1287  				}
  1288  			}
  1289  
  1290  			if addImageToList {
  1291  				return true
  1292  			}
  1293  		}
  1294  
  1295  		return false
  1296  	}
  1297  }
  1298  
  1299  func validateGlobalSearchInput(query string, filter *gql_generated.Filter,
  1300  	requestedPage *gql_generated.PageInput,
  1301  ) error {
  1302  	if len(query) > querySizeLimit {
  1303  		return fmt.Errorf("max string size limit exceeded for query parameter. max=%d current=%d %w",
  1304  			querySizeLimit, len(query), zerr.ErrInvalidRequestParams)
  1305  	}
  1306  
  1307  	err := checkFilter(filter)
  1308  	if err != nil {
  1309  		return err
  1310  	}
  1311  
  1312  	err = checkRequestedPage(requestedPage)
  1313  	if err != nil {
  1314  		return err
  1315  	}
  1316  
  1317  	return nil
  1318  }
  1319  
  1320  func checkFilter(filter *gql_generated.Filter) error {
  1321  	if filter == nil {
  1322  		return nil
  1323  	}
  1324  
  1325  	for _, arch := range filter.Arch {
  1326  		if len(*arch) > querySizeLimit {
  1327  			return fmt.Errorf("max string size limit exceeded for arch parameter. max=%d current=%d %w",
  1328  				querySizeLimit, len(*arch), zerr.ErrInvalidRequestParams)
  1329  		}
  1330  	}
  1331  
  1332  	for _, osSys := range filter.Os {
  1333  		if len(*osSys) > querySizeLimit {
  1334  			return fmt.Errorf("max string size limit exceeded for os parameter. max=%d current=%d %w",
  1335  				querySizeLimit, len(*osSys), zerr.ErrInvalidRequestParams)
  1336  		}
  1337  	}
  1338  
  1339  	return nil
  1340  }
  1341  
  1342  func checkRequestedPage(requestedPage *gql_generated.PageInput) error {
  1343  	if requestedPage == nil {
  1344  		return nil
  1345  	}
  1346  
  1347  	if requestedPage.Limit != nil && *requestedPage.Limit < 0 {
  1348  		return fmt.Errorf("requested page limit parameter can't be negative %w",
  1349  			zerr.ErrInvalidRequestParams)
  1350  	}
  1351  
  1352  	if requestedPage.Offset != nil && *requestedPage.Offset < 0 {
  1353  		return fmt.Errorf("requested page offset parameter can't be negative %w",
  1354  			zerr.ErrInvalidRequestParams)
  1355  	}
  1356  
  1357  	return nil
  1358  }
  1359  
  1360  func cleanQuery(query string) string {
  1361  	query = strings.TrimSpace(query)
  1362  	query = strings.Trim(query, "/")
  1363  	query = strings.ToLower(query)
  1364  
  1365  	return query
  1366  }
  1367  
  1368  func cleanFilter(filter *gql_generated.Filter) *gql_generated.Filter {
  1369  	if filter == nil {
  1370  		return nil
  1371  	}
  1372  
  1373  	if filter.Arch != nil {
  1374  		for i := range filter.Arch {
  1375  			*filter.Arch[i] = strings.ToLower(*filter.Arch[i])
  1376  			*filter.Arch[i] = strings.TrimSpace(*filter.Arch[i])
  1377  		}
  1378  
  1379  		filter.Arch = deleteEmptyElements(filter.Arch)
  1380  	}
  1381  
  1382  	if filter.Os != nil {
  1383  		for i := range filter.Os {
  1384  			*filter.Os[i] = strings.ToLower(*filter.Os[i])
  1385  			*filter.Os[i] = strings.TrimSpace(*filter.Os[i])
  1386  		}
  1387  
  1388  		filter.Os = deleteEmptyElements(filter.Os)
  1389  	}
  1390  
  1391  	return filter
  1392  }
  1393  
  1394  func deleteEmptyElements(slice []*string) []*string {
  1395  	i := 0
  1396  	for i < len(slice) {
  1397  		if elementIsEmpty(*slice[i]) {
  1398  			slice = deleteElementAt(slice, i)
  1399  		} else {
  1400  			i++
  1401  		}
  1402  	}
  1403  
  1404  	return slice
  1405  }
  1406  
  1407  func elementIsEmpty(s string) bool {
  1408  	return s == ""
  1409  }
  1410  
  1411  func deleteElementAt(slice []*string, i int) []*string {
  1412  	slice[i] = slice[len(slice)-1]
  1413  	slice = slice[:len(slice)-1]
  1414  
  1415  	return slice
  1416  }
  1417  
  1418  func expandedRepoInfo(ctx context.Context, repo string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo, log log.Logger,
  1419  ) (*gql_generated.RepoInfo, error) {
  1420  	if ok, err := reqCtx.RepoIsUserAvailable(ctx, repo); !ok || err != nil {
  1421  		log.Info().Err(err).Str("repository", repo).Bool("availability", ok).Str("component", "graphql").
  1422  			Msg("repo user availability")
  1423  
  1424  		return &gql_generated.RepoInfo{}, nil //nolint:nilerr // don't give details to a potential attacker
  1425  	}
  1426  
  1427  	repoMeta, err := metaDB.GetRepoMeta(ctx, repo)
  1428  	if err != nil {
  1429  		log.Error().Err(err).Str("repository", repo).Str("component", "graphql").
  1430  			Msg("can't retrieve repoMeta for repository")
  1431  
  1432  		return &gql_generated.RepoInfo{}, err
  1433  	}
  1434  
  1435  	tagsDigests := []string{}
  1436  
  1437  	for i := range repoMeta.Tags {
  1438  		if i == "" {
  1439  			continue
  1440  		}
  1441  
  1442  		tagsDigests = append(tagsDigests, repoMeta.Tags[i].Digest)
  1443  	}
  1444  
  1445  	imageMetaMap, err := metaDB.FilterImageMeta(ctx, tagsDigests)
  1446  	if err != nil {
  1447  		log.Error().Err(err).Str("repository", repo).Str("component", "graphql").
  1448  			Msg("can't retrieve imageMeta for repo")
  1449  
  1450  		return &gql_generated.RepoInfo{}, err
  1451  	}
  1452  
  1453  	skip := convert.SkipQGLField{
  1454  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Summary.NewestImage.Vulnerabilities") &&
  1455  			canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"),
  1456  	}
  1457  
  1458  	repoSummary, imageSummaries := convert.RepoMeta2ExpandedRepoInfo(ctx, repoMeta, imageMetaMap,
  1459  		skip, cveInfo, log)
  1460  
  1461  	dateSortedImages := make(timeSlice, 0, len(imageSummaries))
  1462  	for _, imgSummary := range imageSummaries {
  1463  		dateSortedImages = append(dateSortedImages, imgSummary)
  1464  	}
  1465  
  1466  	sort.Sort(dateSortedImages)
  1467  
  1468  	return &gql_generated.RepoInfo{Summary: repoSummary, Images: dateSortedImages}, nil
  1469  }
  1470  
  1471  type timeSlice []*gql_generated.ImageSummary
  1472  
  1473  func (p timeSlice) Len() int {
  1474  	return len(p)
  1475  }
  1476  
  1477  func (p timeSlice) Less(i, j int) bool {
  1478  	return p[i].LastUpdated.After(*p[j].LastUpdated)
  1479  }
  1480  
  1481  func (p timeSlice) Swap(i, j int) {
  1482  	p[i], p[j] = p[j], p[i]
  1483  }
  1484  
  1485  // dderef is a default deref.
  1486  func dderef[T any](pointer *T) T {
  1487  	var defValue T
  1488  
  1489  	if pointer != nil {
  1490  		return *pointer
  1491  	}
  1492  
  1493  	return defValue
  1494  }
  1495  
  1496  func deref[T any](pointer *T, defaultVal T) T {
  1497  	if pointer != nil {
  1498  		return *pointer
  1499  	}
  1500  
  1501  	return defaultVal
  1502  }
  1503  
  1504  func ref[T any](input T) *T {
  1505  	ref := input
  1506  
  1507  	return &ref
  1508  }
  1509  
  1510  func getImageList(ctx context.Context, repo string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo,
  1511  	requestedPage *gql_generated.PageInput, log log.Logger, //nolint:unparam
  1512  ) (*gql_generated.PaginatedImagesResult, error) {
  1513  	if requestedPage == nil {
  1514  		requestedPage = &gql_generated.PageInput{}
  1515  	}
  1516  
  1517  	skip := convert.SkipQGLField{
  1518  		Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"),
  1519  	}
  1520  
  1521  	pageInput := pagination.PageInput{
  1522  		Limit:  deref(requestedPage.Limit, 0),
  1523  		Offset: deref(requestedPage.Offset, 0),
  1524  		SortBy: pagination.SortCriteria(
  1525  			deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
  1526  		),
  1527  	}
  1528  
  1529  	var matchRepoName mTypes.FilterRepoTagFunc
  1530  
  1531  	if repo == "" {
  1532  		matchRepoName = mTypes.AcceptAllRepoTag
  1533  	} else {
  1534  		matchRepoName = func(repoName, tag string) bool { return repoName == repo }
  1535  	}
  1536  
  1537  	imageMeta, err := metaDB.FilterTags(ctx, matchRepoName, mTypes.AcceptAllImageMeta)
  1538  	if err != nil {
  1539  		return &gql_generated.PaginatedImagesResult{}, err
  1540  	}
  1541  
  1542  	imageList, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, imageMeta, skip,
  1543  		cveInfo, mTypes.Filter{}, pageInput)
  1544  	if err != nil {
  1545  		return &gql_generated.PaginatedImagesResult{}, err
  1546  	}
  1547  
  1548  	return &gql_generated.PaginatedImagesResult{
  1549  		Results: imageList,
  1550  		Page: &gql_generated.PageInfo{
  1551  			TotalCount: pageInfo.TotalCount,
  1552  			ItemCount:  pageInfo.ItemCount,
  1553  		},
  1554  	}, nil
  1555  }
  1556  
  1557  func getReferrers(metaDB mTypes.MetaDB, repo string, referredDigest string, artifactTypes []string,
  1558  	log log.Logger,
  1559  ) ([]*gql_generated.Referrer, error) {
  1560  	refDigest := godigest.Digest(referredDigest)
  1561  	if err := refDigest.Validate(); err != nil {
  1562  		log.Error().Err(err).Str("digest", referredDigest).Str("component", "graphql").
  1563  			Msg("bad referenced digest string from request")
  1564  
  1565  		return []*gql_generated.Referrer{}, fmt.Errorf("bad digest string from request '%s' %w",
  1566  			referredDigest, err)
  1567  	}
  1568  
  1569  	referrers, err := metaDB.GetReferrersInfo(repo, refDigest, artifactTypes)
  1570  	if err != nil {
  1571  		return nil, err
  1572  	}
  1573  
  1574  	results := make([]*gql_generated.Referrer, 0, len(referrers))
  1575  
  1576  	for _, referrer := range referrers {
  1577  		referrer := referrer
  1578  
  1579  		results = append(results, &gql_generated.Referrer{
  1580  			MediaType:    &referrer.MediaType,
  1581  			ArtifactType: &referrer.ArtifactType,
  1582  			Digest:       &referrer.Digest,
  1583  			Size:         &referrer.Size,
  1584  			Annotations:  convert.StringMap2Annotations(referrer.Annotations),
  1585  		})
  1586  	}
  1587  
  1588  	return results, nil
  1589  }