//go:build imagetrust // +build imagetrust package meta_test import ( "context" "encoding/json" "fmt" "os" "path" "testing" "time" "github.com/alicebob/miniredis/v2" guuid "github.com/gofrs/uuid" "github.com/notaryproject/notation-core-go/signature/jws" "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/signer" godigest "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/specs-go" ispec "github.com/opencontainers/image-spec/specs-go/v1" . "github.com/smartystreets/goconvey/convey" zerr "zotregistry.dev/zot/errors" "zotregistry.dev/zot/pkg/api/config" rediscfg "zotregistry.dev/zot/pkg/api/config/redis" zcommon "zotregistry.dev/zot/pkg/common" "zotregistry.dev/zot/pkg/extensions/imagetrust" "zotregistry.dev/zot/pkg/extensions/search/convert" "zotregistry.dev/zot/pkg/log" "zotregistry.dev/zot/pkg/meta" "zotregistry.dev/zot/pkg/meta/boltdb" "zotregistry.dev/zot/pkg/meta/common" mdynamodb "zotregistry.dev/zot/pkg/meta/dynamodb" "zotregistry.dev/zot/pkg/meta/redis" mTypes "zotregistry.dev/zot/pkg/meta/types" reqCtx "zotregistry.dev/zot/pkg/requestcontext" tCommon "zotregistry.dev/zot/pkg/test/common" . "zotregistry.dev/zot/pkg/test/image-utils" "zotregistry.dev/zot/pkg/test/signature" tskip "zotregistry.dev/zot/pkg/test/skip" ) const ( LINUX = "linux" WINDOWS = "windows" AMD = "amd" ARM = "arm64" ) func getManifestDigest(md mTypes.ManifestMeta) string { return md.Digest.String() } func TestBoltDB(t *testing.T) { Convey("BoltDB creation", t, func() { boltDBParams := boltdb.DBParameters{RootDir: t.TempDir()} repoDBPath := path.Join(boltDBParams.RootDir, "meta.db") defer os.Remove(repoDBPath) boltDriver, err := boltdb.GetBoltDriver(boltDBParams) So(err, ShouldBeNil) log := log.NewLogger("debug", "") metaDB, err := boltdb.New(boltDriver, log) So(metaDB, ShouldNotBeNil) So(err, ShouldBeNil) err = os.Chmod(repoDBPath, 0o200) So(err, ShouldBeNil) _, err = boltdb.GetBoltDriver(boltDBParams) So(err, ShouldNotBeNil) err = os.Chmod(repoDBPath, 0o600) So(err, ShouldBeNil) }) Convey("BoltDB Wrapper", t, func() { boltDBParams := boltdb.DBParameters{RootDir: t.TempDir()} boltDriver, err := boltdb.GetBoltDriver(boltDBParams) So(err, ShouldBeNil) log := log.NewLogger("debug", "") imgTrustStore, err := imagetrust.NewLocalImageTrustStore(boltDBParams.RootDir) So(err, ShouldBeNil) boltdbWrapper, err := boltdb.New(boltDriver, log) boltdbWrapper.SetImageTrustStore(imgTrustStore) defer func() { os.Remove(path.Join(boltDBParams.RootDir, "meta.db")) os.RemoveAll(path.Join(boltDBParams.RootDir, "_cosign")) os.RemoveAll(path.Join(boltDBParams.RootDir, "_notation")) }() So(boltdbWrapper, ShouldNotBeNil) So(err, ShouldBeNil) RunMetaDBTests(t, boltdbWrapper) }) } func TestDynamoDBWrapper(t *testing.T) { tskip.SkipDynamo(t) uuid, err := guuid.NewV4() if err != nil { panic(err) } repoMetaTablename := "RepoMetadataTable" + uuid.String() versionTablename := "Version" + uuid.String() userDataTablename := "UserDataTable" + uuid.String() apiKeyTablename := "ApiKeyTable" + uuid.String() imageMetaTablename := "ImageMeta" + uuid.String() repoBlobsTablename := "RepoBlobs" + uuid.String() Convey("DynamoDB Wrapper", t, func() { dynamoDBDriverParams := mdynamodb.DBDriverParameters{ Endpoint: os.Getenv("DYNAMODBMOCK_ENDPOINT"), RepoMetaTablename: repoMetaTablename, RepoBlobsInfoTablename: repoBlobsTablename, ImageMetaTablename: imageMetaTablename, VersionTablename: versionTablename, UserDataTablename: userDataTablename, APIKeyTablename: apiKeyTablename, Region: "us-east-2", } dynamoClient, err := mdynamodb.GetDynamoClient(dynamoDBDriverParams) So(err, ShouldBeNil) log := log.NewLogger("debug", "") dynamoDriver, err := mdynamodb.New(dynamoClient, dynamoDBDriverParams, log) So(dynamoDriver, ShouldNotBeNil) So(err, ShouldBeNil) imgTrustStore, err := imagetrust.NewAWSImageTrustStore(dynamoDBDriverParams.Region, dynamoDBDriverParams.Endpoint) So(err, ShouldBeNil) dynamoDriver.SetImageTrustStore(imgTrustStore) resetDynamoDBTables := func() error { err := dynamoDriver.ResetTable(dynamoDriver.RepoMetaTablename) if err != nil { return err } err = dynamoDriver.ResetTable(dynamoDriver.ImageMetaTablename) if err != nil { return err } err = dynamoDriver.ResetTable(dynamoDriver.RepoBlobsTablename) if err != nil { return err } // Note: Tests are very slow if we reset the UserData table every new convey. We'll reset it as needed return err } RunMetaDBTests(t, dynamoDriver, resetDynamoDBTables) }) } func TestRedisDB(t *testing.T) { miniRedis := miniredis.RunT(t) Convey("RedisDB Wrapper", t, func() { rootDir := t.TempDir() log := log.NewLogger("debug", "") params := redis.DBDriverParameters{KeyPrefix: "zot"} driverConfig := map[string]interface{}{"url": "redis://" + miniRedis.Addr()} redisDriver, err := rediscfg.GetRedisClient(driverConfig, log) So(err, ShouldBeNil) metaDB, err := redis.New(redisDriver, params, log) So(metaDB, ShouldNotBeNil) So(err, ShouldBeNil) imgTrustStore, err := imagetrust.NewLocalImageTrustStore(rootDir) So(err, ShouldBeNil) metaDB.SetImageTrustStore(imgTrustStore) defer func() { metaDB.ResetDB() //nolint: errcheck os.RemoveAll(path.Join(rootDir, "_cosign")) os.RemoveAll(path.Join(rootDir, "_notation")) }() RunMetaDBTests(t, metaDB) }) } func RunMetaDBTests(t *testing.T, metaDB mTypes.MetaDB, preparationFuncs ...func() error) { //nolint: thelper,gocyclo ctx := context.Background() Convey("Test MetaDB Interface implementation", func() { for _, prepFunc := range preparationFuncs { err := prepFunc() So(err, ShouldBeNil) } Convey("Test CRUD operations on UserData and API keys", func() { hashKey1 := "id" label1 := "apiKey1" apiKeys := make(map[string]mTypes.APIKeyDetails) apiKeyDetails := mTypes.APIKeyDetails{ Label: label1, Scopes: []string{"repo"}, UUID: hashKey1, } apiKeys[hashKey1] = apiKeyDetails userProfileSrc := mTypes.UserData{ Groups: []string{"group1", "group2"}, APIKeys: apiKeys, } Convey("Test basic operations on API keys", func() { hashKey2 := "key" label2 := "apiKey2" userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("test") ctx := userAc.DeriveContext(context.Background()) err := metaDB.AddUserAPIKey(ctx, hashKey1, &apiKeyDetails) So(err, ShouldBeNil) isExpired, err := metaDB.IsAPIKeyExpired(ctx, hashKey1) So(isExpired, ShouldBeFalse) So(err, ShouldBeNil) storedAPIKeys, err := metaDB.GetUserAPIKeys(ctx) So(err, ShouldBeNil) So(len(storedAPIKeys), ShouldEqual, 1) So(storedAPIKeys[0], ShouldResemble, apiKeyDetails) userProfile, err := metaDB.GetUserData(ctx) So(err, ShouldBeNil) So(userProfile.APIKeys, ShouldContainKey, hashKey1) So(userProfile.APIKeys[hashKey1].Label, ShouldEqual, apiKeyDetails.Label) So(userProfile.APIKeys[hashKey1].Scopes, ShouldResemble, apiKeyDetails.Scopes) err = metaDB.SetUserData(ctx, userProfileSrc) So(err, ShouldBeNil) userProfile, err = metaDB.GetUserData(ctx) So(err, ShouldBeNil) So(userProfile.Groups, ShouldResemble, userProfileSrc.Groups) So(userProfile.APIKeys, ShouldContainKey, hashKey1) So(userProfile.APIKeys[hashKey1].Label, ShouldEqual, apiKeyDetails.Label) So(userProfile.APIKeys[hashKey1].Scopes, ShouldResemble, apiKeyDetails.Scopes) storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx) So(err, ShouldBeNil) So(len(storedAPIKeys), ShouldEqual, 1) So(storedAPIKeys[0], ShouldResemble, apiKeyDetails) lastUsed := userProfile.APIKeys[hashKey1].LastUsed err = metaDB.UpdateUserAPIKeyLastUsed(ctx, hashKey1) So(err, ShouldBeNil) userProfile, err = metaDB.GetUserData(ctx) So(err, ShouldBeNil) So(userProfile.APIKeys[hashKey1].LastUsed, ShouldHappenAfter, lastUsed) storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx) So(err, ShouldBeNil) So(len(storedAPIKeys), ShouldEqual, 1) So(storedAPIKeys[0].LastUsed, ShouldHappenAfter, lastUsed) userGroups, err := metaDB.GetUserGroups(ctx) So(err, ShouldBeNil) So(userGroups, ShouldResemble, userProfileSrc.Groups) apiKeyDetails.UUID = hashKey2 apiKeyDetails.Label = label2 err = metaDB.AddUserAPIKey(ctx, hashKey2, &apiKeyDetails) So(err, ShouldBeNil) userProfile, err = metaDB.GetUserData(ctx) So(err, ShouldBeNil) So(userProfile.Groups, ShouldResemble, userProfileSrc.Groups) So(userProfile.APIKeys, ShouldContainKey, hashKey2) So(userProfile.APIKeys[hashKey2].Label, ShouldEqual, apiKeyDetails.Label) So(userProfile.APIKeys[hashKey2].Scopes, ShouldResemble, apiKeyDetails.Scopes) storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx) So(err, ShouldBeNil) So(len(storedAPIKeys), ShouldEqual, 2) So(storedAPIKeys[0].Scopes, ShouldResemble, apiKeyDetails.Scopes) So(storedAPIKeys[1].Scopes, ShouldResemble, apiKeyDetails.Scopes) scopes := []string{storedAPIKeys[0].Label, storedAPIKeys[1].Label} // order is not preserved when getting api keys from db So(scopes, ShouldContain, label1) So(scopes, ShouldContain, label2) email, err := metaDB.GetUserAPIKeyInfo(hashKey2) So(err, ShouldBeNil) So(email, ShouldEqual, "test") email, err = metaDB.GetUserAPIKeyInfo(hashKey1) So(err, ShouldBeNil) So(email, ShouldEqual, "test") err = metaDB.DeleteUserAPIKey(ctx, hashKey1) So(err, ShouldBeNil) storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx) So(err, ShouldBeNil) So(len(storedAPIKeys), ShouldEqual, 1) So(storedAPIKeys[0].Label, ShouldEqual, label2) userProfile, err = metaDB.GetUserData(ctx) So(err, ShouldBeNil) So(len(userProfile.APIKeys), ShouldEqual, 1) err = metaDB.DeleteUserAPIKey(ctx, hashKey2) So(err, ShouldBeNil) storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx) So(err, ShouldBeNil) So(len(storedAPIKeys), ShouldEqual, 0) userProfile, err = metaDB.GetUserData(ctx) So(err, ShouldBeNil) So(len(userProfile.APIKeys), ShouldEqual, 0) So(userProfile.APIKeys, ShouldNotContainKey, hashKey2) // delete non existent api key err = metaDB.DeleteUserAPIKey(ctx, hashKey2) So(err, ShouldBeNil) storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx) So(err, ShouldBeNil) So(len(storedAPIKeys), ShouldEqual, 0) err = metaDB.DeleteUserData(ctx) So(err, ShouldBeNil) storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx) So(err, ShouldBeNil) So(len(storedAPIKeys), ShouldEqual, 0) email, err = metaDB.GetUserAPIKeyInfo(hashKey2) So(err, ShouldNotBeNil) So(email, ShouldBeEmpty) email, err = metaDB.GetUserAPIKeyInfo(hashKey1) So(err, ShouldNotBeNil) So(email, ShouldBeEmpty) _, err = metaDB.GetUserData(ctx) So(err, ShouldNotBeNil) userGroups, err = metaDB.GetUserGroups(ctx) So(err, ShouldNotBeNil) So(userGroups, ShouldBeEmpty) err = metaDB.SetUserGroups(ctx, userProfileSrc.Groups) So(err, ShouldBeNil) userGroups, err = metaDB.GetUserGroups(ctx) So(err, ShouldBeNil) So(userGroups, ShouldResemble, userProfileSrc.Groups) }) Convey("Test API keys operations with invalid access control context", func() { var invalid struct{} key := reqCtx.GetContextKey() ctx := context.WithValue(context.Background(), key, invalid) _, err := metaDB.GetUserAPIKeys(ctx) So(err, ShouldNotBeNil) err = metaDB.AddUserAPIKey(ctx, hashKey1, &apiKeyDetails) So(err, ShouldNotBeNil) isExpired, err := metaDB.IsAPIKeyExpired(ctx, hashKey1) So(isExpired, ShouldBeFalse) So(err, ShouldNotBeNil) err = metaDB.DeleteUserAPIKey(ctx, hashKey1) So(err, ShouldNotBeNil) _, err = metaDB.GetUserData(ctx) So(err, ShouldNotBeNil) _, err = metaDB.GetUserGroups(ctx) So(err, ShouldNotBeNil) _, err = metaDB.GetUserAPIKeyInfo(hashKey1) So(err, ShouldNotBeNil) err = metaDB.UpdateUserAPIKeyLastUsed(ctx, hashKey1) So(err, ShouldNotBeNil) err = metaDB.SetUserData(ctx, userProfileSrc) So(err, ShouldNotBeNil) err = metaDB.SetUserGroups(ctx, []string{"group1", "groups2"}) So(err, ShouldNotBeNil) err = metaDB.DeleteUserData(ctx) So(err, ShouldNotBeNil) }) Convey("Test API keys operations with empty userid", func() { userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("") ctx := userAc.DeriveContext(context.Background()) _, err := metaDB.GetUserAPIKeys(ctx) So(err, ShouldNotBeNil) isExpired, err := metaDB.IsAPIKeyExpired(ctx, hashKey1) So(isExpired, ShouldBeFalse) So(err, ShouldNotBeNil) err = metaDB.AddUserAPIKey(ctx, hashKey1, &apiKeyDetails) So(err, ShouldNotBeNil) err = metaDB.DeleteUserAPIKey(ctx, hashKey1) So(err, ShouldNotBeNil) _, err = metaDB.GetUserData(ctx) So(err, ShouldNotBeNil) _, err = metaDB.GetUserGroups(ctx) So(err, ShouldNotBeNil) _, err = metaDB.GetUserAPIKeyInfo(hashKey1) So(err, ShouldNotBeNil) err = metaDB.UpdateUserAPIKeyLastUsed(ctx, hashKey1) So(err, ShouldNotBeNil) err = metaDB.SetUserData(ctx, userProfileSrc) So(err, ShouldNotBeNil) err = metaDB.SetUserGroups(ctx, []string{"group1", "groups2"}) So(err, ShouldNotBeNil) err = metaDB.DeleteUserData(ctx) So(err, ShouldNotBeNil) }) Convey("Test API keys with short expiration date", func() { expirationDate := time.Now().Add(1 * time.Second) apiKeyDetails.ExpirationDate = expirationDate userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("test") ctx := userAc.DeriveContext(context.Background()) err := metaDB.AddUserAPIKey(ctx, hashKey1, &apiKeyDetails) So(err, ShouldBeNil) storedAPIKeys, err := metaDB.GetUserAPIKeys(ctx) So(err, ShouldBeNil) So(len(storedAPIKeys), ShouldEqual, 1) So(storedAPIKeys[0].ExpirationDate, ShouldResemble, expirationDate) So(storedAPIKeys[0].Label, ShouldEqual, apiKeyDetails.Label) So(storedAPIKeys[0].Scopes, ShouldResemble, apiKeyDetails.Scopes) isExpired, err := metaDB.IsAPIKeyExpired(ctx, hashKey1) So(isExpired, ShouldBeFalse) So(err, ShouldBeNil) time.Sleep(1 * time.Second) Convey("GetUserAPIKeys detects api key expired", func() { storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx) So(err, ShouldBeNil) So(len(storedAPIKeys), ShouldEqual, 1) So(storedAPIKeys[0].IsExpired, ShouldBeTrue) isExpired, err = metaDB.IsAPIKeyExpired(ctx, hashKey1) So(isExpired, ShouldBeTrue) So(err, ShouldBeNil) }) Convey("IsAPIKeyExpired detects api key expired", func() { isExpired, err = metaDB.IsAPIKeyExpired(ctx, hashKey1) So(isExpired, ShouldBeTrue) So(err, ShouldBeNil) storedAPIKeys, err = metaDB.GetUserAPIKeys(ctx) So(err, ShouldBeNil) So(len(storedAPIKeys), ShouldEqual, 1) So(storedAPIKeys[0].IsExpired, ShouldBeTrue) }) }) }) Convey("Test Setting an image by tag and retrieving data", func() { imgData := CreateImageWith(). DefaultLayers(). ImageConfig(ispec.Image{ Created: DateRef(2000, 10, 10, 10, 10, 10, 10, time.UTC), Author: "author", Platform: ispec.Platform{ Architecture: "arch", OS: "os", OSVersion: "os-vers", OSFeatures: []string{"os-features"}, Variant: "variant", }, Config: ispec.ImageConfig{ Labels: map[string]string{"test": "test"}, Env: []string{"test"}, ExposedPorts: map[string]struct{}{"test": {}}, Volumes: map[string]struct{}{"test": {}}, }, RootFS: ispec.RootFS{ DiffIDs: []godigest.Digest{godigest.FromString("test")}, }, }).Build().AsImageMeta() err := metaDB.SetImageMeta(imgData.Digest, imgData) So(err, ShouldBeNil) retrievedImgData, err := metaDB.GetImageMeta(imgData.Digest) So(err, ShouldBeNil) So(imgData, ShouldResemble, retrievedImgData) imgMulti := CreateRandomMultiarch() for i := range imgMulti.Images { err = metaDB.SetImageMeta(imgMulti.Images[i].Digest(), imgMulti.Images[i].AsImageMeta()) So(err, ShouldBeNil) } err = metaDB.SetImageMeta(imgMulti.Digest(), imgMulti.AsImageMeta()) So(err, ShouldBeNil) retrievedImgMultiData, err := metaDB.GetImageMeta(imgMulti.Digest()) So(err, ShouldBeNil) So(imgMulti.AsImageMeta(), ShouldEqual, retrievedImgMultiData) }) Convey("GetFullImageMeta", func() { img1 := CreateRandomImage() multi := CreateMultiarchWith().Images([]Image{img1}).Build() err := metaDB.SetRepoReference(ctx, "repo", img1.Digest().String(), img1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", "tag", multi.AsImageMeta()) So(err, ShouldBeNil) fullImageMeta, err := metaDB.GetFullImageMeta(ctx, "repo", "tag") So(err, ShouldBeNil) So(fullImageMeta.Digest.String(), ShouldResemble, multi.DigestStr()) }) Convey("Set/Get RepoMeta", func() { err := metaDB.SetRepoMeta("repo", mTypes.RepoMeta{ Name: "repo", Tags: map[mTypes.Tag]mTypes.Descriptor{"tag": {Digest: "dig"}}, Statistics: map[mTypes.ImageDigest]mTypes.DescriptorStatistics{}, Signatures: map[mTypes.ImageDigest]mTypes.ManifestSignatures{}, Referrers: map[mTypes.ImageDigest][]mTypes.ReferrerInfo{"digest": {{Digest: "dig"}}}, }) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldBeNil) So(repoMeta.Name, ShouldResemble, "repo") So(repoMeta.Tags, ShouldContainKey, "tag") }) Convey("Test SetRepoReference", func() { var ( repo1 = "repo1" repo2 = "repo2" tag1 = "0.0.1" tag2 = "0.0.2" ) img1 := CreateImageWith().RandomLayers(2, 10).RandomConfig(). Annotations(map[string]string{"test": "annotation"}).Build() imgData1 := img1.AsImageMeta() img1Size := img1.ConfigDescriptor.Size + img1.ManifestDescriptor.Size + 2*10 img2 := CreateImageWith().LayerBlobs(img1.Layers).RandomConfig(). Annotations(map[string]string{"test": "annotation"}).Build() imgData2 := img2.AsImageMeta() img2Size := img2.ConfigDescriptor.Size + img2.ManifestDescriptor.Size + 2*10 multiImages := []Image{ CreateImageWith().RandomLayers(2, 10). ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "multi-os1", Architecture: "multi-arch1"}}). Annotations(map[string]string{ispec.AnnotationVendor: "vendor1"}). Build(), CreateImageWith().RandomLayers(2, 10). ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "multi-os2", Architecture: "multi-arch2"}}). Annotations(map[string]string{ispec.AnnotationVendor: "vendor2"}). Build(), } imgMulti := CreateMultiarchWith(). Images(multiImages). Annotations(map[string]string{ispec.AnnotationVendor: "vendor1"}).Build() Convey("Setting a good repo", func() { err := metaDB.SetRepoReference(ctx, repo1, tag1, imgData1) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.Name, ShouldResemble, repo1) So(repoMeta.Tags[tag1].Digest, ShouldEqual, img1.DigestStr()) }) Convey("Setting an index with it's manifests", func() { _, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldNotBeNil) for i := range imgMulti.Images { err := metaDB.SetRepoReference(ctx, repo1, imgMulti.Images[i].DigestStr(), imgMulti.Images[i].AsImageMeta()) So(err, ShouldBeNil) } err = metaDB.SetRepoReference(ctx, repo1, tag1, imgMulti.AsImageMeta()) So(err, ShouldBeNil) image1TotalSize := multiImages[0].ManifestDescriptor.Size + multiImages[0].ConfigDescriptor.Size + 2*10 image2TotalSize := multiImages[1].ManifestDescriptor.Size + multiImages[1].ConfigDescriptor.Size + 2*10 indexTotalSize := image1TotalSize + image2TotalSize + imgMulti.IndexDescriptor.Size repoMeta, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.Name, ShouldResemble, repo1) So(repoMeta.Platforms, ShouldContain, ispec.Platform{OS: "multi-os1", Architecture: "multi-arch1"}) So(repoMeta.Platforms, ShouldContain, ispec.Platform{OS: "multi-os2", Architecture: "multi-arch2"}) So(repoMeta.Vendors, ShouldContain, "vendor1") So(repoMeta.Vendors, ShouldContain, "vendor2") So(repoMeta.Size, ShouldEqual, indexTotalSize) }) Convey("Set multiple repos", func() { err := metaDB.SetRepoReference(ctx, repo1, tag1, imgData1) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo2, tag1, imgData2) So(err, ShouldBeNil) repoMeta1, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) repoMeta2, err := metaDB.GetRepoMeta(ctx, repo2) So(err, ShouldBeNil) So(repoMeta1.Tags[tag1].Digest, ShouldResemble, imgData1.Digest.String()) So(repoMeta2.Tags[tag1].Digest, ShouldResemble, imgData2.Digest.String()) So(repoMeta1.Size, ShouldEqual, img1Size) So(repoMeta2.Size, ShouldEqual, img2Size) }) Convey("Set, delete and set again", func() { err := metaDB.SetRepoReference(ctx, repo1, tag1, imgData1) So(err, ShouldBeNil) err = metaDB.RemoveRepoReference(repo1, tag1, imgData1.Digest) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, tag1, imgData1) So(err, ShouldBeNil) }) Convey("Check repo blobs info for manifest image", func() { image1 := CreateImageWith().RandomLayers(2, 10). ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os1", Architecture: "arch1"}}). Annotations(map[string]string{ispec.AnnotationVendor: "vendor1"}). Build() imageMeta1 := image1.AsImageMeta() layersSize := int64(2 * 10) image1Size := imageMeta1.Manifests[0].Size + imageMeta1.Manifests[0].Manifest.Config.Size + layersSize err := metaDB.SetRepoReference(ctx, repo1, tag1, imageMeta1) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.Vendors, ShouldContain, "vendor1") So(repoMeta.Platforms, ShouldContain, ispec.Platform{OS: "os1", Architecture: "arch1"}) So(repoMeta.Size, ShouldEqual, image1Size) image2 := CreateImageWith(). LayerBlobs(image1.Layers). ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os2", Architecture: "arch2"}}). Annotations(map[string]string{ispec.AnnotationVendor: "vendor2"}). Build() imageMeta2 := image2.AsImageMeta() // the layers are the same so we add them once repoSize := image1Size + image2.ManifestDescriptor.Size + image2.ConfigDescriptor.Size err = metaDB.SetRepoReference(ctx, repo1, tag2, imageMeta2) So(err, ShouldBeNil) repoMeta, err = metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.Vendors, ShouldContain, "vendor1") So(repoMeta.Vendors, ShouldContain, "vendor2") So(repoMeta.Platforms, ShouldContain, ispec.Platform{OS: "os1", Architecture: "arch1"}) So(repoMeta.Platforms, ShouldContain, ispec.Platform{OS: "os2", Architecture: "arch2"}) So(repoMeta.Size, ShouldEqual, repoSize) }) Convey("Set with a bad reference", func() { err := metaDB.SetRepoReference(ctx, "repo", "", imgData1) So(err, ShouldNotBeNil) err = metaDB.SetRepoReference(ctx, "", "tag", imgData1) So(err, ShouldNotBeNil) }) Convey("Check last updated for indexes", func() { config1 := GetDefaultConfig() config1.Created = DateRef(2009, 2, 1, 12, 0, 0, 0, time.UTC) config2 := GetDefaultConfig() config2.Created = DateRef(2011, 2, 1, 12, 0, 0, 0, time.UTC) config3 := GetDefaultConfig() config3.Created = DateRef(2011, 3, 1, 12, 0, 0, 0, time.UTC) image1 := CreateMultiarchWith().Images([]Image{ CreateImageWith().RandomLayers(1, 10).ImageConfig(config1).Build(), }).Build() image2 := CreateMultiarchWith().Images([]Image{ CreateImageWith().RandomLayers(1, 10).ImageConfig(config2).Build(), CreateImageWith().RandomLayers(1, 10).ImageConfig(config3).Build(), }).Build() _, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldNotBeNil) for i := range image1.Images { err := metaDB.SetRepoReference(ctx, repo1, image1.Images[i].DigestStr(), image1.Images[i].AsImageMeta()) So(err, ShouldBeNil) } err = metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta()) So(err, ShouldBeNil) for i := range image2.Images { err := metaDB.SetRepoReference(ctx, repo1, image2.Images[i].DigestStr(), image2.Images[i].AsImageMeta()) So(err, ShouldBeNil) } err = metaDB.SetRepoReference(ctx, repo1, tag2, image2.AsImageMeta()) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(*repoMeta.LastUpdatedImage.LastUpdated, ShouldEqual, time.Date(2011, 3, 1, 12, 0, 0, 0, time.UTC)) }) }) Convey("Test RemoveRepoReference", func() { var ( repo = "repo1" tag1 = "0.0.1" tag2 = "0.0.2" ) layersSize := int64(2 * 10) image1 := CreateImageWith(). RandomLayers(2, 10). ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os1", Architecture: "arch1"}}). Annotations(map[string]string{ispec.AnnotationVendor: "vendor1"}). Build() imageMeta1 := image1.AsImageMeta() image1Size := imageMeta1.Manifests[0].Size + imageMeta1.Manifests[0].Manifest.Config.Size + layersSize image2 := CreateImageWith(). LayerBlobs(image1.Layers). ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os2", Architecture: "arch2"}}). Annotations(map[string]string{ispec.AnnotationVendor: "vendor2", "annotation": "test"}). Build() imageMeta2 := image2.AsImageMeta() image2Size := imageMeta2.Manifests[0].Size + imageMeta2.Manifests[0].Manifest.Config.Size + layersSize totalRepoSize := image1Size + image2Size - layersSize err := metaDB.SetRepoReference(ctx, repo, tag1, imageMeta1) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo, tag2, imageMeta2) So(err, ShouldBeNil) Convey("Delete reference from repo", func() { err = metaDB.RemoveRepoReference(repo, tag1, imageMeta1.Digest) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo) So(err, ShouldBeNil) _, ok := repoMeta.Tags[tag1] So(ok, ShouldBeFalse) So(repoMeta.Size, ShouldEqual, image2Size) So(repoMeta.Platforms, ShouldNotContain, ispec.Platform{OS: "os1", Architecture: "arch1"}) So(repoMeta.Vendors, ShouldNotContain, "vendor1") }) Convey("Delete a digest from repo", func() { err = metaDB.RemoveRepoReference(repo, tag2, imageMeta2.Digest) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo) So(err, ShouldBeNil) _, ok := repoMeta.Tags[tag2] So(ok, ShouldBeFalse) So(repoMeta.Size, ShouldEqual, image1Size) So(repoMeta.Platforms, ShouldNotContain, ispec.Platform{OS: "os2", Architecture: "arch2"}) So(repoMeta.Vendors, ShouldNotContain, "vendor2") }) Convey("Delete inexistent reference from repo", func() { inexistentDigest := godigest.FromBytes([]byte("inexistent")) err := metaDB.RemoveRepoReference(repo, inexistentDigest.String(), inexistentDigest) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo) So(err, ShouldBeNil) So(repoMeta.Tags[tag1].Digest, ShouldResemble, imageMeta1.Digest.String()) So(repoMeta.Tags[tag2].Digest, ShouldResemble, imageMeta2.Digest.String()) So(repoMeta.Size, ShouldEqual, totalRepoSize) }) Convey("Delete reference from inexistent repo", func() { inexistentDigest := godigest.FromBytes([]byte("inexistent")) err := metaDB.RemoveRepoReference("InexistentRepo", inexistentDigest.String(), inexistentDigest) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo) So(err, ShouldBeNil) So(repoMeta.Tags[tag1].Digest, ShouldResemble, imageMeta1.Digest.String()) So(repoMeta.Tags[tag2].Digest, ShouldResemble, imageMeta2.Digest.String()) So(repoMeta.Size, ShouldEqual, totalRepoSize) }) }) Convey("Test GetMultipleRepoMeta", func() { var ( repo1 = "repo1" repo2 = "repo2" repo3 = "repo3" tag1 = "0.0.1" tag2 = "0.0.2" tag3 = "0.0.3" ) userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("username") userAc.SetGlobPatterns("read", map[string]bool{ repo1: true, repo2: true, repo3: false, }) ctx := userAc.DeriveContext(context.Background()) image1 := CreateImageWith(). RandomLayers(2, 10). ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os1", Architecture: "arch1"}}). Annotations(map[string]string{ispec.AnnotationVendor: "vendor1"}). Build() imageMeta1 := image1.AsImageMeta() image2 := CreateImageWith(). LayerBlobs(image1.Layers). ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os2", Architecture: "arch2"}}). Annotations(map[string]string{ispec.AnnotationVendor: "vendor2"}). Build() imageMeta2 := image2.AsImageMeta() image3 := CreateImageWith(). LayerBlobs(image1.Layers). ImageConfig(ispec.Image{Platform: ispec.Platform{OS: "os3", Architecture: "arch3"}}). Annotations(map[string]string{ispec.AnnotationVendor: "vendor3"}). Build() imageMeta3 := image3.AsImageMeta() err := metaDB.SetRepoReference(ctx, repo1, tag1, imageMeta1) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, tag2, imageMeta2) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo2, tag2, imageMeta2) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo3, tag3, imageMeta3) So(err, ShouldBeNil) Convey("Get all RepoMeta", func() { repoMetaSlice, err := metaDB.GetMultipleRepoMeta(context.TODO(), func(repoMeta mTypes.RepoMeta) bool { return true }) So(err, ShouldBeNil) So(len(repoMetaSlice), ShouldEqual, 3) }) Convey("Get all RepoMeta for user with restricted permissions", func() { repoMetaSlice, err := metaDB.GetMultipleRepoMeta(ctx, func(repoMeta mTypes.RepoMeta) bool { return true }) So(err, ShouldBeNil) So(len(repoMetaSlice), ShouldEqual, 2) }) Convey("Get repo with a tag", func() { repoMetaSlice, err := metaDB.GetMultipleRepoMeta(context.TODO(), func(repoMeta mTypes.RepoMeta) bool { for tag := range repoMeta.Tags { if tag == tag1 { return true } } return false }) So(err, ShouldBeNil) So(len(repoMetaSlice), ShouldEqual, 1) So(repoMetaSlice[0].Tags[tag1].Digest == imageMeta1.Digest.String(), ShouldBeTrue) }) }) Convey("Test IncrementRepoStars", func() { var ( repo1 = "repo1" tag1 = "0.0.1" imageMeta = CreateDefaultImage().AsImageMeta() ) err := metaDB.IncrementRepoStars("missing-repo") So(err, ShouldNotBeNil) err = metaDB.SetRepoReference(ctx, repo1, tag1, imageMeta) So(err, ShouldBeNil) err = metaDB.IncrementRepoStars(repo1) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 1) err = metaDB.IncrementRepoStars(repo1) So(err, ShouldBeNil) repoMeta, err = metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 2) err = metaDB.IncrementRepoStars(repo1) So(err, ShouldBeNil) repoMeta, err = metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 3) }) Convey("Test DecrementRepoStars", func() { var ( repo1 = "repo1" tag1 = "0.0.1" imageMeta = CreateDefaultImage().AsImageMeta() ) err := metaDB.IncrementRepoStars("missing-repo") So(err, ShouldNotBeNil) err = metaDB.SetRepoReference(ctx, repo1, tag1, imageMeta) So(err, ShouldBeNil) err = metaDB.IncrementRepoStars(repo1) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 1) err = metaDB.DecrementRepoStars(repo1) So(err, ShouldBeNil) repoMeta, err = metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 0) err = metaDB.DecrementRepoStars(repo1) So(err, ShouldBeNil) repoMeta, err = metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 0) _, err = metaDB.GetRepoMeta(ctx, "badRepo") So(err, ShouldNotBeNil) }) Convey("Test Repo Stars", func() { var ( repo1 = "repo1" tag1 = "0.0.1" ) err := metaDB.SetRepoReference(ctx, repo1, tag1, CreateDefaultImage().AsImageMeta()) So(err, ShouldBeNil) err = metaDB.IncrementRepoStars(repo1) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 1) err = metaDB.IncrementRepoStars(repo1) So(err, ShouldBeNil) repoMeta, err = metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 2) err = metaDB.IncrementRepoStars(repo1) So(err, ShouldBeNil) repoMeta, err = metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 3) }) Convey("Test bookmarked/starred behavior when user context is not present/invalid", func() { // Context has no user access control data ctx := context.Background() toggleState, err := metaDB.ToggleBookmarkRepo(ctx, repo) So(err, ShouldNotBeNil) So(toggleState, ShouldEqual, mTypes.NotChanged) toggleState, err = metaDB.ToggleStarRepo(ctx, repo) So(err, ShouldNotBeNil) So(toggleState, ShouldEqual, mTypes.NotChanged) // Context has invalid user access control data var invalid struct{} key := reqCtx.GetContextKey() ctx = context.WithValue(context.Background(), key, invalid) toggleState, err = metaDB.ToggleBookmarkRepo(ctx, repo) So(err, ShouldNotBeNil) So(toggleState, ShouldEqual, mTypes.NotChanged) toggleState, err = metaDB.ToggleStarRepo(ctx, repo) So(err, ShouldNotBeNil) So(toggleState, ShouldEqual, mTypes.NotChanged) // Context has user access control data, but it is not completely initialized userAc := reqCtx.NewUserAccessControl() ctx = userAc.DeriveContext(context.Background()) toggleState, err = metaDB.ToggleBookmarkRepo(ctx, repo) So(err, ShouldNotBeNil) So(toggleState, ShouldEqual, mTypes.NotChanged) toggleState, err = metaDB.ToggleStarRepo(ctx, repo) So(err, ShouldNotBeNil) So(toggleState, ShouldEqual, mTypes.NotChanged) // Context has user access control data with all fields initialized userAc.SetUsername("user1") userAc.SetGlobPatterns("read", map[string]bool{ repo: true, }) ctx = userAc.DeriveContext(context.Background()) toggleState, err = metaDB.ToggleBookmarkRepo(ctx, repo) So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Added) toggleState, err = metaDB.ToggleBookmarkRepo(ctx, repo) So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Removed) toggleState, err = metaDB.ToggleStarRepo(ctx, repo) So(err, ShouldEqual, zerr.ErrRepoMetaNotFound) So(toggleState, ShouldEqual, mTypes.NotChanged) }) Convey("Test repo stars for user", func() { var ( repo1 = "repo1" tag1 = "0.0.1" repo2 = "repo2" ) userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("user1") userAc.SetGlobPatterns("read", map[string]bool{ repo1: true, repo2: true, }) // "user1" ctx1 := userAc.DeriveContext(ctx) userAc = reqCtx.NewUserAccessControl() userAc.SetUsername("user2") userAc.SetGlobPatterns("read", map[string]bool{ repo1: true, repo2: true, }) // "user2" ctx2 := userAc.DeriveContext(ctx) userAc = reqCtx.NewUserAccessControl() userAc.SetGlobPatterns("read", map[string]bool{ repo1: true, repo2: true, }) // anonymous user ctx3 := userAc.DeriveContext(ctx) err := metaDB.SetRepoReference(ctx, repo1, tag1, CreateDefaultImage().AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo2, tag1, CreateDefaultImage().AsImageMeta()) So(err, ShouldBeNil) repos, err := metaDB.GetStarredRepos(ctx1) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) repos, err = metaDB.GetStarredRepos(ctx2) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) repos, err = metaDB.GetStarredRepos(ctx3) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) // User 1 bookmarks repo 1, User 2 has no stars toggleState, err := metaDB.ToggleStarRepo(ctx1, repo1) So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Added) repoMeta, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 1) repos, err = metaDB.GetStarredRepos(ctx1) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 1) So(repos, ShouldContain, repo1) repos, err = metaDB.GetStarredRepos(ctx2) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) repos, err = metaDB.GetStarredRepos(ctx3) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) // User 1 and User 2 star only repo 1 toggleState, err = metaDB.ToggleStarRepo(ctx2, repo1) So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Added) repoMeta, err = metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 2) repos, err = metaDB.GetStarredRepos(ctx1) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 1) So(repos, ShouldContain, repo1) repos, err = metaDB.GetStarredRepos(ctx2) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 1) So(repos, ShouldContain, repo1) repos, err = metaDB.GetStarredRepos(ctx3) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) // User 1 stars repos 1 and 2, and User 2 stars only repo 1 toggleState, err = metaDB.ToggleStarRepo(ctx1, repo2) So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Added) repoMeta, err = metaDB.GetRepoMeta(ctx, repo2) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 1) repos, err = metaDB.GetStarredRepos(ctx1) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 2) So(repos, ShouldContain, repo1) So(repos, ShouldContain, repo2) repos, err = metaDB.GetStarredRepos(ctx2) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 1) So(repos, ShouldContain, repo1) repos, err = metaDB.GetStarredRepos(ctx3) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) // User 1 stars only repo 2, and User 2 stars only repo 1 toggleState, err = metaDB.ToggleStarRepo(ctx1, repo1) So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Removed) repoMeta, err = metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 1) repos, err = metaDB.GetStarredRepos(ctx1) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 1) So(repos, ShouldContain, repo2) repos, err = metaDB.GetStarredRepos(ctx2) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 1) So(repos, ShouldContain, repo1) repos, err = metaDB.GetStarredRepos(ctx3) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) // User 1 stars both repos 1 and 2, and User 2 removes all stars toggleState, err = metaDB.ToggleStarRepo(ctx1, repo1) So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Added) toggleState, err = metaDB.ToggleStarRepo(ctx2, repo1) So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Removed) repoMeta, err = metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 1) repoMeta, err = metaDB.GetRepoMeta(ctx, repo2) So(err, ShouldBeNil) So(repoMeta.StarCount, ShouldEqual, 1) repos, err = metaDB.GetStarredRepos(ctx1) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 2) So(repos, ShouldContain, repo1) So(repos, ShouldContain, repo2) repos, err = metaDB.GetStarredRepos(ctx2) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) repos, err = metaDB.GetStarredRepos(ctx3) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) // Anonymous user attempts to toggle a star toggleState, err = metaDB.ToggleStarRepo(ctx3, repo1) So(err, ShouldNotBeNil) So(toggleState, ShouldEqual, mTypes.NotChanged) repos, err = metaDB.GetStarredRepos(ctx3) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) // User 1 stars just repo 1 toggleState, err = metaDB.ToggleStarRepo(ctx1, repo2) So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Removed) repos, err = metaDB.GetStarredRepos(ctx3) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) }) //nolint: contextcheck Convey("Test repo bookmarks for user", func() { var ( repo1 = "repo1" tag1 = "0.0.1" repo2 = "repo2" image1 = CreateRandomImage().AsImageMeta() ) userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("user1") userAc.SetGlobPatterns("read", map[string]bool{ repo1: true, repo2: true, }) // "user1" ctx1 := userAc.DeriveContext(context.Background()) userAc = reqCtx.NewUserAccessControl() userAc.SetUsername("user2") userAc.SetGlobPatterns("read", map[string]bool{ repo1: true, repo2: true, }) // "user2" ctx2 := userAc.DeriveContext(context.Background()) userAc = reqCtx.NewUserAccessControl() userAc.SetGlobPatterns("read", map[string]bool{ repo1: true, repo2: true, }) // anonymous user ctx3 := userAc.DeriveContext(context.Background()) err := metaDB.SetRepoReference(ctx, repo1, tag1, image1) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo2, tag1, image1) So(err, ShouldBeNil) repos, err := metaDB.GetBookmarkedRepos(ctx1) //nolint: contextcheck So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) repos, err = metaDB.GetBookmarkedRepos(ctx2) //nolint: contextcheck So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) // anonymous cannot use bookmarks repos, err = metaDB.GetBookmarkedRepos(ctx3) //nolint: contextcheck So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) toggleState, err := metaDB.ToggleBookmarkRepo(ctx3, repo1) //nolint: contextcheck So(err, ShouldNotBeNil) So(toggleState, ShouldEqual, mTypes.NotChanged) repos, err = metaDB.GetBookmarkedRepos(ctx3) //nolint: contextcheck So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) // User 1 bookmarks repo 1, User 2 has no bookmarks toggleState, err = metaDB.ToggleBookmarkRepo(ctx1, repo1) //nolint: contextcheck So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Added) repos, err = metaDB.GetBookmarkedRepos(ctx1) //nolint: contextcheck So(err, ShouldBeNil) So(len(repos), ShouldEqual, 1) So(repos, ShouldContain, repo1) repos, err = metaDB.GetBookmarkedRepos(ctx2) //nolint: contextcheck So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) // User 1 and User 2 bookmark only repo 1 toggleState, err = metaDB.ToggleBookmarkRepo(ctx2, repo1) //nolint: contextcheck So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Added) repos, err = metaDB.GetBookmarkedRepos(ctx1) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 1) So(repos, ShouldContain, repo1) repos, err = metaDB.GetBookmarkedRepos(ctx2) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 1) So(repos, ShouldContain, repo1) // User 1 bookmarks repos 1 and 2, and User 2 bookmarks only repo 1 toggleState, err = metaDB.ToggleBookmarkRepo(ctx1, repo2) So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Added) repos, err = metaDB.GetBookmarkedRepos(ctx1) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 2) So(repos, ShouldContain, repo1) So(repos, ShouldContain, repo2) repos, err = metaDB.GetBookmarkedRepos(ctx2) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 1) So(repos, ShouldContain, repo1) // User 1 bookmarks only repo 2, and User 2 bookmarks only repo 1 toggleState, err = metaDB.ToggleBookmarkRepo(ctx1, repo1) So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Removed) repos, err = metaDB.GetBookmarkedRepos(ctx1) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 1) So(repos, ShouldContain, repo2) repos, err = metaDB.GetBookmarkedRepos(ctx2) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 1) So(repos, ShouldContain, repo1) // User 1 bookmarks both repos 1 and 2, and User 2 removes all bookmarks toggleState, err = metaDB.ToggleBookmarkRepo(ctx1, repo1) So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Added) toggleState, err = metaDB.ToggleBookmarkRepo(ctx2, repo1) So(err, ShouldBeNil) So(toggleState, ShouldEqual, mTypes.Removed) repos, err = metaDB.GetBookmarkedRepos(ctx1) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 2) So(repos, ShouldContain, repo1) So(repos, ShouldContain, repo2) repos, err = metaDB.GetBookmarkedRepos(ctx2) So(err, ShouldBeNil) So(len(repos), ShouldEqual, 0) }) Convey("Test UpdateStatsOnDownload", func() { var ( repo1 = "repo1" tag1 = "0.0.1" image1 = CreateRandomImage().AsImageMeta() ) err := metaDB.SetRepoReference(ctx, repo1, tag1, image1) So(err, ShouldBeNil) err = metaDB.UpdateStatsOnDownload(repo1, tag1) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.Statistics[image1.Digest.String()].DownloadCount, ShouldEqual, 1) err = metaDB.UpdateStatsOnDownload(repo1, tag1) So(err, ShouldBeNil) repoMeta, err = metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.Statistics[image1.Digest.String()].DownloadCount, ShouldEqual, 2) So(time.Now(), ShouldHappenAfter, repoMeta.Statistics[image1.Digest.String()].LastPullTimestamp) }) Convey("Test AddImageSignature", func() { var ( repo1 = "repo1" tag1 = "0.0.1" image1 = CreateRandomImage().AsImageMeta() ) err := metaDB.SetRepoReference(ctx, repo1, tag1, image1) So(err, ShouldBeNil) err = metaDB.AddManifestSignature(repo1, image1.Digest, mTypes.SignatureMetadata{ SignatureType: "cosign", SignatureDigest: "digest", LayersInfo: []mTypes.LayerInfo{{LayerDigest: "layer-digest", LayerContent: []byte{10}}}, }) So(err, ShouldBeNil) err = metaDB.AddManifestSignature(repo1, image1.Digest, mTypes.SignatureMetadata{ SignatureType: "cosign", SignatureTag: fmt.Sprintf("sha256-%s.sig", image1.Digest.Encoded()), SignatureDigest: "digesttag", LayersInfo: []mTypes.LayerInfo{{LayerDigest: "layer-digest", LayerContent: []byte{10}}}, }) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.Signatures[image1.Digest.String()]["cosign"][0].SignatureManifestDigest, ShouldResemble, "digesttag") So(repoMeta.Signatures[image1.Digest.String()]["cosign"][1].SignatureManifestDigest, ShouldResemble, "digest") imageMeta, err := metaDB.GetImageMeta(image1.Digest) fullImageMeta := convert.GetFullImageMeta(tag1, repoMeta, imageMeta) So(err, ShouldBeNil) So(fullImageMeta.Signatures["cosign"][0].SignatureManifestDigest, ShouldResemble, "digesttag") So(fullImageMeta.Signatures["cosign"][0].LayersInfo[0].LayerDigest, ShouldResemble, "layer-digest") So(fullImageMeta.Signatures["cosign"][0].LayersInfo[0].LayerContent[0], ShouldEqual, 10) So(fullImageMeta.Signatures["cosign"][1].SignatureManifestDigest, ShouldResemble, "digest") So(fullImageMeta.Signatures["cosign"][1].LayersInfo[0].LayerDigest, ShouldResemble, "layer-digest") So(fullImageMeta.Signatures["cosign"][1].LayersInfo[0].LayerContent[0], ShouldEqual, 10) }) Convey("Test UpdateSignaturesValidity", func() { Convey("untrusted signature", func() { var ( repo1 = "repo1" tag1 = "0.0.1" image1 = CreateRandomImage() ) err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta()) So(err, ShouldBeNil) layerInfo := mTypes.LayerInfo{LayerDigest: "", LayerContent: []byte{}, SignatureKey: ""} err = metaDB.AddManifestSignature(repo1, image1.Digest(), mTypes.SignatureMetadata{ SignatureType: "cosign", SignatureDigest: image1.DigestStr(), SignatureTag: fmt.Sprintf("sha256-%s.sig", image1.Digest().Encoded()), LayersInfo: []mTypes.LayerInfo{layerInfo}, }) So(err, ShouldBeNil) err = metaDB.UpdateSignaturesValidity(ctx, repo1, image1.Digest()) So(err, ShouldBeNil) repoData, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoData.Signatures[image1.DigestStr()]["cosign"][0].LayersInfo[0].Signer, ShouldBeEmpty) So(repoData.Signatures[image1.DigestStr()]["cosign"][0].LayersInfo[0].Date, ShouldBeZeroValue) Convey("with context done", func() { ctx, cancel := context.WithCancel(context.Background()) cancel() err = metaDB.UpdateSignaturesValidity(ctx, repo1, image1.Digest()) So(err, ShouldNotBeNil) }) }) //nolint: contextcheck Convey("trusted signature", func() { image1 := CreateRandomImage() repo := "repo1" tag := "0.0.1" err := metaDB.SetRepoReference(ctx, repo, tag, image1.AsImageMeta()) So(err, ShouldBeNil) mediaType := jws.MediaTypeEnvelope signOpts := notation.SignerSignOptions{ SignatureMediaType: mediaType, PluginConfig: map[string]string{}, ExpiryDuration: 24 * time.Hour, } tdir := t.TempDir() uuid, err := guuid.NewV4() So(err, ShouldBeNil) keyName := fmt.Sprintf("notation-sign-test-%s", uuid) signature.NotationPathLock.Lock() defer signature.NotationPathLock.Unlock() signature.LoadNotationPath(tdir) err = signature.GenerateNotationCerts(tdir, keyName) So(err, ShouldBeNil) // getSigner var newSigner notation.Signer // ResolveKey signingKeys, err := signature.LoadNotationSigningkeys(tdir) So(err, ShouldBeNil) idx := tCommon.Index(signingKeys.Keys, keyName) So(idx, ShouldBeGreaterThanOrEqualTo, 0) key := signingKeys.Keys[idx] if key.X509KeyPair != nil { newSigner, err = signer.NewFromFiles(key.X509KeyPair.KeyPath, key.X509KeyPair.CertificatePath) So(err, ShouldBeNil) } descToSign := ispec.Descriptor{ MediaType: image1.Manifest.MediaType, Digest: image1.Digest(), Size: image1.ManifestDescriptor.Size, } ctx := context.Background() sig, _, err := newSigner.Sign(ctx, descToSign, signOpts) So(err, ShouldBeNil) layerInfo := mTypes.LayerInfo{ LayerDigest: string(godigest.FromBytes(sig)), LayerContent: sig, SignatureKey: mediaType, } err = metaDB.AddManifestSignature(repo, image1.Digest(), mTypes.SignatureMetadata{ SignatureType: "notation", SignatureDigest: string(godigest.FromString("signature digest")), LayersInfo: []mTypes.LayerInfo{layerInfo}, }) So(err, ShouldBeNil) certificateContent, err := os.ReadFile(path.Join( tdir, "notation/localkeys", keyName+".crt", )) So(err, ShouldBeNil) So(certificateContent, ShouldNotBeNil) imgTrustStore, ok := metaDB.ImageTrustStore().(*imagetrust.ImageTrustStore) So(ok, ShouldBeTrue) err = imagetrust.UploadCertificate(imgTrustStore.NotationStorage, certificateContent, "ca") So(err, ShouldBeNil) err = metaDB.UpdateSignaturesValidity(ctx, repo, image1.Digest()) //nolint:contextcheck So(err, ShouldBeNil) repoData, err := metaDB.GetRepoMeta(ctx, repo) So(err, ShouldBeNil) So(repoData.Signatures[image1.DigestStr()]["notation"][0].LayersInfo[0].Signer, ShouldNotBeEmpty) So(repoData.Signatures[image1.DigestStr()]["notation"][0].LayersInfo[0].Date, ShouldNotBeZeroValue) }) }) Convey("Test AddImageSignature with inverted order", func() { var ( repo1 = "repo1" tag1 = "0.0.1" image1 = CreateRandomImage() ) err := metaDB.AddManifestSignature(repo1, image1.Digest(), mTypes.SignatureMetadata{ SignatureType: "cosign", SignatureTag: fmt.Sprintf("sha256-%s.sig", image1.Digest().Encoded()), SignatureDigest: "digest", }) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta()) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.Signatures[image1.DigestStr()]["cosign"][0].SignatureManifestDigest, ShouldResemble, "digest") }) Convey("Test DeleteSignature", func() { var ( repo1 = "repo1" tag1 = "0.0.1" image1 = CreateRandomImage() ) err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.AddManifestSignature(repo1, image1.Digest(), mTypes.SignatureMetadata{ SignatureType: "cosign", SignatureTag: fmt.Sprintf("sha256-%s.sig", image1.Digest().Encoded()), SignatureDigest: "digest", }) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.Signatures[image1.DigestStr()]["cosign"][0].SignatureManifestDigest, ShouldResemble, "digest") err = metaDB.DeleteSignature(repo1, image1.Digest(), mTypes.SignatureMetadata{ SignatureType: "cosign", SignatureDigest: "digest", }) So(err, ShouldBeNil) repoMeta, err = metaDB.GetRepoMeta(ctx, repo1) So(err, ShouldBeNil) So(repoMeta.Signatures[image1.DigestStr()]["cosign"], ShouldBeEmpty) err = metaDB.DeleteSignature(repo1, "badDigest", mTypes.SignatureMetadata{ SignatureType: "cosign", SignatureDigest: "digest", }) So(err, ShouldNotBeNil) }) Convey("Test SearchRepos", func() { var ( repo1 = "repo1" repo2 = "repo2" repo3 = "repo3" tag1 = "0.0.1" tag2 = "0.0.2" tag3 = "0.0.3" image1 = CreateRandomImage() image2 = CreateRandomImage() image3 = CreateRandomImage() ctx = context.Background() ) _ = repo3 Convey("Search all repos", func() { err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, tag2, image2.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo2, tag3, image3.AsImageMeta()) So(err, ShouldBeNil) repoMetaList, err := metaDB.SearchRepos(ctx, "") So(err, ShouldBeNil) So(len(repoMetaList), ShouldEqual, 2) repos := map[string]map[string]string{} for _, repoMeta := range repoMetaList { if _, exists := repos[repoMeta.Name]; !exists { repos[repoMeta.Name] = map[string]string{} } for tag, descriptor := range repoMeta.Tags { repos[repoMeta.Name][tag] = descriptor.Digest } } So(repos[repo1][tag1], ShouldEqual, image1.DigestStr()) So(repos[repo1][tag2], ShouldEqual, image2.DigestStr()) So(repos[repo2][tag3], ShouldEqual, image3.DigestStr()) }) Convey("Search repos including empty ones", func() { err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, tag2, image2.AsImageMeta()) So(err, ShouldBeNil) // We need to add a new reference and then remove it in order to obtain the empty repo err = metaDB.SetRepoReference(ctx, repo2, tag3, image3.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.RemoveRepoReference(repo2, tag3, image3.Digest()) So(err, ShouldBeNil) repoMetaList, err := metaDB.SearchRepos(ctx, "") So(err, ShouldBeNil) So(len(repoMetaList), ShouldEqual, 1) // Empty repos are not returned repos := map[string]map[string]string{} for _, repoMeta := range repoMetaList { if _, exists := repos[repoMeta.Name]; !exists { repos[repoMeta.Name] = map[string]string{} } for tag, descriptor := range repoMeta.Tags { repos[repoMeta.Name][tag] = descriptor.Digest } } So(repos[repo1][tag1], ShouldEqual, image1.DigestStr()) So(repos[repo1][tag2], ShouldEqual, image2.DigestStr()) So(len(repos[repo2]), ShouldEqual, 0) }) Convey("Search a repo by name", func() { err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta()) So(err, ShouldBeNil) repoMetaList, err := metaDB.SearchRepos(ctx, repo1) So(err, ShouldBeNil) So(len(repoMetaList), ShouldEqual, 1) So(repoMetaList[0].Tags[tag1].Digest, ShouldResemble, image1.DigestStr()) }) Convey("Search non-existing repo by name", func() { err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, tag2, image2.AsImageMeta()) So(err, ShouldBeNil) repoMetaList, err := metaDB.SearchRepos(ctx, "RepoThatDoesntExist") So(err, ShouldBeNil) So(len(repoMetaList), ShouldEqual, 0) }) Convey("Search with partial match", func() { err := metaDB.SetRepoReference(ctx, "alpine", tag1, image1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "pine", tag2, image2.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "golang", tag3, image3.AsImageMeta()) So(err, ShouldBeNil) repoMetaList, err := metaDB.SearchRepos(ctx, "pine") So(err, ShouldBeNil) So(len(repoMetaList), ShouldEqual, 2) }) Convey("Search multiple repos that share manifests", func() { err := metaDB.SetRepoReference(ctx, "alpine", tag1, image1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "pine", tag2, image1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "golang", tag3, image1.AsImageMeta()) So(err, ShouldBeNil) repoMetaList, err := metaDB.SearchRepos(ctx, "") So(err, ShouldBeNil) So(len(repoMetaList), ShouldEqual, 3) }) Convey("Search repos with access control", func() { err := metaDB.SetRepoReference(ctx, repo1, tag1, image1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo2, tag2, image2.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo3, tag3, image3.AsImageMeta()) So(err, ShouldBeNil) userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("username") userAc.SetGlobPatterns("read", map[string]bool{ repo1: true, repo2: true, }) ctx := userAc.DeriveContext(context.Background()) //nolint: contextcheck repoMetaList, err := metaDB.SearchRepos(ctx, "repo") //nolint: contextcheck So(err, ShouldBeNil) So(len(repoMetaList), ShouldEqual, 2) for _, k := range repoMetaList { So(k.Name, ShouldBeIn, []string{repo1, repo2}) } }) Convey("Search Repos with Indexes", func() { var ( tag4 = "0.0.4" subImage1 = CreateRandomImage() subImage2 = CreateRandomImage() multiarch = CreateMultiarchWith().Images([]Image{subImage1, subImage2}).Build() tag5 = "0.0.5" image1 = CreateRandomImage() ) err := metaDB.SetRepoReference(ctx, "repo", subImage1.DigestStr(), subImage1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", subImage2.DigestStr(), subImage2.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", tag4, multiarch.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", tag5, image1.AsImageMeta()) So(err, ShouldBeNil) repoMetaList, err := metaDB.SearchRepos(ctx, "repo") So(err, ShouldBeNil) So(len(repoMetaList), ShouldEqual, 1) So(repoMetaList[0].Name, ShouldResemble, "repo") So(repoMetaList[0].Tags, ShouldContainKey, tag4) So(repoMetaList[0].Tags[tag4].MediaType, ShouldResemble, ispec.MediaTypeImageIndex) So(repoMetaList[0].Tags, ShouldContainKey, tag5) So(repoMetaList[0].Tags[tag5].MediaType, ShouldResemble, ispec.MediaTypeImageManifest) }) }) Convey("Test SearchTags", func() { var ( repo1 = "repo1" repo2 = "repo2" image1 = CreateRandomImage() image2 = CreateRandomImage() image3 = CreateRandomImage() ctx = context.Background() ) err := metaDB.SetRepoReference(ctx, repo1, "0.0.1", image1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, "0.0.2", image3.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, "0.1.0", image2.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, "1.0.0", image2.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, "1.0.1", image2.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo2, "0.0.1", image3.AsImageMeta()) So(err, ShouldBeNil) Convey("With exact match", func() { fullImageMetaList, err := metaDB.SearchTags(ctx, "repo1:0.0.1") So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 1) So(fullImageMetaList[0].Digest.String(), ShouldResemble, image1.DigestStr()) }) Convey("With no match", func() { fullImageMetaList, err := metaDB.SearchTags(ctx, "repo1:badtag") So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 0) }) Convey("With no permission", func() { userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("user1") userAc.SetGlobPatterns("read", map[string]bool{ repo1: false, repo2: false, }, ) ctx1 := userAc.DeriveContext(context.Background()) fullImageMetaList, err := metaDB.SearchTags(ctx1, "repo1:0.0.1") So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 0) }) Convey("With partial repo path", func() { fullImageMetaList, err := metaDB.SearchTags(ctx, "repo:0.0.1") So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 0) }) Convey("With partial tag", func() { fullImageMetaList, err := metaDB.SearchTags(ctx, "repo1:0.0") So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 2) tags := map[string]struct{}{} for _, imageMeta := range fullImageMetaList { tags[imageMeta.Tag] = struct{}{} } So(tags, ShouldContainKey, "0.0.2") So(tags, ShouldContainKey, "0.0.1") fullImageMetaList, err = metaDB.SearchTags(ctx, "repo1:0.") So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 3) tags = map[string]struct{}{} for _, imageMeta := range fullImageMetaList { tags[imageMeta.Tag] = struct{}{} } So(tags, ShouldContainKey, "0.0.1") So(tags, ShouldContainKey, "0.0.2") So(tags, ShouldContainKey, "0.1.0") }) Convey("With bad query", func() { fullImageMetaList, err := metaDB.SearchTags(ctx, "repo:0.0.1:test") So(err, ShouldNotBeNil) So(len(fullImageMetaList), ShouldEqual, 0) }) Convey("Search with access control", func() { userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("username") userAc.SetGlobPatterns("read", map[string]bool{ repo1: true, repo2: false, }) ctx := userAc.DeriveContext(context.Background()) fullImageMetaList, err := metaDB.SearchTags(ctx, "repo1:") So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 5) So(fullImageMetaList[0].Repo, ShouldResemble, repo1) fullImageMetaList, err = metaDB.SearchTags(ctx, "repo2:") So(err, ShouldBeNil) So(fullImageMetaList, ShouldBeEmpty) }) Convey("Search Tags with Indexes", func() { var ( tag4 = "0.0.4" subImage1 = CreateRandomImage() subImage2 = CreateRandomImage() multiarch = CreateMultiarchWith().Images([]Image{subImage1, subImage2}).Build() tag5 = "0.0.5" image5 = CreateRandomImage() tag6 = "6.0.0" image6 = CreateRandomImage() ) err = metaDB.SetRepoReference(ctx, "repo", subImage1.DigestStr(), subImage1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", subImage2.DigestStr(), subImage2.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", tag4, multiarch.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", tag5, image5.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", tag6, image6.AsImageMeta()) So(err, ShouldBeNil) fullImageMetaList, err := metaDB.SearchTags(ctx, "repo:0.0") So(err, ShouldBeNil) tags := map[string]struct{}{} for _, imageMeta := range fullImageMetaList { tags[imageMeta.Tag] = struct{}{} } So(len(fullImageMetaList), ShouldEqual, 2) So(tags, ShouldContainKey, tag4) So(tags, ShouldContainKey, tag5) So(tags, ShouldNotContainKey, tag6) multiarchImageMeta := mTypes.FullImageMeta{} found := false for _, imageMeta := range fullImageMetaList { if imageMeta.MediaType == ispec.MediaTypeImageIndex { multiarchImageMeta = imageMeta found = true } } So(found, ShouldBeTrue) So(len(multiarchImageMeta.Manifests), ShouldEqual, 2) digests := []string{} for _, manifest := range multiarchImageMeta.Manifests { digests = append(digests, manifest.Digest.String()) } So(digests, ShouldContain, subImage1.DigestStr()) So(digests, ShouldContain, subImage2.DigestStr()) }) Convey("With referrer", func() { refImage := CreateRandomImageWith().Subject(image1.DescriptorRef()).Build() err := metaDB.SetRepoReference(ctx, repo1, "ref-tag", refImage.AsImageMeta()) So(err, ShouldBeNil) fullImageMetaList, err := metaDB.SearchTags(ctx, "repo1:0.0.1") So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 1) So(len(fullImageMetaList[0].Referrers), ShouldEqual, 1) So(fullImageMetaList[0].Referrers[0].Digest, ShouldResemble, refImage.DigestStr()) }) }) Convey("Test FilterTags", func() { var ( repo1 = "repo1" repo2 = "repo2" image1 = CreateRandomImage() image2 = CreateRandomImage() image3 = CreateRandomImage() subImage1 = CreateRandomImage() subImage2 = CreateRandomImage() multiarch = CreateMultiarchWith().Images([]Image{subImage1, subImage2}).Build() ctx = context.Background() ) err := metaDB.SetRepoReference(ctx, repo1, subImage1.DigestStr(), subImage1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, subImage2.DigestStr(), subImage2.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, "2.0.0", multiarch.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, "0.0.1", image1.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, "0.0.2", image3.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, "0.1.0", image2.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, "1.0.0", image2.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo1, "1.0.1", image2.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo2, "0.0.1", image3.AsImageMeta()) So(err, ShouldBeNil) Convey("Return all tags", func() { fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, mTypes.AcceptAllImageMeta) So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 7) tags := []string{} indexImage := mTypes.FullImageMeta{} for _, imageMeta := range fullImageMetaList { tags = append(tags, imageMeta.Tag) if imageMeta.MediaType == ispec.MediaTypeImageIndex { indexImage = imageMeta } } So(zcommon.Contains(tags, "0.0.1"), ShouldBeTrue) So(zcommon.Contains(tags, "0.0.2"), ShouldBeTrue) So(zcommon.Contains(tags, "0.1.0"), ShouldBeTrue) So(zcommon.Contains(tags, "1.0.0"), ShouldBeTrue) So(zcommon.Contains(tags, "1.0.1"), ShouldBeTrue) So(zcommon.Contains(tags, "2.0.0"), ShouldBeTrue) So(zcommon.Contains(tags, "0.0.1"), ShouldBeTrue) So(indexImage.Digest.String(), ShouldResemble, multiarch.DigestStr()) digests := []string{} for _, manifest := range indexImage.Manifests { digests = append(digests, manifest.Digest.String()) } So(digests, ShouldContain, subImage1.DigestStr()) So(digests, ShouldContain, subImage2.DigestStr()) }) Convey("Return all tags in a specific repo", func() { fullImageMetaList, err := metaDB.FilterTags(ctx, func(repo, tag string) bool { return repo == repo1 }, mTypes.AcceptAllImageMeta) So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 6) tags := map[string]struct{}{} indexImage := mTypes.FullImageMeta{} for _, imageMeta := range fullImageMetaList { tags[imageMeta.Tag] = struct{}{} if imageMeta.MediaType == ispec.MediaTypeImageIndex { indexImage = imageMeta } } So(tags, ShouldContainKey, "0.0.1") So(tags, ShouldContainKey, "0.0.2") So(tags, ShouldContainKey, "0.1.0") So(tags, ShouldContainKey, "1.0.0") So(tags, ShouldContainKey, "1.0.1") So(tags, ShouldContainKey, "2.0.0") So(indexImage.Digest.String(), ShouldResemble, multiarch.DigestStr()) digests := []string{} for _, manifest := range indexImage.Manifests { digests = append(digests, manifest.Digest.String()) } So(digests, ShouldContain, subImage1.DigestStr()) So(digests, ShouldContain, subImage2.DigestStr()) }) Convey("Filter everything out", func() { fullImageMetaList, err := metaDB.FilterTags(ctx, func(repo, tag string) bool { return false }, func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool { return false }, ) So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 0) }) Convey("Search with access control", func() { userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("username") userAc.SetGlobPatterns("read", map[string]bool{ repo1: false, repo2: true, }) ctx := userAc.DeriveContext(context.Background()) fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, mTypes.AcceptAllImageMeta) So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 1) So(fullImageMetaList[0].Repo, ShouldResemble, repo2) So(fullImageMetaList[0].Tag, ShouldResemble, "0.0.1") }) }) Convey("Test Referrers", func() { image1 := CreateRandomImage() err := metaDB.SetRepoReference(ctx, "repo", "tag", image1.AsImageMeta()) So(err, ShouldBeNil) // Artifact 1 with artifact type in Manifest artifact1 := CreateImageWith(). RandomLayers(10, 2).DefaultConfig(). ArtifactType("art-type1"). Subject(image1.DescriptorRef()). Build() err = metaDB.SetRepoReference(ctx, "repo", artifact1.DigestStr(), artifact1.AsImageMeta()) So(err, ShouldBeNil) // Artifact 2 with artifact type in Config media type artifact2 := CreateImageWith(). RandomLayers(10, 2). ArtifactConfig("art-type2"). Subject(image1.DescriptorRef()). Build() err = metaDB.SetRepoReference(ctx, "repo", artifact2.DigestStr(), artifact2.AsImageMeta()) So(err, ShouldBeNil) // GetReferrers referrers, err := metaDB.GetReferrersInfo("repo", image1.Digest(), nil) So(len(referrers), ShouldEqual, 2) So(referrers, ShouldContain, mTypes.ReferrerInfo{ Digest: artifact1.DigestStr(), MediaType: ispec.MediaTypeImageManifest, ArtifactType: "art-type1", Size: int(artifact1.ManifestDescriptor.Size), }) So(referrers, ShouldContain, mTypes.ReferrerInfo{ Digest: artifact2.DigestStr(), MediaType: ispec.MediaTypeImageManifest, ArtifactType: "art-type2", Size: int(artifact2.ManifestDescriptor.Size), }) So(err, ShouldBeNil) // Delete the Referrers err = metaDB.RemoveRepoReference("repo", artifact1.DigestStr(), artifact1.Digest()) So(err, ShouldBeNil) referrers, err = metaDB.GetReferrersInfo("repo", image1.Digest(), nil) So(err, ShouldBeNil) So(len(referrers), ShouldEqual, 1) err = metaDB.RemoveRepoReference("repo", artifact2.DigestStr(), artifact2.Digest()) So(err, ShouldBeNil) referrers, err = metaDB.GetReferrersInfo("repo", image1.Digest(), nil) So(err, ShouldBeNil) So(len(referrers), ShouldEqual, 0) }) Convey("Test Referrers add same one twice but with different tags - delete by tag then digest", func() { _, err := metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldNotBeNil) tag := "tag1" refTag := "refTag" image := CreateRandomImage() referrer := CreateRandomImageWith().Subject(image.DescriptorRef()).Build() err = metaDB.SetRepoReference(ctx, "repo", tag, image.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", refTag, referrer.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", referrer.DigestStr(), referrer.AsImageMeta()) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldBeNil) So(len(repoMeta.Referrers[image.DigestStr()]), ShouldEqual, 1) err = metaDB.RemoveRepoReference("repo", refTag, referrer.Digest()) So(err, ShouldBeNil) // we still have the untagged manifest repoMeta, err = metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldBeNil) So(len(repoMeta.Referrers[image.DigestStr()]), ShouldEqual, 1) err = metaDB.RemoveRepoReference("repo", referrer.DigestStr(), referrer.Digest()) So(err, ShouldBeNil) repoMeta, err = metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldBeNil) So(len(repoMeta.Referrers[image.DigestStr()]), ShouldEqual, 0) }) Convey("Test Referrers add same one twice but with different tags - delete by digest", func() { _, err := metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldNotBeNil) tag := "tag-1" refTag := "refTag" image := CreateRandomImage() referrer := CreateRandomImageWith().Subject(image.DescriptorRef()).Build() err = metaDB.SetRepoReference(ctx, "repo", tag, image.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", refTag, referrer.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", referrer.DigestStr(), referrer.AsImageMeta()) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldBeNil) So(len(repoMeta.Referrers[image.DigestStr()]), ShouldEqual, 1) // this should delete all references err = metaDB.RemoveRepoReference("repo", referrer.DigestStr(), referrer.Digest()) So(err, ShouldBeNil) repoMeta, err = metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldBeNil) So(len(repoMeta.Referrers[image.DigestStr()]), ShouldEqual, 0) }) Convey("Test Referrers add same one twice", func() { _, err := metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldNotBeNil) tag := "tag-ref" image := CreateRandomImage() referrer := CreateRandomImageWith().Subject(image.DescriptorRef()).Build() err = metaDB.SetRepoReference(ctx, "repo", tag, image.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", referrer.DigestStr(), referrer.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, "repo", referrer.DigestStr(), referrer.AsImageMeta()) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldBeNil) So(len(repoMeta.Referrers[image.DigestStr()]), ShouldEqual, 1) }) Convey("GetReferrersInfo", func() { repo := "repo" tag := "test-tag" image := CreateRandomImage() err := metaDB.SetRepoReference(ctx, repo, tag, image.AsImageMeta()) So(err, ShouldBeNil) referrerWantedType := CreateRandomImageWith(). ArtifactType("wanted-type"). Subject(image.DescriptorRef()).Build() referrerNotWantedType := CreateRandomImageWith(). ArtifactType("not-wanted-type"). Subject(image.DescriptorRef()).Build() err = metaDB.SetRepoReference(ctx, repo, referrerWantedType.DigestStr(), referrerWantedType.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo, referrerNotWantedType.DigestStr(), referrerNotWantedType.AsImageMeta()) So(err, ShouldBeNil) referrerInfo, err := metaDB.GetReferrersInfo("repo", image.Digest(), []string{"wanted-type"}) So(err, ShouldBeNil) So(len(referrerInfo), ShouldEqual, 1) So(referrerInfo[0].ArtifactType, ShouldResemble, "wanted-type") So(referrerInfo[0].Digest, ShouldResemble, referrerWantedType.DigestStr()) }) Convey("FilterImageMeta", func() { repo := "repo" tag := "tag" Convey("Just manifests", func() { image := CreateRandomImage() err := metaDB.SetRepoReference(ctx, repo, tag, image.AsImageMeta()) So(err, ShouldBeNil) imageMeta, err := metaDB.FilterImageMeta(ctx, []string{image.DigestStr()}) So(err, ShouldBeNil) So(imageMeta, ShouldContainKey, image.DigestStr()) _, err = metaDB.FilterImageMeta(ctx, []string{image.DigestStr(), "bad-digest"}) So(err, ShouldNotBeNil) }) Convey("Index", func() { multi := CreateRandomMultiarch() digests := []string{} for i := range multi.Images { err := metaDB.SetRepoReference(ctx, repo, multi.Images[i].DigestStr(), multi.Images[i].AsImageMeta()) So(err, ShouldBeNil) digests = append(digests, multi.Images[i].DigestStr()) } err := metaDB.SetRepoReference(ctx, repo, tag, multi.AsImageMeta()) So(err, ShouldBeNil) imageMeta, err := metaDB.FilterImageMeta(ctx, []string{multi.DigestStr()}) So(err, ShouldBeNil) So(imageMeta, ShouldContainKey, multi.DigestStr()) actualDigests := tCommon.AccumulateField(imageMeta[multi.DigestStr()].Manifests, getManifestDigest) So(tCommon.ContainSameElements(actualDigests, digests), ShouldBeTrue) }) }) Convey("ResetDB", func() { repo := "repo-reset" tag := "tag-reset" image := CreateRandomImage() referrer := CreateRandomImageWith().Subject(image.DescriptorRef()).Build() err := metaDB.SetRepoReference(ctx, repo, tag, image.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo, tag, referrer.AsImageMeta()) So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, repo) So(err, ShouldBeNil) So(repoMeta.Tags, ShouldNotBeEmpty) So(repoMeta.Statistics, ShouldNotBeEmpty) So(repoMeta.Referrers, ShouldNotBeEmpty) err = metaDB.ResetDB() So(err, ShouldBeNil) _, err = metaDB.GetRepoMeta(ctx, repo) So(err, ShouldNotBeNil) }) Convey("FilterRepos", func() { repo := "repoFilter" repoUnmatched := "repoExcluded" tag1 := "tag1" tag2 := "tag22" tag3 := "tag3" tag4 := "tag44" userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("username") userAc.SetGlobPatterns("read", map[string]bool{ repo: true, repoUnmatched: false, }) ctx := userAc.DeriveContext(context.Background()) image := CreateImageWith().DefaultLayers().PlatformConfig("image-platform", "image-os").Build() err := metaDB.SetRepoReference(ctx, repo, tag1, image.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repoUnmatched, tag3, image.AsImageMeta()) So(err, ShouldBeNil) multiarch := CreateMultiarchWith(). Images([]Image{ CreateImageWith().DefaultLayers().PlatformConfig("sub-platform1", "sub-os1").Build(), CreateImageWith().DefaultLayers().PlatformConfig("sub-platform2", "sub-os2").Build(), }).Build() for _, img := range multiarch.Images { err := metaDB.SetRepoReference(ctx, repo, img.DigestStr(), img.AsImageMeta()) So(err, ShouldBeNil) } err = metaDB.SetRepoReference(ctx, repo, tag2, multiarch.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repoUnmatched, tag4, multiarch.AsImageMeta()) So(err, ShouldBeNil) //nolint: contextcheck repoMetaList, err := metaDB.FilterRepos(ctx, mTypes.AcceptAllRepoNames, mTypes.AcceptAllRepoMeta) So(err, ShouldBeNil) So(len(repoMetaList), ShouldEqual, 1) repoMeta := repoMetaList[0] So(repoMeta.Platforms, ShouldContain, ispec.Platform{Architecture: "image-platform", OS: "image-os"}) So(repoMeta.Platforms, ShouldContain, ispec.Platform{Architecture: "sub-platform1", OS: "sub-os1"}) So(repoMeta.Platforms, ShouldContain, ispec.Platform{Architecture: "sub-platform2", OS: "sub-os2"}) }) Convey("Test bookmarked/starred field present in returned RepoMeta", func() { repo99 := "repo99" userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("user1") userAc.SetGlobPatterns("read", map[string]bool{ repo99: true, }) ctx := userAc.DeriveContext(context.Background()) image := CreateRandomImage() err := metaDB.SetRepoReference(ctx, repo99, "tag", image.AsImageMeta()) So(err, ShouldBeNil) repoMetaList, err := metaDB.SearchRepos(ctx, repo99) So(err, ShouldBeNil) So(len(repoMetaList), ShouldEqual, 1) So(repoMetaList[0].IsBookmarked, ShouldBeFalse) So(repoMetaList[0].IsStarred, ShouldBeFalse) fullImageMetaList, err := metaDB.SearchTags(ctx, repo99+":") So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 1) So(fullImageMetaList[0].IsBookmarked, ShouldBeFalse) So(fullImageMetaList[0].IsStarred, ShouldBeFalse) repoMetaList, err = metaDB.FilterRepos(ctx, mTypes.AcceptAllRepoNames, mTypes.AcceptAllRepoMeta) So(err, ShouldBeNil) So(len(repoMetaList), ShouldEqual, 1) So(repoMetaList[0].IsBookmarked, ShouldBeFalse) So(repoMetaList[0].IsStarred, ShouldBeFalse) fullImageMetaList, err = metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, mTypes.AcceptAllImageMeta) So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 1) So(fullImageMetaList[0].IsBookmarked, ShouldBeFalse) So(fullImageMetaList[0].IsStarred, ShouldBeFalse) _, err = metaDB.ToggleBookmarkRepo(ctx, repo99) So(err, ShouldBeNil) _, err = metaDB.ToggleStarRepo(ctx, repo99) So(err, ShouldBeNil) repoMetaList, err = metaDB.SearchRepos(ctx, repo99) So(err, ShouldBeNil) So(len(repoMetaList), ShouldEqual, 1) So(repoMetaList[0].IsBookmarked, ShouldBeTrue) So(repoMetaList[0].IsStarred, ShouldBeTrue) fullImageMetaList, err = metaDB.SearchTags(ctx, repo99+":") So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 1) So(fullImageMetaList[0].IsBookmarked, ShouldBeTrue) So(fullImageMetaList[0].IsStarred, ShouldBeTrue) repoMetaList, err = metaDB.FilterRepos(ctx, mTypes.AcceptAllRepoNames, mTypes.AcceptAllRepoMeta) So(err, ShouldBeNil) So(len(repoMetaList), ShouldEqual, 1) So(repoMetaList[0].IsBookmarked, ShouldBeTrue) So(repoMetaList[0].IsStarred, ShouldBeTrue) fullImageMetaList, err = metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, mTypes.AcceptAllImageMeta) So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 1) So(fullImageMetaList[0].IsBookmarked, ShouldBeTrue) So(fullImageMetaList[0].IsStarred, ShouldBeTrue) }) Convey("Test GetUserRepoMeta", func() { err := metaDB.ResetDB() So(err, ShouldBeNil) userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("user1") userAc.SetGlobPatterns("read", map[string]bool{ "repo": true, }) ctx := userAc.DeriveContext(context.Background()) err = metaDB.SetRepoReference(ctx, "repo", "tag", CreateDefaultImage().AsImageMeta()) So(err, ShouldBeNil) _, err = metaDB.ToggleBookmarkRepo(ctx, "repo") So(err, ShouldBeNil) _, err = metaDB.ToggleStarRepo(ctx, "repo") So(err, ShouldBeNil) repoMeta, err := metaDB.GetRepoMeta(ctx, "repo") So(err, ShouldBeNil) So(repoMeta.IsBookmarked, ShouldBeTrue) So(repoMeta.IsStarred, ShouldBeTrue) So(repoMeta.Tags, ShouldContainKey, "tag") }) Convey("GetAllRepoNames", func() { repo1 := "repo1" repo2 := "repo2" repo3 := "repo3" imageMeta := CreateRandomImage().AsImageMeta() err := metaDB.SetRepoReference(ctx, repo1, "tag", imageMeta) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo2, "tag", imageMeta) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repo3, "tag", imageMeta) So(err, ShouldBeNil) repos, err := metaDB.GetAllRepoNames() So(err, ShouldBeNil) So(repos, ShouldContain, repo1) So(repos, ShouldContain, repo2) So(repos, ShouldContain, repo3) err = metaDB.DeleteRepoMeta(repo1) So(err, ShouldBeNil) repos, err = metaDB.GetAllRepoNames() So(err, ShouldBeNil) So(repos, ShouldNotContain, repo1) So(repos, ShouldContain, repo2) So(repos, ShouldContain, repo3) err = metaDB.DeleteRepoMeta(repo2) So(err, ShouldBeNil) repos, err = metaDB.GetAllRepoNames() So(err, ShouldBeNil) So(repos, ShouldNotContain, repo1) So(repos, ShouldNotContain, repo2) So(repos, ShouldContain, repo3) err = metaDB.SetRepoReference(ctx, repo1, "tag", imageMeta) So(err, ShouldBeNil) repos, err = metaDB.GetAllRepoNames() So(err, ShouldBeNil) So(repos, ShouldContain, repo1) So(repos, ShouldNotContain, repo2) So(repos, ShouldContain, repo3) err = metaDB.DeleteRepoMeta(repo1) So(err, ShouldBeNil) repos, err = metaDB.GetAllRepoNames() So(err, ShouldBeNil) So(repos, ShouldNotContain, repo1) So(repos, ShouldNotContain, repo2) So(repos, ShouldContain, repo3) err = metaDB.DeleteRepoMeta(repo3) So(err, ShouldBeNil) repos, err = metaDB.GetAllRepoNames() So(err, ShouldBeNil) So(repos, ShouldNotContain, repo1) So(repos, ShouldNotContain, repo2) So(repos, ShouldNotContain, repo3) }) Convey("Test Nested Indexes", func() { // nested manifest/indexes: // image111 -> multiArchBottom11 -> multiArchMiddle1 -> multiArchTop // image112 -> multiArchBottom11 -> multiArchMiddle1 -> multiArchTop // image121 -> multiArchBottom12 -> multiArchMiddle1 -> multiArchTop // image122 -> multiArchBottom12 -> multiArchMiddle1 -> multiArchTop // image211 -> multiArchBottom21 -> multiArchMiddle2 -> multiArchTop // image212 -> multiArchBottom21 -> multiArchMiddle2 -> multiArchTop // image31 -> multiArchMiddle3 -> multiArchTop // image32 -> multiArchMiddle3 -> multiArchTop repoName := "nested" image111 := CreateRandomImage() image112 := CreateRandomImage() multiArchBottom11 := CreateMultiarchWith().Images([]Image{image111, image112}).Build() err := metaDB.SetRepoReference(ctx, repoName, image111.DigestStr(), image111.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repoName, image112.DigestStr(), image112.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repoName, multiArchBottom11.DigestStr(), multiArchBottom11.AsImageMeta()) So(err, ShouldBeNil) image121 := CreateRandomImage() image122 := CreateRandomImage() multiArchBottom12 := CreateMultiarchWith().Images([]Image{image121, image122}).Build() err = metaDB.SetRepoReference(ctx, repoName, image121.DigestStr(), image121.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repoName, image122.DigestStr(), image122.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repoName, multiArchBottom12.DigestStr(), multiArchBottom12.AsImageMeta()) So(err, ShouldBeNil) indexMultiArchMiddle1 := ispec.Index{ Versioned: specs.Versioned{SchemaVersion: 2}, MediaType: ispec.MediaTypeImageIndex, Manifests: []ispec.Descriptor{ { Digest: multiArchBottom11.IndexDescriptor.Digest, Size: multiArchBottom11.IndexDescriptor.Size, MediaType: ispec.MediaTypeImageIndex, }, { Digest: multiArchBottom12.IndexDescriptor.Digest, Size: multiArchBottom12.IndexDescriptor.Size, MediaType: ispec.MediaTypeImageIndex, }, }, } indexMultiArchMiddle1Blob, err := json.Marshal(indexMultiArchMiddle1) So(err, ShouldBeNil) indexMultiArchMiddle1Digest := godigest.FromBytes(indexMultiArchMiddle1Blob) indexMultiArchMiddle1Meta := mTypes.ImageMeta{ MediaType: ispec.MediaTypeImageIndex, Digest: indexMultiArchMiddle1Digest, Size: int64(len(indexMultiArchMiddle1Blob)), Index: &indexMultiArchMiddle1, } err = metaDB.SetRepoReference(ctx, repoName, "multiArchMiddle1", indexMultiArchMiddle1Meta) So(err, ShouldBeNil) image211 := CreateRandomImage() image212 := CreateRandomImage() multiArchBottom21 := CreateMultiarchWith().Images([]Image{image211, image212}).Build() err = metaDB.SetRepoReference(ctx, repoName, image211.DigestStr(), image211.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repoName, image212.DigestStr(), image212.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repoName, multiArchBottom21.DigestStr(), multiArchBottom21.AsImageMeta()) So(err, ShouldBeNil) indexMultiArchMiddle2 := ispec.Index{ Versioned: specs.Versioned{SchemaVersion: 2}, MediaType: ispec.MediaTypeImageIndex, Manifests: []ispec.Descriptor{ { Digest: multiArchBottom21.IndexDescriptor.Digest, Size: multiArchBottom21.IndexDescriptor.Size, MediaType: ispec.MediaTypeImageIndex, }, }, } indexMultiArchMiddle2Blob, err := json.Marshal(indexMultiArchMiddle2) So(err, ShouldBeNil) indexMultiArchMiddle2Digest := godigest.FromBytes(indexMultiArchMiddle2Blob) indexMultiArchMiddle2Meta := mTypes.ImageMeta{ MediaType: ispec.MediaTypeImageIndex, Digest: indexMultiArchMiddle2Digest, Size: int64(len(indexMultiArchMiddle2Blob)), Index: &indexMultiArchMiddle2, } err = metaDB.SetRepoReference(ctx, repoName, "multiArchMiddle2", indexMultiArchMiddle2Meta) So(err, ShouldBeNil) image31 := CreateRandomImage() image32 := CreateRandomImage() multiArchBottom3 := CreateMultiarchWith().Images([]Image{image31, image32}).Build() err = metaDB.SetRepoReference(ctx, repoName, image31.DigestStr(), image31.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repoName, image32.DigestStr(), image32.AsImageMeta()) So(err, ShouldBeNil) err = metaDB.SetRepoReference(ctx, repoName, multiArchBottom3.DigestStr(), multiArchBottom3.AsImageMeta()) So(err, ShouldBeNil) indexMultiArchTop := ispec.Index{ Versioned: specs.Versioned{SchemaVersion: 2}, MediaType: ispec.MediaTypeImageIndex, Manifests: []ispec.Descriptor{ { Digest: indexMultiArchMiddle1Digest, Size: int64(len(indexMultiArchMiddle1Blob)), MediaType: ispec.MediaTypeImageIndex, }, { Digest: indexMultiArchMiddle2Digest, Size: int64(len(indexMultiArchMiddle2Blob)), MediaType: ispec.MediaTypeImageIndex, }, { Digest: multiArchBottom3.IndexDescriptor.Digest, Size: multiArchBottom3.IndexDescriptor.Size, MediaType: ispec.MediaTypeImageIndex, }, }, } indexMultiArchTopBlob, err := json.Marshal(indexMultiArchTop) So(err, ShouldBeNil) indexMultiArchTopBlobDigest := godigest.FromBytes(indexMultiArchTopBlob) indexMultiArchTopMeta := mTypes.ImageMeta{ MediaType: ispec.MediaTypeImageIndex, Digest: indexMultiArchTopBlobDigest, Size: int64(len(indexMultiArchTopBlob)), Index: &indexMultiArchTop, } err = metaDB.SetRepoReference(ctx, repoName, "multiArchTop", indexMultiArchTopMeta) So(err, ShouldBeNil) Convey("Test searchTags", func() { fullImageMetaList, err := metaDB.SearchTags(ctx, repoName+":"+"multiArch") So(err, ShouldBeNil) So(len(fullImageMetaList), ShouldEqual, 3) tags := map[string][]string{} for _, imageMeta := range fullImageMetaList { So(imageMeta.MediaType, ShouldEqual, ispec.MediaTypeImageIndex) digests := []string{} for _, manifest := range imageMeta.Manifests { digests = append(digests, manifest.Digest.String()) } tags[imageMeta.Tag] = digests } So(tags, ShouldContainKey, "multiArchMiddle1") So(tags, ShouldContainKey, "multiArchMiddle2") So(tags, ShouldContainKey, "multiArchTop") So(len(tags["multiArchMiddle1"]), ShouldEqual, 4) So(len(tags["multiArchMiddle2"]), ShouldEqual, 2) So(len(tags["multiArchTop"]), ShouldEqual, 8) }) }) }) } func TestRelevanceSorting(t *testing.T) { Convey("Test Relevance Sorting", t, func() { So(common.RankRepoName("alpine", "alpine"), ShouldEqual, 0) So(common.RankRepoName("test/alpine", "test/alpine"), ShouldEqual, 0) So(common.RankRepoName("test/alpine", "alpine"), ShouldEqual, -1) So(common.RankRepoName("alpine", "test/alpine"), ShouldEqual, 1) So(common.RankRepoName("test", "test/alpine"), ShouldEqual, 10) So(common.RankRepoName("pine", "test/alpine"), ShouldEqual, 3) So(common.RankRepoName("pine", "alpine/alpine"), ShouldEqual, 3) So(common.RankRepoName("pine", "alpine/test"), ShouldEqual, 30) So(common.RankRepoName("test/pine", "alpine"), ShouldEqual, -1) So(common.RankRepoName("repo/test", "repo/test/alpine"), ShouldEqual, 10) So(common.RankRepoName("repo/test/golang", "repo/test2/alpine"), ShouldEqual, -1) So(common.RankRepoName("repo/test/pine", "repo/test/alpine"), ShouldEqual, 3) So(common.RankRepoName("debian", "c3/debian/base-amd64"), ShouldEqual, 400) So(common.RankRepoName("debian/base-amd64", "c3/debian/base-amd64"), ShouldEqual, 400) So(common.RankRepoName("debian/base-amd64", "c3/aux/debian/base-amd64"), ShouldEqual, 800) So(common.RankRepoName("aux/debian", "c3/aux/debian/base-amd64"), ShouldEqual, 400) }) } func TestCreateBoltDB(t *testing.T) { Convey("New() succeeds", t, func() { rootDir := t.TempDir() conf := config.New() conf.Storage.RootDirectory = rootDir log := log.NewLogger("debug", "") So(log, ShouldNotBeNil) Convey("Test New() with unspecified driver", func() { conf.Storage.CacheDriver = map[string]interface{}{} }) Convey("Test New() with bad driver", func() { // we default to bolt in case of misconfiguration conf.Storage.CacheDriver = map[string]interface{}{"name": "somedriver"} }) Convey("Test New() with specified driver", func() { conf.Storage.CacheDriver = map[string]interface{}{"name": "cache"} }) repoDBPath := path.Join(rootDir, "meta.db") defer os.Remove(repoDBPath) metaDB, err := meta.New(conf.Storage.StorageConfig, log) So(err, ShouldBeNil) So(metaDB, ShouldNotBeNil) err = os.Chmod(repoDBPath, 0o200) So(err, ShouldBeNil) metaDB, err = meta.New(conf.Storage.StorageConfig, log) So(err, ShouldNotBeNil) So(metaDB, ShouldBeNil) err = os.Chmod(repoDBPath, 0o600) So(err, ShouldBeNil) }) } func TestCreateRedisDB(t *testing.T) { Convey("Test New()", t, func() { conf := config.New() conf.Storage.RemoteCache = true log := log.NewLogger("debug", "") So(log, ShouldNotBeNil) Convey("Succeeds with default key prefix", func() { miniRedis := miniredis.RunT(t) cacheDriverParams := map[string]interface{}{ "name": "redis", "url": "redis://" + miniRedis.Addr(), } conf.Storage.CacheDriver = cacheDriverParams metaDB, err := meta.New(conf.Storage.StorageConfig, log) So(err, ShouldBeNil) So(metaDB, ShouldNotBeNil) }) Convey("Succeeds with specific key prefix", func() { miniRedis := miniredis.RunT(t) cacheDriverParams := map[string]interface{}{ "name": "redis", "url": "redis://" + miniRedis.Addr(), "key": "keyPrefix", } conf.Storage.CacheDriver = cacheDriverParams metaDB, err := meta.New(conf.Storage.StorageConfig, log) So(err, ShouldBeNil) So(metaDB, ShouldNotBeNil) }) Convey("Fails on Ping()", func() { // Redis client will not be responding cacheDriverParams := map[string]interface{}{ "name": "redis", "url": "redis://127.0.0.1:" + tCommon.GetFreePort(), } conf.Storage.CacheDriver = cacheDriverParams _, err := meta.New(conf.Storage.StorageConfig, log) So(err, ShouldNotBeNil) }) Convey("Fail on invalid parameters", func() { // Bad key types cacheDriverParams := map[string]interface{}{ "name": "redis", "url": "redis://127.0.0.1:" + tCommon.GetFreePort(), "keyprefix": true, } conf.Storage.CacheDriver = cacheDriverParams testFunc := func() { _, _ = meta.New(conf.Storage.StorageConfig, log) } So(testFunc, ShouldPanic) cacheDriverParams = map[string]interface{}{ "name": "redis", "url": "redis://127.0.0.1:" + tCommon.GetFreePort(), "keyprefix": "", } conf.Storage.CacheDriver = cacheDriverParams testFunc = func() { _, _ = meta.New(conf.Storage.StorageConfig, log) } So(testFunc, ShouldPanic) cacheDriverParams = map[string]interface{}{ "name": "redis", "url": false, "keyprefix": "zot", } conf.Storage.CacheDriver = cacheDriverParams _, err := meta.New(conf.Storage.StorageConfig, log) So(err, ShouldNotBeNil) }) }) }