diff --git a/errors/errors.go b/errors/errors.go index 44191b1d2..09207d9e0 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -168,4 +168,6 @@ var ( ErrAPINotSupported = errors.New("registry at the given address doesn't implement the correct API") ErrURLNotFound = errors.New("url not found") ErrInvalidSearchQuery = errors.New("invalid search query") + ErrImageNotFound = errors.New("image not found") + ErrAmbiguousInput = errors.New("input is not specific enough") ) diff --git a/pkg/extensions/search/cve/cve.go b/pkg/extensions/search/cve/cve.go index f188e7a3a..1c3aeeb66 100644 --- a/pkg/extensions/search/cve/cve.go +++ b/pkg/extensions/search/cve/cve.go @@ -21,8 +21,10 @@ import ( type CveInfo interface { GetImageListForCVE(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error) GetImageListWithCVEFixed(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error) - GetCVEListForImage(ctx context.Context, repo, tag string, searchedCVE string, excludedCVE string, + GetCVEListForImage(ctx context.Context, repo, tag string, searchedCVE, excludedCVE string, pageinput cvemodel.PageInput) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error) + GetCVEDiffListForImages(ctx context.Context, minuend, subtrahend, searchedCVE, excludedCVE string, + pageInput cvemodel.PageInput) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error) GetCVESummaryForImageMedia(ctx context.Context, repo, digestStr, mediaType string) (cvemodel.ImageCVESummary, error) } @@ -329,7 +331,21 @@ func getConfigAndDigest(metaDB mTypes.MetaDB, manifestDigestStr string) (ispec.I return manifestData.Manifests[0].Config, manifestDigest, err } -func filterCVEList(cveMap map[string]cvemodel.CVE, searchedCVE, excludedCVE string, pageFinder *CvePageFinder) { +func filterCVEMap(cveMap map[string]cvemodel.CVE, searchedCVE, excludedCVE string, pageFinder *CvePageFinder) { + searchedCVE = strings.ToUpper(searchedCVE) + + for _, cve := range cveMap { + if excludedCVE != "" && cve.ContainsStr(excludedCVE) { + continue + } + + if cve.ContainsStr(searchedCVE) { + pageFinder.Add(cve) + } + } +} + +func filterCVEList(cveMap []cvemodel.CVE, searchedCVE, excludedCVE string, pageFinder *CvePageFinder) { searchedCVE = strings.ToUpper(searchedCVE) for _, cve := range cveMap { @@ -373,13 +389,98 @@ func (cveinfo BaseCveInfo) GetCVEListForImage(ctx context.Context, repo, ref str return []cvemodel.CVE{}, imageCVESummary, zcommon.PageInfo{}, err } - filterCVEList(cveMap, searchedCVE, excludedCVE, pageFinder) + filterCVEMap(cveMap, searchedCVE, excludedCVE, pageFinder) cveList, pageInfo := pageFinder.Page() return cveList, imageCVESummary, pageInfo, nil } +func (cveinfo BaseCveInfo) GetCVEDiffListForImages(ctx context.Context, minuend, subtrahend, searchedCVE string, + excludedCVE string, pageInput cvemodel.PageInput, +) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error) { + minuendRepo, minuendRef, _ := zcommon.GetImageDirAndReference(minuend) + subtrahendRepo, subtrahendRef, _ := zcommon.GetImageDirAndReference(subtrahend) + + // get the CVEs of image and comparedImage + minuendCVEList, _, _, err := cveinfo.GetCVEListForImage(ctx, minuendRepo, minuendRef, searchedCVE, excludedCVE, + cvemodel.PageInput{}) + if err != nil { + return nil, cvemodel.ImageCVESummary{}, zcommon.PageInfo{}, err + } + + subtrahendCVEList, _, _, err := cveinfo.GetCVEListForImage(ctx, subtrahendRepo, subtrahendRef, + searchedCVE, excludedCVE, cvemodel.PageInput{}) + if err != nil { + return nil, cvemodel.ImageCVESummary{}, zcommon.PageInfo{}, err + } + + subtrahendCVEMap := map[string]cvemodel.CVE{} + + for _, cve := range subtrahendCVEList { + cve := cve + subtrahendCVEMap[cve.ID] = cve + } + + var ( + count int + unknownCount int + lowCount int + mediumCount int + highCount int + criticalCount int + maxSeverity string + + diffCVEs = []cvemodel.CVE{} + ) + + for i := range minuendCVEList { + if _, ok := subtrahendCVEMap[minuendCVEList[i].ID]; !ok { + diffCVEs = append(diffCVEs, minuendCVEList[i]) + + switch minuendCVEList[i].Severity { + case cvemodel.SeverityUnknown: + unknownCount++ + case cvemodel.SeverityLow: + lowCount++ + case cvemodel.SeverityMedium: + mediumCount++ + case cvemodel.SeverityHigh: + highCount++ + case cvemodel.SeverityCritical: + criticalCount++ + } + + if cvemodel.CompareSeverities(maxSeverity, minuendCVEList[i].Severity) > 0 { + maxSeverity = minuendCVEList[i].Severity + } + } + } + + pageFinder, err := NewCvePageFinder(pageInput.Limit, pageInput.Offset, pageInput.SortBy) + if err != nil { + return nil, cvemodel.ImageCVESummary{}, zcommon.PageInfo{}, err + } + + filterCVEList(diffCVEs, "", "", pageFinder) + + cveList, pageInfo := pageFinder.Page() + + count = unknownCount + lowCount + mediumCount + highCount + criticalCount + + diffCVESummary := cvemodel.ImageCVESummary{ + Count: count, + UnknownCount: unknownCount, + LowCount: lowCount, + MediumCount: mediumCount, + HighCount: highCount, + CriticalCount: criticalCount, + MaxSeverity: maxSeverity, + } + + return cveList, diffCVESummary, pageInfo, nil +} + func (cveinfo BaseCveInfo) GetCVESummaryForImageMedia(ctx context.Context, repo, digestStr, mediaType string, ) (cvemodel.ImageCVESummary, error) { // There are several cases, expected returned values below: diff --git a/pkg/extensions/search/cve/cve_test.go b/pkg/extensions/search/cve/cve_test.go index 8c5656c8b..66a91e7c7 100644 --- a/pkg/extensions/search/cve/cve_test.go +++ b/pkg/extensions/search/cve/cve_test.go @@ -1277,6 +1277,11 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo So(cveSummary.CriticalCount, ShouldEqual, 2) So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") + _, _, _, err = cveInfo.GetCVEDiffListForImages(ctx, "repo8:1.0.0", "repo1@"+image13Digest, "", "", pageInput) + So(err, ShouldBeNil) + _, _, _, err = cveInfo.GetCVEDiffListForImages(ctx, "repo8:1.0.0", "repo1:0.1.0", "", "", pageInput) + So(err, ShouldBeNil) + // Image is multiarch cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repoMultiarch, "tagIndex", "", "", pageInput) So(err, ShouldBeNil) @@ -1625,6 +1630,35 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo _, err = cveInfo.GetImageListForCVE(ctx, repoMultiarch, "CVE1") So(err, ShouldBeNil) + + cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: mocks.CveScannerMock{ + IsImageFormatScannableFn: func(repo, reference string) (bool, error) { + return true, nil + }, + ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) { + return nil, zerr.ErrTypeAssertionFailed + }, + }, MetaDB: metaDB} + _, _, _, err = cveInfo.GetCVEDiffListForImages(ctx, "repo8:1.0.0", "repo1:0.1.0", "", "", pageInput) + So(err, ShouldNotBeNil) + + try := 0 + cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: mocks.CveScannerMock{ + IsImageFormatScannableFn: func(repo, reference string) (bool, error) { + return true, nil + }, + ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) { + if try == 1 { + return nil, zerr.ErrTypeAssertionFailed + } + + try++ + + return make(map[string]cvemodel.CVE), nil + }, + }, MetaDB: metaDB} + _, _, _, err = cveInfo.GetCVEDiffListForImages(ctx, "repo8:1.0.0", "repo6:0.1.0", "", "", pageInput) + So(err, ShouldNotBeNil) }) } diff --git a/pkg/extensions/search/gql_generated/generated.go b/pkg/extensions/search/gql_generated/generated.go index 33ba9a404..f8e90c89f 100644 --- a/pkg/extensions/search/gql_generated/generated.go +++ b/pkg/extensions/search/gql_generated/generated.go @@ -59,6 +59,14 @@ type ComplexityRoot struct { Title func(childComplexity int) int } + CVEDiffResult struct { + CVEList func(childComplexity int) int + Minuend func(childComplexity int) int + Page func(childComplexity int) int + Subtrahend func(childComplexity int) int + Summary func(childComplexity int) int + } + CVEResultForImage struct { CVEList func(childComplexity int) int Page func(childComplexity int) int @@ -81,6 +89,13 @@ type ComplexityRoot struct { EmptyLayer func(childComplexity int) int } + ImageIdentifier struct { + Digest func(childComplexity int) int + Platform func(childComplexity int) int + Repo func(childComplexity int) int + Tag func(childComplexity int) int + } + ImageSummary struct { Authors func(childComplexity int) int Description func(childComplexity int) int @@ -170,6 +185,7 @@ type ComplexityRoot struct { Query struct { BaseImageList func(childComplexity int, image string, digest *string, requestedPage *PageInput) int BookmarkedRepos func(childComplexity int, requestedPage *PageInput) int + CVEDiffListForImages func(childComplexity int, minuend ImageInput, subtrahend ImageInput, requestedPage *PageInput, searchedCve *string, excludedCve *string) int CVEListForImage func(childComplexity int, image string, requestedPage *PageInput, searchedCve *string, excludedCve *string) int DerivedImageList func(childComplexity int, image string, digest *string, requestedPage *PageInput) int ExpandedRepoInfo func(childComplexity int, repo string) int @@ -220,6 +236,7 @@ type ComplexityRoot struct { type QueryResolver interface { CVEListForImage(ctx context.Context, image string, requestedPage *PageInput, searchedCve *string, excludedCve *string) (*CVEResultForImage, error) + CVEDiffListForImages(ctx context.Context, minuend ImageInput, subtrahend ImageInput, requestedPage *PageInput, searchedCve *string, excludedCve *string) (*CVEDiffResult, error) ImageListForCve(ctx context.Context, id string, filter *Filter, requestedPage *PageInput) (*PaginatedImagesResult, error) ImageListWithCVEFixed(ctx context.Context, id string, image string, filter *Filter, requestedPage *PageInput) (*PaginatedImagesResult, error) ImageListForDigest(ctx context.Context, id string, requestedPage *PageInput) (*PaginatedImagesResult, error) @@ -310,6 +327,41 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.CVE.Title(childComplexity), true + case "CVEDiffResult.CVEList": + if e.complexity.CVEDiffResult.CVEList == nil { + break + } + + return e.complexity.CVEDiffResult.CVEList(childComplexity), true + + case "CVEDiffResult.Minuend": + if e.complexity.CVEDiffResult.Minuend == nil { + break + } + + return e.complexity.CVEDiffResult.Minuend(childComplexity), true + + case "CVEDiffResult.Page": + if e.complexity.CVEDiffResult.Page == nil { + break + } + + return e.complexity.CVEDiffResult.Page(childComplexity), true + + case "CVEDiffResult.Subtrahend": + if e.complexity.CVEDiffResult.Subtrahend == nil { + break + } + + return e.complexity.CVEDiffResult.Subtrahend(childComplexity), true + + case "CVEDiffResult.Summary": + if e.complexity.CVEDiffResult.Summary == nil { + break + } + + return e.complexity.CVEDiffResult.Summary(childComplexity), true + case "CVEResultForImage.CVEList": if e.complexity.CVEResultForImage.CVEList == nil { break @@ -401,6 +453,34 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.HistoryDescription.EmptyLayer(childComplexity), true + case "ImageIdentifier.Digest": + if e.complexity.ImageIdentifier.Digest == nil { + break + } + + return e.complexity.ImageIdentifier.Digest(childComplexity), true + + case "ImageIdentifier.Platform": + if e.complexity.ImageIdentifier.Platform == nil { + break + } + + return e.complexity.ImageIdentifier.Platform(childComplexity), true + + case "ImageIdentifier.Repo": + if e.complexity.ImageIdentifier.Repo == nil { + break + } + + return e.complexity.ImageIdentifier.Repo(childComplexity), true + + case "ImageIdentifier.Tag": + if e.complexity.ImageIdentifier.Tag == nil { + break + } + + return e.complexity.ImageIdentifier.Tag(childComplexity), true + case "ImageSummary.Authors": if e.complexity.ImageSummary.Authors == nil { break @@ -817,6 +897,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.BookmarkedRepos(childComplexity, args["requestedPage"].(*PageInput)), true + case "Query.CVEDiffListForImages": + if e.complexity.Query.CVEDiffListForImages == nil { + break + } + + args, err := ec.field_Query_CVEDiffListForImages_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.CVEDiffListForImages(childComplexity, args["minuend"].(ImageInput), args["subtrahend"].(ImageInput), args["requestedPage"].(*PageInput), args["searchedCVE"].(*string), args["excludedCVE"].(*string)), true + case "Query.CVEListForImage": if e.complexity.Query.CVEListForImage == nil { break @@ -1117,7 +1209,9 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec := executionContext{rc, e, 0, 0, make(chan graphql.DeferredResult)} inputUnmarshalMap := graphql.BuildUnmarshalerMap( ec.unmarshalInputFilter, + ec.unmarshalInputImageInput, ec.unmarshalInputPageInput, + ec.unmarshalInputPlatformInput, ) first := true @@ -1235,6 +1329,54 @@ type CVEResultForImage { Page: PageInfo } +""" +ImageIdentifier +""" +type ImageIdentifier { + """ + Repo name of the image + """ + Repo: String! + """ + The tag of the image + """ + Tag: String! + """ + The digest of the image + """ + Digest: String + """ + The platform of the image + """ + Platform: Platform +} + +""" +Contains the diff results of subtracting Subtrahend's CVEs from Minuend's CVEs +""" +type CVEDiffResult { + """ + Minuend is the image from which CVE's we subtract + """ + Minuend: ImageIdentifier! + """ + Subtrahend is the image which CVE's are subtracted + """ + Subtrahend: ImageIdentifier! + """ + List of CVE objects which are present in minuend but not in subtrahend + """ + CVEList: [CVE] + """ + Summary of the findings for this image + """ + Summary: ImageVulnerabilitySummary + """ + The CVE pagination information, see PageInfo object for more details + """ + Page: PageInfo +} + """ Contains various details about the CVE (Common Vulnerabilities and Exposures) and a list of PackageInfo about the affected packages @@ -1765,6 +1907,42 @@ input PageInput { sortBy: SortCriteria } +""" +PlatformInput contains the Os and the Arch of the input image +""" +input PlatformInput { + """ + The os of the image + """ + Os: String + """ + The arch of the image + """ + Arch: String +} + +""" +ImageInput +""" +input ImageInput { + """ + Repo name of the image + """ + repo: String! + """ + The tag of the image + """ + tag: String! + """ + The digest of the image + """ + digest: String + """ + The platform of the image + """ + platform: PlatformInput +} + """ Paginated list of RepoSummary objects """ @@ -1841,6 +2019,22 @@ type Query { excludedCVE: String ): CVEResultForImage! + """ + Returns a list with CVE's that are present in ` + "`" + `image` + "`" + ` but not in ` + "`" + `comparedImage` + "`" + ` + """ + CVEDiffListForImages( + "Image name in format ` + "`" + `repository:tag` + "`" + ` or ` + "`" + `repository@digest` + "`" + `" + minuend: ImageInput!, + "Compared image name in format ` + "`" + `repository:tag` + "`" + ` or ` + "`" + `repository@digest` + "`" + `" + subtrahend: ImageInput!, + "Sets the parameters of the requested page" + requestedPage: PageInput + "Search term for specific CVE by title/id" + searchedCVE: String + "Search term that must not be present in the returned results" + excludedCVE: String + ): CVEDiffResult! + """ Returns a list of images vulnerable to the CVE of the specified ID """ @@ -2036,6 +2230,57 @@ func (ec *executionContext) field_Query_BookmarkedRepos_args(ctx context.Context return args, nil } +func (ec *executionContext) field_Query_CVEDiffListForImages_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 ImageInput + if tmp, ok := rawArgs["minuend"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("minuend")) + arg0, err = ec.unmarshalNImageInput2zotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["minuend"] = arg0 + var arg1 ImageInput + if tmp, ok := rawArgs["subtrahend"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("subtrahend")) + arg1, err = ec.unmarshalNImageInput2zotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["subtrahend"] = arg1 + var arg2 *PageInput + if tmp, ok := rawArgs["requestedPage"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("requestedPage")) + arg2, err = ec.unmarshalOPageInput2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPageInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["requestedPage"] = arg2 + var arg3 *string + if tmp, ok := rawArgs["searchedCVE"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("searchedCVE")) + arg3, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + if err != nil { + return nil, err + } + } + args["searchedCVE"] = arg3 + var arg4 *string + if tmp, ok := rawArgs["excludedCVE"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("excludedCVE")) + arg4, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + if err != nil { + return nil, err + } + } + args["excludedCVE"] = arg4 + return args, nil +} + func (ec *executionContext) field_Query_CVEListForImage_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2749,8 +2994,8 @@ func (ec *executionContext) fieldContext_CVE_PackageList(ctx context.Context, fi return fc, nil } -func (ec *executionContext) _CVEResultForImage_Tag(ctx context.Context, field graphql.CollectedField, obj *CVEResultForImage) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_CVEResultForImage_Tag(ctx, field) +func (ec *executionContext) _CVEDiffResult_Minuend(ctx context.Context, field graphql.CollectedField, obj *CVEDiffResult) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CVEDiffResult_Minuend(ctx, field) if err != nil { return graphql.Null } @@ -2763,35 +3008,102 @@ func (ec *executionContext) _CVEResultForImage_Tag(ctx context.Context, field gr }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Tag, nil + return obj.Minuend, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(*string) + res := resTmp.(*ImageIdentifier) fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) + return ec.marshalNImageIdentifier2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageIdentifier(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_CVEResultForImage_Tag(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_CVEDiffResult_Minuend(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "CVEResultForImage", + Object: "CVEDiffResult", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + switch field.Name { + case "Repo": + return ec.fieldContext_ImageIdentifier_Repo(ctx, field) + case "Tag": + return ec.fieldContext_ImageIdentifier_Tag(ctx, field) + case "Digest": + return ec.fieldContext_ImageIdentifier_Digest(ctx, field) + case "Platform": + return ec.fieldContext_ImageIdentifier_Platform(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ImageIdentifier", field.Name) }, } return fc, nil } -func (ec *executionContext) _CVEResultForImage_CVEList(ctx context.Context, field graphql.CollectedField, obj *CVEResultForImage) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_CVEResultForImage_CVEList(ctx, field) +func (ec *executionContext) _CVEDiffResult_Subtrahend(ctx context.Context, field graphql.CollectedField, obj *CVEDiffResult) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CVEDiffResult_Subtrahend(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Subtrahend, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*ImageIdentifier) + fc.Result = res + return ec.marshalNImageIdentifier2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageIdentifier(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_CVEDiffResult_Subtrahend(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "CVEDiffResult", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "Repo": + return ec.fieldContext_ImageIdentifier_Repo(ctx, field) + case "Tag": + return ec.fieldContext_ImageIdentifier_Tag(ctx, field) + case "Digest": + return ec.fieldContext_ImageIdentifier_Digest(ctx, field) + case "Platform": + return ec.fieldContext_ImageIdentifier_Platform(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ImageIdentifier", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _CVEDiffResult_CVEList(ctx context.Context, field graphql.CollectedField, obj *CVEDiffResult) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CVEDiffResult_CVEList(ctx, field) if err != nil { return graphql.Null } @@ -2818,9 +3130,9 @@ func (ec *executionContext) _CVEResultForImage_CVEList(ctx context.Context, fiel return ec.marshalOCVE2ᚕᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐCve(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_CVEResultForImage_CVEList(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_CVEDiffResult_CVEList(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "CVEResultForImage", + Object: "CVEDiffResult", Field: field, IsMethod: false, IsResolver: false, @@ -2845,8 +3157,8 @@ func (ec *executionContext) fieldContext_CVEResultForImage_CVEList(ctx context.C return fc, nil } -func (ec *executionContext) _CVEResultForImage_Summary(ctx context.Context, field graphql.CollectedField, obj *CVEResultForImage) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_CVEResultForImage_Summary(ctx, field) +func (ec *executionContext) _CVEDiffResult_Summary(ctx context.Context, field graphql.CollectedField, obj *CVEDiffResult) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CVEDiffResult_Summary(ctx, field) if err != nil { return graphql.Null } @@ -2873,9 +3185,9 @@ func (ec *executionContext) _CVEResultForImage_Summary(ctx context.Context, fiel return ec.marshalOImageVulnerabilitySummary2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageVulnerabilitySummary(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_CVEResultForImage_Summary(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_CVEDiffResult_Summary(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "CVEResultForImage", + Object: "CVEDiffResult", Field: field, IsMethod: false, IsResolver: false, @@ -2902,8 +3214,8 @@ func (ec *executionContext) fieldContext_CVEResultForImage_Summary(ctx context.C return fc, nil } -func (ec *executionContext) _CVEResultForImage_Page(ctx context.Context, field graphql.CollectedField, obj *CVEResultForImage) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_CVEResultForImage_Page(ctx, field) +func (ec *executionContext) _CVEDiffResult_Page(ctx context.Context, field graphql.CollectedField, obj *CVEDiffResult) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CVEDiffResult_Page(ctx, field) if err != nil { return graphql.Null } @@ -2930,9 +3242,9 @@ func (ec *executionContext) _CVEResultForImage_Page(ctx context.Context, field g return ec.marshalOPageInfo2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPageInfo(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_CVEResultForImage_Page(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_CVEDiffResult_Page(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "CVEResultForImage", + Object: "CVEDiffResult", Field: field, IsMethod: false, IsResolver: false, @@ -2949,8 +3261,8 @@ func (ec *executionContext) fieldContext_CVEResultForImage_Page(ctx context.Cont return fc, nil } -func (ec *executionContext) _GlobalSearchResult_Page(ctx context.Context, field graphql.CollectedField, obj *GlobalSearchResult) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_GlobalSearchResult_Page(ctx, field) +func (ec *executionContext) _CVEResultForImage_Tag(ctx context.Context, field graphql.CollectedField, obj *CVEResultForImage) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CVEResultForImage_Tag(ctx, field) if err != nil { return graphql.Null } @@ -2963,7 +3275,7 @@ func (ec *executionContext) _GlobalSearchResult_Page(ctx context.Context, field }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Page, nil + return obj.Tag, nil }) if err != nil { ec.Error(ctx, err) @@ -2972,32 +3284,26 @@ func (ec *executionContext) _GlobalSearchResult_Page(ctx context.Context, field if resTmp == nil { return graphql.Null } - res := resTmp.(*PageInfo) + res := resTmp.(*string) fc.Result = res - return ec.marshalOPageInfo2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPageInfo(ctx, field.Selections, res) + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_GlobalSearchResult_Page(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_CVEResultForImage_Tag(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "GlobalSearchResult", + Object: "CVEResultForImage", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "TotalCount": - return ec.fieldContext_PageInfo_TotalCount(ctx, field) - case "ItemCount": - return ec.fieldContext_PageInfo_ItemCount(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name) + return nil, errors.New("field of type String does not have child fields") }, } return fc, nil } -func (ec *executionContext) _GlobalSearchResult_Images(ctx context.Context, field graphql.CollectedField, obj *GlobalSearchResult) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_GlobalSearchResult_Images(ctx, field) +func (ec *executionContext) _CVEResultForImage_CVEList(ctx context.Context, field graphql.CollectedField, obj *CVEResultForImage) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CVEResultForImage_CVEList(ctx, field) if err != nil { return graphql.Null } @@ -3010,7 +3316,7 @@ func (ec *executionContext) _GlobalSearchResult_Images(ctx context.Context, fiel }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Images, nil + return obj.CVEList, nil }) if err != nil { ec.Error(ctx, err) @@ -3019,21 +3325,227 @@ func (ec *executionContext) _GlobalSearchResult_Images(ctx context.Context, fiel if resTmp == nil { return graphql.Null } - res := resTmp.([]*ImageSummary) + res := resTmp.([]*Cve) fc.Result = res - return ec.marshalOImageSummary2ᚕᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummary(ctx, field.Selections, res) + return ec.marshalOCVE2ᚕᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐCve(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_GlobalSearchResult_Images(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_CVEResultForImage_CVEList(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "GlobalSearchResult", + Object: "CVEResultForImage", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "RepoName": - return ec.fieldContext_ImageSummary_RepoName(ctx, field) + case "Id": + return ec.fieldContext_CVE_Id(ctx, field) + case "Title": + return ec.fieldContext_CVE_Title(ctx, field) + case "Description": + return ec.fieldContext_CVE_Description(ctx, field) + case "Reference": + return ec.fieldContext_CVE_Reference(ctx, field) + case "Severity": + return ec.fieldContext_CVE_Severity(ctx, field) + case "PackageList": + return ec.fieldContext_CVE_PackageList(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type CVE", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _CVEResultForImage_Summary(ctx context.Context, field graphql.CollectedField, obj *CVEResultForImage) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CVEResultForImage_Summary(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Summary, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*ImageVulnerabilitySummary) + fc.Result = res + return ec.marshalOImageVulnerabilitySummary2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageVulnerabilitySummary(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_CVEResultForImage_Summary(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "CVEResultForImage", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "MaxSeverity": + return ec.fieldContext_ImageVulnerabilitySummary_MaxSeverity(ctx, field) + case "Count": + return ec.fieldContext_ImageVulnerabilitySummary_Count(ctx, field) + case "UnknownCount": + return ec.fieldContext_ImageVulnerabilitySummary_UnknownCount(ctx, field) + case "LowCount": + return ec.fieldContext_ImageVulnerabilitySummary_LowCount(ctx, field) + case "MediumCount": + return ec.fieldContext_ImageVulnerabilitySummary_MediumCount(ctx, field) + case "HighCount": + return ec.fieldContext_ImageVulnerabilitySummary_HighCount(ctx, field) + case "CriticalCount": + return ec.fieldContext_ImageVulnerabilitySummary_CriticalCount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ImageVulnerabilitySummary", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _CVEResultForImage_Page(ctx context.Context, field graphql.CollectedField, obj *CVEResultForImage) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CVEResultForImage_Page(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Page, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*PageInfo) + fc.Result = res + return ec.marshalOPageInfo2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPageInfo(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_CVEResultForImage_Page(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "CVEResultForImage", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "TotalCount": + return ec.fieldContext_PageInfo_TotalCount(ctx, field) + case "ItemCount": + return ec.fieldContext_PageInfo_ItemCount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _GlobalSearchResult_Page(ctx context.Context, field graphql.CollectedField, obj *GlobalSearchResult) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_GlobalSearchResult_Page(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Page, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*PageInfo) + fc.Result = res + return ec.marshalOPageInfo2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPageInfo(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_GlobalSearchResult_Page(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "GlobalSearchResult", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "TotalCount": + return ec.fieldContext_PageInfo_TotalCount(ctx, field) + case "ItemCount": + return ec.fieldContext_PageInfo_ItemCount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _GlobalSearchResult_Images(ctx context.Context, field graphql.CollectedField, obj *GlobalSearchResult) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_GlobalSearchResult_Images(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Images, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*ImageSummary) + fc.Result = res + return ec.marshalOImageSummary2ᚕᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummary(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_GlobalSearchResult_Images(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "GlobalSearchResult", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "RepoName": + return ec.fieldContext_ImageSummary_RepoName(ctx, field) case "Tag": return ec.fieldContext_ImageSummary_Tag(ctx, field) case "Digest": @@ -3344,9 +3856,179 @@ func (ec *executionContext) _HistoryDescription_Comment(ctx context.Context, fie return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_HistoryDescription_Comment(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_HistoryDescription_Comment(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "HistoryDescription", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _HistoryDescription_EmptyLayer(ctx context.Context, field graphql.CollectedField, obj *HistoryDescription) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_HistoryDescription_EmptyLayer(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.EmptyLayer, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*bool) + fc.Result = res + return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_HistoryDescription_EmptyLayer(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "HistoryDescription", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _ImageIdentifier_Repo(ctx context.Context, field graphql.CollectedField, obj *ImageIdentifier) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ImageIdentifier_Repo(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Repo, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ImageIdentifier_Repo(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ImageIdentifier", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _ImageIdentifier_Tag(ctx context.Context, field graphql.CollectedField, obj *ImageIdentifier) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ImageIdentifier_Tag(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Tag, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ImageIdentifier_Tag(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ImageIdentifier", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _ImageIdentifier_Digest(ctx context.Context, field graphql.CollectedField, obj *ImageIdentifier) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ImageIdentifier_Digest(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Digest, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ImageIdentifier_Digest(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "HistoryDescription", + Object: "ImageIdentifier", Field: field, IsMethod: false, IsResolver: false, @@ -3357,8 +4039,8 @@ func (ec *executionContext) fieldContext_HistoryDescription_Comment(ctx context. return fc, nil } -func (ec *executionContext) _HistoryDescription_EmptyLayer(ctx context.Context, field graphql.CollectedField, obj *HistoryDescription) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_HistoryDescription_EmptyLayer(ctx, field) +func (ec *executionContext) _ImageIdentifier_Platform(ctx context.Context, field graphql.CollectedField, obj *ImageIdentifier) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ImageIdentifier_Platform(ctx, field) if err != nil { return graphql.Null } @@ -3371,7 +4053,7 @@ func (ec *executionContext) _HistoryDescription_EmptyLayer(ctx context.Context, }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.EmptyLayer, nil + return obj.Platform, nil }) if err != nil { ec.Error(ctx, err) @@ -3380,19 +4062,25 @@ func (ec *executionContext) _HistoryDescription_EmptyLayer(ctx context.Context, if resTmp == nil { return graphql.Null } - res := resTmp.(*bool) + res := resTmp.(*Platform) fc.Result = res - return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res) + return ec.marshalOPlatform2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPlatform(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_HistoryDescription_EmptyLayer(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_ImageIdentifier_Platform(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "HistoryDescription", + Object: "ImageIdentifier", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type Boolean does not have child fields") + switch field.Name { + case "Os": + return ec.fieldContext_Platform_Os(ctx, field) + case "Arch": + return ec.fieldContext_Platform_Arch(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Platform", field.Name) }, } return fc, nil @@ -5987,6 +6675,73 @@ func (ec *executionContext) fieldContext_Query_CVEListForImage(ctx context.Conte return fc, nil } +func (ec *executionContext) _Query_CVEDiffListForImages(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_CVEDiffListForImages(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().CVEDiffListForImages(rctx, fc.Args["minuend"].(ImageInput), fc.Args["subtrahend"].(ImageInput), fc.Args["requestedPage"].(*PageInput), fc.Args["searchedCVE"].(*string), fc.Args["excludedCVE"].(*string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*CVEDiffResult) + fc.Result = res + return ec.marshalNCVEDiffResult2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐCVEDiffResult(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_CVEDiffListForImages(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "Minuend": + return ec.fieldContext_CVEDiffResult_Minuend(ctx, field) + case "Subtrahend": + return ec.fieldContext_CVEDiffResult_Subtrahend(ctx, field) + case "CVEList": + return ec.fieldContext_CVEDiffResult_CVEList(ctx, field) + case "Summary": + return ec.fieldContext_CVEDiffResult_Summary(ctx, field) + case "Page": + return ec.fieldContext_CVEDiffResult_Page(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type CVEDiffResult", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_CVEDiffListForImages_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Query_ImageListForCVE(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_ImageListForCVE(ctx, field) if err != nil { @@ -9773,6 +10528,54 @@ func (ec *executionContext) unmarshalInputFilter(ctx context.Context, obj interf return it, nil } +func (ec *executionContext) unmarshalInputImageInput(ctx context.Context, obj interface{}) (ImageInput, error) { + var it ImageInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"repo", "tag", "digest", "platform"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "repo": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("repo")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.Repo = data + case "tag": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("tag")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.Tag = data + case "digest": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("digest")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Digest = data + case "platform": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("platform")) + data, err := ec.unmarshalOPlatformInput2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPlatformInput(ctx, v) + if err != nil { + return it, err + } + it.Platform = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputPageInput(ctx context.Context, obj interface{}) (PageInput, error) { var it PageInput asMap := map[string]interface{}{} @@ -9814,6 +10617,40 @@ func (ec *executionContext) unmarshalInputPageInput(ctx context.Context, obj int return it, nil } +func (ec *executionContext) unmarshalInputPlatformInput(ctx context.Context, obj interface{}) (PlatformInput, error) { + var it PlatformInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"Os", "Arch"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "Os": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("Os")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Os = data + case "Arch": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("Arch")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Arch = data + } + } + + return it, nil +} + // endregion **************************** input.gotpl ***************************** // region ************************** interface.gotpl *************************** @@ -9906,6 +10743,56 @@ func (ec *executionContext) _CVE(ctx context.Context, sel ast.SelectionSet, obj return out } +var cVEDiffResultImplementors = []string{"CVEDiffResult"} + +func (ec *executionContext) _CVEDiffResult(ctx context.Context, sel ast.SelectionSet, obj *CVEDiffResult) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, cVEDiffResultImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("CVEDiffResult") + case "Minuend": + out.Values[i] = ec._CVEDiffResult_Minuend(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "Subtrahend": + out.Values[i] = ec._CVEDiffResult_Subtrahend(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "CVEList": + out.Values[i] = ec._CVEDiffResult_CVEList(ctx, field, obj) + case "Summary": + out.Values[i] = ec._CVEDiffResult_Summary(ctx, field, obj) + case "Page": + out.Values[i] = ec._CVEDiffResult_Page(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var cVEResultForImageImplementors = []string{"CVEResultForImage"} func (ec *executionContext) _CVEResultForImage(ctx context.Context, sel ast.SelectionSet, obj *CVEResultForImage) graphql.Marshaler { @@ -10034,6 +10921,54 @@ func (ec *executionContext) _HistoryDescription(ctx context.Context, sel ast.Sel return out } +var imageIdentifierImplementors = []string{"ImageIdentifier"} + +func (ec *executionContext) _ImageIdentifier(ctx context.Context, sel ast.SelectionSet, obj *ImageIdentifier) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, imageIdentifierImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ImageIdentifier") + case "Repo": + out.Values[i] = ec._ImageIdentifier_Repo(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "Tag": + out.Values[i] = ec._ImageIdentifier_Tag(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "Digest": + out.Values[i] = ec._ImageIdentifier_Digest(ctx, field, obj) + case "Platform": + out.Values[i] = ec._ImageIdentifier_Platform(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var imageSummaryImplementors = []string{"ImageSummary"} func (ec *executionContext) _ImageSummary(ctx context.Context, sel ast.SelectionSet, obj *ImageSummary) graphql.Marshaler { @@ -10538,6 +11473,28 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "CVEDiffListForImages": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_CVEDiffListForImages(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "ImageListForCVE": field := field @@ -11416,6 +12373,20 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se return res } +func (ec *executionContext) marshalNCVEDiffResult2zotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐCVEDiffResult(ctx context.Context, sel ast.SelectionSet, v CVEDiffResult) graphql.Marshaler { + return ec._CVEDiffResult(ctx, sel, &v) +} + +func (ec *executionContext) marshalNCVEDiffResult2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐCVEDiffResult(ctx context.Context, sel ast.SelectionSet, v *CVEDiffResult) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._CVEDiffResult(ctx, sel, v) +} + func (ec *executionContext) marshalNCVEResultForImage2zotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐCVEResultForImage(ctx context.Context, sel ast.SelectionSet, v CVEResultForImage) graphql.Marshaler { return ec._CVEResultForImage(ctx, sel, &v) } @@ -11444,6 +12415,21 @@ func (ec *executionContext) marshalNGlobalSearchResult2ᚖzotregistryᚗdevᚋzo return ec._GlobalSearchResult(ctx, sel, v) } +func (ec *executionContext) marshalNImageIdentifier2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageIdentifier(ctx context.Context, sel ast.SelectionSet, v *ImageIdentifier) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._ImageIdentifier(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNImageInput2zotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageInput(ctx context.Context, v interface{}) (ImageInput, error) { + res, err := ec.unmarshalInputImageInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalNImageSummary2zotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐImageSummary(ctx context.Context, sel ast.SelectionSet, v ImageSummary) graphql.Marshaler { return ec._ImageSummary(ctx, sel, &v) } @@ -12341,6 +13327,14 @@ func (ec *executionContext) marshalOPlatform2ᚖzotregistryᚗdevᚋzotᚋpkgᚋ return ec._Platform(ctx, sel, v) } +func (ec *executionContext) unmarshalOPlatformInput2ᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐPlatformInput(ctx context.Context, v interface{}) (*PlatformInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputPlatformInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalOReferrer2ᚕᚖzotregistryᚗdevᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐReferrer(ctx context.Context, sel ast.SelectionSet, v []*Referrer) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/pkg/extensions/search/gql_generated/models_gen.go b/pkg/extensions/search/gql_generated/models_gen.go index 00a82c757..782087d7a 100644 --- a/pkg/extensions/search/gql_generated/models_gen.go +++ b/pkg/extensions/search/gql_generated/models_gen.go @@ -35,6 +35,20 @@ type Cve struct { PackageList []*PackageInfo `json:"PackageList,omitempty"` } +// Contains the diff results of subtracting Subtrahend's CVEs from Minuend's CVEs +type CVEDiffResult struct { + // Minuend is the image from which CVE's we subtract + Minuend *ImageIdentifier `json:"Minuend"` + // Subtrahend is the image which CVE's are subtracted + Subtrahend *ImageIdentifier `json:"Subtrahend"` + // List of CVE objects which are present in minuend but not in subtrahend + CVEList []*Cve `json:"CVEList,omitempty"` + // Summary of the findings for this image + Summary *ImageVulnerabilitySummary `json:"Summary,omitempty"` + // The CVE pagination information, see PageInfo object for more details + Page *PageInfo `json:"Page,omitempty"` +} + // Contains the tag of the image and a list of CVEs type CVEResultForImage struct { // Tag affected by the CVEs @@ -92,6 +106,30 @@ type HistoryDescription struct { EmptyLayer *bool `json:"EmptyLayer,omitempty"` } +// ImageIdentifier +type ImageIdentifier struct { + // Repo name of the image + Repo string `json:"Repo"` + // The tag of the image + Tag string `json:"Tag"` + // The digest of the image + Digest *string `json:"Digest,omitempty"` + // The platform of the image + Platform *Platform `json:"Platform,omitempty"` +} + +// ImageInput +type ImageInput struct { + // Repo name of the image + Repo string `json:"repo"` + // The tag of the image + Tag string `json:"tag"` + // The digest of the image + Digest *string `json:"digest,omitempty"` + // The platform of the image + Platform *PlatformInput `json:"platform,omitempty"` +} + // Details about a specific image, it is used by queries returning a list of images // We define an image as a pairing or a repository and a tag belonging to that repository type ImageSummary struct { @@ -262,6 +300,14 @@ type Platform struct { Arch *string `json:"Arch,omitempty"` } +// PlatformInput contains the Os and the Arch of the input image +type PlatformInput struct { + // The os of the image + Os *string `json:"Os,omitempty"` + // The arch of the image + Arch *string `json:"Arch,omitempty"` +} + // A referrer is an object which has a reference to a another object type Referrer struct { // Referrer MediaType diff --git a/pkg/extensions/search/resolver.go b/pkg/extensions/search/resolver.go index aa374d1be..8fe96c72e 100644 --- a/pkg/extensions/search/resolver.go +++ b/pkg/extensions/search/resolver.go @@ -277,6 +277,263 @@ func getCVEListForImage( }, nil } +func getCVEDiffListForImages( + ctx context.Context, //nolint:unparam // may be used in the future to filter by permissions + minuend gql_generated.ImageInput, + subtrahend gql_generated.ImageInput, + metaDB mTypes.MetaDB, + cveInfo cveinfo.CveInfo, + requestedPage *gql_generated.PageInput, + searchedCVE string, + excludedCVE string, + log log.Logger, //nolint:unparam // may be used by devs for debugging +) (*gql_generated.CVEDiffResult, error) { + minuend, err := checkImageInput(ctx, minuend, metaDB) + if err != nil { + return nil, err + } + + resultMinuend := getImageIdentifier(minuend) + resultSubtrahend := gql_generated.ImageIdentifier{} + + if subtrahend.Repo != "" { + subtrahend, err = checkImageInput(ctx, subtrahend, metaDB) + if err != nil { + return nil, err + } + resultSubtrahend = getImageIdentifier(subtrahend) + } else { + // search for base images + // get minuend image meta + minuendSummary, err := metaDB.GetImageMeta(godigest.Digest(deref(minuend.Digest, ""))) + if err != nil { + return &gql_generated.CVEDiffResult{}, err + } + + // get the base images for the minuend + minuendBaseImages, err := metaDB.FilterTags(ctx, mTypes.AcceptOnlyRepo(minuend.Repo), + filterBaseImagesForMeta(minuendSummary)) + if err != nil { + return &gql_generated.CVEDiffResult{}, err + } + + // get the best base image as subtrahend + // get the one with most layers in common + imgLayers := map[string]struct{}{} + + for _, layer := range minuendSummary.Manifests[0].Manifest.Layers { + imgLayers[layer.Digest.String()] = struct{}{} + } + + bestMatchingScore := 0 + + for _, baseImage := range minuendBaseImages { + for _, baseManifest := range baseImage.Manifests { + currentMatchingScore := 0 + + for _, layer := range baseManifest.Manifest.Layers { + if _, ok := imgLayers[layer.Digest.String()]; ok { + currentMatchingScore++ + } + } + + if currentMatchingScore > bestMatchingScore { + bestMatchingScore = currentMatchingScore + + resultSubtrahend = gql_generated.ImageIdentifier{ + Repo: baseImage.Repo, + Tag: baseImage.Tag, + Digest: ref(baseImage.Manifests[0].Digest.String()), + Platform: &gql_generated.Platform{ + Os: ref(baseImage.Manifests[0].Config.OS), + Arch: ref(getArch(baseImage.Manifests[0].Config.Platform)), + }, + } + subtrahend.Repo = baseImage.Repo + subtrahend.Tag = baseImage.Tag + subtrahend.Digest = ref(baseImage.Manifests[0].Digest.String()) + } + } + } + } + + minuendRepoRef := minuend.Repo + "@" + deref(minuend.Digest, "") + subtrahendRepoRef := subtrahend.Repo + "@" + deref(subtrahend.Digest, "") + + diffCVEs, diffSummary, _, err := cveInfo.GetCVEDiffListForImages(ctx, minuendRepoRef, subtrahendRepoRef, searchedCVE, + excludedCVE, cvemodel.PageInput{ + Limit: deref(requestedPage.Limit, 0), + Offset: deref(requestedPage.Offset, 0), + SortBy: cvemodel.SortCriteria(deref(requestedPage.SortBy, gql_generated.SortCriteriaSeverity)), + }) + if err != nil { + return nil, err + } + + cveids := []*gql_generated.Cve{} + + for _, cveDetail := range diffCVEs { + vulID := cveDetail.ID + desc := cveDetail.Description + title := cveDetail.Title + severity := cveDetail.Severity + referenceURL := cveDetail.Reference + + pkgList := make([]*gql_generated.PackageInfo, 0) + + for _, pkg := range cveDetail.PackageList { + pkg := pkg + + pkgList = append(pkgList, + &gql_generated.PackageInfo{ + Name: &pkg.Name, + InstalledVersion: &pkg.InstalledVersion, + FixedVersion: &pkg.FixedVersion, + }, + ) + } + + cveids = append(cveids, + &gql_generated.Cve{ + ID: &vulID, + Title: &title, + Description: &desc, + Severity: &severity, + Reference: &referenceURL, + PackageList: pkgList, + }, + ) + } + + return &gql_generated.CVEDiffResult{ + Minuend: &resultMinuend, + Subtrahend: &resultSubtrahend, + Summary: &gql_generated.ImageVulnerabilitySummary{ + Count: &diffSummary.Count, + UnknownCount: &diffSummary.UnknownCount, + LowCount: &diffSummary.LowCount, + MediumCount: &diffSummary.MediumCount, + HighCount: &diffSummary.HighCount, + CriticalCount: &diffSummary.CriticalCount, + MaxSeverity: &diffSummary.MaxSeverity, + }, + CVEList: cveids, + Page: &gql_generated.PageInfo{}, + }, nil +} + +func getImageIdentifier(img gql_generated.ImageInput) gql_generated.ImageIdentifier { + return gql_generated.ImageIdentifier{ + Repo: img.Repo, + Tag: img.Tag, + Digest: img.Digest, + Platform: getIdentifierPlatform(img.Platform), + } +} + +func getIdentifierPlatform(platform *gql_generated.PlatformInput) *gql_generated.Platform { + if platform == nil { + return nil + } + + return &gql_generated.Platform{ + Os: platform.Os, + Arch: platform.Arch, + } +} + +// rename idea: identify image from input. +func checkImageInput(ctx context.Context, imageInput gql_generated.ImageInput, metaDB mTypes.MetaDB, +) (gql_generated.ImageInput, error) { + if imageInput.Repo == "" { + return gql_generated.ImageInput{}, zerr.ErrEmptyRepoName + } + + if imageInput.Tag == "" { + return gql_generated.ImageInput{}, zerr.ErrEmptyTag + } + + // try checking if the tag is a simple image first + repoMeta, err := metaDB.GetRepoMeta(ctx, imageInput.Repo) + if err != nil { + return gql_generated.ImageInput{}, err + } + + descriptor, ok := repoMeta.Tags[imageInput.Tag] + if !ok { + return gql_generated.ImageInput{}, zerr.ErrImageNotFound + } + + switch descriptor.MediaType { + case ispec.MediaTypeImageManifest: + imageInput.Digest = ref(descriptor.Digest) + + return imageInput, nil + case ispec.MediaTypeImageIndex: + if dderef(imageInput.Digest) == "" && !isPlatformSpecified(imageInput.Platform) { + return gql_generated.ImageInput{}, + fmt.Errorf("%w: platform or specific manifest digest needed", zerr.ErrAmbiguousInput) + } + + imageMeta, err := metaDB.GetImageMeta(godigest.Digest(descriptor.Digest)) + if err != nil { + return gql_generated.ImageInput{}, err + } + + for _, manifest := range imageMeta.Manifests { + if manifest.Digest.String() == dderef(imageInput.Digest) || + isMatchingPlatform(manifest.Config.Platform, dderef(imageInput.Platform)) { + imageInput.Digest = ref(manifest.Digest.String()) + imageInput.Platform = &gql_generated.PlatformInput{ + Os: ref(manifest.Config.OS), + Arch: ref(getArch(manifest.Config.Platform)), + } + + return imageInput, nil + } + } + + return imageInput, zerr.ErrImageNotFound + } + + return imageInput, nil +} + +func isPlatformSpecified(platformInput *gql_generated.PlatformInput) bool { + if platformInput == nil { + return false + } + + if dderef(platformInput.Os) == "" || dderef(platformInput.Arch) == "" { + return false + } + + return true +} + +func isMatchingPlatform(platform ispec.Platform, platformInput gql_generated.PlatformInput) bool { + if platform.OS != deref(platformInput.Os, "") { + return false + } + + arch := getArch(platform) + + if arch == deref(platformInput.Arch, "") { + return true + } + + return true +} + +func getArch(platform ispec.Platform) string { + arch := platform.Architecture + if arch != "" && platform.Variant != "" { + arch += "/" + platform.Variant + } + + return arch +} + func FilterByTagInfo(tagsInfo []cvemodel.TagInfo) mTypes.FilterFunc { return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool { manifestDigest := imageMeta.Manifests[0].Digest.String() @@ -999,6 +1256,47 @@ func filterBaseImages(image *gql_generated.ImageSummary) mTypes.FilterFunc { } } +func filterBaseImagesForMeta(image mTypes.ImageMeta) mTypes.FilterFunc { + return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool { + var addImageToList bool + + manifest := imageMeta.Manifests[0] + + for i := range image.Manifests { + manifestDigest := manifest.Digest.String() + if manifestDigest == image.Manifests[i].Digest.String() { + return false + } + + addImageToList = true + + for _, l := range manifest.Manifest.Layers { + foundLayer := false + + for _, k := range image.Manifests[i].Manifest.Layers { + if l.Digest.String() == k.Digest.String() { + foundLayer = true + + break + } + } + + if !foundLayer { + addImageToList = false + + break + } + } + + if addImageToList { + return true + } + } + + return false + } +} + func validateGlobalSearchInput(query string, filter *gql_generated.Filter, requestedPage *gql_generated.PageInput, ) error { @@ -1185,6 +1483,17 @@ func (p timeSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +// dderef is a default deref. +func dderef[T any](pointer *T) T { + var defValue T + + if pointer != nil { + return *pointer + } + + return defValue +} + func deref[T any](pointer *T, defaultVal T) T { if pointer != nil { return *pointer @@ -1193,6 +1502,12 @@ func deref[T any](pointer *T, defaultVal T) T { return defaultVal } +func ref[T any](input T) *T { + ref := input + + return &ref +} + func getImageList(ctx context.Context, repo string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo, requestedPage *gql_generated.PageInput, log log.Logger, //nolint:unparam ) (*gql_generated.PaginatedImagesResult, error) { diff --git a/pkg/extensions/search/resolver_test.go b/pkg/extensions/search/resolver_test.go index 239fa0895..93f45caec 100644 --- a/pkg/extensions/search/resolver_test.go +++ b/pkg/extensions/search/resolver_test.go @@ -14,6 +14,7 @@ import ( ispec "github.com/opencontainers/image-spec/specs-go/v1" . "github.com/smartystreets/goconvey/convey" + zerr "zotregistry.dev/zot/errors" "zotregistry.dev/zot/pkg/common" "zotregistry.dev/zot/pkg/extensions/search/convert" cveinfo "zotregistry.dev/zot/pkg/extensions/search/cve" @@ -730,6 +731,50 @@ func TestQueryResolverErrors(t *testing.T) { So(err, ShouldNotBeNil) }) + Convey("CVEDiffListForImages nill cveinfo", func() { + resolverConfig := NewResolver( + log, + storage.StoreController{ + DefaultStore: mocks.MockedImageStore{}, + }, + mocks.MetaDBMock{ + GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool, + ) ([]mTypes.RepoMeta, error) { + return []mTypes.RepoMeta{}, ErrTestError + }, + }, + nil, + ) + + qr := queryResolver{resolverConfig} + + _, err := qr.CVEDiffListForImages(ctx, gql_generated.ImageInput{}, gql_generated.ImageInput{}, + &gql_generated.PageInput{}, nil, nil) + So(err, ShouldNotBeNil) + }) + + Convey("CVEDiffListForImages error", func() { + resolverConfig := NewResolver( + log, + storage.StoreController{ + DefaultStore: mocks.MockedImageStore{}, + }, + mocks.MetaDBMock{ + GetMultipleRepoMetaFn: func(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool, + ) ([]mTypes.RepoMeta, error) { + return []mTypes.RepoMeta{}, ErrTestError + }, + }, + mocks.CveInfoMock{}, + ) + + qr := queryResolver{resolverConfig} + + _, err := qr.CVEDiffListForImages(ctx, gql_generated.ImageInput{}, gql_generated.ImageInput{}, + &gql_generated.PageInput{}, nil, nil) + So(err, ShouldNotBeNil) + }) + Convey("ImageListForCve error in GetMultipleRepoMeta", func() { resolverConfig := NewResolver( log, @@ -1850,6 +1895,266 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo ) So(err, ShouldNotBeNil) }) + + Convey("CVE Diff between images", t, func() { + // image := "image:tag" + // baseImage := "base:basetag" + ctx := context.Background() + pageInput := &gql_generated.PageInput{ + SortBy: ref(gql_generated.SortCriteriaAlphabeticAsc), + } + + boltDriver, err := boltdb.GetBoltDriver(boltdb.DBParameters{RootDir: t.TempDir()}) + if err != nil { + panic(err) + } + + metaDB, err := boltdb.New(boltDriver, log) + if err != nil { + panic(err) + } + + layer1 := []byte{10, 20, 30} + layer2 := []byte{11, 21, 31} + layer3 := []byte{12, 22, 23} + + otherImage := CreateImageWith().LayerBlobs([][]byte{ + layer1, + }).DefaultConfig().Build() + + baseImage := CreateImageWith().LayerBlobs([][]byte{ + layer1, + layer2, + }).PlatformConfig("testArch", "testOs").Build() + + image := CreateImageWith().LayerBlobs([][]byte{ + layer1, + layer2, + layer3, + }).PlatformConfig("testArch", "testOs").Build() + + multiArchBase := CreateMultiarchWith().Images([]Image{baseImage, CreateRandomImage(), CreateRandomImage()}). + Build() + multiArchImage := CreateMultiarchWith().Images([]Image{image, CreateRandomImage(), CreateRandomImage()}). + Build() + + getCveResults := func(digestStr string) map[string]cvemodel.CVE { + switch digestStr { + case image.DigestStr(): + return map[string]cvemodel.CVE{ + "CVE1": { + ID: "CVE1", + Severity: "HIGH", + Title: "Title CVE1", + Description: "Description CVE1", + PackageList: []cvemodel.Package{{}}, + }, + "CVE2": { + ID: "CVE2", + Severity: "MEDIUM", + Title: "Title CVE2", + Description: "Description CVE2", + PackageList: []cvemodel.Package{{}}, + }, + "CVE3": { + ID: "CVE3", + Severity: "LOW", + Title: "Title CVE3", + Description: "Description CVE3", + PackageList: []cvemodel.Package{{}}, + }, + } + case baseImage.DigestStr(): + return map[string]cvemodel.CVE{ + "CVE1": { + ID: "CVE1", + Severity: "HIGH", + Title: "Title CVE1", + Description: "Description CVE1", + PackageList: []cvemodel.Package{{}}, + }, + "CVE2": { + ID: "CVE2", + Severity: "MEDIUM", + Title: "Title CVE2", + Description: "Description CVE2", + PackageList: []cvemodel.Package{{}}, + }, + } + case otherImage.DigestStr(): + return map[string]cvemodel.CVE{ + "CVE1": { + ID: "CVE1", + Severity: "HIGH", + Title: "Title CVE1", + Description: "Description CVE1", + PackageList: []cvemodel.Package{{}}, + }, + } + } + + // By default the image has no vulnerabilities + return map[string]cvemodel.CVE{} + } + + // MetaDB loaded with initial data, now mock the scanner + // Setup test CVE data in mock scanner + scanner := mocks.CveScannerMock{ + ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) { + repo, ref, _, _ := common.GetRepoReference(image) + + if common.IsDigest(ref) { + return getCveResults(ref), nil + } + + repoMeta, _ := metaDB.GetRepoMeta(ctx, repo) + + if _, ok := repoMeta.Tags[ref]; !ok { + panic("unexpected tag '" + ref + "', test might be wrong") + } + + return getCveResults(repoMeta.Tags[ref].Digest), nil + }, + GetCachedResultFn: func(digestStr string) map[string]cvemodel.CVE { + return getCveResults(digestStr) + }, + IsResultCachedFn: func(digestStr string) bool { + return true + }, + } + + cveInfo := &cveinfo.BaseCveInfo{ + Log: log, + Scanner: scanner, + MetaDB: metaDB, + } + + ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, + ociutils.Repo{ + Name: "repo", + Images: []ociutils.RepoImage{ + {Image: otherImage, Reference: "other-image"}, + {Image: baseImage, Reference: "base-image"}, + {Image: image, Reference: "image"}, + }, + }, + ociutils.Repo{ + Name: "repo-multi", + MultiArchImages: []ociutils.RepoMultiArchImage{ + {MultiarchImage: CreateRandomMultiarch(), Reference: "multi-rand"}, + {MultiarchImage: multiArchBase, Reference: "multi-base"}, + {MultiarchImage: multiArchImage, Reference: "multi-img"}, + }, + }, + ) + So(err, ShouldBeNil) + + minuend := gql_generated.ImageInput{Repo: "repo", Tag: "image"} + subtrahend := gql_generated.ImageInput{Repo: "repo", Tag: "image"} + diffResult, err := getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) + So(err, ShouldBeNil) + So(len(diffResult.CVEList), ShouldEqual, 0) + + minuend = gql_generated.ImageInput{Repo: "repo", Tag: "image"} + subtrahend = gql_generated.ImageInput{} + diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) + So(err, ShouldBeNil) + So(len(diffResult.CVEList), ShouldEqual, 1) + + minuend = gql_generated.ImageInput{Repo: "repo", Tag: "base-image"} + subtrahend = gql_generated.ImageInput{Repo: "repo", Tag: "image"} + diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) + So(err, ShouldBeNil) + So(len(diffResult.CVEList), ShouldEqual, 0) + + minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{}} + subtrahend = gql_generated.ImageInput{} + _, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) + So(err, ShouldNotBeNil) + + minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{ + Os: ref("testOs"), + Arch: ref("testArch"), + }} + subtrahend = gql_generated.ImageInput{} + diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) + So(err, ShouldBeNil) + So(len(diffResult.CVEList), ShouldEqual, 1) + So(diffResult.Subtrahend.Repo, ShouldEqual, "repo-multi") + So(diffResult.Subtrahend.Tag, ShouldEqual, "multi-base") + So(dderef(diffResult.Subtrahend.Platform.Os), ShouldResemble, "testOs") + So(dderef(diffResult.Subtrahend.Platform.Arch), ShouldResemble, "testArch") + + minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{ + Os: ref("testOs"), + Arch: ref("testArch"), + }} + subtrahend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-base", Platform: &gql_generated.PlatformInput{ + Os: ref("testOs"), + Arch: ref("testArch"), + }} + diffResult, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) + So(err, ShouldBeNil) + So(len(diffResult.CVEList), ShouldEqual, 1) + + minuend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-img", Platform: &gql_generated.PlatformInput{ + Os: ref("testOs"), + Arch: ref("testArch"), + }} + subtrahend = gql_generated.ImageInput{Repo: "repo-multi", Tag: "multi-base", Platform: &gql_generated.PlatformInput{}} + _, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, pageInput, "", "", log) + So(err, ShouldNotBeNil) + }) + + Convey("CVE Diff Errors", t, func() { + ctx := context.Background() + metaDB := mocks.MetaDBMock{} + cveInfo := mocks.CveInfoMock{} + emptyImage := gql_generated.ImageInput{} + + Convey("minuend is empty", func() { + _, err := getCVEDiffListForImages(ctx, emptyImage, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log) + So(err, ShouldNotBeNil) + }) + + Convey("getImageSummary for subtrahend errors", func() { + metaDB.GetRepoMetaFn = func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { + return mTypes.RepoMeta{}, ErrTestError + } + minuend := gql_generated.ImageInput{Repo: "test", Tag: "img"} + _, err := getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log) + So(err, ShouldNotBeNil) + + metaDB.GetRepoMetaFn = func(ctx context.Context, repo string) (mTypes.RepoMeta, error) { + return mTypes.RepoMeta{}, zerr.ErrRepoMetaNotFound + } + minuend = gql_generated.ImageInput{Repo: "test", Tag: "img"} + _, err = getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log) + So(err, ShouldNotBeNil) + }) + + Convey("FilterTags for subtrahend errors", func() { + metaDB.FilterTagsFn = func(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc, filterFunc mTypes.FilterFunc, + ) ([]mTypes.FullImageMeta, error) { + return nil, ErrTestError + } + minuend := gql_generated.ImageInput{Repo: "test", Tag: "img"} + _, err = getCVEDiffListForImages(ctx, minuend, emptyImage, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log) + So(err, ShouldNotBeNil) + }) + + Convey("GetCVEDiffListForImages errors", func() { + cveInfo.GetCVEDiffListForImagesFn = func(ctx context.Context, minuend, subtrahend, searchedCVE, excluded string, + pageInput cvemodel.PageInput, + ) ([]cvemodel.CVE, cvemodel.ImageCVESummary, common.PageInfo, error) { + return nil, cvemodel.ImageCVESummary{}, common.PageInfo{}, ErrTestError + } + minuend := gql_generated.ImageInput{Repo: "test", Tag: "img"} + subtrahend := gql_generated.ImageInput{Repo: "sub", Tag: "img"} + _, err = getCVEDiffListForImages(ctx, minuend, subtrahend, metaDB, cveInfo, getGQLPageInput(0, 0), "", "", log) + So(err, ShouldNotBeNil) + }) + }) } func TestMockedDerivedImageList(t *testing.T) { @@ -2319,12 +2624,6 @@ func TestExpandedRepoInfoErrors(t *testing.T) { }) } -func ref[T any](input T) *T { - ref := input - - return &ref -} - func getGQLPageInput(limit int, offset int) *gql_generated.PageInput { sortCriteria := gql_generated.SortCriteriaAlphabeticAsc diff --git a/pkg/extensions/search/schema.graphql b/pkg/extensions/search/schema.graphql index 45ca42913..ecfc6ff80 100644 --- a/pkg/extensions/search/schema.graphql +++ b/pkg/extensions/search/schema.graphql @@ -33,6 +33,54 @@ type CVEResultForImage { Page: PageInfo } +""" +ImageIdentifier +""" +type ImageIdentifier { + """ + Repo name of the image + """ + Repo: String! + """ + The tag of the image + """ + Tag: String! + """ + The digest of the image + """ + Digest: String + """ + The platform of the image + """ + Platform: Platform +} + +""" +Contains the diff results of subtracting Subtrahend's CVEs from Minuend's CVEs +""" +type CVEDiffResult { + """ + Minuend is the image from which CVE's we subtract + """ + Minuend: ImageIdentifier! + """ + Subtrahend is the image which CVE's are subtracted + """ + Subtrahend: ImageIdentifier! + """ + List of CVE objects which are present in minuend but not in subtrahend + """ + CVEList: [CVE] + """ + Summary of the findings for this image + """ + Summary: ImageVulnerabilitySummary + """ + The CVE pagination information, see PageInfo object for more details + """ + Page: PageInfo +} + """ Contains various details about the CVE (Common Vulnerabilities and Exposures) and a list of PackageInfo about the affected packages @@ -563,6 +611,42 @@ input PageInput { sortBy: SortCriteria } +""" +PlatformInput contains the Os and the Arch of the input image +""" +input PlatformInput { + """ + The os of the image + """ + Os: String + """ + The arch of the image + """ + Arch: String +} + +""" +ImageInput +""" +input ImageInput { + """ + Repo name of the image + """ + repo: String! + """ + The tag of the image + """ + tag: String! + """ + The digest of the image + """ + digest: String + """ + The platform of the image + """ + platform: PlatformInput +} + """ Paginated list of RepoSummary objects """ @@ -639,6 +723,22 @@ type Query { excludedCVE: String ): CVEResultForImage! + """ + Returns a list with CVE's that are present in `image` but not in `comparedImage` + """ + CVEDiffListForImages( + "Image name in format `repository:tag` or `repository@digest`" + minuend: ImageInput!, + "Compared image name in format `repository:tag` or `repository@digest`" + subtrahend: ImageInput!, + "Sets the parameters of the requested page" + requestedPage: PageInput + "Search term for specific CVE by title/id" + searchedCVE: String + "Search term that must not be present in the returned results" + excludedCVE: String + ): CVEDiffResult! + """ Returns a list of images vulnerable to the CVE of the specified ID """ diff --git a/pkg/extensions/search/schema.resolvers.go b/pkg/extensions/search/schema.resolvers.go index f6abb49cf..7799a8bb7 100644 --- a/pkg/extensions/search/schema.resolvers.go +++ b/pkg/extensions/search/schema.resolvers.go @@ -23,6 +23,16 @@ func (r *queryResolver) CVEListForImage(ctx context.Context, image string, reque return getCVEListForImage(ctx, image, r.cveInfo, requestedPage, deref(searchedCve, ""), deref(excludedCve, ""), r.log) } +// CVEDiffListForImages is the resolver for the CVEDiffListForImages field. +func (r *queryResolver) CVEDiffListForImages(ctx context.Context, minuend gql_generated.ImageInput, subtrahend gql_generated.ImageInput, requestedPage *gql_generated.PageInput, searchedCve *string, excludedCve *string) (*gql_generated.CVEDiffResult, error) { + if r.cveInfo == nil { + return &gql_generated.CVEDiffResult{}, zerr.ErrCVESearchDisabled + } + + return getCVEDiffListForImages(ctx, minuend, subtrahend, r.metaDB, r.cveInfo, requestedPage, + dderef(searchedCve), dderef(excludedCve), r.log) +} + // ImageListForCve is the resolver for the ImageListForCVE field. func (r *queryResolver) ImageListForCve(ctx context.Context, id string, filter *gql_generated.Filter, requestedPage *gql_generated.PageInput) (*gql_generated.PaginatedImagesResult, error) { if r.cveInfo == nil { diff --git a/pkg/meta/types/types.go b/pkg/meta/types/types.go index 812190f33..ffcfb2c85 100644 --- a/pkg/meta/types/types.go +++ b/pkg/meta/types/types.go @@ -38,6 +38,10 @@ func AcceptAllRepoTag(repo, tag string) bool { return true } +func AcceptOnlyRepo(repo string) func(repo, tag string) bool { + return func(r, t string) bool { return repo == r } +} + func AcceptAllImageMeta(repoMeta RepoMeta, imageMeta ImageMeta) bool { return true } diff --git a/pkg/test/mocks/cve_mock.go b/pkg/test/mocks/cve_mock.go index 944fe7ff7..8a7d201c8 100644 --- a/pkg/test/mocks/cve_mock.go +++ b/pkg/test/mocks/cve_mock.go @@ -8,12 +8,29 @@ import ( ) type CveInfoMock struct { - GetImageListForCVEFn func(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error) + GetImageListForCVEFn func(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error) + GetImageListWithCVEFixedFn func(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error) - GetCVEListForImageFn func(ctx context.Context, repo, reference, searchedCVE, excludedCVE string, + + GetCVEListForImageFn func(ctx context.Context, repo, reference, searchedCVE, excludedCVE string, pageInput cvemodel.PageInput) ([]cvemodel.CVE, cvemodel.ImageCVESummary, common.PageInfo, error) + GetCVESummaryForImageMediaFn func(ctx context.Context, repo string, digest, mediaType string, ) (cvemodel.ImageCVESummary, error) + + GetCVEDiffListForImagesFn func(ctx context.Context, minuend, subtrahend, searchedCVE string, + excludedCVE string, pageInput cvemodel.PageInput, + ) ([]cvemodel.CVE, cvemodel.ImageCVESummary, common.PageInfo, error) +} + +func (cveInfo CveInfoMock) GetCVEDiffListForImages(ctx context.Context, minuend, subtrahend, searchedCVE string, + excludedCVE string, pageInput cvemodel.PageInput, +) ([]cvemodel.CVE, cvemodel.ImageCVESummary, common.PageInfo, error) { + if cveInfo.GetCVEDiffListForImagesFn != nil { + return cveInfo.GetCVEDiffListForImagesFn(ctx, minuend, subtrahend, searchedCVE, excludedCVE, pageInput) + } + + return []cvemodel.CVE{}, cvemodel.ImageCVESummary{}, common.PageInfo{}, nil } func (cveInfo CveInfoMock) GetImageListForCVE(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error) {