0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2025-01-06 22:40:28 -05:00
zot/pkg/meta/dynamodb/dynamodb_test.go
peusebiu 9074f8483b
feat(retention): added image retention policies (#1866)
feat(metaDB): add more image statistics info

Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
2023-11-01 09:16:18 -07:00

628 lines
19 KiB
Go

package dynamodb_test
import (
"context"
"os"
"testing"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
guuid "github.com/gofrs/uuid"
"github.com/rs/zerolog"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.io/zot/pkg/extensions/imagetrust"
"zotregistry.io/zot/pkg/log"
mdynamodb "zotregistry.io/zot/pkg/meta/dynamodb"
mTypes "zotregistry.io/zot/pkg/meta/types"
reqCtx "zotregistry.io/zot/pkg/requestcontext"
. "zotregistry.io/zot/pkg/test/image-utils"
tskip "zotregistry.io/zot/pkg/test/skip"
)
const badTablename = "bad tablename"
func TestIterator(t *testing.T) {
tskip.SkipDynamo(t)
const region = "us-east-2"
endpoint := os.Getenv("DYNAMODBMOCK_ENDPOINT")
uuid, err := guuid.NewV4()
if err != nil {
panic(err)
}
repoMetaTablename := "RepoMetadataTable" + uuid.String()
versionTablename := "Version" + uuid.String()
imageMetaTablename := "ImageMeta" + uuid.String()
repoBlobsTablename := "RepoBlobs" + uuid.String()
userDataTablename := "UserDataTable" + uuid.String()
apiKeyTablename := "ApiKeyTable" + uuid.String()
log := log.NewLogger("debug", "")
Convey("TestIterator", t, func() {
params := mdynamodb.DBDriverParameters{
Endpoint: endpoint,
Region: region,
RepoMetaTablename: repoMetaTablename,
ImageMetaTablename: imageMetaTablename,
RepoBlobsInfoTablename: repoBlobsTablename,
VersionTablename: versionTablename,
APIKeyTablename: apiKeyTablename,
UserDataTablename: userDataTablename,
}
client, err := mdynamodb.GetDynamoClient(params)
So(err, ShouldBeNil)
dynamoWrapper, err := mdynamodb.New(client, params, log)
So(err, ShouldBeNil)
So(dynamoWrapper.ResetTable(dynamoWrapper.ImageMetaTablename), ShouldBeNil)
So(dynamoWrapper.ResetTable(dynamoWrapper.RepoMetaTablename), ShouldBeNil)
err = dynamoWrapper.SetRepoReference(context.Background(), "repo1", "tag1", CreateRandomImage().AsImageMeta())
So(err, ShouldBeNil)
err = dynamoWrapper.SetRepoReference(context.Background(), "repo2", "tag2", CreateRandomImage().AsImageMeta())
So(err, ShouldBeNil)
err = dynamoWrapper.SetRepoReference(context.Background(), "repo3", "tag3", CreateRandomImage().AsImageMeta())
So(err, ShouldBeNil)
repoMetaAttributeIterator := mdynamodb.NewBaseDynamoAttributesIterator(
dynamoWrapper.Client,
dynamoWrapper.RepoMetaTablename,
"RepoMetadata",
1,
log,
)
attribute, err := repoMetaAttributeIterator.First(context.Background())
So(err, ShouldBeNil)
So(attribute, ShouldNotBeNil)
attribute, err = repoMetaAttributeIterator.Next(context.Background())
So(err, ShouldBeNil)
So(attribute, ShouldNotBeNil)
attribute, err = repoMetaAttributeIterator.Next(context.Background())
So(err, ShouldBeNil)
So(attribute, ShouldNotBeNil)
attribute, err = repoMetaAttributeIterator.Next(context.Background())
So(err, ShouldBeNil)
So(attribute, ShouldBeNil)
})
}
func TestIteratorErrors(t *testing.T) {
Convey("errors", t, func() {
customResolver := aws.EndpointResolverWithOptionsFunc(
func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
PartitionID: "aws",
URL: "endpoint",
SigningRegion: region,
}, nil
})
cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion("region"),
config.WithEndpointResolverWithOptions(customResolver))
So(err, ShouldBeNil)
repoMetaAttributeIterator := mdynamodb.NewBaseDynamoAttributesIterator(
dynamodb.NewFromConfig(cfg),
"RepoMetadataTable",
"RepoMetadata",
1,
log.Logger{Logger: zerolog.New(os.Stdout)},
)
_, err = repoMetaAttributeIterator.First(context.Background())
So(err, ShouldNotBeNil)
})
}
func TestWrapperErrors(t *testing.T) {
tskip.SkipDynamo(t)
const region = "us-east-2"
endpoint := os.Getenv("DYNAMODBMOCK_ENDPOINT")
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()
wrongTableName := "WRONG Tables"
imageMetaTablename := "ImageMeta" + uuid.String()
repoBlobsTablename := "RepoBlobs" + uuid.String()
log := log.NewLogger("debug", "")
Convey("Errors", t, func() {
params := mdynamodb.DBDriverParameters{ //nolint:contextcheck
Endpoint: endpoint,
Region: region,
RepoMetaTablename: repoMetaTablename,
ImageMetaTablename: imageMetaTablename,
RepoBlobsInfoTablename: repoBlobsTablename,
UserDataTablename: userDataTablename,
APIKeyTablename: apiKeyTablename,
VersionTablename: versionTablename,
}
client, err := mdynamodb.GetDynamoClient(params) //nolint:contextcheck
So(err, ShouldBeNil)
imgTrustStore, err := imagetrust.NewAWSImageTrustStore(params.Region, params.Endpoint)
So(err, ShouldBeNil)
dynamoWrapper, err := mdynamodb.New(client, params, log) //nolint:contextcheck
So(err, ShouldBeNil)
dynamoWrapper.SetImageTrustStore(imgTrustStore)
So(dynamoWrapper.ResetTable(dynamoWrapper.RepoMetaTablename), ShouldBeNil) //nolint:contextcheck
So(dynamoWrapper.ResetTable(dynamoWrapper.ImageMetaTablename), ShouldBeNil) //nolint:contextcheck
So(dynamoWrapper.ResetTable(dynamoWrapper.UserDataTablename), ShouldBeNil) //nolint:contextcheck
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("test")
ctx := userAc.DeriveContext(context.Background())
Convey("SetUserData", func() {
hashKey := "id"
apiKeys := make(map[string]mTypes.APIKeyDetails)
apiKeyDetails := mTypes.APIKeyDetails{
Label: "apiKey",
Scopes: []string{"repo"},
UUID: hashKey,
}
apiKeys[hashKey] = apiKeyDetails
userProfileSrc := mTypes.UserData{
Groups: []string{"group1", "group2"},
APIKeys: apiKeys,
}
err := dynamoWrapper.SetUserData(ctx, userProfileSrc)
So(err, ShouldBeNil)
userAc := reqCtx.NewUserAccessControl()
ctx := userAc.DeriveContext(context.Background())
err = dynamoWrapper.SetUserData(ctx, mTypes.UserData{}) //nolint: contextcheck
So(err, ShouldNotBeNil)
})
Convey("DeleteUserData", func() {
err := dynamoWrapper.DeleteUserData(ctx)
So(err, ShouldBeNil)
userAc := reqCtx.NewUserAccessControl()
ctx := userAc.DeriveContext(context.Background())
err = dynamoWrapper.DeleteUserData(ctx) //nolint: contextcheck
So(err, ShouldNotBeNil)
})
Convey("ToggleBookmarkRepo no access", func() {
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("username")
userAc.SetGlobPatterns("read", map[string]bool{
"repo": false,
})
ctx := userAc.DeriveContext(context.Background())
_, err := dynamoWrapper.ToggleBookmarkRepo(ctx, "unaccesible")
So(err, ShouldNotBeNil)
})
Convey("ToggleBookmarkRepo GetUserMeta no user data", func() {
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("username")
userAc.SetGlobPatterns("read", map[string]bool{
"repo": true,
})
ctx := userAc.DeriveContext(context.Background())
status, err := dynamoWrapper.ToggleBookmarkRepo(ctx, "repo")
So(err, ShouldBeNil)
So(status, ShouldEqual, mTypes.Added)
})
Convey("ToggleBookmarkRepo GetUserMeta client error", func() {
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("username")
userAc.SetGlobPatterns("read", map[string]bool{
"repo": false,
})
ctx := userAc.DeriveContext(context.Background())
dynamoWrapper.UserDataTablename = badTablename
status, err := dynamoWrapper.ToggleBookmarkRepo(ctx, "repo")
So(err, ShouldNotBeNil)
So(status, ShouldEqual, mTypes.NotChanged)
})
Convey("GetBookmarkedRepos", func() {
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("username")
userAc.SetGlobPatterns("read", map[string]bool{
"repo": false,
})
ctx := userAc.DeriveContext(context.Background())
repos, err := dynamoWrapper.GetBookmarkedRepos(ctx)
So(err, ShouldBeNil)
So(len(repos), ShouldEqual, 0)
})
Convey("ToggleStarRepo GetUserMeta bad context", func() {
uacKey := reqCtx.GetContextKey()
ctx := context.WithValue(context.Background(), uacKey, "bad context")
_, err := dynamoWrapper.ToggleStarRepo(ctx, "repo")
So(err, ShouldNotBeNil)
})
Convey("ToggleStarRepo GetUserMeta no access", func() {
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("username")
userAc.SetGlobPatterns("read", map[string]bool{
"repo": false,
})
ctx := userAc.DeriveContext(context.Background())
_, err := dynamoWrapper.ToggleStarRepo(ctx, "unaccesible")
So(err, ShouldNotBeNil)
})
Convey("ToggleStarRepo GetUserMeta error", func() {
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("username")
userAc.SetGlobPatterns("read", map[string]bool{
"repo": false,
})
ctx := userAc.DeriveContext(context.Background())
dynamoWrapper.UserDataTablename = badTablename
_, err := dynamoWrapper.ToggleStarRepo(ctx, "repo")
So(err, ShouldNotBeNil)
})
Convey("ToggleStarRepo GetRepoMeta error", func() {
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("username")
userAc.SetGlobPatterns("read", map[string]bool{
"repo": true,
})
ctx := userAc.DeriveContext(context.Background())
dynamoWrapper.RepoMetaTablename = badTablename
_, err := dynamoWrapper.ToggleStarRepo(ctx, "repo")
So(err, ShouldNotBeNil)
})
Convey("GetUserData bad context", func() {
uacKey := reqCtx.GetContextKey()
ctx := context.WithValue(context.Background(), uacKey, "bad context")
userData, err := dynamoWrapper.GetUserData(ctx)
So(err, ShouldNotBeNil)
So(userData.BookmarkedRepos, ShouldBeEmpty)
So(userData.StarredRepos, ShouldBeEmpty)
})
Convey("GetUserData client error", func() {
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("username")
userAc.SetGlobPatterns("read", map[string]bool{
"repo": true,
})
ctx := userAc.DeriveContext(context.Background())
dynamoWrapper.UserDataTablename = badTablename
_, err := dynamoWrapper.GetUserData(ctx)
So(err, ShouldNotBeNil)
})
Convey("GetUserMeta unmarshal error, bad user data", func() {
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("username")
userAc.SetGlobPatterns("read", map[string]bool{
"repo": true,
})
ctx := userAc.DeriveContext(context.Background())
err := setBadUserData(dynamoWrapper.Client, userDataTablename, userAc.GetUsername())
So(err, ShouldBeNil)
_, err = dynamoWrapper.GetUserData(ctx)
So(err, ShouldNotBeNil)
})
Convey("SetUserData bad context", func() {
uacKey := reqCtx.GetContextKey()
ctx := context.WithValue(context.Background(), uacKey, "bad context")
err := dynamoWrapper.SetUserData(ctx, mTypes.UserData{})
So(err, ShouldNotBeNil)
})
Convey("GetUserData bad context errors", func() {
uacKey := reqCtx.GetContextKey()
ctx := context.WithValue(context.Background(), uacKey, "bad context")
_, err := dynamoWrapper.GetUserData(ctx)
So(err, ShouldNotBeNil)
})
Convey("SetUserData bad context errors", func() {
uacKey := reqCtx.GetContextKey()
ctx := context.WithValue(context.Background(), uacKey, "bad context")
err := dynamoWrapper.SetUserData(ctx, mTypes.UserData{})
So(err, ShouldNotBeNil)
})
Convey("AddUserAPIKey bad context errors", func() {
uacKey := reqCtx.GetContextKey()
ctx := context.WithValue(context.Background(), uacKey, "bad context")
err := dynamoWrapper.AddUserAPIKey(ctx, "", &mTypes.APIKeyDetails{})
So(err, ShouldNotBeNil)
})
Convey("DeleteUserAPIKey bad context errors", func() {
uacKey := reqCtx.GetContextKey()
ctx := context.WithValue(context.Background(), uacKey, "bad context")
err := dynamoWrapper.DeleteUserAPIKey(ctx, "")
So(err, ShouldNotBeNil)
})
Convey("UpdateUserAPIKeyLastUsed bad context errors", func() {
uacKey := reqCtx.GetContextKey()
ctx := context.WithValue(context.Background(), uacKey, "bad context")
err := dynamoWrapper.UpdateUserAPIKeyLastUsed(ctx, "")
So(err, ShouldNotBeNil)
})
Convey("DeleteUserData bad context errors", func() {
uacKey := reqCtx.GetContextKey()
ctx := context.WithValue(context.Background(), uacKey, "bad context")
err := dynamoWrapper.DeleteUserData(ctx)
So(err, ShouldNotBeNil)
})
Convey("DeleteUserAPIKey returns nil", func() {
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("email")
ctx := userAc.DeriveContext(context.Background())
apiKeyDetails := make(map[string]mTypes.APIKeyDetails)
apiKeyDetails["id"] = mTypes.APIKeyDetails{
UUID: "id",
}
err := dynamoWrapper.SetUserData(ctx, mTypes.UserData{
APIKeys: apiKeyDetails,
})
So(err, ShouldBeNil)
dynamoWrapper.APIKeyTablename = wrongTableName
err = dynamoWrapper.DeleteUserAPIKey(ctx, "id")
So(err, ShouldNotBeNil)
})
Convey("AddUserAPIKey", func() {
Convey("no userid found", func() {
userAc := reqCtx.NewUserAccessControl()
ctx := userAc.DeriveContext(context.Background())
err = dynamoWrapper.AddUserAPIKey(ctx, "key", &mTypes.APIKeyDetails{})
So(err, ShouldNotBeNil)
})
userAc := reqCtx.NewUserAccessControl()
userAc.SetUsername("email")
ctx := userAc.DeriveContext(context.Background())
err := dynamoWrapper.AddUserAPIKey(ctx, "key", &mTypes.APIKeyDetails{})
So(err, ShouldBeNil)
dynamoWrapper.APIKeyTablename = wrongTableName
err = dynamoWrapper.AddUserAPIKey(ctx, "key", &mTypes.APIKeyDetails{})
So(err, ShouldNotBeNil)
})
Convey("GetUserAPIKeyInfo", func() {
dynamoWrapper.APIKeyTablename = wrongTableName
_, err := dynamoWrapper.GetUserAPIKeyInfo("key")
So(err, ShouldNotBeNil)
})
Convey("GetUserData", func() {
userAc := reqCtx.NewUserAccessControl()
ctx := userAc.DeriveContext(context.Background())
_, err := dynamoWrapper.GetUserData(ctx)
So(err, ShouldNotBeNil)
userAc = reqCtx.NewUserAccessControl()
userAc.SetUsername("email")
ctx = userAc.DeriveContext(context.Background())
dynamoWrapper.UserDataTablename = wrongTableName
_, err = dynamoWrapper.GetUserData(ctx)
So(err, ShouldNotBeNil)
})
Convey("PatchDB dwr.getDBVersion errors", func() {
dynamoWrapper.VersionTablename = badTablename
err := dynamoWrapper.PatchDB()
So(err, ShouldNotBeNil)
})
Convey("PatchDB patchIndex < version.GetVersionIndex", func() {
err := setVersion(dynamoWrapper.Client, versionTablename, "V2")
So(err, ShouldBeNil)
dynamoWrapper.Patches = []func(client *dynamodb.Client, tableNames map[string]string) error{
func(client *dynamodb.Client, tableNames map[string]string) error { return nil },
func(client *dynamodb.Client, tableNames map[string]string) error { return nil },
func(client *dynamodb.Client, tableNames map[string]string) error { return nil },
}
err = dynamoWrapper.PatchDB()
So(err, ShouldBeNil)
})
Convey("ResetRepoMetaTable client errors", func() {
dynamoWrapper.RepoMetaTablename = badTablename
err := dynamoWrapper.ResetTable(dynamoWrapper.RepoMetaTablename)
So(err, ShouldNotBeNil)
})
Convey("getDBVersion client errors", func() {
dynamoWrapper.VersionTablename = badTablename
err := dynamoWrapper.PatchDB()
So(err, ShouldNotBeNil)
})
})
Convey("NewDynamoDBWrapper errors", t, func() {
params := mdynamodb.DBDriverParameters{ //nolint:contextcheck
Endpoint: endpoint,
Region: region,
RepoMetaTablename: "",
ImageMetaTablename: imageMetaTablename,
RepoBlobsInfoTablename: repoBlobsTablename,
UserDataTablename: userDataTablename,
APIKeyTablename: apiKeyTablename,
VersionTablename: versionTablename,
}
client, err := mdynamodb.GetDynamoClient(params)
So(err, ShouldBeNil)
_, err = mdynamodb.New(client, params, log)
So(err, ShouldNotBeNil)
params = mdynamodb.DBDriverParameters{ //nolint:contextcheck
Endpoint: endpoint,
Region: region,
RepoMetaTablename: repoMetaTablename,
ImageMetaTablename: imageMetaTablename,
RepoBlobsInfoTablename: repoBlobsTablename,
UserDataTablename: userDataTablename,
APIKeyTablename: apiKeyTablename,
VersionTablename: "",
}
client, err = mdynamodb.GetDynamoClient(params)
So(err, ShouldBeNil)
_, err = mdynamodb.New(client, params, log)
So(err, ShouldNotBeNil)
params = mdynamodb.DBDriverParameters{ //nolint:contextcheck
Endpoint: endpoint,
Region: region,
RepoMetaTablename: repoMetaTablename,
ImageMetaTablename: imageMetaTablename,
RepoBlobsInfoTablename: repoBlobsTablename,
VersionTablename: versionTablename,
UserDataTablename: "",
APIKeyTablename: apiKeyTablename,
}
client, err = mdynamodb.GetDynamoClient(params)
So(err, ShouldBeNil)
_, err = mdynamodb.New(client, params, log)
So(err, ShouldNotBeNil)
params = mdynamodb.DBDriverParameters{ //nolint:contextcheck
Endpoint: endpoint,
Region: region,
RepoMetaTablename: repoMetaTablename,
ImageMetaTablename: imageMetaTablename,
RepoBlobsInfoTablename: repoBlobsTablename,
VersionTablename: versionTablename,
UserDataTablename: userDataTablename,
APIKeyTablename: "",
}
client, err = mdynamodb.GetDynamoClient(params)
So(err, ShouldBeNil)
_, err = mdynamodb.New(client, params, log)
So(err, ShouldNotBeNil)
})
}
func setBadUserData(client *dynamodb.Client, userDataTablename, userID string) error {
userAttributeValue, err := attributevalue.Marshal("string")
if err != nil {
return err
}
_, err = client.UpdateItem(context.Background(), &dynamodb.UpdateItemInput{
ExpressionAttributeNames: map[string]string{
"#UM": "UserData",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":UserData": userAttributeValue,
},
Key: map[string]types.AttributeValue{
"Key": &types.AttributeValueMemberS{
Value: userID,
},
},
TableName: aws.String(userDataTablename),
UpdateExpression: aws.String("SET #UM = :UserData"),
})
return err
}
func setVersion(client *dynamodb.Client, versionTablename string, version string) error {
mdAttributeValue, err := attributevalue.Marshal(version)
if err != nil {
return err
}
_, err = client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
ExpressionAttributeNames: map[string]string{
"#V": "Version",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":Version": mdAttributeValue,
},
Key: map[string]types.AttributeValue{
"Key": &types.AttributeValueMemberS{
Value: "DBVersion",
},
},
TableName: aws.String(versionTablename),
UpdateExpression: aws.String("SET #V = :Version"),
})
return err
}