//go:build search package convert_test import ( "context" "errors" "testing" "time" godigest "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" . "github.com/smartystreets/goconvey/convey" "zotregistry.dev/zot/pkg/extensions/search/convert" "zotregistry.dev/zot/pkg/extensions/search/gql_generated" "zotregistry.dev/zot/pkg/extensions/search/pagination" "zotregistry.dev/zot/pkg/log" "zotregistry.dev/zot/pkg/meta/boltdb" mTypes "zotregistry.dev/zot/pkg/meta/types" reqCtx "zotregistry.dev/zot/pkg/requestcontext" . "zotregistry.dev/zot/pkg/test/image-utils" "zotregistry.dev/zot/pkg/test/mocks" ociutils "zotregistry.dev/zot/pkg/test/oci-utils" ) var ErrTestError = errors.New("TestError") func TestUpdateLastUpdatedTimestamp(t *testing.T) { Convey("Image summary is the first image checked for the repo", t, func() { before := time.Time{} after := time.Date(2023, time.April, 1, 11, 0, 0, 0, time.UTC) img := convert.UpdateLastUpdatedTimestamp( &before, &gql_generated.ImageSummary{LastUpdated: &before}, &gql_generated.ImageSummary{LastUpdated: &after}, ) So(*img.LastUpdated, ShouldResemble, after) }) Convey("Image summary is updated after the current latest image", t, func() { before := time.Date(2022, time.April, 1, 11, 0, 0, 0, time.UTC) after := time.Date(2023, time.April, 1, 11, 0, 0, 0, time.UTC) img := convert.UpdateLastUpdatedTimestamp( &before, &gql_generated.ImageSummary{LastUpdated: &before}, &gql_generated.ImageSummary{LastUpdated: &after}, ) So(*img.LastUpdated, ShouldResemble, after) }) Convey("Image summary is updated before the current latest image", t, func() { before := time.Date(2022, time.April, 1, 11, 0, 0, 0, time.UTC) after := time.Date(2023, time.April, 1, 11, 0, 0, 0, time.UTC) img := convert.UpdateLastUpdatedTimestamp( &after, &gql_generated.ImageSummary{LastUpdated: &after}, &gql_generated.ImageSummary{LastUpdated: &before}, ) So(*img.LastUpdated, ShouldResemble, after) }) } func TestLabels(t *testing.T) { Convey("Test labels", t, func() { // Test various labels labels := make(map[string]string) desc := convert.GetDescription(labels) So(desc, ShouldEqual, "") license := convert.GetLicenses(labels) So(license, ShouldEqual, "") vendor := convert.GetVendor(labels) So(vendor, ShouldEqual, "") categories := convert.GetCategories(labels) So(categories, ShouldEqual, "") labels[ispec.AnnotationVendor] = "zot" labels[ispec.AnnotationDescription] = "zot-desc" labels[ispec.AnnotationLicenses] = "zot-license" labels[convert.AnnotationLabels] = "zot-labels" desc = convert.GetDescription(labels) So(desc, ShouldEqual, "zot-desc") license = convert.GetLicenses(labels) So(license, ShouldEqual, "zot-license") vendor = convert.GetVendor(labels) So(vendor, ShouldEqual, "zot") categories = convert.GetCategories(labels) So(categories, ShouldEqual, "zot-labels") labels = make(map[string]string) // Use diff key labels[convert.LabelAnnotationVendor] = "zot-vendor" labels[convert.LabelAnnotationDescription] = "zot-label-desc" labels[ispec.AnnotationLicenses] = "zot-label-license" desc = convert.GetDescription(labels) So(desc, ShouldEqual, "zot-label-desc") license = convert.GetLicenses(labels) So(license, ShouldEqual, "zot-label-license") vendor = convert.GetVendor(labels) So(vendor, ShouldEqual, "zot-vendor") }) } func TestGetSignaturesInfo(t *testing.T) { Convey("Test get signatures info - cosign", t, func() { digest := godigest.FromString("dig") signatures := map[string]mTypes.ManifestSignatures{ digest.String(): { "cosign": []mTypes.SignatureInfo{ { LayersInfo: []mTypes.LayerInfo{ { LayerContent: []byte{}, LayerDigest: "", SignatureKey: "", Signer: "author", }, }, }, }, }, } signaturesSummary := convert.GetSignaturesInfo(true, signatures[digest.String()]) So(signaturesSummary, ShouldNotBeEmpty) So(*signaturesSummary[0].Author, ShouldEqual, "author") So(*signaturesSummary[0].IsTrusted, ShouldEqual, true) So(*signaturesSummary[0].Tool, ShouldEqual, "cosign") }) Convey("Test get signatures info - notation", t, func() { digest := godigest.FromString("dig") signatures := map[string]mTypes.ManifestSignatures{ digest.String(): { "notation": []mTypes.SignatureInfo{ { LayersInfo: []mTypes.LayerInfo{ { LayerContent: []byte{}, LayerDigest: "", SignatureKey: "", Signer: "author", Date: time.Now().AddDate(0, 0, -1), }, }, }, }, }, } signaturesSummary := convert.GetSignaturesInfo(true, signatures[digest.String()]) So(signaturesSummary, ShouldNotBeEmpty) So(*signaturesSummary[0].Author, ShouldEqual, "author") So(*signaturesSummary[0].IsTrusted, ShouldEqual, false) So(*signaturesSummary[0].Tool, ShouldEqual, "notation") }) } func TestAcceptedByFilter(t *testing.T) { Convey("Images", t, func() { Convey("Os not found", func() { found := convert.ImgSumAcceptedByFilter( &gql_generated.ImageSummary{ Manifests: []*gql_generated.ManifestSummary{ {Platform: &gql_generated.Platform{Os: ref("os1")}}, {Platform: &gql_generated.Platform{Os: ref("os2")}}, }, }, mTypes.Filter{Os: []*string{ref("os3")}}, ) So(found, ShouldBeFalse) }) Convey("Has to be signed ", func() { found := convert.ImgSumAcceptedByFilter( &gql_generated.ImageSummary{ Manifests: []*gql_generated.ManifestSummary{ {IsSigned: ref(false)}, }, IsSigned: ref(false), }, mTypes.Filter{HasToBeSigned: ref(true)}, ) So(found, ShouldBeFalse) }) }) Convey("Repos", t, func() { Convey("Os not found", func() { found := convert.RepoSumAcceptedByFilter( &gql_generated.RepoSummary{ Platforms: []*gql_generated.Platform{ {Os: ref("os1")}, {Os: ref("os2")}, }, }, mTypes.Filter{Os: []*string{ref("os3")}}, ) So(found, ShouldBeFalse) }) Convey("Arch not found", func() { found := convert.RepoSumAcceptedByFilter( &gql_generated.RepoSummary{ Platforms: []*gql_generated.Platform{ {Arch: ref("Arch")}, }, }, mTypes.Filter{Arch: []*string{ref("arch_not_found")}}, ) So(found, ShouldBeFalse) }) Convey("Has to be signed ", func() { found := convert.ImgSumAcceptedByFilter( &gql_generated.ImageSummary{ Manifests: []*gql_generated.ManifestSummary{ {IsSigned: ref(false)}, }, IsSigned: ref(false), }, mTypes.Filter{HasToBeSigned: ref(true)}, ) So(found, ShouldBeFalse) }) }) } func ref[T any](val T) *T { ref := val return &ref } func TestPaginatedConvert(t *testing.T) { ctx := context.Background() tempDir := t.TempDir() driver, err := boltdb.GetBoltDriver(boltdb.DBParameters{RootDir: tempDir}) if err != nil { t.FailNow() } metaDB, err := boltdb.New(driver, log.NewLogger("debug", "")) if err != nil { t.FailNow() } var ( badBothImage = CreateImageWith().DefaultLayers().ImageConfig( ispec.Image{Platform: ispec.Platform{OS: "bad-os", Architecture: "bad-arch"}}).Build() badOsImage = CreateImageWith().DefaultLayers().ImageConfig( ispec.Image{Platform: ispec.Platform{OS: "bad-os", Architecture: "good-arch"}}).Build() badArchImage = CreateImageWith().DefaultLayers().ImageConfig( ispec.Image{Platform: ispec.Platform{OS: "good-os", Architecture: "bad-arch"}}).Build() goodImage = CreateImageWith().DefaultLayers().ImageConfig( ispec.Image{Platform: ispec.Platform{OS: "good-os", Architecture: "good-arch"}}).Build() randomImage1 = CreateRandomImage() randomImage2 = CreateRandomImage() signatureDigest = godigest.FromString("signature") badMultiArch = CreateMultiarchWith().Images( []Image{badBothImage, badOsImage, badArchImage, randomImage1}).Build() goodMultiArch = CreateMultiarchWith().Images( []Image{badOsImage, badArchImage, randomImage2, goodImage}).Build() ) ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, ociutils.Repo{ Name: "repo1-only-images", Images: []ociutils.RepoImage{ {Image: goodImage, Reference: "goodImage"}, {Image: badOsImage, Reference: "badOsImage"}, {Image: badArchImage, Reference: "badArchImage"}, {Image: badBothImage, Reference: "badBothImage"}, }, IsBookmarked: true, IsStarred: true, }, ociutils.Repo{ Name: "repo2-only-bad-images", Images: []ociutils.RepoImage{ {Image: randomImage1, Reference: "randomImage1"}, {Image: randomImage2, Reference: "randomImage2"}, {Image: badBothImage, Reference: "badBothImage"}, }, IsBookmarked: true, IsStarred: true, }, ociutils.Repo{ Name: "repo3-only-multiarch", MultiArchImages: []ociutils.RepoMultiArchImage{ {MultiarchImage: badMultiArch, Reference: "badMultiArch"}, {MultiarchImage: goodMultiArch, Reference: "goodMultiArch"}, }, IsBookmarked: true, IsStarred: true, }, ociutils.Repo{ Name: "repo4-not-bookmarked-or-starred", Images: []ociutils.RepoImage{ {Image: goodImage, Reference: "goodImage"}, }, MultiArchImages: []ociutils.RepoMultiArchImage{ {MultiarchImage: goodMultiArch, Reference: "goodMultiArch"}, }, }, ociutils.Repo{ Name: "repo5-signed", Images: []ociutils.RepoImage{ {Image: goodImage, Reference: "goodImage"}, // is fake signed by the image below }, Signatures: map[string]mTypes.ManifestSignatures{ goodImage.DigestStr(): ociutils.GetFakeSignatureInfo(signatureDigest.String()), }, }, ) if err != nil { t.FailNow() } skipCVE := convert.SkipQGLField{Vulnerabilities: true} Convey("PaginatedRepoMeta2RepoSummaries filtering and sorting", t, func() { // Test different combinations of the filter repoMetaList, err := metaDB.FilterRepos(ctx, mTypes.AcceptAllRepoNames, mTypes.AcceptAllRepoMeta) So(err, ShouldBeNil) imageMeta, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList)) So(err, ShouldBeNil) reposSum, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries( ctx, repoMetaList, imageMeta, mTypes.Filter{ Os: []*string{ref("good-os")}, Arch: []*string{ref("good-arch")}, IsBookmarked: ref(true), IsStarred: ref(true), }, pagination.PageInput{SortBy: pagination.AlphabeticAsc}, mocks.CveInfoMock{}, skipCVE, ) So(err, ShouldBeNil) So(len(reposSum), ShouldEqual, 2) So(*reposSum[0].Name, ShouldResemble, "repo1-only-images") So(*reposSum[1].Name, ShouldResemble, "repo3-only-multiarch") So(pageInfo.ItemCount, ShouldEqual, 2) So(pageInfo.ItemCount, ShouldEqual, 2) So(pageInfo.ItemCount, ShouldEqual, 2) So(pageInfo.ItemCount, ShouldEqual, 2) reposSum, pageInfo, err = convert.PaginatedRepoMeta2RepoSummaries( ctx, repoMetaList, imageMeta, mTypes.Filter{ Os: []*string{ref("good-os")}, Arch: []*string{ref("good-arch")}, IsBookmarked: ref(true), IsStarred: ref(true), HasToBeSigned: ref(true), }, pagination.PageInput{SortBy: pagination.AlphabeticAsc}, mocks.CveInfoMock{}, skipCVE, ) So(err, ShouldBeNil) So(len(reposSum), ShouldEqual, 0) So(pageInfo.ItemCount, ShouldEqual, 0) reposSum, pageInfo, err = convert.PaginatedRepoMeta2RepoSummaries( ctx, repoMetaList, imageMeta, mTypes.Filter{ HasToBeSigned: ref(true), }, pagination.PageInput{SortBy: pagination.AlphabeticAsc}, mocks.CveInfoMock{}, skipCVE, ) So(err, ShouldBeNil) So(len(reposSum), ShouldEqual, 1) So(*reposSum[0].Name, ShouldResemble, "repo5-signed") So(pageInfo.ItemCount, ShouldEqual, 1) // no filter reposSum, pageInfo, err = convert.PaginatedRepoMeta2RepoSummaries( ctx, repoMetaList, imageMeta, mTypes.Filter{}, pagination.PageInput{SortBy: pagination.AlphabeticAsc}, mocks.CveInfoMock{}, skipCVE, ) So(err, ShouldBeNil) So(len(reposSum), ShouldEqual, 5) So(*reposSum[0].Name, ShouldResemble, "repo1-only-images") So(*reposSum[1].Name, ShouldResemble, "repo2-only-bad-images") So(*reposSum[2].Name, ShouldResemble, "repo3-only-multiarch") So(*reposSum[3].Name, ShouldResemble, "repo4-not-bookmarked-or-starred") So(*reposSum[4].Name, ShouldResemble, "repo5-signed") So(pageInfo.ItemCount, ShouldEqual, 5) // no filter opposite sorting reposSum, pageInfo, err = convert.PaginatedRepoMeta2RepoSummaries( ctx, repoMetaList, imageMeta, mTypes.Filter{}, pagination.PageInput{SortBy: pagination.AlphabeticDsc}, mocks.CveInfoMock{}, skipCVE, ) So(err, ShouldBeNil) So(len(reposSum), ShouldEqual, 5) So(*reposSum[0].Name, ShouldResemble, "repo5-signed") So(*reposSum[1].Name, ShouldResemble, "repo4-not-bookmarked-or-starred") So(*reposSum[2].Name, ShouldResemble, "repo3-only-multiarch") So(*reposSum[3].Name, ShouldResemble, "repo2-only-bad-images") So(*reposSum[4].Name, ShouldResemble, "repo1-only-images") So(pageInfo.ItemCount, ShouldEqual, 5) // add pagination reposSum, pageInfo, err = convert.PaginatedRepoMeta2RepoSummaries( ctx, repoMetaList, imageMeta, mTypes.Filter{ Os: []*string{ref("good-os")}, Arch: []*string{ref("good-arch")}, IsBookmarked: ref(true), IsStarred: ref(true), }, pagination.PageInput{Limit: 1, Offset: 0, SortBy: pagination.AlphabeticAsc}, mocks.CveInfoMock{}, skipCVE, ) So(err, ShouldBeNil) So(len(reposSum), ShouldEqual, 1) So(*reposSum[0].Name, ShouldResemble, "repo1-only-images") So(pageInfo.ItemCount, ShouldEqual, 1) So(pageInfo.TotalCount, ShouldEqual, 2) reposSum, pageInfo, err = convert.PaginatedRepoMeta2RepoSummaries( ctx, repoMetaList, imageMeta, mTypes.Filter{ Os: []*string{ref("good-os")}, Arch: []*string{ref("good-arch")}, IsBookmarked: ref(true), IsStarred: ref(true), }, pagination.PageInput{Limit: 1, Offset: 1, SortBy: pagination.AlphabeticAsc}, mocks.CveInfoMock{}, skipCVE, ) So(err, ShouldBeNil) So(len(reposSum), ShouldEqual, 1) So(*reposSum[0].Name, ShouldResemble, "repo3-only-multiarch") So(pageInfo.ItemCount, ShouldEqual, 1) So(pageInfo.TotalCount, ShouldEqual, 2) }) Convey("PaginatedRepoMeta2ImageSummaries filtering and sorting", t, func() { fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, mTypes.AcceptAllImageMeta) So(err, ShouldBeNil) imgSum, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries( ctx, fullImageMetaList, skipCVE, mocks.CveInfoMock{}, mTypes.Filter{ Os: []*string{ref("good-os")}, Arch: []*string{ref("good-arch")}, }, pagination.PageInput{SortBy: pagination.AlphabeticAsc}, ) So(err, ShouldBeNil) So(len(imgSum), ShouldEqual, 5) So(*imgSum[0].RepoName, ShouldResemble, "repo1-only-images") So(*imgSum[0].Tag, ShouldResemble, "goodImage") So(*imgSum[1].RepoName, ShouldResemble, "repo3-only-multiarch") So(*imgSum[1].Tag, ShouldResemble, "goodMultiArch") So(*imgSum[2].RepoName, ShouldResemble, "repo4-not-bookmarked-or-starred") So(*imgSum[2].Tag, ShouldResemble, "goodImage") So(*imgSum[3].RepoName, ShouldResemble, "repo4-not-bookmarked-or-starred") So(*imgSum[3].Tag, ShouldResemble, "goodMultiArch") So(*imgSum[4].RepoName, ShouldResemble, "repo5-signed") So(*imgSum[4].Tag, ShouldResemble, "goodImage") So(pageInfo.ItemCount, ShouldEqual, 5) // add page of size 2 imgSum, pageInfo, err = convert.PaginatedFullImageMeta2ImageSummaries( ctx, fullImageMetaList, skipCVE, mocks.CveInfoMock{}, mTypes.Filter{ Os: []*string{ref("good-os")}, Arch: []*string{ref("good-arch")}, }, pagination.PageInput{Limit: 2, Offset: 0, SortBy: pagination.AlphabeticAsc}, ) So(err, ShouldBeNil) So(len(imgSum), ShouldEqual, 2) So(*imgSum[0].RepoName, ShouldResemble, "repo1-only-images") So(*imgSum[0].Tag, ShouldResemble, "goodImage") So(*imgSum[1].RepoName, ShouldResemble, "repo3-only-multiarch") So(*imgSum[1].Tag, ShouldResemble, "goodMultiArch") So(pageInfo.ItemCount, ShouldEqual, 2) So(pageInfo.TotalCount, ShouldEqual, 5) // next page imgSum, pageInfo, err = convert.PaginatedFullImageMeta2ImageSummaries( ctx, fullImageMetaList, skipCVE, mocks.CveInfoMock{}, mTypes.Filter{ Os: []*string{ref("good-os")}, Arch: []*string{ref("good-arch")}, }, pagination.PageInput{Limit: 2, Offset: 2, SortBy: pagination.AlphabeticAsc}, ) So(err, ShouldBeNil) So(len(imgSum), ShouldEqual, 2) So(*imgSum[0].RepoName, ShouldResemble, "repo4-not-bookmarked-or-starred") So(*imgSum[0].Tag, ShouldResemble, "goodImage") So(*imgSum[1].RepoName, ShouldResemble, "repo4-not-bookmarked-or-starred") So(*imgSum[1].Tag, ShouldResemble, "goodMultiArch") So(pageInfo.ItemCount, ShouldEqual, 2) So(pageInfo.TotalCount, ShouldEqual, 5) // last page imgSum, pageInfo, err = convert.PaginatedFullImageMeta2ImageSummaries( ctx, fullImageMetaList, skipCVE, mocks.CveInfoMock{}, mTypes.Filter{ Os: []*string{ref("good-os")}, Arch: []*string{ref("good-arch")}, }, pagination.PageInput{Limit: 2, Offset: 4, SortBy: pagination.AlphabeticAsc}, ) So(err, ShouldBeNil) So(len(imgSum), ShouldEqual, 1) So(*imgSum[0].RepoName, ShouldResemble, "repo5-signed") So(*imgSum[0].Tag, ShouldResemble, "goodImage") So(pageInfo.ItemCount, ShouldEqual, 1) So(pageInfo.TotalCount, ShouldEqual, 5) // has to be signed imgSum, pageInfo, err = convert.PaginatedFullImageMeta2ImageSummaries( ctx, fullImageMetaList, skipCVE, mocks.CveInfoMock{}, mTypes.Filter{ Os: []*string{ref("good-os")}, Arch: []*string{ref("good-arch")}, HasToBeSigned: ref(true), }, pagination.PageInput{SortBy: pagination.AlphabeticAsc}, ) So(err, ShouldBeNil) So(len(imgSum), ShouldEqual, 1) So(*imgSum[0].RepoName, ShouldResemble, "repo5-signed") So(*imgSum[0].Tag, ShouldResemble, "goodImage") So(pageInfo.ItemCount, ShouldEqual, 1) }) } func TestIndexAnnotations(t *testing.T) { Convey("Test ImageIndex2ImageSummary annotations logic", t, func() { ctx := context.Background() tempDir := t.TempDir() driver, err := boltdb.GetBoltDriver(boltdb.DBParameters{RootDir: tempDir}) if err != nil { t.FailNow() } metaDB, err := boltdb.New(driver, log.NewLogger("debug", "")) So(err, ShouldBeNil) 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 := CreateImageWith().DefaultLayers(). ImageConfig(ispec.Image{ Config: ispec.ImageConfig{ Labels: configLabels, }, }).Build() imageWithManifestAndConfigAnnotations := CreateImageWith().DefaultLayers(). ImageConfig(ispec.Image{ Config: ispec.ImageConfig{ Labels: configLabels, }, }).Annotations(manifestAnnotations).Build() // -------------------------------------------------------- indexWithAnnotations := CreateMultiarchWith().Images( []Image{imageWithManifestAndConfigAnnotations}, ).Annotations(indexAnnotations).Build() ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, ociutils.Repo{ Name: "repo", MultiArchImages: []ociutils.RepoMultiArchImage{ {MultiarchImage: indexWithAnnotations, Reference: "tag"}, }, }) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldBeNil) imageMeta, err := metaDB.FilterImageMeta(ctx, []string{indexWithAnnotations.DigestStr()}) So(err, ShouldBeNil) imageSummary, _, err := convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta, imageMeta[indexWithAnnotations.DigestStr()])) 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") err = metaDB.ResetDB() So(err, ShouldBeNil) // -------------------------------------------------------- indexWithManifestAndConfigAnnotations := CreateMultiarchWith().Images( []Image{imageWithManifestAndConfigAnnotations, CreateRandomImage(), CreateRandomImage()}, ).Build() ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, ociutils.Repo{ Name: "repo", MultiArchImages: []ociutils.RepoMultiArchImage{ {MultiarchImage: indexWithManifestAndConfigAnnotations, Reference: "tag"}, }, }) So(err, ShouldBeNil) digest := indexWithManifestAndConfigAnnotations.DigestStr() repoMeta, err = metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldBeNil) imageMeta, err = metaDB.FilterImageMeta(ctx, []string{digest}) So(err, ShouldBeNil) imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta, imageMeta[digest])) 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") err = metaDB.ResetDB() So(err, ShouldBeNil) // -------------------------------------------------------- indexWithConfigAnnotations := CreateMultiarchWith().Images( []Image{imageWithConfigAnnotations, CreateRandomImage(), CreateRandomImage()}, ).Build() ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, ociutils.Repo{ Name: "repo", MultiArchImages: []ociutils.RepoMultiArchImage{ {MultiarchImage: indexWithConfigAnnotations, Reference: "tag"}, }, }) So(err, ShouldBeNil) digest = indexWithConfigAnnotations.DigestStr() repoMeta, err = metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldBeNil) imageMeta, err = metaDB.FilterImageMeta(ctx, []string{digest}) So(err, ShouldBeNil) imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta, imageMeta[digest])) 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") err = metaDB.ResetDB() So(err, ShouldBeNil) //-------------------------------------------------------- indexWithMixAnnotations := CreateMultiarchWith().Images( []Image{ 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(), CreateRandomImage(), CreateRandomImage(), }, ).Annotations( map[string]string{ ispec.AnnotationTitle: "IndexTitle", ispec.AnnotationDocumentation: "IndexDocumentation", ispec.AnnotationSource: "IndexSource", }, ).Build() ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, ociutils.Repo{ Name: "repo", MultiArchImages: []ociutils.RepoMultiArchImage{ {MultiarchImage: indexWithMixAnnotations, Reference: "tag"}, }, }) So(err, ShouldBeNil) digest = indexWithMixAnnotations.DigestStr() repoMeta, err = metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldBeNil) imageMeta, err = metaDB.FilterImageMeta(ctx, []string{digest}) So(err, ShouldBeNil) imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta, imageMeta[digest])) 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") err = metaDB.ResetDB() So(err, ShouldBeNil) //-------------------------------------------------------- indexWithNoAnnotations := CreateRandomMultiarch() ctx, err = ociutils.InitializeTestMetaDB(ctx, metaDB, ociutils.Repo{ Name: "repo", MultiArchImages: []ociutils.RepoMultiArchImage{ {MultiarchImage: indexWithNoAnnotations, Reference: "tag"}, }, }) So(err, ShouldBeNil) digest = indexWithNoAnnotations.DigestStr() repoMeta, err = metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldBeNil) imageMeta, err = metaDB.FilterImageMeta(ctx, []string{digest}) So(err, ShouldBeNil) imageSummary, _, err = convert.ImageIndex2ImageSummary(ctx, convert.GetFullImageMeta("tag", repoMeta, imageMeta[digest])) 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) err = metaDB.ResetDB() So(err, ShouldBeNil) }) } func TestConvertErrors(t *testing.T) { ctx := context.Background() log := log.NewLogger("debug", "") Convey("Errors", t, func() { Convey("RepoMeta2ExpandedRepoInfo", func() { _, imgSums := convert.RepoMeta2ExpandedRepoInfo(ctx, mTypes.RepoMeta{ Tags: map[mTypes.Tag]mTypes.Descriptor{"tag": {MediaType: "bad-type", Digest: "digest"}}, }, map[string]mTypes.ImageMeta{ "digest": {}, }, convert.SkipQGLField{}, nil, log, ) So(len(imgSums), ShouldEqual, 0) }) Convey("RepoMeta2ExpandedRepoInfo - bad ctx value", func() { uacKey := reqCtx.GetContextKey() ctx := context.WithValue(ctx, uacKey, "bad context") _, imgSums := convert.RepoMeta2ExpandedRepoInfo(ctx, mTypes.RepoMeta{}, map[string]mTypes.ImageMeta{ "digest": {}, }, convert.SkipQGLField{}, nil, log, ) So(len(imgSums), ShouldEqual, 0) }) Convey("RepoMeta2ExpandedRepoInfo - nil ctx value", func() { uacKey := reqCtx.GetContextKey() ctx := context.WithValue(ctx, uacKey, nil) _, imgSums := convert.RepoMeta2ExpandedRepoInfo(ctx, mTypes.RepoMeta{}, map[string]mTypes.ImageMeta{ "digest": {}, }, convert.SkipQGLField{}, nil, log, ) So(len(imgSums), ShouldEqual, 0) }) }) }