zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/meta/dynamodb/dynamodb.go (about)

     1  package dynamodb
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go-v2/aws"
    11  	"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
    12  	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
    13  	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
    14  	godigest "github.com/opencontainers/go-digest"
    15  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    16  	"google.golang.org/protobuf/proto"
    17  	"google.golang.org/protobuf/types/known/timestamppb"
    18  
    19  	zerr "zotregistry.dev/zot/errors"
    20  	"zotregistry.dev/zot/pkg/api/constants"
    21  	zcommon "zotregistry.dev/zot/pkg/common"
    22  	"zotregistry.dev/zot/pkg/log"
    23  	"zotregistry.dev/zot/pkg/meta/common"
    24  	mConvert "zotregistry.dev/zot/pkg/meta/convert"
    25  	proto_go "zotregistry.dev/zot/pkg/meta/proto/gen"
    26  	mTypes "zotregistry.dev/zot/pkg/meta/types"
    27  	"zotregistry.dev/zot/pkg/meta/version"
    28  	reqCtx "zotregistry.dev/zot/pkg/requestcontext"
    29  )
    30  
    31  type DynamoDB struct {
    32  	Client             *dynamodb.Client
    33  	APIKeyTablename    string
    34  	RepoMetaTablename  string
    35  	RepoBlobsTablename string
    36  	ImageMetaTablename string
    37  	UserDataTablename  string
    38  	VersionTablename   string
    39  	Patches            []func(client *dynamodb.Client, tableNames map[string]string) error
    40  	imgTrustStore      mTypes.ImageTrustStore
    41  	Log                log.Logger
    42  }
    43  
    44  func New(client *dynamodb.Client, params DBDriverParameters, log log.Logger,
    45  ) (*DynamoDB, error) {
    46  	dynamoWrapper := DynamoDB{
    47  		Client:             client,
    48  		VersionTablename:   params.VersionTablename,
    49  		UserDataTablename:  params.UserDataTablename,
    50  		APIKeyTablename:    params.APIKeyTablename,
    51  		RepoMetaTablename:  params.RepoMetaTablename,
    52  		ImageMetaTablename: params.ImageMetaTablename,
    53  		RepoBlobsTablename: params.RepoBlobsInfoTablename,
    54  		Patches:            version.GetDynamoDBPatches(),
    55  		imgTrustStore:      nil,
    56  		Log:                log,
    57  	}
    58  
    59  	err := dynamoWrapper.createVersionTable()
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	err = dynamoWrapper.createTable(dynamoWrapper.RepoMetaTablename)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	err = dynamoWrapper.createTable(dynamoWrapper.RepoBlobsTablename)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	err = dynamoWrapper.createTable(dynamoWrapper.ImageMetaTablename)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	err = dynamoWrapper.createTable(dynamoWrapper.UserDataTablename)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	err = dynamoWrapper.createTable(dynamoWrapper.APIKeyTablename)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	// Using the Config value, create the DynamoDB client
    90  	return &dynamoWrapper, nil
    91  }
    92  
    93  func (dwr *DynamoDB) GetAllRepoNames() ([]string, error) {
    94  	ctx := context.Background()
    95  	attributeIterator := NewBaseDynamoAttributesIterator(dwr.Client, dwr.RepoMetaTablename, "TableKey", 0, dwr.Log)
    96  
    97  	repoNames := []string{}
    98  
    99  	repoNameAttribute, err := attributeIterator.First(ctx)
   100  
   101  	for ; repoNameAttribute != nil; repoNameAttribute, err = attributeIterator.Next(ctx) {
   102  		if err != nil {
   103  			return []string{}, err
   104  		}
   105  
   106  		var repoName string
   107  
   108  		err := attributevalue.Unmarshal(repoNameAttribute, &repoName)
   109  		if err != nil {
   110  			continue
   111  		}
   112  
   113  		repoNames = append(repoNames, repoName)
   114  	}
   115  
   116  	return repoNames, nil
   117  }
   118  
   119  func (dwr *DynamoDB) GetRepoLastUpdated(repo string) time.Time {
   120  	resp, err := dwr.Client.GetItem(context.Background(), &dynamodb.GetItemInput{
   121  		TableName: aws.String(dwr.RepoBlobsTablename),
   122  		Key: map[string]types.AttributeValue{
   123  			"TableKey": &types.AttributeValueMemberS{Value: repo},
   124  		},
   125  		ProjectionExpression: aws.String("RepoLastUpdated"),
   126  	})
   127  	if err != nil {
   128  		return time.Time{}
   129  	}
   130  
   131  	protoRepoLastUpdated := &timestamppb.Timestamp{}
   132  	repoLastUpdatedBlob := []byte{}
   133  
   134  	if resp.Item != nil {
   135  		err = attributevalue.Unmarshal(resp.Item["RepoLastUpdated"], &repoLastUpdatedBlob)
   136  		if err != nil {
   137  			return time.Time{}
   138  		}
   139  
   140  		if len(repoLastUpdatedBlob) > 0 {
   141  			err := proto.Unmarshal(repoLastUpdatedBlob, protoRepoLastUpdated)
   142  			if err != nil {
   143  				return time.Time{}
   144  			}
   145  		}
   146  	}
   147  
   148  	lastUpdated := *mConvert.GetTime(protoRepoLastUpdated)
   149  
   150  	return lastUpdated
   151  }
   152  
   153  func (dwr *DynamoDB) ImageTrustStore() mTypes.ImageTrustStore {
   154  	return dwr.imgTrustStore
   155  }
   156  
   157  func (dwr *DynamoDB) SetImageTrustStore(imgTrustStore mTypes.ImageTrustStore) {
   158  	dwr.imgTrustStore = imgTrustStore
   159  }
   160  
   161  func (dwr *DynamoDB) SetProtoImageMeta(digest godigest.Digest, protoImageMeta *proto_go.ImageMeta) error {
   162  	bytes, err := proto.Marshal(protoImageMeta)
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	mdAttributeValue, err := attributevalue.Marshal(bytes)
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	_, err = dwr.Client.UpdateItem(context.Background(), &dynamodb.UpdateItemInput{
   173  		ExpressionAttributeNames: map[string]string{
   174  			"#IM": "ImageMeta",
   175  		},
   176  		ExpressionAttributeValues: map[string]types.AttributeValue{
   177  			":ImageMeta": mdAttributeValue,
   178  		},
   179  		Key: map[string]types.AttributeValue{
   180  			"TableKey": &types.AttributeValueMemberS{
   181  				Value: digest.String(),
   182  			},
   183  		},
   184  		TableName:        aws.String(dwr.ImageMetaTablename),
   185  		UpdateExpression: aws.String("SET #IM = :ImageMeta"),
   186  	})
   187  
   188  	return err
   189  }
   190  
   191  func (dwr *DynamoDB) SetImageMeta(digest godigest.Digest, imageMeta mTypes.ImageMeta) error {
   192  	return dwr.SetProtoImageMeta(digest, mConvert.GetProtoImageMeta(imageMeta))
   193  }
   194  
   195  func (dwr *DynamoDB) GetProtoImageMeta(ctx context.Context, digest godigest.Digest) (*proto_go.ImageMeta, error) {
   196  	resp, err := dwr.Client.GetItem(ctx, &dynamodb.GetItemInput{
   197  		TableName: aws.String(dwr.ImageMetaTablename),
   198  		Key: map[string]types.AttributeValue{
   199  			"TableKey": &types.AttributeValueMemberS{Value: digest.String()},
   200  		},
   201  	})
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	blob := []byte{}
   207  
   208  	if resp.Item == nil {
   209  		return nil, zerr.ErrImageMetaNotFound
   210  	}
   211  
   212  	err = attributevalue.Unmarshal(resp.Item["ImageMeta"], &blob)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	imageMeta := &proto_go.ImageMeta{}
   218  
   219  	err = proto.Unmarshal(blob, imageMeta)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	return imageMeta, nil
   225  }
   226  
   227  func (dwr *DynamoDB) setProtoRepoMeta(repo string, repoMeta *proto_go.RepoMeta) error {
   228  	repoMeta.Name = repo
   229  
   230  	repoMetaBlob, err := proto.Marshal(repoMeta)
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	repoAttributeValue, err := attributevalue.Marshal(repoMetaBlob)
   236  	if err != nil {
   237  		return err
   238  	}
   239  
   240  	_, err = dwr.Client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
   241  		ExpressionAttributeNames: map[string]string{
   242  			"#RM": "RepoMeta",
   243  		},
   244  		ExpressionAttributeValues: map[string]types.AttributeValue{
   245  			":RepoMeta": repoAttributeValue,
   246  		},
   247  		Key: map[string]types.AttributeValue{
   248  			"TableKey": &types.AttributeValueMemberS{
   249  				Value: repo,
   250  			},
   251  		},
   252  		TableName:        aws.String(dwr.RepoMetaTablename),
   253  		UpdateExpression: aws.String("SET #RM = :RepoMeta"),
   254  	})
   255  
   256  	return err
   257  }
   258  
   259  func (dwr *DynamoDB) getProtoRepoMeta(ctx context.Context, repo string) (*proto_go.RepoMeta, error) {
   260  	resp, err := dwr.Client.GetItem(ctx, &dynamodb.GetItemInput{
   261  		TableName: aws.String(dwr.RepoMetaTablename),
   262  		Key: map[string]types.AttributeValue{
   263  			"TableKey": &types.AttributeValueMemberS{Value: repo},
   264  		},
   265  	})
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  
   270  	protoRepoMeta := &proto_go.RepoMeta{
   271  		Name: repo,
   272  	}
   273  
   274  	blob := []byte{}
   275  
   276  	if resp.Item != nil {
   277  		err = attributevalue.Unmarshal(resp.Item["RepoMeta"], &blob)
   278  		if err != nil {
   279  			return nil, err
   280  		}
   281  
   282  		err = proto.Unmarshal(blob, protoRepoMeta)
   283  		if err != nil {
   284  			return nil, err
   285  		}
   286  	}
   287  
   288  	if protoRepoMeta.Tags == nil {
   289  		protoRepoMeta.Tags = map[string]*proto_go.TagDescriptor{"": {}}
   290  	}
   291  
   292  	if protoRepoMeta.Statistics == nil {
   293  		protoRepoMeta.Statistics = map[string]*proto_go.DescriptorStatistics{"": {}}
   294  	}
   295  
   296  	if protoRepoMeta.Signatures == nil {
   297  		protoRepoMeta.Signatures = map[string]*proto_go.ManifestSignatures{"": {}}
   298  	}
   299  
   300  	if protoRepoMeta.Referrers == nil {
   301  		protoRepoMeta.Referrers = map[string]*proto_go.ReferrersInfo{"": {}}
   302  	}
   303  
   304  	if len(blob) == 0 || resp.Item == nil {
   305  		return protoRepoMeta, zerr.ErrRepoMetaNotFound
   306  	}
   307  
   308  	return protoRepoMeta, nil
   309  }
   310  
   311  func (dwr *DynamoDB) SetRepoReference(ctx context.Context, repo string, reference string,
   312  	imageMeta mTypes.ImageMeta,
   313  ) error {
   314  	if err := common.ValidateRepoReferenceInput(repo, reference, imageMeta.Digest); err != nil {
   315  		return err
   316  	}
   317  
   318  	var userid string
   319  
   320  	userAc, err := reqCtx.UserAcFromContext(ctx)
   321  	if err == nil {
   322  		userid = userAc.GetUsername()
   323  	}
   324  
   325  	// 1. Add image data to db if needed
   326  	protoImageMeta := mConvert.GetProtoImageMeta(imageMeta)
   327  
   328  	err = dwr.SetProtoImageMeta(imageMeta.Digest, protoImageMeta) //nolint: contextcheck
   329  	if err != nil {
   330  		return err
   331  	}
   332  
   333  	repoMeta, err := dwr.getProtoRepoMeta(ctx, repo)
   334  	if err != nil && !errors.Is(err, zerr.ErrRepoMetaNotFound) {
   335  		return err
   336  	}
   337  
   338  	// 2. Referrers
   339  	if subject := mConvert.GetImageSubject(protoImageMeta); subject != nil {
   340  		refInfo := &proto_go.ReferrersInfo{}
   341  		if repoMeta.Referrers[subject.Digest.String()] != nil {
   342  			refInfo = repoMeta.Referrers[subject.Digest.String()]
   343  		}
   344  
   345  		foundReferrer := false
   346  
   347  		for i := range refInfo.List {
   348  			if refInfo.List[i].Digest == mConvert.GetImageDigestStr(protoImageMeta) {
   349  				foundReferrer = true
   350  				refInfo.List[i].Count += 1
   351  
   352  				break
   353  			}
   354  		}
   355  
   356  		if !foundReferrer {
   357  			refInfo.List = append(refInfo.List, &proto_go.ReferrerInfo{
   358  				Count:        1,
   359  				MediaType:    protoImageMeta.MediaType,
   360  				Digest:       mConvert.GetImageDigestStr(protoImageMeta),
   361  				ArtifactType: mConvert.GetImageArtifactType(protoImageMeta),
   362  				Size:         mConvert.GetImageManifestSize(protoImageMeta),
   363  				Annotations:  mConvert.GetImageAnnotations(protoImageMeta),
   364  			})
   365  		}
   366  
   367  		repoMeta.Referrers[subject.Digest.String()] = refInfo
   368  	}
   369  
   370  	// 3. Update tag
   371  	if !common.ReferenceIsDigest(reference) {
   372  		repoMeta.Tags[reference] = &proto_go.TagDescriptor{
   373  			Digest:    imageMeta.Digest.String(),
   374  			MediaType: imageMeta.MediaType,
   375  		}
   376  	}
   377  
   378  	if _, ok := repoMeta.Statistics[imageMeta.Digest.String()]; !ok {
   379  		repoMeta.Statistics[imageMeta.Digest.String()] = &proto_go.DescriptorStatistics{
   380  			DownloadCount:     0,
   381  			LastPullTimestamp: &timestamppb.Timestamp{},
   382  			PushTimestamp:     timestamppb.Now(),
   383  			PushedBy:          userid,
   384  		}
   385  	} else if repoMeta.Statistics[imageMeta.Digest.String()].PushTimestamp.AsTime().IsZero() {
   386  		repoMeta.Statistics[imageMeta.Digest.String()].PushTimestamp = timestamppb.Now()
   387  	}
   388  
   389  	if _, ok := repoMeta.Signatures[imageMeta.Digest.String()]; !ok {
   390  		repoMeta.Signatures[imageMeta.Digest.String()] = &proto_go.ManifestSignatures{
   391  			Map: map[string]*proto_go.SignaturesInfo{"": {}},
   392  		}
   393  	}
   394  
   395  	if _, ok := repoMeta.Referrers[imageMeta.Digest.String()]; !ok {
   396  		repoMeta.Referrers[imageMeta.Digest.String()] = &proto_go.ReferrersInfo{
   397  			List: []*proto_go.ReferrerInfo{},
   398  		}
   399  	}
   400  
   401  	// 4. Blobs
   402  	repoBlobs, err := dwr.getProtoRepoBlobs(ctx, repo)
   403  	if err != nil {
   404  		return err
   405  	}
   406  
   407  	repoMeta, repoBlobs = common.AddImageMetaToRepoMeta(repoMeta, repoBlobs, reference, imageMeta)
   408  
   409  	err = dwr.setRepoBlobsInfo(repo, repoBlobs) //nolint: contextcheck
   410  	if err != nil {
   411  		return err
   412  	}
   413  
   414  	return dwr.setProtoRepoMeta(repo, repoMeta) //nolint: contextcheck
   415  }
   416  
   417  func (dwr *DynamoDB) updateRepoLastUpdated(ctx context.Context, repo string, time time.Time) error {
   418  	protoTime := timestamppb.New(time)
   419  
   420  	protoTimeBlob, err := proto.Marshal(protoTime)
   421  	if err != nil {
   422  		return err
   423  	}
   424  
   425  	mdAttributeValue, err := attributevalue.Marshal(protoTimeBlob)
   426  	if err != nil {
   427  		return err
   428  	}
   429  
   430  	_, err = dwr.Client.UpdateItem(ctx, &dynamodb.UpdateItemInput{
   431  		ExpressionAttributeNames: map[string]string{
   432  			"#RLU": "RepoLastUpdated",
   433  		},
   434  		ExpressionAttributeValues: map[string]types.AttributeValue{
   435  			":RepoLastUpdated": mdAttributeValue,
   436  		},
   437  		Key: map[string]types.AttributeValue{
   438  			"TableKey": &types.AttributeValueMemberS{
   439  				Value: repo,
   440  			},
   441  		},
   442  		TableName:        aws.String(dwr.RepoBlobsTablename),
   443  		UpdateExpression: aws.String("SET #RLU = :RepoLastUpdated"),
   444  	})
   445  
   446  	return err
   447  }
   448  
   449  func (dwr *DynamoDB) getProtoRepoBlobs(ctx context.Context, repo string) (*proto_go.RepoBlobs, error) {
   450  	resp, err := dwr.Client.GetItem(ctx, &dynamodb.GetItemInput{
   451  		TableName: aws.String(dwr.RepoBlobsTablename),
   452  		Key: map[string]types.AttributeValue{
   453  			"TableKey": &types.AttributeValueMemberS{Value: repo},
   454  		},
   455  	})
   456  	if err != nil {
   457  		return nil, err
   458  	}
   459  
   460  	repoBlobs := &proto_go.RepoBlobs{
   461  		Name: repo,
   462  	}
   463  
   464  	repoBlobsBytes := []byte{}
   465  
   466  	if resp.Item != nil {
   467  		err = attributevalue.Unmarshal(resp.Item["RepoBlobsInfo"], &repoBlobsBytes)
   468  		if err != nil {
   469  			return nil, err
   470  		}
   471  
   472  		if len(repoBlobsBytes) > 0 {
   473  			err := proto.Unmarshal(repoBlobsBytes, repoBlobs)
   474  			if err != nil {
   475  				return nil, err
   476  			}
   477  		}
   478  	}
   479  
   480  	if repoBlobs.Blobs == nil {
   481  		repoBlobs.Blobs = map[string]*proto_go.BlobInfo{"": {}}
   482  	}
   483  
   484  	return repoBlobs, nil
   485  }
   486  
   487  func (dwr *DynamoDB) setRepoBlobsInfo(repo string, repoBlobs *proto_go.RepoBlobs) error {
   488  	protoTime := timestamppb.Now()
   489  
   490  	protoTimeBlob, err := proto.Marshal(protoTime)
   491  	if err != nil {
   492  		return err
   493  	}
   494  
   495  	timeAttributeValue, err := attributevalue.Marshal(protoTimeBlob)
   496  	if err != nil {
   497  		return err
   498  	}
   499  
   500  	bytes, err := proto.Marshal(repoBlobs)
   501  	if err != nil {
   502  		return err
   503  	}
   504  
   505  	mdAttributeValue, err := attributevalue.Marshal(bytes)
   506  	if err != nil {
   507  		return err
   508  	}
   509  
   510  	_, err = dwr.Client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
   511  		ExpressionAttributeNames: map[string]string{
   512  			"#RBI": "RepoBlobsInfo",
   513  			"#RLU": "RepoLastUpdated",
   514  		},
   515  		ExpressionAttributeValues: map[string]types.AttributeValue{
   516  			":RepoBlobsInfo":   mdAttributeValue,
   517  			":RepoLastUpdated": timeAttributeValue,
   518  		},
   519  		Key: map[string]types.AttributeValue{
   520  			"TableKey": &types.AttributeValueMemberS{
   521  				Value: repo,
   522  			},
   523  		},
   524  		TableName:        aws.String(dwr.RepoBlobsTablename),
   525  		UpdateExpression: aws.String("SET #RBI = :RepoBlobsInfo, #RLU = :RepoLastUpdated"),
   526  	})
   527  
   528  	return err
   529  }
   530  
   531  func (dwr *DynamoDB) SearchRepos(ctx context.Context, searchText string) ([]mTypes.RepoMeta, error) {
   532  	repos := []mTypes.RepoMeta{}
   533  
   534  	userBookmarks := getUserBookmarks(ctx, dwr)
   535  	userStars := getUserStars(ctx, dwr)
   536  
   537  	repoMetaAttributeIterator := NewBaseDynamoAttributesIterator(
   538  		dwr.Client, dwr.RepoMetaTablename, "RepoMeta", 0, dwr.Log,
   539  	)
   540  
   541  	repoMetaAttribute, err := repoMetaAttributeIterator.First(ctx)
   542  
   543  	for ; repoMetaAttribute != nil; repoMetaAttribute, err = repoMetaAttributeIterator.Next(ctx) {
   544  		if err != nil {
   545  			return []mTypes.RepoMeta{}, err
   546  		}
   547  
   548  		repoMetaBlob := []byte{}
   549  
   550  		err := attributevalue.Unmarshal(repoMetaAttribute, &repoMetaBlob)
   551  		if err != nil {
   552  			return []mTypes.RepoMeta{}, err
   553  		}
   554  
   555  		protoRepoMeta := &proto_go.RepoMeta{}
   556  
   557  		err = proto.Unmarshal(repoMetaBlob, protoRepoMeta)
   558  		if err != nil {
   559  			return []mTypes.RepoMeta{}, err
   560  		}
   561  
   562  		if ok, err := reqCtx.RepoIsUserAvailable(ctx, protoRepoMeta.Name); !ok || err != nil {
   563  			continue
   564  		}
   565  
   566  		delete(protoRepoMeta.Tags, "")
   567  
   568  		if len(protoRepoMeta.Tags) == 0 {
   569  			continue
   570  		}
   571  
   572  		rank := common.RankRepoName(searchText, protoRepoMeta.Name)
   573  		if rank == -1 {
   574  			continue
   575  		}
   576  
   577  		protoRepoMeta.Rank = int32(rank)
   578  		protoRepoMeta.IsStarred = zcommon.Contains(userStars, protoRepoMeta.Name)
   579  		protoRepoMeta.IsBookmarked = zcommon.Contains(userBookmarks, protoRepoMeta.Name)
   580  
   581  		repos = append(repos, mConvert.GetRepoMeta(protoRepoMeta))
   582  	}
   583  
   584  	return repos, nil
   585  }
   586  
   587  func (dwr *DynamoDB) SearchTags(ctx context.Context, searchText string) ([]mTypes.FullImageMeta, error) {
   588  	images := []mTypes.FullImageMeta{}
   589  	userBookmarks := getUserBookmarks(ctx, dwr)
   590  	userStars := getUserStars(ctx, dwr)
   591  
   592  	searchedRepo, searchedTag, err := common.GetRepoTag(searchText)
   593  	if err != nil {
   594  		return []mTypes.FullImageMeta{},
   595  			fmt.Errorf("failed to parse search text, invalid format %w", err)
   596  	}
   597  
   598  	if ok, err := reqCtx.RepoIsUserAvailable(ctx, searchedRepo); !ok || err != nil {
   599  		return []mTypes.FullImageMeta{}, err
   600  	}
   601  
   602  	protoRepoMeta, err := dwr.getProtoRepoMeta(ctx, searchedRepo)
   603  	if err != nil {
   604  		if errors.Is(err, zerr.ErrRepoMetaNotFound) {
   605  			return []mTypes.FullImageMeta{}, nil
   606  		}
   607  
   608  		return nil, err
   609  	}
   610  
   611  	delete(protoRepoMeta.Tags, "")
   612  
   613  	protoRepoMeta.IsBookmarked = zcommon.Contains(userBookmarks, searchedRepo)
   614  	protoRepoMeta.IsStarred = zcommon.Contains(userStars, searchedRepo)
   615  
   616  	for tag, descriptor := range protoRepoMeta.Tags {
   617  		if !strings.HasPrefix(tag, searchedTag) {
   618  			continue
   619  		}
   620  
   621  		var protoImageMeta *proto_go.ImageMeta
   622  
   623  		switch descriptor.MediaType {
   624  		case ispec.MediaTypeImageManifest:
   625  			manifestDigest := descriptor.Digest
   626  
   627  			imageManifestData, err := dwr.GetProtoImageMeta(ctx, godigest.Digest(manifestDigest))
   628  			if err != nil {
   629  				return []mTypes.FullImageMeta{},
   630  					fmt.Errorf("error fetching manifest meta for manifest with digest %s %w", manifestDigest, err)
   631  			}
   632  
   633  			protoImageMeta = imageManifestData
   634  		case ispec.MediaTypeImageIndex:
   635  			indexDigest := godigest.Digest(descriptor.Digest)
   636  
   637  			imageIndexData, err := dwr.GetProtoImageMeta(ctx, indexDigest)
   638  			if err != nil {
   639  				return []mTypes.FullImageMeta{},
   640  					fmt.Errorf("error fetching manifest meta for manifest with digest %s %w", indexDigest, err)
   641  			}
   642  
   643  			manifestDataList := make([]*proto_go.ManifestMeta, 0, len(imageIndexData.Index.Index.Manifests))
   644  
   645  			for _, manifest := range imageIndexData.Index.Index.Manifests {
   646  				manifestDigest := godigest.Digest(manifest.Digest)
   647  
   648  				imageManifestData, err := dwr.GetProtoImageMeta(ctx, manifestDigest)
   649  				if err != nil {
   650  					return []mTypes.FullImageMeta{}, err
   651  				}
   652  
   653  				manifestDataList = append(manifestDataList, imageManifestData.Manifests[0])
   654  			}
   655  
   656  			imageIndexData.Manifests = manifestDataList
   657  
   658  			protoImageMeta = imageIndexData
   659  		default:
   660  			dwr.Log.Error().Str("mediaType", descriptor.MediaType).Msg("unsupported media type")
   661  
   662  			continue
   663  		}
   664  
   665  		images = append(images, mConvert.GetFullImageMetaFromProto(tag, protoRepoMeta, protoImageMeta))
   666  	}
   667  
   668  	return images, err
   669  }
   670  
   671  func (dwr *DynamoDB) FilterTags(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc,
   672  	filterFunc mTypes.FilterFunc,
   673  ) ([]mTypes.FullImageMeta, error) {
   674  	images := []mTypes.FullImageMeta{}
   675  	userBookmarks := getUserBookmarks(ctx, dwr)
   676  	userStars := getUserStars(ctx, dwr)
   677  
   678  	var viewError error
   679  
   680  	repoMetaAttributeIterator := NewBaseDynamoAttributesIterator(
   681  		dwr.Client, dwr.RepoMetaTablename, "RepoMeta", 0, dwr.Log,
   682  	)
   683  
   684  	repoMetaAttribute, err := repoMetaAttributeIterator.First(ctx)
   685  
   686  	for ; repoMetaAttribute != nil; repoMetaAttribute, err = repoMetaAttributeIterator.Next(ctx) {
   687  		if err != nil {
   688  			viewError = errors.Join(viewError, err)
   689  
   690  			continue
   691  		}
   692  
   693  		protoRepoMeta, err := getProtoRepoMetaFromAttribute(repoMetaAttribute)
   694  		if err != nil {
   695  			viewError = errors.Join(viewError, err)
   696  
   697  			continue
   698  		}
   699  
   700  		if ok, err := reqCtx.RepoIsUserAvailable(ctx, protoRepoMeta.Name); !ok || err != nil {
   701  			continue
   702  		}
   703  
   704  		protoRepoMeta.IsBookmarked = zcommon.Contains(userBookmarks, protoRepoMeta.Name)
   705  		protoRepoMeta.IsStarred = zcommon.Contains(userStars, protoRepoMeta.Name)
   706  		repoMeta := mConvert.GetRepoMeta(protoRepoMeta)
   707  
   708  		for tag, descriptor := range repoMeta.Tags {
   709  			if !filterRepoTag(repoMeta.Name, tag) {
   710  				continue
   711  			}
   712  
   713  			switch descriptor.MediaType {
   714  			case ispec.MediaTypeImageManifest:
   715  				manifestDigest := descriptor.Digest
   716  
   717  				imageManifestData, err := dwr.GetProtoImageMeta(ctx, godigest.Digest(manifestDigest))
   718  				if err != nil {
   719  					viewError = errors.Join(viewError, err)
   720  
   721  					continue
   722  				}
   723  
   724  				imageMeta := mConvert.GetImageMeta(imageManifestData)
   725  
   726  				if filterFunc(repoMeta, imageMeta) {
   727  					images = append(images, mConvert.GetFullImageMetaFromProto(tag, protoRepoMeta, imageManifestData))
   728  				}
   729  			case ispec.MediaTypeImageIndex:
   730  				indexDigest := descriptor.Digest
   731  
   732  				protoImageIndexMeta, err := dwr.GetProtoImageMeta(ctx, godigest.Digest(indexDigest))
   733  				if err != nil {
   734  					viewError = errors.Join(viewError, err)
   735  
   736  					continue
   737  				}
   738  
   739  				imageIndexMeta := mConvert.GetImageMeta(protoImageIndexMeta)
   740  				matchedManifests := []*proto_go.ManifestMeta{}
   741  
   742  				for _, manifest := range protoImageIndexMeta.Index.Index.Manifests {
   743  					manifestDigest := manifest.Digest
   744  
   745  					imageManifestData, err := dwr.GetProtoImageMeta(ctx, godigest.Digest(manifestDigest))
   746  					if err != nil {
   747  						viewError = errors.Join(viewError, err)
   748  
   749  						continue
   750  					}
   751  
   752  					imageMeta := mConvert.GetImageMeta(imageManifestData)
   753  					partialImageMeta := common.GetPartialImageMeta(imageIndexMeta, imageMeta)
   754  
   755  					if filterFunc(repoMeta, partialImageMeta) {
   756  						matchedManifests = append(matchedManifests, imageManifestData.Manifests[0])
   757  					}
   758  				}
   759  
   760  				if len(matchedManifests) > 0 {
   761  					protoImageIndexMeta.Manifests = matchedManifests
   762  
   763  					images = append(images, mConvert.GetFullImageMetaFromProto(tag, protoRepoMeta, protoImageIndexMeta))
   764  				}
   765  			default:
   766  				dwr.Log.Error().Str("mediaType", descriptor.MediaType).Msg("unsupported media type")
   767  
   768  				continue
   769  			}
   770  		}
   771  	}
   772  
   773  	viewError = errors.Join(viewError, err)
   774  
   775  	return images, viewError
   776  }
   777  
   778  func getProtoRepoMetaFromAttribute(repoMetaAttribute types.AttributeValue) (*proto_go.RepoMeta, error) {
   779  	blob := []byte{}
   780  
   781  	err := attributevalue.Unmarshal(repoMetaAttribute, &blob)
   782  	if err != nil {
   783  		return nil, err
   784  	}
   785  
   786  	protoRepoMeta := &proto_go.RepoMeta{}
   787  
   788  	err = proto.Unmarshal(blob, protoRepoMeta)
   789  	if err != nil {
   790  		return nil, err
   791  	}
   792  
   793  	return protoRepoMeta, nil
   794  }
   795  
   796  func getProtoImageMetaFromAttribute(imageMetaAttribute types.AttributeValue) (*proto_go.ImageMeta, error) {
   797  	blob := []byte{}
   798  
   799  	err := attributevalue.Unmarshal(imageMetaAttribute, &blob)
   800  	if err != nil {
   801  		return nil, err
   802  	}
   803  
   804  	protoImageMeta := &proto_go.ImageMeta{}
   805  
   806  	err = proto.Unmarshal(blob, protoImageMeta)
   807  	if err != nil {
   808  		return nil, err
   809  	}
   810  
   811  	return protoImageMeta, nil
   812  }
   813  
   814  func (dwr *DynamoDB) ResetRepoReferences(repo string) error {
   815  	protoRepoMeta, err := dwr.getProtoRepoMeta(context.Background(), repo)
   816  	if err != nil {
   817  		return err
   818  	}
   819  
   820  	return dwr.setProtoRepoMeta(repo, &proto_go.RepoMeta{
   821  		Name:       repo,
   822  		Statistics: protoRepoMeta.Statistics,
   823  		Stars:      protoRepoMeta.Stars,
   824  		Tags:       map[string]*proto_go.TagDescriptor{"": {}},
   825  		Referrers:  map[string]*proto_go.ReferrersInfo{"": {}},
   826  		Signatures: map[string]*proto_go.ManifestSignatures{"": {Map: map[string]*proto_go.SignaturesInfo{"": {}}}},
   827  	})
   828  }
   829  
   830  func (dwr *DynamoDB) GetRepoMeta(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
   831  	protoRepoMeta, err := dwr.getProtoRepoMeta(ctx, repo)
   832  	if err != nil {
   833  		return mTypes.RepoMeta{}, err
   834  	}
   835  
   836  	delete(protoRepoMeta.Tags, "")
   837  
   838  	userBookmarks := getUserBookmarks(ctx, dwr)
   839  	userStars := getUserStars(ctx, dwr)
   840  
   841  	protoRepoMeta.IsBookmarked = zcommon.Contains(userBookmarks, repo)
   842  	protoRepoMeta.IsStarred = zcommon.Contains(userStars, repo)
   843  
   844  	return mConvert.GetRepoMeta(protoRepoMeta), nil
   845  }
   846  
   847  func (dwr *DynamoDB) GetFullImageMeta(ctx context.Context, repo string, tag string) (mTypes.FullImageMeta, error) {
   848  	protoRepoMeta, err := dwr.getProtoRepoMeta(ctx, repo)
   849  	if err != nil {
   850  		return mTypes.FullImageMeta{}, err
   851  	}
   852  
   853  	delete(protoRepoMeta.Tags, "")
   854  
   855  	bookmarks, stars := dwr.getUserBookmarksAndStars(ctx)
   856  
   857  	protoRepoMeta.IsBookmarked = zcommon.Contains(bookmarks, repo)
   858  	protoRepoMeta.IsStarred = zcommon.Contains(stars, repo)
   859  
   860  	descriptor, ok := protoRepoMeta.Tags[tag]
   861  	if !ok {
   862  		return mTypes.FullImageMeta{}, zerr.ErrImageMetaNotFound
   863  	}
   864  
   865  	protoImageMeta, err := dwr.GetProtoImageMeta(ctx, godigest.Digest(descriptor.Digest))
   866  	if err != nil {
   867  		return mTypes.FullImageMeta{}, err
   868  	}
   869  
   870  	if protoImageMeta.MediaType == ispec.MediaTypeImageIndex {
   871  		manifestDataList := make([]*proto_go.ManifestMeta, 0, len(protoImageMeta.Index.Index.Manifests))
   872  
   873  		for _, manifest := range protoImageMeta.Index.Index.Manifests {
   874  			imageManifestData, err := dwr.GetProtoImageMeta(ctx, godigest.Digest(manifest.Digest))
   875  			if err != nil {
   876  				return mTypes.FullImageMeta{}, err
   877  			}
   878  
   879  			manifestDataList = append(manifestDataList, imageManifestData.Manifests[0])
   880  		}
   881  
   882  		protoImageMeta.Manifests = manifestDataList
   883  	}
   884  
   885  	return mConvert.GetFullImageMetaFromProto(tag, protoRepoMeta, protoImageMeta), nil
   886  }
   887  
   888  func (dwr *DynamoDB) getUserBookmarksAndStars(ctx context.Context) ([]string, []string) {
   889  	userData, err := dwr.GetUserData(ctx)
   890  	if err != nil {
   891  		return []string{}, []string{}
   892  	}
   893  
   894  	return userData.BookmarkedRepos, userData.StarredRepos
   895  }
   896  
   897  func (dwr *DynamoDB) GetImageMeta(digest godigest.Digest) (mTypes.ImageMeta, error) {
   898  	protoImageMeta, err := dwr.GetProtoImageMeta(context.Background(), digest)
   899  	if err != nil {
   900  		return mTypes.ImageMeta{}, err
   901  	}
   902  
   903  	if protoImageMeta.MediaType == ispec.MediaTypeImageIndex {
   904  		manifestDataList := make([]*proto_go.ManifestMeta, 0, len(protoImageMeta.Index.Index.Manifests))
   905  
   906  		for _, manifest := range protoImageMeta.Index.Index.Manifests {
   907  			manifestDigest := godigest.Digest(manifest.Digest)
   908  
   909  			imageManifestData, err := dwr.GetProtoImageMeta(context.Background(), manifestDigest)
   910  			if err != nil {
   911  				return mTypes.ImageMeta{}, err
   912  			}
   913  
   914  			manifestDataList = append(manifestDataList, imageManifestData.Manifests[0])
   915  		}
   916  
   917  		protoImageMeta.Manifests = manifestDataList
   918  	}
   919  
   920  	return mConvert.GetImageMeta(protoImageMeta), nil
   921  }
   922  
   923  func (dwr *DynamoDB) GetMultipleRepoMeta(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool,
   924  ) ([]mTypes.RepoMeta, error) {
   925  	var (
   926  		foundRepos                = []mTypes.RepoMeta{}
   927  		repoMetaAttributeIterator AttributesIterator
   928  	)
   929  
   930  	repoMetaAttributeIterator = NewBaseDynamoAttributesIterator(
   931  		dwr.Client, dwr.RepoMetaTablename, "RepoMeta", 0, dwr.Log,
   932  	)
   933  
   934  	repoMetaAttribute, err := repoMetaAttributeIterator.First(ctx)
   935  
   936  	for ; repoMetaAttribute != nil; repoMetaAttribute, err = repoMetaAttributeIterator.Next(ctx) {
   937  		if err != nil {
   938  			return []mTypes.RepoMeta{}, err
   939  		}
   940  
   941  		repoMetaBlob := []byte{}
   942  
   943  		err := attributevalue.Unmarshal(repoMetaAttribute, &repoMetaBlob)
   944  		if err != nil {
   945  			return []mTypes.RepoMeta{}, err
   946  		}
   947  
   948  		protoRepoMeta := &proto_go.RepoMeta{}
   949  
   950  		err = proto.Unmarshal(repoMetaBlob, protoRepoMeta)
   951  		if err != nil {
   952  			return []mTypes.RepoMeta{}, err
   953  		}
   954  
   955  		delete(protoRepoMeta.Tags, "")
   956  
   957  		if ok, err := reqCtx.RepoIsUserAvailable(ctx, protoRepoMeta.Name); !ok || err != nil {
   958  			continue
   959  		}
   960  
   961  		repoMeta := mConvert.GetRepoMeta(protoRepoMeta)
   962  
   963  		if filter(repoMeta) {
   964  			foundRepos = append(foundRepos, repoMeta)
   965  		}
   966  	}
   967  
   968  	return foundRepos, err
   969  }
   970  
   971  func (dwr *DynamoDB) FilterRepos(ctx context.Context, acceptName mTypes.FilterRepoNameFunc,
   972  	filterFunc mTypes.FilterFullRepoFunc,
   973  ) ([]mTypes.RepoMeta, error) {
   974  	repos := []mTypes.RepoMeta{}
   975  	userBookmarks := getUserBookmarks(ctx, dwr)
   976  	userStars := getUserStars(ctx, dwr)
   977  
   978  	repoMetaAttributeIterator := NewBaseDynamoAttributesIterator(
   979  		dwr.Client, dwr.RepoMetaTablename, "RepoMeta", 0, dwr.Log,
   980  	)
   981  
   982  	repoMetaAttribute, err := repoMetaAttributeIterator.First(ctx)
   983  
   984  	for ; repoMetaAttribute != nil; repoMetaAttribute, err = repoMetaAttributeIterator.Next(ctx) {
   985  		if err != nil {
   986  			return []mTypes.RepoMeta{},
   987  				err
   988  		}
   989  
   990  		protoRepoMeta, err := getProtoRepoMetaFromAttribute(repoMetaAttribute)
   991  		if err != nil {
   992  			return nil, err
   993  		}
   994  
   995  		if ok, err := reqCtx.RepoIsUserAvailable(ctx, protoRepoMeta.Name); !ok || err != nil {
   996  			continue
   997  		}
   998  
   999  		if !acceptName(protoRepoMeta.Name) {
  1000  			continue
  1001  		}
  1002  
  1003  		protoRepoMeta.IsBookmarked = zcommon.Contains(userBookmarks, protoRepoMeta.Name)
  1004  		protoRepoMeta.IsStarred = zcommon.Contains(userStars, protoRepoMeta.Name)
  1005  
  1006  		fullRepoMeta := mConvert.GetRepoMeta(protoRepoMeta)
  1007  
  1008  		if filterFunc(fullRepoMeta) {
  1009  			repos = append(repos, fullRepoMeta)
  1010  		}
  1011  	}
  1012  
  1013  	return repos, err
  1014  }
  1015  
  1016  func (dwr *DynamoDB) IncrementRepoStars(repo string) error {
  1017  	repoMeta, err := dwr.getProtoRepoMeta(context.Background(), repo)
  1018  	if err != nil {
  1019  		return err
  1020  	}
  1021  
  1022  	repoMeta.Stars++
  1023  
  1024  	return dwr.setProtoRepoMeta(repo, repoMeta)
  1025  }
  1026  
  1027  func (dwr *DynamoDB) DecrementRepoStars(repo string) error {
  1028  	repoMeta, err := dwr.getProtoRepoMeta(context.Background(), repo)
  1029  	if err != nil {
  1030  		return err
  1031  	}
  1032  
  1033  	if repoMeta.Stars > 0 {
  1034  		repoMeta.Stars--
  1035  	}
  1036  
  1037  	return dwr.setProtoRepoMeta(repo, repoMeta)
  1038  }
  1039  
  1040  func (dwr *DynamoDB) SetRepoMeta(repo string, repoMeta mTypes.RepoMeta) error {
  1041  	protoRepoMeta := mConvert.GetProtoRepoMeta(repoMeta)
  1042  
  1043  	err := dwr.updateRepoLastUpdated(context.Background(), repo, time.Time{})
  1044  	if err != nil {
  1045  		return err
  1046  	}
  1047  
  1048  	return dwr.setProtoRepoMeta(repo, protoRepoMeta)
  1049  }
  1050  
  1051  func (dwr *DynamoDB) DeleteRepoMeta(repo string) error {
  1052  	_, err := dwr.Client.TransactWriteItems(context.Background(), &dynamodb.TransactWriteItemsInput{
  1053  		TransactItems: []types.TransactWriteItem{
  1054  			{
  1055  				Delete: &types.Delete{
  1056  					Key: map[string]types.AttributeValue{
  1057  						"TableKey": &types.AttributeValueMemberS{
  1058  							Value: repo,
  1059  						},
  1060  					},
  1061  					TableName: aws.String(dwr.RepoMetaTablename),
  1062  				},
  1063  			},
  1064  			{
  1065  				Delete: &types.Delete{
  1066  					Key: map[string]types.AttributeValue{
  1067  						"TableKey": &types.AttributeValueMemberS{
  1068  							Value: repo,
  1069  						},
  1070  					},
  1071  					TableName: aws.String(dwr.RepoBlobsTablename),
  1072  				},
  1073  			},
  1074  		},
  1075  	})
  1076  
  1077  	return err
  1078  }
  1079  
  1080  func (dwr *DynamoDB) GetReferrersInfo(repo string, referredDigest godigest.Digest, artifactTypes []string,
  1081  ) ([]mTypes.ReferrerInfo, error) {
  1082  	repoMeta, err := dwr.GetRepoMeta(context.Background(), repo)
  1083  	if err != nil {
  1084  		return []mTypes.ReferrerInfo{}, err
  1085  	}
  1086  
  1087  	referrersInfo := repoMeta.Referrers[referredDigest.String()]
  1088  
  1089  	filteredResults := make([]mTypes.ReferrerInfo, 0, len(referrersInfo))
  1090  
  1091  	for _, referrerInfo := range referrersInfo {
  1092  		if !common.MatchesArtifactTypes(referrerInfo.ArtifactType, artifactTypes) {
  1093  			continue
  1094  		}
  1095  
  1096  		filteredResults = append(filteredResults, referrerInfo)
  1097  	}
  1098  
  1099  	return filteredResults, nil
  1100  }
  1101  
  1102  func (dwr *DynamoDB) UpdateStatsOnDownload(repo string, reference string) error {
  1103  	repoMeta, err := dwr.getProtoRepoMeta(context.Background(), repo)
  1104  	if err != nil {
  1105  		return err
  1106  	}
  1107  
  1108  	descriptorDigest := reference
  1109  
  1110  	if !common.ReferenceIsDigest(reference) {
  1111  		// search digest for tag
  1112  		descriptor, found := repoMeta.Tags[reference]
  1113  
  1114  		if !found {
  1115  			return zerr.ErrImageMetaNotFound
  1116  		}
  1117  
  1118  		descriptorDigest = descriptor.Digest
  1119  	}
  1120  
  1121  	manifestStatistics, ok := repoMeta.Statistics[descriptorDigest]
  1122  	if !ok {
  1123  		return zerr.ErrImageMetaNotFound
  1124  	}
  1125  
  1126  	manifestStatistics.DownloadCount++
  1127  	manifestStatistics.LastPullTimestamp = timestamppb.Now()
  1128  	repoMeta.Statistics[descriptorDigest] = manifestStatistics
  1129  
  1130  	return dwr.setProtoRepoMeta(repo, repoMeta)
  1131  }
  1132  
  1133  func (dwr *DynamoDB) UpdateSignaturesValidity(ctx context.Context, repo string, manifestDigest godigest.Digest) error {
  1134  	imgTrustStore := dwr.ImageTrustStore()
  1135  
  1136  	if imgTrustStore == nil {
  1137  		return nil
  1138  	}
  1139  
  1140  	protoImageMeta, err := dwr.GetProtoImageMeta(ctx, manifestDigest)
  1141  	if err != nil {
  1142  		return err
  1143  	}
  1144  
  1145  	// update signatures with details about validity and author
  1146  	protoRepoMeta, err := dwr.getProtoRepoMeta(ctx, repo)
  1147  	if err != nil {
  1148  		return err
  1149  	}
  1150  
  1151  	manifestSignatures := proto_go.ManifestSignatures{Map: map[string]*proto_go.SignaturesInfo{"": {}}}
  1152  
  1153  	for sigType, sigs := range protoRepoMeta.Signatures[manifestDigest.String()].Map {
  1154  		if zcommon.IsContextDone(ctx) {
  1155  			return ctx.Err()
  1156  		}
  1157  
  1158  		signaturesInfo := []*proto_go.SignatureInfo{}
  1159  
  1160  		for _, sigInfo := range sigs.List {
  1161  			layersInfo := []*proto_go.LayersInfo{}
  1162  
  1163  			for _, layerInfo := range sigInfo.LayersInfo {
  1164  				author, date, isTrusted, _ := imgTrustStore.VerifySignature(sigType, layerInfo.LayerContent,
  1165  					layerInfo.SignatureKey, manifestDigest, mConvert.GetImageMeta(protoImageMeta), repo)
  1166  
  1167  				if isTrusted {
  1168  					layerInfo.Signer = author
  1169  				}
  1170  
  1171  				if !date.IsZero() {
  1172  					layerInfo.Signer = author
  1173  					layerInfo.Date = timestamppb.New(date)
  1174  				}
  1175  
  1176  				layersInfo = append(layersInfo, layerInfo)
  1177  			}
  1178  
  1179  			signaturesInfo = append(signaturesInfo, &proto_go.SignatureInfo{
  1180  				SignatureManifestDigest: sigInfo.SignatureManifestDigest,
  1181  				LayersInfo:              layersInfo,
  1182  			})
  1183  		}
  1184  
  1185  		manifestSignatures.Map[sigType] = &proto_go.SignaturesInfo{List: signaturesInfo}
  1186  	}
  1187  
  1188  	protoRepoMeta.Signatures[manifestDigest.String()] = &manifestSignatures
  1189  
  1190  	return dwr.setProtoRepoMeta(protoRepoMeta.Name, protoRepoMeta) //nolint: contextcheck
  1191  }
  1192  
  1193  func (dwr *DynamoDB) AddManifestSignature(repo string, signedManifestDigest godigest.Digest,
  1194  	sigMeta mTypes.SignatureMetadata,
  1195  ) error {
  1196  	protoRepoMeta, err := dwr.getProtoRepoMeta(context.Background(), repo)
  1197  	if err != nil {
  1198  		if errors.Is(err, zerr.ErrRepoMetaNotFound) {
  1199  			protoRepoMeta = &proto_go.RepoMeta{
  1200  				Name:       repo,
  1201  				Tags:       map[string]*proto_go.TagDescriptor{"": {}},
  1202  				Statistics: map[string]*proto_go.DescriptorStatistics{"": {}},
  1203  				Referrers:  map[string]*proto_go.ReferrersInfo{"": {}},
  1204  				Signatures: map[string]*proto_go.ManifestSignatures{
  1205  					signedManifestDigest.String(): {
  1206  						Map: map[string]*proto_go.SignaturesInfo{
  1207  							sigMeta.SignatureType: {
  1208  								List: []*proto_go.SignatureInfo{
  1209  									{
  1210  										SignatureManifestDigest: sigMeta.SignatureDigest,
  1211  										LayersInfo:              mConvert.GetProtoLayersInfo(sigMeta.LayersInfo),
  1212  									},
  1213  								},
  1214  							},
  1215  						},
  1216  					},
  1217  				},
  1218  			}
  1219  
  1220  			return dwr.setProtoRepoMeta(repo, protoRepoMeta)
  1221  		}
  1222  
  1223  		return err
  1224  	}
  1225  
  1226  	var (
  1227  		manifestSignatures *proto_go.ManifestSignatures
  1228  		found              bool
  1229  	)
  1230  
  1231  	if manifestSignatures, found = protoRepoMeta.Signatures[signedManifestDigest.String()]; !found {
  1232  		manifestSignatures = &proto_go.ManifestSignatures{Map: map[string]*proto_go.SignaturesInfo{"": {}}}
  1233  	}
  1234  
  1235  	signatureSlice := &proto_go.SignaturesInfo{List: []*proto_go.SignatureInfo{}}
  1236  	if sigSlice, found := manifestSignatures.Map[sigMeta.SignatureType]; found {
  1237  		signatureSlice = sigSlice
  1238  	}
  1239  
  1240  	if !common.ProtoSignatureAlreadyExists(signatureSlice.List, sigMeta) {
  1241  		switch sigMeta.SignatureType {
  1242  		case zcommon.NotationSignature:
  1243  			signatureSlice.List = append(signatureSlice.List, &proto_go.SignatureInfo{
  1244  				SignatureManifestDigest: sigMeta.SignatureDigest,
  1245  				LayersInfo:              mConvert.GetProtoLayersInfo(sigMeta.LayersInfo),
  1246  			})
  1247  		case zcommon.CosignSignature:
  1248  			newCosignSig := &proto_go.SignatureInfo{
  1249  				SignatureManifestDigest: sigMeta.SignatureDigest,
  1250  				LayersInfo:              mConvert.GetProtoLayersInfo(sigMeta.LayersInfo),
  1251  			}
  1252  
  1253  			if zcommon.IsCosignTag(sigMeta.SignatureTag) {
  1254  				// the entry for "sha256-{digest}.sig" signatures should be overwritten if
  1255  				// it exists or added on the first position if it doesn't exist
  1256  				if len(signatureSlice.GetList()) == 0 {
  1257  					signatureSlice.List = []*proto_go.SignatureInfo{newCosignSig}
  1258  				} else {
  1259  					signatureSlice.List[0] = newCosignSig
  1260  				}
  1261  			} else {
  1262  				// the first position should be reserved for "sha256-{digest}.sig" signatures
  1263  				if len(signatureSlice.GetList()) == 0 {
  1264  					signatureSlice.List = []*proto_go.SignatureInfo{{
  1265  						SignatureManifestDigest: "",
  1266  						LayersInfo:              []*proto_go.LayersInfo{},
  1267  					}}
  1268  				}
  1269  
  1270  				signatureSlice.List = append(signatureSlice.List, newCosignSig)
  1271  			}
  1272  		}
  1273  	}
  1274  
  1275  	manifestSignatures.Map[sigMeta.SignatureType] = signatureSlice
  1276  	protoRepoMeta.Signatures[signedManifestDigest.String()] = manifestSignatures
  1277  
  1278  	return dwr.setProtoRepoMeta(protoRepoMeta.Name, protoRepoMeta)
  1279  }
  1280  
  1281  func (dwr *DynamoDB) DeleteSignature(repo string, signedManifestDigest godigest.Digest,
  1282  	sigMeta mTypes.SignatureMetadata,
  1283  ) error {
  1284  	protoRepoMeta, err := dwr.getProtoRepoMeta(context.Background(), repo)
  1285  	if err != nil {
  1286  		return err
  1287  	}
  1288  
  1289  	sigType := sigMeta.SignatureType
  1290  
  1291  	var (
  1292  		manifestSignatures *proto_go.ManifestSignatures
  1293  		found              bool
  1294  	)
  1295  
  1296  	if manifestSignatures, found = protoRepoMeta.Signatures[signedManifestDigest.String()]; !found {
  1297  		return zerr.ErrImageMetaNotFound
  1298  	}
  1299  
  1300  	signatureSlice := manifestSignatures.Map[sigType]
  1301  
  1302  	newSignatureSlice := make([]*proto_go.SignatureInfo, 0, len(signatureSlice.List)-1)
  1303  
  1304  	for _, sigDigest := range signatureSlice.List {
  1305  		if sigDigest.SignatureManifestDigest != sigMeta.SignatureDigest {
  1306  			newSignatureSlice = append(newSignatureSlice, sigDigest)
  1307  		}
  1308  	}
  1309  
  1310  	manifestSignatures.Map[sigMeta.SignatureType] = &proto_go.SignaturesInfo{List: newSignatureSlice}
  1311  
  1312  	protoRepoMeta.Signatures[signedManifestDigest.String()] = manifestSignatures
  1313  
  1314  	return dwr.setProtoRepoMeta(protoRepoMeta.Name, protoRepoMeta)
  1315  }
  1316  
  1317  func (dwr *DynamoDB) FilterImageMeta(ctx context.Context, digests []string,
  1318  ) (map[string]mTypes.ImageMeta, error) {
  1319  	imageMetaAttributes, err := dwr.fetchImageMetaAttributesByDigest(ctx, digests)
  1320  	if err != nil {
  1321  		return nil, err
  1322  	}
  1323  
  1324  	results := map[string]mTypes.ImageMeta{}
  1325  
  1326  	for _, attributes := range imageMetaAttributes {
  1327  		protoImageMeta, err := getProtoImageMetaFromAttribute(attributes["ImageMeta"])
  1328  		if err != nil {
  1329  			return nil, err
  1330  		}
  1331  
  1332  		if protoImageMeta.MediaType == ispec.MediaTypeImageIndex {
  1333  			manifestDataList := make([]*proto_go.ManifestMeta, 0, len(protoImageMeta.Index.Index.Manifests))
  1334  
  1335  			indexDigests := make([]string, 0, len(protoImageMeta.Index.Index.Manifests))
  1336  			for i := range protoImageMeta.Index.Index.Manifests {
  1337  				indexDigests = append(indexDigests, protoImageMeta.Index.Index.Manifests[i].Digest)
  1338  			}
  1339  
  1340  			manifestsAttributes, err := dwr.fetchImageMetaAttributesByDigest(ctx, indexDigests)
  1341  			if err != nil {
  1342  				return nil, err
  1343  			}
  1344  
  1345  			for _, manifestAttribute := range manifestsAttributes {
  1346  				imageManifestData, err := getProtoImageMetaFromAttribute(manifestAttribute["ImageMeta"])
  1347  				if err != nil {
  1348  					return nil, err
  1349  				}
  1350  
  1351  				manifestDataList = append(manifestDataList, imageManifestData.Manifests[0])
  1352  			}
  1353  
  1354  			protoImageMeta.Manifests = manifestDataList
  1355  		}
  1356  
  1357  		results[mConvert.GetImageDigestStr(protoImageMeta)] = mConvert.GetImageMeta(protoImageMeta)
  1358  	}
  1359  
  1360  	return results, nil
  1361  }
  1362  
  1363  func (dwr *DynamoDB) RemoveRepoReference(repo, reference string, manifestDigest godigest.Digest,
  1364  ) error {
  1365  	ctx := context.Background()
  1366  
  1367  	protoRepoMeta, err := dwr.getProtoRepoMeta(context.Background(), repo)
  1368  	if err != nil {
  1369  		if errors.Is(err, zerr.ErrRepoMetaNotFound) {
  1370  			return nil
  1371  		}
  1372  
  1373  		return err
  1374  	}
  1375  
  1376  	protoImageMeta, err := dwr.GetProtoImageMeta(context.TODO(), manifestDigest)
  1377  	if err != nil {
  1378  		if errors.Is(err, zerr.ErrImageMetaNotFound) {
  1379  			return nil
  1380  		}
  1381  
  1382  		return err
  1383  	}
  1384  
  1385  	// Remove Referrers
  1386  	if subject := mConvert.GetImageSubject(protoImageMeta); subject != nil {
  1387  		referredDigest := subject.Digest.String()
  1388  		refInfo := &proto_go.ReferrersInfo{}
  1389  
  1390  		if protoRepoMeta.Referrers[referredDigest] != nil {
  1391  			refInfo = protoRepoMeta.Referrers[referredDigest]
  1392  		}
  1393  
  1394  		referrers := refInfo.List
  1395  
  1396  		for i := range referrers {
  1397  			if referrers[i].Digest == manifestDigest.String() {
  1398  				referrers[i].Count -= 1
  1399  
  1400  				if referrers[i].Count == 0 || common.ReferenceIsDigest(reference) {
  1401  					referrers = append(referrers[:i], referrers[i+1:]...)
  1402  				}
  1403  
  1404  				break
  1405  			}
  1406  		}
  1407  
  1408  		refInfo.List = referrers
  1409  
  1410  		protoRepoMeta.Referrers[referredDigest] = refInfo
  1411  	}
  1412  
  1413  	if !common.ReferenceIsDigest(reference) {
  1414  		delete(protoRepoMeta.Tags, reference)
  1415  	} else {
  1416  		// find all tags pointing to this digest
  1417  		tags := []string{}
  1418  		for tag, desc := range protoRepoMeta.Tags {
  1419  			if desc.Digest == reference {
  1420  				tags = append(tags, tag)
  1421  			}
  1422  		}
  1423  
  1424  		// remove all tags
  1425  		for _, tag := range tags {
  1426  			delete(protoRepoMeta.Tags, tag)
  1427  		}
  1428  	}
  1429  
  1430  	/* try to find at least one tag pointing to manifestDigest
  1431  	if not found then we can also remove everything related to this digest */
  1432  	var foundTag bool
  1433  
  1434  	for _, desc := range protoRepoMeta.Tags {
  1435  		if desc.Digest == manifestDigest.String() {
  1436  			foundTag = true
  1437  		}
  1438  	}
  1439  
  1440  	if !foundTag {
  1441  		delete(protoRepoMeta.Statistics, manifestDigest.String())
  1442  		delete(protoRepoMeta.Signatures, manifestDigest.String())
  1443  		delete(protoRepoMeta.Referrers, manifestDigest.String())
  1444  	}
  1445  
  1446  	repoBlobsInfo, err := dwr.getProtoRepoBlobs(ctx, repo)
  1447  	if err != nil {
  1448  		return err
  1449  	}
  1450  
  1451  	protoRepoMeta, repoBlobsInfo = common.RemoveImageFromRepoMeta(protoRepoMeta, repoBlobsInfo, reference)
  1452  
  1453  	err = dwr.setRepoBlobsInfo(repo, repoBlobsInfo) //nolint: contextcheck
  1454  	if err != nil {
  1455  		return err
  1456  	}
  1457  	err = dwr.setProtoRepoMeta(repo, protoRepoMeta) //nolint: contextcheck
  1458  
  1459  	return err
  1460  }
  1461  
  1462  func getUserStars(ctx context.Context, dwr *DynamoDB) []string {
  1463  	starredRepos, err := dwr.GetStarredRepos(ctx)
  1464  	if err != nil {
  1465  		return []string{}
  1466  	}
  1467  
  1468  	return starredRepos
  1469  }
  1470  
  1471  func getUserBookmarks(ctx context.Context, dwr *DynamoDB) []string {
  1472  	bookmarkedRepos, err := dwr.GetBookmarkedRepos(ctx)
  1473  	if err != nil {
  1474  		return []string{}
  1475  	}
  1476  
  1477  	return bookmarkedRepos
  1478  }
  1479  
  1480  func (dwr *DynamoDB) ToggleBookmarkRepo(ctx context.Context, repo string) (
  1481  	mTypes.ToggleState, error,
  1482  ) {
  1483  	res := mTypes.NotChanged
  1484  
  1485  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1486  	if err != nil {
  1487  		return mTypes.NotChanged, err
  1488  	}
  1489  
  1490  	if userAc.IsAnonymous() || !userAc.Can(constants.ReadPermission, repo) {
  1491  		return mTypes.NotChanged, zerr.ErrUserDataNotAllowed
  1492  	}
  1493  
  1494  	userData, err := dwr.GetUserData(ctx)
  1495  	if err != nil && !errors.Is(err, zerr.ErrUserDataNotFound) {
  1496  		return res, err
  1497  	}
  1498  
  1499  	if !zcommon.Contains(userData.BookmarkedRepos, repo) {
  1500  		userData.BookmarkedRepos = append(userData.BookmarkedRepos, repo)
  1501  		res = mTypes.Added
  1502  	} else {
  1503  		userData.BookmarkedRepos = zcommon.RemoveFrom(userData.BookmarkedRepos, repo)
  1504  		res = mTypes.Removed
  1505  	}
  1506  
  1507  	if res != mTypes.NotChanged {
  1508  		err = dwr.SetUserData(ctx, userData)
  1509  	}
  1510  
  1511  	if err != nil {
  1512  		res = mTypes.NotChanged
  1513  
  1514  		return res, err
  1515  	}
  1516  
  1517  	return res, nil
  1518  }
  1519  
  1520  func (dwr *DynamoDB) GetBookmarkedRepos(ctx context.Context) ([]string, error) {
  1521  	userMeta, err := dwr.GetUserData(ctx)
  1522  
  1523  	if errors.Is(err, zerr.ErrUserDataNotFound) || errors.Is(err, zerr.ErrUserDataNotAllowed) {
  1524  		return []string{}, nil
  1525  	}
  1526  
  1527  	return userMeta.BookmarkedRepos, err
  1528  }
  1529  
  1530  func (dwr *DynamoDB) ToggleStarRepo(ctx context.Context, repo string) (
  1531  	mTypes.ToggleState, error,
  1532  ) {
  1533  	res := mTypes.NotChanged
  1534  
  1535  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1536  	if err != nil {
  1537  		return mTypes.NotChanged, err
  1538  	}
  1539  
  1540  	if userAc.IsAnonymous() || !userAc.Can(constants.ReadPermission, repo) {
  1541  		return mTypes.NotChanged, zerr.ErrUserDataNotAllowed
  1542  	}
  1543  
  1544  	userid := userAc.GetUsername()
  1545  
  1546  	userData, err := dwr.GetUserData(ctx)
  1547  	if err != nil && !errors.Is(err, zerr.ErrUserDataNotFound) {
  1548  		return res, err
  1549  	}
  1550  
  1551  	if !zcommon.Contains(userData.StarredRepos, repo) {
  1552  		userData.StarredRepos = append(userData.StarredRepos, repo)
  1553  		res = mTypes.Added
  1554  	} else {
  1555  		userData.StarredRepos = zcommon.RemoveFrom(userData.StarredRepos, repo)
  1556  		res = mTypes.Removed
  1557  	}
  1558  
  1559  	if res != mTypes.NotChanged {
  1560  		repoMeta, err := dwr.getProtoRepoMeta(ctx, repo) //nolint:contextcheck
  1561  		if err != nil {
  1562  			return mTypes.NotChanged, err
  1563  		}
  1564  
  1565  		switch res {
  1566  		case mTypes.Added:
  1567  			repoMeta.Stars++
  1568  		case mTypes.Removed:
  1569  			repoMeta.Stars--
  1570  		}
  1571  
  1572  		repoMetaBlob, err := proto.Marshal(repoMeta)
  1573  		if err != nil {
  1574  			return mTypes.NotChanged, err
  1575  		}
  1576  
  1577  		repoAttributeValue, err := attributevalue.Marshal(repoMetaBlob)
  1578  		if err != nil {
  1579  			return mTypes.NotChanged, err
  1580  		}
  1581  
  1582  		userAttributeValue, err := attributevalue.Marshal(userData)
  1583  		if err != nil {
  1584  			return mTypes.NotChanged, err
  1585  		}
  1586  
  1587  		_, err = dwr.Client.TransactWriteItems(ctx, &dynamodb.TransactWriteItemsInput{
  1588  			TransactItems: []types.TransactWriteItem{
  1589  				{
  1590  					// Update User Profile
  1591  					Update: &types.Update{
  1592  						ExpressionAttributeNames: map[string]string{
  1593  							"#UP": "UserData",
  1594  						},
  1595  						ExpressionAttributeValues: map[string]types.AttributeValue{
  1596  							":UserData": userAttributeValue,
  1597  						},
  1598  						Key: map[string]types.AttributeValue{
  1599  							"TableKey": &types.AttributeValueMemberS{
  1600  								Value: userid,
  1601  							},
  1602  						},
  1603  						TableName:        aws.String(dwr.UserDataTablename),
  1604  						UpdateExpression: aws.String("SET #UP = :UserData"),
  1605  					},
  1606  				},
  1607  				{
  1608  					// Update Repo Meta with updated repo stars
  1609  					Update: &types.Update{
  1610  						ExpressionAttributeNames: map[string]string{
  1611  							"#RM": "RepoMeta",
  1612  						},
  1613  						ExpressionAttributeValues: map[string]types.AttributeValue{
  1614  							":RepoMeta": repoAttributeValue,
  1615  						},
  1616  						Key: map[string]types.AttributeValue{
  1617  							"TableKey": &types.AttributeValueMemberS{
  1618  								Value: repo,
  1619  							},
  1620  						},
  1621  						TableName:        aws.String(dwr.RepoMetaTablename),
  1622  						UpdateExpression: aws.String("SET #RM = :RepoMeta"),
  1623  					},
  1624  				},
  1625  			},
  1626  		})
  1627  		if err != nil {
  1628  			return mTypes.NotChanged, err
  1629  		}
  1630  	}
  1631  
  1632  	return res, nil
  1633  }
  1634  
  1635  func (dwr *DynamoDB) GetStarredRepos(ctx context.Context) ([]string, error) {
  1636  	userMeta, err := dwr.GetUserData(ctx)
  1637  
  1638  	if errors.Is(err, zerr.ErrUserDataNotFound) || errors.Is(err, zerr.ErrUserDataNotAllowed) {
  1639  		return []string{}, nil
  1640  	}
  1641  
  1642  	return userMeta.StarredRepos, err
  1643  }
  1644  
  1645  func (dwr DynamoDB) SetUserGroups(ctx context.Context, groups []string) error {
  1646  	userData, err := dwr.GetUserData(ctx)
  1647  	if err != nil && !errors.Is(err, zerr.ErrUserDataNotFound) {
  1648  		return err
  1649  	}
  1650  
  1651  	userData.Groups = append(userData.Groups, groups...)
  1652  
  1653  	return dwr.SetUserData(ctx, userData)
  1654  }
  1655  
  1656  func (dwr DynamoDB) GetUserGroups(ctx context.Context) ([]string, error) {
  1657  	userData, err := dwr.GetUserData(ctx)
  1658  
  1659  	return userData.Groups, err
  1660  }
  1661  
  1662  func (dwr *DynamoDB) IsAPIKeyExpired(ctx context.Context, hashedKey string) (bool, error) {
  1663  	userData, err := dwr.GetUserData(ctx)
  1664  	if err != nil {
  1665  		return false, err
  1666  	}
  1667  
  1668  	var isExpired bool
  1669  
  1670  	apiKeyDetails := userData.APIKeys[hashedKey]
  1671  	if apiKeyDetails.IsExpired {
  1672  		isExpired = true
  1673  
  1674  		return isExpired, nil
  1675  	}
  1676  
  1677  	// if expiresAt is not nil value
  1678  	if !apiKeyDetails.ExpirationDate.Equal(time.Time{}) && time.Now().After(apiKeyDetails.ExpirationDate) {
  1679  		isExpired = true
  1680  		apiKeyDetails.IsExpired = true
  1681  	}
  1682  
  1683  	userData.APIKeys[hashedKey] = apiKeyDetails
  1684  
  1685  	err = dwr.SetUserData(ctx, userData)
  1686  
  1687  	return isExpired, err
  1688  }
  1689  
  1690  func (dwr DynamoDB) UpdateUserAPIKeyLastUsed(ctx context.Context, hashedKey string) error {
  1691  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1692  	if err != nil {
  1693  		return err
  1694  	}
  1695  
  1696  	if userAc.IsAnonymous() {
  1697  		return zerr.ErrUserDataNotAllowed
  1698  	}
  1699  
  1700  	userData, err := dwr.GetUserData(ctx)
  1701  	if err != nil {
  1702  		return err
  1703  	}
  1704  
  1705  	apiKeyDetails := userData.APIKeys[hashedKey]
  1706  	apiKeyDetails.LastUsed = time.Now()
  1707  
  1708  	userData.APIKeys[hashedKey] = apiKeyDetails
  1709  
  1710  	err = dwr.SetUserData(ctx, userData)
  1711  
  1712  	return err
  1713  }
  1714  
  1715  func (dwr DynamoDB) GetUserAPIKeys(ctx context.Context) ([]mTypes.APIKeyDetails, error) {
  1716  	apiKeys := make([]mTypes.APIKeyDetails, 0)
  1717  
  1718  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1719  	if err != nil {
  1720  		return nil, err
  1721  	}
  1722  
  1723  	if userAc.IsAnonymous() {
  1724  		return nil, zerr.ErrUserDataNotAllowed
  1725  	}
  1726  
  1727  	userid := userAc.GetUsername()
  1728  
  1729  	userData, err := dwr.GetUserData(ctx)
  1730  	if err != nil && !errors.Is(err, zerr.ErrUserDataNotFound) {
  1731  		return nil, fmt.Errorf("failed to get userData for identity %s %w", userid, err)
  1732  	}
  1733  
  1734  	for hashedKey, apiKeyDetails := range userData.APIKeys {
  1735  		// if expiresAt is not nil value
  1736  		if !apiKeyDetails.ExpirationDate.Equal(time.Time{}) && time.Now().After(apiKeyDetails.ExpirationDate) {
  1737  			apiKeyDetails.IsExpired = true
  1738  		}
  1739  
  1740  		userData.APIKeys[hashedKey] = apiKeyDetails
  1741  
  1742  		err = dwr.SetUserData(ctx, userData)
  1743  		if err != nil {
  1744  			return nil, err
  1745  		}
  1746  
  1747  		apiKeys = append(apiKeys, apiKeyDetails)
  1748  	}
  1749  
  1750  	return apiKeys, nil
  1751  }
  1752  
  1753  func (dwr DynamoDB) AddUserAPIKey(ctx context.Context, hashedKey string, apiKeyDetails *mTypes.APIKeyDetails) error {
  1754  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1755  	if err != nil {
  1756  		return err
  1757  	}
  1758  
  1759  	if userAc.IsAnonymous() {
  1760  		return zerr.ErrUserDataNotAllowed
  1761  	}
  1762  
  1763  	userid := userAc.GetUsername()
  1764  
  1765  	userData, err := dwr.GetUserData(ctx)
  1766  	if err != nil && !errors.Is(err, zerr.ErrUserDataNotFound) {
  1767  		return fmt.Errorf("failed to get userData for identity %s %w", userid, err)
  1768  	}
  1769  
  1770  	if userData.APIKeys == nil {
  1771  		userData.APIKeys = make(map[string]mTypes.APIKeyDetails)
  1772  	}
  1773  
  1774  	userData.APIKeys[hashedKey] = *apiKeyDetails
  1775  
  1776  	userAttributeValue, err := attributevalue.Marshal(userData)
  1777  	if err != nil {
  1778  		return err
  1779  	}
  1780  
  1781  	_, err = dwr.Client.TransactWriteItems(ctx, &dynamodb.TransactWriteItemsInput{
  1782  		TransactItems: []types.TransactWriteItem{
  1783  			{
  1784  				// Update UserData
  1785  				Update: &types.Update{
  1786  					ExpressionAttributeNames: map[string]string{
  1787  						"#UP": "UserData",
  1788  					},
  1789  					ExpressionAttributeValues: map[string]types.AttributeValue{
  1790  						":UserData": userAttributeValue,
  1791  					},
  1792  					Key: map[string]types.AttributeValue{
  1793  						"TableKey": &types.AttributeValueMemberS{
  1794  							Value: userid,
  1795  						},
  1796  					},
  1797  					TableName:        aws.String(dwr.UserDataTablename),
  1798  					UpdateExpression: aws.String("SET #UP = :UserData"),
  1799  				},
  1800  			},
  1801  			{
  1802  				// Update APIKeyInfo
  1803  				Update: &types.Update{
  1804  					ExpressionAttributeNames: map[string]string{
  1805  						"#EM": "Identity",
  1806  					},
  1807  					ExpressionAttributeValues: map[string]types.AttributeValue{
  1808  						":Identity": &types.AttributeValueMemberS{Value: userid},
  1809  					},
  1810  					Key: map[string]types.AttributeValue{
  1811  						"TableKey": &types.AttributeValueMemberS{
  1812  							Value: hashedKey,
  1813  						},
  1814  					},
  1815  					TableName:        aws.String(dwr.APIKeyTablename),
  1816  					UpdateExpression: aws.String("SET #EM = :Identity"),
  1817  				},
  1818  			},
  1819  		},
  1820  	})
  1821  
  1822  	return err
  1823  }
  1824  
  1825  func (dwr DynamoDB) DeleteUserAPIKey(ctx context.Context, keyID string) error {
  1826  	userData, err := dwr.GetUserData(ctx)
  1827  	if err != nil {
  1828  		return fmt.Errorf("failed to get userData %w", err)
  1829  	}
  1830  
  1831  	for hash, apiKeyDetails := range userData.APIKeys {
  1832  		if apiKeyDetails.UUID == keyID {
  1833  			delete(userData.APIKeys, hash)
  1834  
  1835  			_, err = dwr.Client.DeleteItem(ctx, &dynamodb.DeleteItemInput{
  1836  				TableName: aws.String(dwr.APIKeyTablename),
  1837  				Key: map[string]types.AttributeValue{
  1838  					"TableKey": &types.AttributeValueMemberS{Value: hash},
  1839  				},
  1840  			})
  1841  			if err != nil {
  1842  				return fmt.Errorf("failed to delete userAPIKey entry for hash %s %w", hash, err)
  1843  			}
  1844  
  1845  			err := dwr.SetUserData(ctx, userData)
  1846  
  1847  			return err
  1848  		}
  1849  	}
  1850  
  1851  	return nil
  1852  }
  1853  
  1854  func (dwr DynamoDB) GetUserAPIKeyInfo(hashedKey string) (string, error) {
  1855  	var userid string
  1856  
  1857  	resp, err := dwr.Client.GetItem(context.Background(), &dynamodb.GetItemInput{
  1858  		TableName: aws.String(dwr.APIKeyTablename),
  1859  		Key: map[string]types.AttributeValue{
  1860  			"TableKey": &types.AttributeValueMemberS{Value: hashedKey},
  1861  		},
  1862  	})
  1863  	if err != nil {
  1864  		return "", err
  1865  	}
  1866  
  1867  	if resp.Item == nil {
  1868  		return "", zerr.ErrUserAPIKeyNotFound
  1869  	}
  1870  
  1871  	err = attributevalue.Unmarshal(resp.Item["Identity"], &userid)
  1872  	if err != nil {
  1873  		return "", err
  1874  	}
  1875  
  1876  	return userid, nil
  1877  }
  1878  
  1879  func (dwr DynamoDB) GetUserData(ctx context.Context) (mTypes.UserData, error) {
  1880  	var userData mTypes.UserData
  1881  
  1882  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1883  	if err != nil {
  1884  		return userData, err
  1885  	}
  1886  
  1887  	if userAc.IsAnonymous() {
  1888  		return userData, zerr.ErrUserDataNotAllowed
  1889  	}
  1890  
  1891  	userid := userAc.GetUsername()
  1892  
  1893  	resp, err := dwr.Client.GetItem(ctx, &dynamodb.GetItemInput{
  1894  		TableName: aws.String(dwr.UserDataTablename),
  1895  		Key: map[string]types.AttributeValue{
  1896  			"TableKey": &types.AttributeValueMemberS{Value: userid},
  1897  		},
  1898  	})
  1899  	if err != nil {
  1900  		return mTypes.UserData{}, err
  1901  	}
  1902  
  1903  	if resp.Item == nil {
  1904  		return mTypes.UserData{}, zerr.ErrUserDataNotFound
  1905  	}
  1906  
  1907  	err = attributevalue.Unmarshal(resp.Item["UserData"], &userData)
  1908  	if err != nil {
  1909  		return mTypes.UserData{}, err
  1910  	}
  1911  
  1912  	return userData, nil
  1913  }
  1914  
  1915  func (dwr DynamoDB) SetUserData(ctx context.Context, userData mTypes.UserData) error {
  1916  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1917  	if err != nil {
  1918  		return err
  1919  	}
  1920  
  1921  	if userAc.IsAnonymous() {
  1922  		return zerr.ErrUserDataNotAllowed
  1923  	}
  1924  
  1925  	userid := userAc.GetUsername()
  1926  
  1927  	userAttributeValue, err := attributevalue.Marshal(userData)
  1928  	if err != nil {
  1929  		return err
  1930  	}
  1931  
  1932  	_, err = dwr.Client.UpdateItem(ctx, &dynamodb.UpdateItemInput{
  1933  		ExpressionAttributeNames: map[string]string{
  1934  			"#UP": "UserData",
  1935  		},
  1936  		ExpressionAttributeValues: map[string]types.AttributeValue{
  1937  			":UserData": userAttributeValue,
  1938  		},
  1939  		Key: map[string]types.AttributeValue{
  1940  			"TableKey": &types.AttributeValueMemberS{
  1941  				Value: userid,
  1942  			},
  1943  		},
  1944  		TableName:        aws.String(dwr.UserDataTablename),
  1945  		UpdateExpression: aws.String("SET #UP = :UserData"),
  1946  	})
  1947  
  1948  	return err
  1949  }
  1950  
  1951  func (dwr DynamoDB) DeleteUserData(ctx context.Context) error {
  1952  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1953  	if err != nil {
  1954  		return err
  1955  	}
  1956  
  1957  	if userAc.IsAnonymous() {
  1958  		return zerr.ErrUserDataNotAllowed
  1959  	}
  1960  
  1961  	userid := userAc.GetUsername()
  1962  
  1963  	_, err = dwr.Client.DeleteItem(ctx, &dynamodb.DeleteItemInput{
  1964  		TableName: aws.String(dwr.UserDataTablename),
  1965  		Key: map[string]types.AttributeValue{
  1966  			"TableKey": &types.AttributeValueMemberS{Value: userid},
  1967  		},
  1968  	})
  1969  
  1970  	return err
  1971  }
  1972  
  1973  func (dwr *DynamoDB) fetchImageMetaAttributesByDigest(ctx context.Context, digests []string,
  1974  ) ([]map[string]types.AttributeValue, error) {
  1975  	resp, err := dwr.Client.BatchGetItem(ctx, &dynamodb.BatchGetItemInput{
  1976  		RequestItems: map[string]types.KeysAndAttributes{
  1977  			dwr.ImageMetaTablename: {
  1978  				Keys: getBatchImageKeys(digests),
  1979  			},
  1980  		},
  1981  	})
  1982  	if err != nil {
  1983  		return nil, err
  1984  	}
  1985  
  1986  	if len(resp.Responses[dwr.ImageMetaTablename]) != len(digests) {
  1987  		return nil, zerr.ErrImageMetaNotFound
  1988  	}
  1989  
  1990  	return resp.Responses[dwr.ImageMetaTablename], nil
  1991  }
  1992  
  1993  func getBatchImageKeys(digests []string) []map[string]types.AttributeValue {
  1994  	result := []map[string]types.AttributeValue{}
  1995  
  1996  	for _, digest := range digests {
  1997  		result = append(result, map[string]types.AttributeValue{
  1998  			"TableKey": &types.AttributeValueMemberS{
  1999  				Value: digest,
  2000  			},
  2001  		})
  2002  	}
  2003  
  2004  	return result
  2005  }
  2006  
  2007  func (dwr *DynamoDB) PatchDB() error {
  2008  	DBVersion, err := dwr.getDBVersion()
  2009  	if err != nil {
  2010  		return fmt.Errorf("patching dynamo failed, error retrieving database version %w", err)
  2011  	}
  2012  
  2013  	if version.GetVersionIndex(DBVersion) == -1 {
  2014  		return fmt.Errorf("DB has broken format, no version found %w", err)
  2015  	}
  2016  
  2017  	for patchIndex, patch := range dwr.Patches {
  2018  		if patchIndex < version.GetVersionIndex(DBVersion) {
  2019  			continue
  2020  		}
  2021  
  2022  		tableNames := map[string]string{
  2023  			"RepoMetaTablename": dwr.RepoMetaTablename,
  2024  			"VersionTablename":  dwr.VersionTablename,
  2025  		}
  2026  
  2027  		err := patch(dwr.Client, tableNames)
  2028  		if err != nil {
  2029  			return err
  2030  		}
  2031  	}
  2032  
  2033  	return nil
  2034  }
  2035  
  2036  func (dwr *DynamoDB) ResetDB() error {
  2037  	err := dwr.ResetTable(dwr.APIKeyTablename)
  2038  	if err != nil {
  2039  		return err
  2040  	}
  2041  
  2042  	err = dwr.ResetTable(dwr.ImageMetaTablename)
  2043  	if err != nil {
  2044  		return err
  2045  	}
  2046  
  2047  	err = dwr.ResetTable(dwr.RepoBlobsTablename)
  2048  	if err != nil {
  2049  		return err
  2050  	}
  2051  
  2052  	err = dwr.ResetTable(dwr.RepoMetaTablename)
  2053  	if err != nil {
  2054  		return err
  2055  	}
  2056  
  2057  	err = dwr.ResetTable(dwr.UserDataTablename)
  2058  	if err != nil {
  2059  		return err
  2060  	}
  2061  
  2062  	return nil
  2063  }
  2064  
  2065  func (dwr *DynamoDB) ResetTable(tableName string) error {
  2066  	err := dwr.deleteTable(tableName)
  2067  	if err != nil {
  2068  		return err
  2069  	}
  2070  
  2071  	return dwr.createTable(tableName)
  2072  }
  2073  
  2074  func (dwr *DynamoDB) createTable(tableName string) error {
  2075  	_, err := dwr.Client.CreateTable(context.Background(), &dynamodb.CreateTableInput{
  2076  		TableName: aws.String(tableName),
  2077  		AttributeDefinitions: []types.AttributeDefinition{
  2078  			{
  2079  				AttributeName: aws.String("TableKey"),
  2080  				AttributeType: types.ScalarAttributeTypeS,
  2081  			},
  2082  		},
  2083  		KeySchema: []types.KeySchemaElement{
  2084  			{
  2085  				AttributeName: aws.String("TableKey"),
  2086  				KeyType:       types.KeyTypeHash,
  2087  			},
  2088  		},
  2089  		BillingMode: types.BillingModePayPerRequest,
  2090  	})
  2091  
  2092  	if err != nil && !strings.Contains(err.Error(), "Table already exists") {
  2093  		return err
  2094  	}
  2095  
  2096  	return dwr.waitTableToBeCreated(tableName)
  2097  }
  2098  
  2099  func (dwr *DynamoDB) deleteTable(tableName string) error {
  2100  	_, err := dwr.Client.DeleteTable(context.Background(), &dynamodb.DeleteTableInput{
  2101  		TableName: aws.String(tableName),
  2102  	})
  2103  
  2104  	if temp := new(types.ResourceNotFoundException); errors.As(err, &temp) {
  2105  		return nil
  2106  	}
  2107  
  2108  	return dwr.waitTableToBeDeleted(tableName)
  2109  }
  2110  
  2111  func (dwr *DynamoDB) waitTableToBeCreated(tableName string) error {
  2112  	const maxWaitTime = 20 * time.Second
  2113  
  2114  	waiter := dynamodb.NewTableExistsWaiter(dwr.Client)
  2115  
  2116  	return waiter.Wait(context.Background(), &dynamodb.DescribeTableInput{
  2117  		TableName: &tableName,
  2118  	}, maxWaitTime)
  2119  }
  2120  
  2121  func (dwr *DynamoDB) waitTableToBeDeleted(tableName string) error {
  2122  	const maxWaitTime = 20 * time.Second
  2123  
  2124  	waiter := dynamodb.NewTableNotExistsWaiter(dwr.Client)
  2125  
  2126  	return waiter.Wait(context.Background(), &dynamodb.DescribeTableInput{
  2127  		TableName: &tableName,
  2128  	}, maxWaitTime)
  2129  }
  2130  
  2131  func (dwr *DynamoDB) createVersionTable() error {
  2132  	_, err := dwr.Client.CreateTable(context.Background(), &dynamodb.CreateTableInput{
  2133  		TableName: aws.String(dwr.VersionTablename),
  2134  		AttributeDefinitions: []types.AttributeDefinition{
  2135  			{
  2136  				AttributeName: aws.String("TableKey"),
  2137  				AttributeType: types.ScalarAttributeTypeS,
  2138  			},
  2139  		},
  2140  		KeySchema: []types.KeySchemaElement{
  2141  			{
  2142  				AttributeName: aws.String("TableKey"),
  2143  				KeyType:       types.KeyTypeHash,
  2144  			},
  2145  		},
  2146  		BillingMode: types.BillingModePayPerRequest,
  2147  	})
  2148  	if err != nil {
  2149  		if strings.Contains(err.Error(), "Table already exists") {
  2150  			return nil
  2151  		}
  2152  
  2153  		return err
  2154  	}
  2155  
  2156  	err = dwr.waitTableToBeCreated(dwr.VersionTablename)
  2157  	if err != nil {
  2158  		return err
  2159  	}
  2160  
  2161  	if err == nil {
  2162  		mdAttributeValue, err := attributevalue.Marshal(version.CurrentVersion)
  2163  		if err != nil {
  2164  			return err
  2165  		}
  2166  
  2167  		_, err = dwr.Client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
  2168  			ExpressionAttributeNames: map[string]string{
  2169  				"#V": "Version",
  2170  			},
  2171  			ExpressionAttributeValues: map[string]types.AttributeValue{
  2172  				":Version": mdAttributeValue,
  2173  			},
  2174  			Key: map[string]types.AttributeValue{
  2175  				"TableKey": &types.AttributeValueMemberS{
  2176  					Value: version.DBVersionKey,
  2177  				},
  2178  			},
  2179  			TableName:        aws.String(dwr.VersionTablename),
  2180  			UpdateExpression: aws.String("SET #V = :Version"),
  2181  		})
  2182  
  2183  		if err != nil {
  2184  			return err
  2185  		}
  2186  	}
  2187  
  2188  	return nil
  2189  }
  2190  
  2191  func (dwr *DynamoDB) getDBVersion() (string, error) {
  2192  	resp, err := dwr.Client.GetItem(context.TODO(), &dynamodb.GetItemInput{
  2193  		TableName: aws.String(dwr.VersionTablename),
  2194  		Key: map[string]types.AttributeValue{
  2195  			"TableKey": &types.AttributeValueMemberS{Value: version.DBVersionKey},
  2196  		},
  2197  	})
  2198  	if err != nil {
  2199  		return "", err
  2200  	}
  2201  
  2202  	if resp.Item == nil {
  2203  		return "", nil
  2204  	}
  2205  
  2206  	var version string
  2207  
  2208  	err = attributevalue.Unmarshal(resp.Item["Version"], &version)
  2209  	if err != nil {
  2210  		return "", err
  2211  	}
  2212  
  2213  	return version, nil
  2214  }