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

     1  package convert
     2  
     3  import (
     4  	"context"
     5  	"sort"
     6  	"strconv"
     7  	"time"
     8  
     9  	"github.com/99designs/gqlgen/graphql"
    10  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    11  	"github.com/vektah/gqlparser/v2/gqlerror"
    12  
    13  	zerr "zotregistry.io/zot/errors"
    14  	zcommon "zotregistry.io/zot/pkg/common"
    15  	cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
    16  	"zotregistry.io/zot/pkg/extensions/search/gql_generated"
    17  	"zotregistry.io/zot/pkg/extensions/search/pagination"
    18  	"zotregistry.io/zot/pkg/log"
    19  	mTypes "zotregistry.io/zot/pkg/meta/types"
    20  )
    21  
    22  type SkipQGLField struct {
    23  	Vulnerabilities bool
    24  }
    25  
    26  func UpdateLastUpdatedTimestamp(repoLastUpdatedTimestamp *time.Time,
    27  	lastUpdatedImageSummary *gql_generated.ImageSummary, imageSummary *gql_generated.ImageSummary,
    28  ) *gql_generated.ImageSummary {
    29  	newLastUpdatedImageSummary := lastUpdatedImageSummary
    30  
    31  	if repoLastUpdatedTimestamp.Equal(time.Time{}) {
    32  		// initialize with first time value
    33  		*repoLastUpdatedTimestamp = *imageSummary.LastUpdated
    34  		newLastUpdatedImageSummary = imageSummary
    35  	} else if repoLastUpdatedTimestamp.Before(*imageSummary.LastUpdated) {
    36  		*repoLastUpdatedTimestamp = *imageSummary.LastUpdated
    37  		newLastUpdatedImageSummary = imageSummary
    38  	}
    39  
    40  	return newLastUpdatedImageSummary
    41  }
    42  
    43  func getReferrers(referrersInfo []mTypes.ReferrerInfo) []*gql_generated.Referrer {
    44  	referrers := make([]*gql_generated.Referrer, 0, len(referrersInfo))
    45  
    46  	for _, referrerInfo := range referrersInfo {
    47  		referrerInfo := referrerInfo
    48  
    49  		referrers = append(referrers, &gql_generated.Referrer{
    50  			MediaType:    &referrerInfo.MediaType,
    51  			ArtifactType: &referrerInfo.ArtifactType,
    52  			Size:         &referrerInfo.Size,
    53  			Digest:       &referrerInfo.Digest,
    54  			Annotations:  getAnnotationsFromMap(referrerInfo.Annotations),
    55  		})
    56  	}
    57  
    58  	return referrers
    59  }
    60  
    61  func getAnnotationsFromMap(annotationsMap map[string]string) []*gql_generated.Annotation {
    62  	annotations := make([]*gql_generated.Annotation, 0, len(annotationsMap))
    63  
    64  	for key, value := range annotationsMap {
    65  		key := key
    66  		value := value
    67  
    68  		annotations = append(annotations, &gql_generated.Annotation{
    69  			Key:   &key,
    70  			Value: &value,
    71  		})
    72  	}
    73  
    74  	return annotations
    75  }
    76  
    77  func getImageBlobsInfo(manifestDigest string, manifestSize int64, configDigest string, configSize int64,
    78  	layers []ispec.Descriptor,
    79  ) (int64, map[string]int64) {
    80  	imageBlobsMap := map[string]int64{}
    81  	imageSize := int64(0)
    82  
    83  	// add config size
    84  	imageSize += configSize
    85  	imageBlobsMap[configDigest] = configSize
    86  
    87  	// add manifest size
    88  	imageSize += manifestSize
    89  	imageBlobsMap[manifestDigest] = manifestSize
    90  
    91  	// add layers size
    92  	for _, layer := range layers {
    93  		imageBlobsMap[layer.Digest.String()] = layer.Size
    94  		imageSize += layer.Size
    95  	}
    96  
    97  	return imageSize, imageBlobsMap
    98  }
    99  
   100  func RepoMeta2ImageSummaries(ctx context.Context, repoMeta mTypes.RepoMeta,
   101  	imageMeta map[string]mTypes.ImageMeta, skip SkipQGLField, cveInfo cveinfo.CveInfo,
   102  ) []*gql_generated.ImageSummary {
   103  	imageSummaries := make([]*gql_generated.ImageSummary, 0, len(repoMeta.Tags))
   104  
   105  	// Make sure the tags are sorted
   106  	// We need to implement a proper fix for this taking into account
   107  	// the sorting criteria used in the requested page
   108  	tags := make([]string, 0, len(repoMeta.Tags))
   109  	for tag := range repoMeta.Tags {
   110  		tags = append(tags, tag)
   111  	}
   112  
   113  	// Sorting ascending by tag name should do for now
   114  	sort.Strings(tags)
   115  
   116  	for _, tag := range tags {
   117  		descriptor := repoMeta.Tags[tag]
   118  
   119  		imageSummary, _, err := FullImageMeta2ImageSummary(ctx, GetFullImageMeta(tag, repoMeta, imageMeta[descriptor.Digest]))
   120  		if err != nil {
   121  			continue
   122  		}
   123  
   124  		// CVE scanning is expensive, only scan for final slice of results
   125  		updateImageSummaryVulnerabilities(ctx, imageSummary, skip, cveInfo)
   126  
   127  		imageSummaries = append(imageSummaries, imageSummary)
   128  	}
   129  
   130  	return imageSummaries
   131  }
   132  
   133  func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta mTypes.RepoMeta,
   134  	imageMetaMap map[string]mTypes.ImageMeta, skip SkipQGLField, cveInfo cveinfo.CveInfo, log log.Logger,
   135  ) (*gql_generated.RepoSummary, []*gql_generated.ImageSummary) {
   136  	repoName := repoMeta.Name
   137  	imageSummaries := make([]*gql_generated.ImageSummary, 0, len(repoMeta.Tags))
   138  
   139  	for tag, descriptor := range repoMeta.Tags {
   140  		imageMeta := imageMetaMap[descriptor.Digest]
   141  
   142  		imageSummary, _, err := FullImageMeta2ImageSummary(ctx, GetFullImageMeta(tag, repoMeta, imageMeta))
   143  		if err != nil {
   144  			log.Error().Str("repository", repoName).Str("reference", tag).
   145  				Msg("metadb: error while converting descriptor for image")
   146  
   147  			continue
   148  		}
   149  
   150  		updateImageSummaryVulnerabilities(ctx, imageSummary, skip, cveInfo)
   151  
   152  		imageSummaries = append(imageSummaries, imageSummary)
   153  	}
   154  
   155  	repoSummary := RepoMeta2RepoSummary(ctx, repoMeta, imageMetaMap)
   156  
   157  	updateRepoSummaryVulnerabilities(ctx, repoSummary, skip, cveInfo)
   158  
   159  	return repoSummary, imageSummaries
   160  }
   161  
   162  func GetFullImageMeta(tag string, repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta,
   163  ) mTypes.FullImageMeta {
   164  	return mTypes.FullImageMeta{
   165  		Repo:       repoMeta.Name,
   166  		Tag:        tag,
   167  		MediaType:  imageMeta.MediaType,
   168  		Digest:     imageMeta.Digest,
   169  		Size:       imageMeta.Size,
   170  		Index:      imageMeta.Index,
   171  		Manifests:  GetFullManifestMeta(repoMeta, imageMeta.Manifests),
   172  		Referrers:  repoMeta.Referrers[imageMeta.Digest.String()],
   173  		Statistics: repoMeta.Statistics[imageMeta.Digest.String()],
   174  		Signatures: repoMeta.Signatures[imageMeta.Digest.String()],
   175  	}
   176  }
   177  
   178  func GetFullManifestMeta(repoMeta mTypes.RepoMeta, manifests []mTypes.ManifestMeta) []mTypes.FullManifestMeta {
   179  	results := make([]mTypes.FullManifestMeta, 0, len(manifests))
   180  
   181  	for i := range manifests {
   182  		results = append(results, mTypes.FullManifestMeta{
   183  			ManifestMeta: manifests[i],
   184  			Referrers:    repoMeta.Referrers[manifests[i].Digest.String()],
   185  			Statistics:   repoMeta.Statistics[manifests[i].Digest.String()],
   186  			Signatures:   repoMeta.Signatures[manifests[i].Digest.String()],
   187  		})
   188  	}
   189  
   190  	return results
   191  }
   192  
   193  func StringMap2Annotations(strMap map[string]string) []*gql_generated.Annotation {
   194  	annotations := make([]*gql_generated.Annotation, 0, len(strMap))
   195  
   196  	for key, value := range strMap {
   197  		key := key
   198  		value := value
   199  
   200  		annotations = append(annotations, &gql_generated.Annotation{
   201  			Key:   &key,
   202  			Value: &value,
   203  		})
   204  	}
   205  
   206  	return annotations
   207  }
   208  
   209  func GetPreloads(ctx context.Context) map[string]bool {
   210  	if !graphql.HasOperationContext(ctx) {
   211  		return map[string]bool{}
   212  	}
   213  
   214  	nestedPreloads := GetNestedPreloads(
   215  		graphql.GetOperationContext(ctx),
   216  		graphql.CollectFieldsCtx(ctx, nil),
   217  		"",
   218  	)
   219  
   220  	preloads := map[string]bool{}
   221  
   222  	for _, str := range nestedPreloads {
   223  		preloads[str] = true
   224  	}
   225  
   226  	return preloads
   227  }
   228  
   229  func GetNestedPreloads(ctx *graphql.OperationContext, fields []graphql.CollectedField, prefix string,
   230  ) []string {
   231  	preloads := []string{}
   232  
   233  	for _, column := range fields {
   234  		prefixColumn := GetPreloadString(prefix, column.Name)
   235  		preloads = append(preloads, prefixColumn)
   236  		preloads = append(preloads,
   237  			GetNestedPreloads(ctx, graphql.CollectFields(ctx, column.Selections, nil), prefixColumn)...,
   238  		)
   239  	}
   240  
   241  	return preloads
   242  }
   243  
   244  func GetPreloadString(prefix, name string) string {
   245  	if len(prefix) > 0 {
   246  		return prefix + "." + name
   247  	}
   248  
   249  	return name
   250  }
   251  
   252  func GetSignaturesInfo(isSigned bool, signatures mTypes.ManifestSignatures) []*gql_generated.SignatureSummary {
   253  	signaturesInfo := []*gql_generated.SignatureSummary{}
   254  
   255  	if !isSigned {
   256  		return signaturesInfo
   257  	}
   258  
   259  	for sigType, signatures := range signatures {
   260  		for _, sig := range signatures {
   261  			for _, layer := range sig.LayersInfo {
   262  				var (
   263  					isTrusted bool
   264  					author    string
   265  					tool      string
   266  				)
   267  
   268  				if layer.Signer != "" {
   269  					author = layer.Signer
   270  
   271  					if !layer.Date.IsZero() && time.Now().After(layer.Date) {
   272  						isTrusted = false
   273  					} else {
   274  						isTrusted = true
   275  					}
   276  				} else {
   277  					isTrusted = false
   278  					author = ""
   279  				}
   280  
   281  				tool = sigType
   282  
   283  				signaturesInfo = append(signaturesInfo,
   284  					&gql_generated.SignatureSummary{Tool: &tool, IsTrusted: &isTrusted, Author: &author})
   285  			}
   286  		}
   287  	}
   288  
   289  	return signaturesInfo
   290  }
   291  
   292  func PaginatedRepoMeta2RepoSummaries(ctx context.Context, repoMetaList []mTypes.RepoMeta,
   293  	imageMetaMap map[string]mTypes.ImageMeta, filter mTypes.Filter, pageInput pagination.PageInput,
   294  	cveInfo cveinfo.CveInfo, skip SkipQGLField,
   295  ) ([]*gql_generated.RepoSummary, zcommon.PageInfo, error) {
   296  	reposPageFinder, err := pagination.NewRepoSumPageFinder(pageInput.Limit, pageInput.Offset, pageInput.SortBy)
   297  	if err != nil {
   298  		return []*gql_generated.RepoSummary{}, zcommon.PageInfo{}, err
   299  	}
   300  
   301  	for _, repoMeta := range repoMetaList {
   302  		repoSummary := RepoMeta2RepoSummary(ctx, repoMeta, imageMetaMap)
   303  
   304  		if RepoSumAcceptedByFilter(repoSummary, filter) {
   305  			reposPageFinder.Add(repoSummary)
   306  		}
   307  	}
   308  
   309  	page, pageInfo := reposPageFinder.Page()
   310  
   311  	// CVE scanning is expensive, only scan for the current page
   312  	for _, repoSummary := range page {
   313  		updateRepoSummaryVulnerabilities(ctx, repoSummary, skip, cveInfo)
   314  	}
   315  
   316  	return page, pageInfo, nil
   317  }
   318  
   319  func RepoMeta2RepoSummary(ctx context.Context, repoMeta mTypes.RepoMeta,
   320  	imageMetaMap map[string]mTypes.ImageMeta,
   321  ) *gql_generated.RepoSummary {
   322  	var (
   323  		repoName                 = repoMeta.Name
   324  		lastUpdatedImage         = deref(repoMeta.LastUpdatedImage, mTypes.LastUpdatedImage{})
   325  		lastUpdatedImageMeta     = imageMetaMap[lastUpdatedImage.Digest]
   326  		lastUpdatedTag           = lastUpdatedImage.Tag
   327  		repoLastUpdatedTimestamp = lastUpdatedImage.LastUpdated
   328  		repoPlatforms            = repoMeta.Platforms
   329  		repoVendors              = repoMeta.Vendors
   330  		repoDownloadCount        = repoMeta.DownloadCount
   331  		repoStarCount            = repoMeta.StarCount
   332  		repoIsUserStarred        = repoMeta.IsStarred    // value specific to the current user
   333  		repoIsUserBookMarked     = repoMeta.IsBookmarked // value specific to the current user
   334  		repoSize                 = repoMeta.Size
   335  	)
   336  
   337  	if repoLastUpdatedTimestamp == nil {
   338  		repoLastUpdatedTimestamp = &time.Time{}
   339  	}
   340  
   341  	imageSummary, _, err := FullImageMeta2ImageSummary(ctx, GetFullImageMeta(lastUpdatedTag, repoMeta,
   342  		lastUpdatedImageMeta))
   343  	_ = err
   344  
   345  	return &gql_generated.RepoSummary{
   346  		Name:          &repoName,
   347  		LastUpdated:   repoLastUpdatedTimestamp,
   348  		Size:          ref(strconv.FormatInt(repoSize, 10)),
   349  		Platforms:     getGqlPlatforms(repoPlatforms),
   350  		Vendors:       getGqlVendors(repoVendors),
   351  		NewestImage:   imageSummary,
   352  		DownloadCount: &repoDownloadCount,
   353  		StarCount:     &repoStarCount,
   354  		IsBookmarked:  &repoIsUserBookMarked,
   355  		IsStarred:     &repoIsUserStarred,
   356  		Rank:          ref(repoMeta.Rank),
   357  	}
   358  }
   359  
   360  func getGqlVendors(repoVendors []string) []*string {
   361  	result := make([]*string, 0, len(repoVendors))
   362  
   363  	for i := range repoVendors {
   364  		result = append(result, &repoVendors[i])
   365  	}
   366  
   367  	return result
   368  }
   369  
   370  func getGqlPlatforms(repoPlatforms []ispec.Platform) []*gql_generated.Platform {
   371  	result := make([]*gql_generated.Platform, 0, len(repoPlatforms))
   372  
   373  	for i := range repoPlatforms {
   374  		result = append(result, &gql_generated.Platform{
   375  			Os:   ref(repoPlatforms[i].OS),
   376  			Arch: ref(getArch(repoPlatforms[i].Architecture, repoPlatforms[i].Variant)),
   377  		})
   378  	}
   379  
   380  	return result
   381  }
   382  
   383  type (
   384  	ManifestDigest = string
   385  	BlobDigest     = string
   386  )
   387  
   388  func FullImageMeta2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullImageMeta,
   389  ) (*gql_generated.ImageSummary, map[BlobDigest]int64, error) {
   390  	switch fullImageMeta.MediaType {
   391  	case ispec.MediaTypeImageManifest:
   392  		return ImageManifest2ImageSummary(ctx, fullImageMeta)
   393  	case ispec.MediaTypeImageIndex:
   394  		return ImageIndex2ImageSummary(ctx, fullImageMeta)
   395  	default:
   396  		return nil, nil, zerr.ErrMediaTypeNotSupported
   397  	}
   398  }
   399  
   400  func ImageIndex2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullImageMeta,
   401  ) (*gql_generated.ImageSummary, map[BlobDigest]int64, error) {
   402  	var (
   403  		repo                = fullImageMeta.Repo
   404  		tag                 = fullImageMeta.Tag
   405  		indexLastUpdated    time.Time
   406  		isSigned            = isImageSigned(fullImageMeta.Signatures)
   407  		indexSize           = int64(0)
   408  		manifestAnnotations *ImageAnnotations
   409  		manifestSummaries   = make([]*gql_generated.ManifestSummary, 0, len(fullImageMeta.Manifests))
   410  		indexBlobs          = map[string]int64{}
   411  
   412  		indexDigestStr = fullImageMeta.Digest.String()
   413  		indexMediaType = ispec.MediaTypeImageIndex
   414  	)
   415  
   416  	for _, imageManifest := range fullImageMeta.Manifests {
   417  		imageManifestSummary, manifestBlobs, err := ImageManifest2ImageSummary(ctx, mTypes.FullImageMeta{
   418  			Repo:       fullImageMeta.Repo,
   419  			Tag:        fullImageMeta.Tag,
   420  			MediaType:  ispec.MediaTypeImageManifest,
   421  			Digest:     imageManifest.Digest,
   422  			Size:       imageManifest.Size,
   423  			Manifests:  []mTypes.FullManifestMeta{imageManifest},
   424  			Referrers:  imageManifest.Referrers,
   425  			Statistics: imageManifest.Statistics,
   426  			Signatures: imageManifest.Signatures,
   427  		})
   428  		if err != nil {
   429  			return &gql_generated.ImageSummary{}, map[string]int64{}, err
   430  		}
   431  
   432  		manifestSize := int64(0)
   433  
   434  		for digest, size := range manifestBlobs {
   435  			indexBlobs[digest] = size
   436  			manifestSize += size
   437  		}
   438  
   439  		if indexLastUpdated.Before(*imageManifestSummary.LastUpdated) {
   440  			indexLastUpdated = *imageManifestSummary.LastUpdated
   441  		}
   442  
   443  		annotations := GetAnnotations(imageManifest.Manifest.Annotations, imageManifest.Config.Config.Labels)
   444  		if manifestAnnotations == nil {
   445  			manifestAnnotations = &annotations
   446  		}
   447  
   448  		indexSize += manifestSize
   449  
   450  		manifestSummaries = append(manifestSummaries, imageManifestSummary.Manifests[0])
   451  	}
   452  
   453  	signaturesInfo := GetSignaturesInfo(isSigned, fullImageMeta.Signatures)
   454  
   455  	if manifestAnnotations == nil {
   456  		manifestAnnotations = &ImageAnnotations{}
   457  	}
   458  
   459  	annotations := GetIndexAnnotations(fullImageMeta.Index.Annotations, manifestAnnotations)
   460  
   461  	indexSummary := gql_generated.ImageSummary{
   462  		RepoName:      &repo,
   463  		Tag:           &tag,
   464  		Digest:        &indexDigestStr,
   465  		MediaType:     &indexMediaType,
   466  		Manifests:     manifestSummaries,
   467  		LastUpdated:   &indexLastUpdated,
   468  		IsSigned:      &isSigned,
   469  		SignatureInfo: signaturesInfo,
   470  		Size:          ref(strconv.FormatInt(indexSize, 10)),
   471  		DownloadCount: ref(fullImageMeta.Statistics.DownloadCount),
   472  		Description:   &annotations.Description,
   473  		Title:         &annotations.Title,
   474  		Documentation: &annotations.Documentation,
   475  		Licenses:      &annotations.Licenses,
   476  		Labels:        &annotations.Labels,
   477  		Source:        &annotations.Source,
   478  		Vendor:        &annotations.Vendor,
   479  		Authors:       &annotations.Authors,
   480  		Referrers:     getReferrers(fullImageMeta.Referrers),
   481  	}
   482  
   483  	return &indexSummary, indexBlobs, nil
   484  }
   485  
   486  func ImageManifest2ImageSummary(ctx context.Context, fullImageMeta mTypes.FullImageMeta,
   487  ) (*gql_generated.ImageSummary, map[BlobDigest]int64, error) {
   488  	manifest := fullImageMeta.Manifests[0]
   489  
   490  	var (
   491  		repoName         = fullImageMeta.Repo
   492  		tag              = fullImageMeta.Tag
   493  		configDigest     = manifest.Manifest.Config.Digest.String()
   494  		configSize       = manifest.Manifest.Config.Size
   495  		manifestDigest   = manifest.Digest.String()
   496  		manifestSize     = manifest.Size
   497  		mediaType        = manifest.Manifest.MediaType
   498  		artifactType     = zcommon.GetManifestArtifactType(fullImageMeta.Manifests[0].Manifest)
   499  		platform         = getPlatform(manifest.Config.Platform)
   500  		imageLastUpdated = zcommon.GetImageLastUpdated(manifest.Config)
   501  		downloadCount    = fullImageMeta.Statistics.DownloadCount
   502  		isSigned         = isImageSigned(fullImageMeta.Signatures)
   503  	)
   504  
   505  	imageSize, imageBlobsMap := getImageBlobsInfo(manifestDigest, manifestSize, configDigest, configSize,
   506  		manifest.Manifest.Layers)
   507  	imageSizeStr := strconv.FormatInt(imageSize, 10)
   508  	annotations := GetAnnotations(manifest.Manifest.Annotations, manifest.Config.Config.Labels)
   509  
   510  	authors := annotations.Authors
   511  	if authors == "" {
   512  		authors = manifest.Config.Author
   513  	}
   514  
   515  	historyEntries, err := getAllHistory(manifest.Manifest, manifest.Config)
   516  	if err != nil {
   517  		graphql.AddError(ctx, gqlerror.Errorf("error generating history on tag %s in repo %s: "+
   518  			"manifest digest: %s, error: %s", tag, repoName, manifest.Digest, err.Error()))
   519  	}
   520  
   521  	signaturesInfo := GetSignaturesInfo(isSigned, fullImageMeta.Signatures)
   522  
   523  	manifestSummary := gql_generated.ManifestSummary{
   524  		Digest:        &manifestDigest,
   525  		ConfigDigest:  &configDigest,
   526  		LastUpdated:   &imageLastUpdated,
   527  		Size:          &imageSizeStr,
   528  		IsSigned:      &isSigned,
   529  		SignatureInfo: signaturesInfo,
   530  		Platform:      &platform,
   531  		DownloadCount: &downloadCount,
   532  		Layers:        getLayersSummaries(manifest.Manifest),
   533  		History:       historyEntries,
   534  		Referrers:     getReferrers(fullImageMeta.Referrers),
   535  		ArtifactType:  &artifactType,
   536  	}
   537  
   538  	imageSummary := gql_generated.ImageSummary{
   539  		RepoName:      &repoName,
   540  		Tag:           &tag,
   541  		Digest:        &manifestDigest,
   542  		MediaType:     &mediaType,
   543  		Manifests:     []*gql_generated.ManifestSummary{&manifestSummary},
   544  		LastUpdated:   &imageLastUpdated,
   545  		IsSigned:      &isSigned,
   546  		SignatureInfo: signaturesInfo,
   547  		Size:          &imageSizeStr,
   548  		DownloadCount: &downloadCount,
   549  		Description:   &annotations.Description,
   550  		Title:         &annotations.Title,
   551  		Documentation: &annotations.Documentation,
   552  		Licenses:      &annotations.Licenses,
   553  		Labels:        &annotations.Labels,
   554  		Source:        &annotations.Source,
   555  		Vendor:        &annotations.Vendor,
   556  		Authors:       &authors,
   557  		Referrers:     manifestSummary.Referrers,
   558  	}
   559  
   560  	return &imageSummary, imageBlobsMap, nil
   561  }
   562  
   563  func isImageSigned(manifestSignatures mTypes.ManifestSignatures) bool {
   564  	for _, signatures := range manifestSignatures {
   565  		if len(signatures) > 0 {
   566  			return true
   567  		}
   568  	}
   569  
   570  	return false
   571  }
   572  
   573  func getPlatform(platform ispec.Platform) gql_generated.Platform {
   574  	return gql_generated.Platform{
   575  		Os:   ref(platform.OS),
   576  		Arch: ref(getArch(platform.Architecture, platform.Variant)),
   577  	}
   578  }
   579  
   580  func getArch(arch string, variant string) string {
   581  	if variant != "" {
   582  		arch = arch + "/" + variant
   583  	}
   584  
   585  	return arch
   586  }
   587  
   588  func ref[T any](val T) *T {
   589  	ref := val
   590  
   591  	return &ref
   592  }
   593  
   594  func deref[T any](pointer *T, defaultVal T) T {
   595  	if pointer != nil {
   596  		return *pointer
   597  	}
   598  
   599  	return defaultVal
   600  }
   601  
   602  func PaginatedFullImageMeta2ImageSummaries(ctx context.Context, imageMetaList []mTypes.FullImageMeta, skip SkipQGLField,
   603  	cveInfo cveinfo.CveInfo, filter mTypes.Filter, pageInput pagination.PageInput,
   604  ) ([]*gql_generated.ImageSummary, zcommon.PageInfo, error) {
   605  	imagePageFinder, err := pagination.NewImgSumPageFinder(pageInput.Limit, pageInput.Offset, pageInput.SortBy)
   606  	if err != nil {
   607  		return []*gql_generated.ImageSummary{}, zcommon.PageInfo{}, err
   608  	}
   609  
   610  	for _, imageMeta := range imageMetaList {
   611  		imageSummary, _, err := FullImageMeta2ImageSummary(ctx, imageMeta)
   612  		if err != nil {
   613  			continue
   614  		}
   615  
   616  		if ImgSumAcceptedByFilter(imageSummary, filter) {
   617  			imagePageFinder.Add(imageSummary)
   618  		}
   619  	}
   620  
   621  	page, pageInfo := imagePageFinder.Page()
   622  
   623  	for _, imageSummary := range page {
   624  		// CVE scanning is expensive, only scan for this page
   625  		updateImageSummaryVulnerabilities(ctx, imageSummary, skip, cveInfo)
   626  	}
   627  
   628  	return page, pageInfo, nil
   629  }