From fce9a02ed54b9fdfd923e069c3a2f7f9dcb467e8 Mon Sep 17 00:00:00 2001 From: LaurentiuNiculae Date: Mon, 7 Aug 2023 10:25:05 +0300 Subject: [PATCH] fix(convert): now returned annotations for an index will fallback to annotations from a random manifest if the annotations field is not present on the index manifest (#1667) Signed-off-by: Laurentiu Niculae --- pkg/cli/cve_cmd.go | 2 +- pkg/extensions/search/convert/annotations.go | 55 +++++ pkg/extensions/search/convert/convert_test.go | 210 ++++++++++++++++++ pkg/extensions/search/convert/metadb.go | 29 ++- 4 files changed, 293 insertions(+), 3 deletions(-) diff --git a/pkg/cli/cve_cmd.go b/pkg/cli/cve_cmd.go index 68a4ec2f..875eebc1 100644 --- a/pkg/cli/cve_cmd.go +++ b/pkg/cli/cve_cmd.go @@ -154,7 +154,7 @@ func searchCve(searchConfig searchConfig) error { } for _, searcher := range searchers { - // there can be CVE DB readyness issues on the server side + // there can be CVE DB readiness issues on the server side // we need a retry mechanism for that specific type of errors maxAttempts := 20 diff --git a/pkg/extensions/search/convert/annotations.go b/pkg/extensions/search/convert/annotations.go index ad530fa4..99c83c8d 100644 --- a/pkg/extensions/search/convert/annotations.go +++ b/pkg/extensions/search/convert/annotations.go @@ -133,3 +133,58 @@ func GetAnnotations(annotations, labels map[string]string) ImageAnnotations { Authors: authors, } } + +func GetIndexAnnotations(indexAnnotations, manifestAnnotations, manifestLabels map[string]string) ImageAnnotations { + annotationsFromManifest := GetAnnotations(manifestAnnotations, manifestLabels) + + description := GetDescription(indexAnnotations) + if description == "" { + description = annotationsFromManifest.Description + } + + title := GetTitle(indexAnnotations) + if title == "" { + title = annotationsFromManifest.Title + } + + documentation := GetDocumentation(indexAnnotations) + if documentation == "" { + documentation = annotationsFromManifest.Documentation + } + + source := GetSource(indexAnnotations) + if source == "" { + source = annotationsFromManifest.Source + } + + licenses := GetLicenses(indexAnnotations) + if licenses == "" { + licenses = annotationsFromManifest.Licenses + } + + categories := GetCategories(indexAnnotations) + if categories == "" { + categories = annotationsFromManifest.Labels + } + + vendor := GetVendor(indexAnnotations) + if vendor == "" { + vendor = annotationsFromManifest.Vendor + } + + authors := GetAuthors(indexAnnotations) + if authors == "" { + authors = annotationsFromManifest.Authors + } + + return ImageAnnotations{ + Description: description, + Title: title, + Documentation: documentation, + Source: source, + Licenses: licenses, + Labels: categories, + Vendor: vendor, + Authors: authors, + } +} diff --git a/pkg/extensions/search/convert/convert_test.go b/pkg/extensions/search/convert/convert_test.go index 39a75d77..8532663a 100644 --- a/pkg/extensions/search/convert/convert_test.go +++ b/pkg/extensions/search/convert/convert_test.go @@ -770,3 +770,213 @@ func TestPaginatedConvert(t *testing.T) { So(pageInfo.ItemCount, ShouldEqual, 1) }) } + +func TestGetOneManifestAnnotations(t *testing.T) { + Convey("GetOneManifestAnnotations errors", t, func() { + manifestAnnotations, configLabels := convert.GetOneManifestAnnotations( + ispec.Index{Manifests: []ispec.Descriptor{ + {Digest: "bad-manifest"}, {Digest: "dig2"}, + }}, + map[string]mTypes.ManifestMetadata{ + "bad-manifest": { + ManifestBlob: []byte(`bad`), + ConfigBlob: []byte("{}"), + }, + }, + ) + So(manifestAnnotations, ShouldBeEmpty) + So(configLabels, ShouldBeEmpty) + + manifestAnnotations, configLabels = convert.GetOneManifestAnnotations( + ispec.Index{Manifests: []ispec.Descriptor{ + {Digest: "bad-config"}, + }}, + map[string]mTypes.ManifestMetadata{ + "bad-config": { + ManifestBlob: []byte("{}"), + ConfigBlob: []byte("bad"), + }, + }, + ) + So(manifestAnnotations, ShouldBeEmpty) + So(configLabels, ShouldBeEmpty) + }) + + Convey("Test ImageIndex2ImageSummary annotations logic", t, func() { + ctx := context.Background() + + configLabels := map[string]string{ + ispec.AnnotationDescription: "ConfigDescription", + ispec.AnnotationLicenses: "ConfigLicenses", + ispec.AnnotationVendor: "ConfigVendor", + ispec.AnnotationAuthors: "ConfigAuthors", + ispec.AnnotationTitle: "ConfigTitle", + ispec.AnnotationDocumentation: "ConfigDocumentation", + ispec.AnnotationSource: "ConfigSource", + } + + manifestAnnotations := map[string]string{ + ispec.AnnotationDescription: "ManifestDescription", + ispec.AnnotationLicenses: "ManifestLicenses", + ispec.AnnotationVendor: "ManifestVendor", + ispec.AnnotationAuthors: "ManifestAuthors", + ispec.AnnotationTitle: "ManifestTitle", + ispec.AnnotationDocumentation: "ManifestDocumentation", + ispec.AnnotationSource: "ManifestSource", + } + + indexAnnotations := map[string]string{ + ispec.AnnotationDescription: "IndexDescription", + ispec.AnnotationLicenses: "IndexLicenses", + ispec.AnnotationVendor: "IndexVendor", + ispec.AnnotationAuthors: "IndexAuthors", + ispec.AnnotationTitle: "IndexTitle", + ispec.AnnotationDocumentation: "IndexDocumentation", + ispec.AnnotationSource: "IndexSource", + } + + imageWithConfigAnnotations := test.CreateImageWith().DefaultLayers(). + ImageConfig(ispec.Image{ + Config: ispec.ImageConfig{ + Labels: configLabels, + }, + }).Build() + + imageWithManifestAndConfigAnnotations := test.CreateImageWith().DefaultLayers(). + ImageConfig(ispec.Image{ + Config: ispec.ImageConfig{ + Labels: configLabels, + }, + }).Annotations(manifestAnnotations).Build() + + // -------------------------------------------------------- + indexWithAnnotations := test.CreateMultiarchWith().Images( + []test.Image{imageWithManifestAndConfigAnnotations}, + ).Annotations(indexAnnotations).Build() + + repoMeta, manifestMetadata, indexData := test.GetMetadataForRepos(test.Repo{ + Name: "repo", + MultiArchImages: []test.RepoMultiArchImage{ + {MultiarchImage: indexWithAnnotations, Tag: "tag"}, + }, + }) + + digest := indexWithAnnotations.Digest() + + imageSummary, _, err := convert.ImageIndex2ImageSummary(ctx, "repo", "tag", digest, true, repoMeta[0], + indexData[digest.String()], manifestMetadata, nil) + So(err, ShouldBeNil) + So(*imageSummary.Description, ShouldResemble, "IndexDescription") + So(*imageSummary.Licenses, ShouldResemble, "IndexLicenses") + So(*imageSummary.Title, ShouldResemble, "IndexTitle") + So(*imageSummary.Source, ShouldResemble, "IndexSource") + So(*imageSummary.Documentation, ShouldResemble, "IndexDocumentation") + So(*imageSummary.Vendor, ShouldResemble, "IndexVendor") + So(*imageSummary.Authors, ShouldResemble, "IndexAuthors") + + // -------------------------------------------------------- + indexWithManifestAndConfigAnnotations := test.CreateMultiarchWith().Images( + []test.Image{imageWithManifestAndConfigAnnotations, test.CreateRandomImage(), test.CreateRandomImage()}, + ).Build() + + repoMeta, manifestMetadata, indexData = test.GetMetadataForRepos(test.Repo{ + Name: "repo", + MultiArchImages: []test.RepoMultiArchImage{{MultiarchImage: indexWithManifestAndConfigAnnotations}}, + }) + digest = indexWithManifestAndConfigAnnotations.Digest() + + imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, "repo", "tag", digest, + true, repoMeta[0], indexData[digest.String()], manifestMetadata, nil) + So(err, ShouldBeNil) + So(*imageSummary.Description, ShouldResemble, "ManifestDescription") + So(*imageSummary.Licenses, ShouldResemble, "ManifestLicenses") + So(*imageSummary.Title, ShouldResemble, "ManifestTitle") + So(*imageSummary.Source, ShouldResemble, "ManifestSource") + So(*imageSummary.Documentation, ShouldResemble, "ManifestDocumentation") + So(*imageSummary.Vendor, ShouldResemble, "ManifestVendor") + So(*imageSummary.Authors, ShouldResemble, "ManifestAuthors") + // -------------------------------------------------------- + indexWithConfigAnnotations := test.CreateMultiarchWith().Images( + []test.Image{imageWithConfigAnnotations, test.CreateRandomImage(), test.CreateRandomImage()}, + ).Build() + + repoMeta, manifestMetadata, indexData = test.GetMetadataForRepos(test.Repo{ + Name: "repo", + MultiArchImages: []test.RepoMultiArchImage{{MultiarchImage: indexWithConfigAnnotations, Tag: "tag"}}, + }) + digest = indexWithConfigAnnotations.Digest() + + imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, "repo", "tag", digest, + true, repoMeta[0], indexData[digest.String()], manifestMetadata, nil) + So(err, ShouldBeNil) + So(*imageSummary.Description, ShouldResemble, "ConfigDescription") + So(*imageSummary.Licenses, ShouldResemble, "ConfigLicenses") + So(*imageSummary.Title, ShouldResemble, "ConfigTitle") + So(*imageSummary.Source, ShouldResemble, "ConfigSource") + So(*imageSummary.Documentation, ShouldResemble, "ConfigDocumentation") + So(*imageSummary.Vendor, ShouldResemble, "ConfigVendor") + So(*imageSummary.Authors, ShouldResemble, "ConfigAuthors") + //-------------------------------------------------------- + + indexWithMixAnnotations := test.CreateMultiarchWith().Images( + []test.Image{ + test.CreateImageWith().DefaultLayers().ImageConfig(ispec.Image{ + Config: ispec.ImageConfig{ + Labels: map[string]string{ + ispec.AnnotationDescription: "ConfigDescription", + ispec.AnnotationLicenses: "ConfigLicenses", + }, + }, + }).Annotations(map[string]string{ + ispec.AnnotationVendor: "ManifestVendor", + ispec.AnnotationAuthors: "ManifestAuthors", + }).Build(), + test.CreateRandomImage(), + test.CreateRandomImage(), + }, + ).Annotations( + map[string]string{ + ispec.AnnotationTitle: "IndexTitle", + ispec.AnnotationDocumentation: "IndexDocumentation", + ispec.AnnotationSource: "IndexSource", + }, + ).Build() + + repoMeta, manifestMetadata, indexData = test.GetMetadataForRepos(test.Repo{ + Name: "repo", + MultiArchImages: []test.RepoMultiArchImage{{MultiarchImage: indexWithMixAnnotations, Tag: "tag"}}, + }) + digest = indexWithMixAnnotations.Digest() + + imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, "repo", "tag", digest, + true, repoMeta[0], indexData[digest.String()], manifestMetadata, nil) + So(err, ShouldBeNil) + So(*imageSummary.Description, ShouldResemble, "ConfigDescription") + So(*imageSummary.Licenses, ShouldResemble, "ConfigLicenses") + So(*imageSummary.Vendor, ShouldResemble, "ManifestVendor") + So(*imageSummary.Authors, ShouldResemble, "ManifestAuthors") + So(*imageSummary.Title, ShouldResemble, "IndexTitle") + So(*imageSummary.Documentation, ShouldResemble, "IndexDocumentation") + So(*imageSummary.Source, ShouldResemble, "IndexSource") + + //-------------------------------------------------------- + indexWithNoAnnotations := test.CreateRandomMultiarch() + + repoMeta, manifestMetadata, indexData = test.GetMetadataForRepos(test.Repo{ + Name: "repo", + MultiArchImages: []test.RepoMultiArchImage{{MultiarchImage: indexWithNoAnnotations, Tag: "tag"}}, + }) + digest = indexWithNoAnnotations.Digest() + + imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, "repo", "tag", digest, + true, repoMeta[0], indexData[digest.String()], manifestMetadata, nil) + So(err, ShouldBeNil) + So(*imageSummary.Description, ShouldBeBlank) + So(*imageSummary.Licenses, ShouldBeBlank) + So(*imageSummary.Vendor, ShouldBeBlank) + So(*imageSummary.Authors, ShouldBeBlank) + So(*imageSummary.Title, ShouldBeBlank) + So(*imageSummary.Documentation, ShouldBeBlank) + So(*imageSummary.Source, ShouldBeBlank) + }) +} diff --git a/pkg/extensions/search/convert/metadb.go b/pkg/extensions/search/convert/metadb.go index 4b607a79..4b126c2d 100644 --- a/pkg/extensions/search/convert/metadb.go +++ b/pkg/extensions/search/convert/metadb.go @@ -268,10 +268,11 @@ func ImageIndex2ImageSummary(ctx context.Context, repo, tag string, indexDigest indexSize = strconv.FormatInt(totalIndexSize, 10) - annotations := GetAnnotations(indexContent.Annotations, map[string]string{}) - signaturesInfo := GetSignaturesInfo(isSigned, repoMeta, indexDigest) + manifestAnnotations, configLabels := GetOneManifestAnnotations(indexContent, manifestMetaMap) + annotations := GetIndexAnnotations(indexContent.Annotations, manifestAnnotations, configLabels) + indexSummary := gql_generated.ImageSummary{ RepoName: &repo, Tag: &tag, @@ -301,6 +302,30 @@ func ImageIndex2ImageSummary(ctx context.Context, repo, tag string, indexDigest return &indexSummary, indexBlobs, nil } +func GetOneManifestAnnotations(indexContent ispec.Index, manifestMetaMap map[string]mTypes.ManifestMetadata, +) (map[string]string, map[string]string) { + if len(manifestMetaMap) == 0 || len(indexContent.Manifests) == 0 { + return map[string]string{}, map[string]string{} + } + + manifestMeta := manifestMetaMap[indexContent.Manifests[0].Digest.String()] + + manifestContent := ispec.Manifest{} + configContent := ispec.Image{} + + err := json.Unmarshal(manifestMeta.ManifestBlob, &manifestContent) + if err != nil { + return map[string]string{}, map[string]string{} + } + + err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent) + if err != nil { + return map[string]string{}, map[string]string{} + } + + return manifestContent.Annotations, configContent.Config.Labels +} + func ImageManifest2ImageSummary(ctx context.Context, repo, tag string, digest godigest.Digest, skipCVE bool, repoMeta mTypes.RepoMetadata, manifestMeta mTypes.ManifestMetadata, cveInfo cveinfo.CveInfo, ) (*gql_generated.ImageSummary, map[string]int64, error) {