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 := ×tamppb.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: ×tamppb.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 }