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