github.com/lalkh/containerd@v1.4.3/metadata/content.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package metadata 18 19 import ( 20 "context" 21 "encoding/binary" 22 "strings" 23 "sync" 24 "sync/atomic" 25 "time" 26 27 "github.com/containerd/containerd/content" 28 "github.com/containerd/containerd/errdefs" 29 "github.com/containerd/containerd/filters" 30 "github.com/containerd/containerd/labels" 31 "github.com/containerd/containerd/log" 32 "github.com/containerd/containerd/metadata/boltutil" 33 "github.com/containerd/containerd/namespaces" 34 digest "github.com/opencontainers/go-digest" 35 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 36 "github.com/pkg/errors" 37 bolt "go.etcd.io/bbolt" 38 ) 39 40 type contentStore struct { 41 content.Store 42 db *DB 43 shared bool 44 l sync.RWMutex 45 } 46 47 // newContentStore returns a namespaced content store using an existing 48 // content store interface. 49 // policy defines the sharing behavior for content between namespaces. Both 50 // modes will result in shared storage in the backend for committed. Choose 51 // "shared" to prevent separate namespaces from having to pull the same content 52 // twice. Choose "isolated" if the content must not be shared between 53 // namespaces. 54 // 55 // If the policy is "shared", writes will try to resolve the "expected" digest 56 // against the backend, allowing imports of content from other namespaces. In 57 // "isolated" mode, the client must prove they have the content by providing 58 // the entire blob before the content can be added to another namespace. 59 // 60 // Since we have only two policies right now, it's simpler using bool to 61 // represent it internally. 62 func newContentStore(db *DB, shared bool, cs content.Store) *contentStore { 63 return &contentStore{ 64 Store: cs, 65 db: db, 66 shared: shared, 67 } 68 } 69 70 func (cs *contentStore) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) { 71 ns, err := namespaces.NamespaceRequired(ctx) 72 if err != nil { 73 return content.Info{}, err 74 } 75 76 var info content.Info 77 if err := view(ctx, cs.db, func(tx *bolt.Tx) error { 78 bkt := getBlobBucket(tx, ns, dgst) 79 if bkt == nil { 80 return errors.Wrapf(errdefs.ErrNotFound, "content digest %v", dgst) 81 } 82 83 info.Digest = dgst 84 return readInfo(&info, bkt) 85 }); err != nil { 86 return content.Info{}, err 87 } 88 89 return info, nil 90 } 91 92 func (cs *contentStore) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) { 93 ns, err := namespaces.NamespaceRequired(ctx) 94 if err != nil { 95 return content.Info{}, err 96 } 97 98 cs.l.RLock() 99 defer cs.l.RUnlock() 100 101 updated := content.Info{ 102 Digest: info.Digest, 103 } 104 if err := update(ctx, cs.db, func(tx *bolt.Tx) error { 105 bkt := getBlobBucket(tx, ns, info.Digest) 106 if bkt == nil { 107 return errors.Wrapf(errdefs.ErrNotFound, "content digest %v", info.Digest) 108 } 109 110 if err := readInfo(&updated, bkt); err != nil { 111 return errors.Wrapf(err, "info %q", info.Digest) 112 } 113 114 if len(fieldpaths) > 0 { 115 for _, path := range fieldpaths { 116 if strings.HasPrefix(path, "labels.") { 117 if updated.Labels == nil { 118 updated.Labels = map[string]string{} 119 } 120 121 key := strings.TrimPrefix(path, "labels.") 122 updated.Labels[key] = info.Labels[key] 123 continue 124 } 125 126 switch path { 127 case "labels": 128 updated.Labels = info.Labels 129 default: 130 return errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on content info %q", path, info.Digest) 131 } 132 } 133 } else { 134 // Set mutable fields 135 updated.Labels = info.Labels 136 } 137 if err := validateInfo(&updated); err != nil { 138 return err 139 } 140 141 updated.UpdatedAt = time.Now().UTC() 142 return writeInfo(&updated, bkt) 143 }); err != nil { 144 return content.Info{}, err 145 } 146 return updated, nil 147 } 148 149 func (cs *contentStore) Walk(ctx context.Context, fn content.WalkFunc, fs ...string) error { 150 ns, err := namespaces.NamespaceRequired(ctx) 151 if err != nil { 152 return err 153 } 154 155 filter, err := filters.ParseAll(fs...) 156 if err != nil { 157 return err 158 } 159 160 // TODO: Batch results to keep from reading all info into memory 161 var infos []content.Info 162 if err := view(ctx, cs.db, func(tx *bolt.Tx) error { 163 bkt := getBlobsBucket(tx, ns) 164 if bkt == nil { 165 return nil 166 } 167 168 return bkt.ForEach(func(k, v []byte) error { 169 dgst, err := digest.Parse(string(k)) 170 if err != nil { 171 // Not a digest, skip 172 return nil 173 } 174 bbkt := bkt.Bucket(k) 175 if bbkt == nil { 176 return nil 177 } 178 info := content.Info{ 179 Digest: dgst, 180 } 181 if err := readInfo(&info, bkt.Bucket(k)); err != nil { 182 return err 183 } 184 if filter.Match(adaptContentInfo(info)) { 185 infos = append(infos, info) 186 } 187 return nil 188 }) 189 }); err != nil { 190 return err 191 } 192 193 for _, info := range infos { 194 if err := fn(info); err != nil { 195 return err 196 } 197 } 198 199 return nil 200 } 201 202 func (cs *contentStore) Delete(ctx context.Context, dgst digest.Digest) error { 203 ns, err := namespaces.NamespaceRequired(ctx) 204 if err != nil { 205 return err 206 } 207 208 cs.l.RLock() 209 defer cs.l.RUnlock() 210 211 return update(ctx, cs.db, func(tx *bolt.Tx) error { 212 bkt := getBlobBucket(tx, ns, dgst) 213 if bkt == nil { 214 return errors.Wrapf(errdefs.ErrNotFound, "content digest %v", dgst) 215 } 216 217 if err := getBlobsBucket(tx, ns).DeleteBucket([]byte(dgst.String())); err != nil { 218 return err 219 } 220 if err := removeContentLease(ctx, tx, dgst); err != nil { 221 return err 222 } 223 224 // Mark content store as dirty for triggering garbage collection 225 atomic.AddUint32(&cs.db.dirty, 1) 226 cs.db.dirtyCS = true 227 228 return nil 229 }) 230 } 231 232 func (cs *contentStore) ListStatuses(ctx context.Context, fs ...string) ([]content.Status, error) { 233 ns, err := namespaces.NamespaceRequired(ctx) 234 if err != nil { 235 return nil, err 236 } 237 238 filter, err := filters.ParseAll(fs...) 239 if err != nil { 240 return nil, err 241 } 242 243 brefs := map[string]string{} 244 if err := view(ctx, cs.db, func(tx *bolt.Tx) error { 245 bkt := getIngestsBucket(tx, ns) 246 if bkt == nil { 247 return nil 248 } 249 250 return bkt.ForEach(func(k, v []byte) error { 251 if v == nil { 252 // TODO(dmcgowan): match name and potentially labels here 253 brefs[string(k)] = string(bkt.Bucket(k).Get(bucketKeyRef)) 254 } 255 return nil 256 }) 257 }); err != nil { 258 return nil, err 259 } 260 261 statuses := make([]content.Status, 0, len(brefs)) 262 for k, bref := range brefs { 263 status, err := cs.Store.Status(ctx, bref) 264 if err != nil { 265 if errdefs.IsNotFound(err) { 266 continue 267 } 268 return nil, err 269 } 270 status.Ref = k 271 272 if filter.Match(adaptContentStatus(status)) { 273 statuses = append(statuses, status) 274 } 275 } 276 277 return statuses, nil 278 279 } 280 281 func getRef(tx *bolt.Tx, ns, ref string) string { 282 bkt := getIngestBucket(tx, ns, ref) 283 if bkt == nil { 284 return "" 285 } 286 v := bkt.Get(bucketKeyRef) 287 if len(v) == 0 { 288 return "" 289 } 290 return string(v) 291 } 292 293 func (cs *contentStore) Status(ctx context.Context, ref string) (content.Status, error) { 294 ns, err := namespaces.NamespaceRequired(ctx) 295 if err != nil { 296 return content.Status{}, err 297 } 298 299 var bref string 300 if err := view(ctx, cs.db, func(tx *bolt.Tx) error { 301 bref = getRef(tx, ns, ref) 302 if bref == "" { 303 return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref) 304 } 305 306 return nil 307 }); err != nil { 308 return content.Status{}, err 309 } 310 311 st, err := cs.Store.Status(ctx, bref) 312 if err != nil { 313 return content.Status{}, err 314 } 315 st.Ref = ref 316 return st, nil 317 } 318 319 func (cs *contentStore) Abort(ctx context.Context, ref string) error { 320 ns, err := namespaces.NamespaceRequired(ctx) 321 if err != nil { 322 return err 323 } 324 325 cs.l.RLock() 326 defer cs.l.RUnlock() 327 328 return update(ctx, cs.db, func(tx *bolt.Tx) error { 329 ibkt := getIngestsBucket(tx, ns) 330 if ibkt == nil { 331 return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref) 332 } 333 bkt := ibkt.Bucket([]byte(ref)) 334 if bkt == nil { 335 return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref) 336 } 337 bref := string(bkt.Get(bucketKeyRef)) 338 if bref == "" { 339 return errors.Wrapf(errdefs.ErrNotFound, "reference %v", ref) 340 } 341 expected := string(bkt.Get(bucketKeyExpected)) 342 if err := ibkt.DeleteBucket([]byte(ref)); err != nil { 343 return err 344 } 345 346 if err := removeIngestLease(ctx, tx, ref); err != nil { 347 return err 348 } 349 350 // if not shared content, delete active ingest on backend 351 if expected == "" { 352 return cs.Store.Abort(ctx, bref) 353 } 354 355 return nil 356 }) 357 358 } 359 360 func (cs *contentStore) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) { 361 var wOpts content.WriterOpts 362 for _, opt := range opts { 363 if err := opt(&wOpts); err != nil { 364 return nil, err 365 } 366 } 367 // TODO(AkihiroSuda): we could create a random string or one calculated based on the context 368 // https://github.com/containerd/containerd/issues/2129#issuecomment-380255019 369 if wOpts.Ref == "" { 370 return nil, errors.Wrap(errdefs.ErrInvalidArgument, "ref must not be empty") 371 } 372 ns, err := namespaces.NamespaceRequired(ctx) 373 if err != nil { 374 return nil, err 375 } 376 377 cs.l.RLock() 378 defer cs.l.RUnlock() 379 380 var ( 381 w content.Writer 382 exists bool 383 bref string 384 ) 385 if err := update(ctx, cs.db, func(tx *bolt.Tx) error { 386 var shared bool 387 if wOpts.Desc.Digest != "" { 388 cbkt := getBlobBucket(tx, ns, wOpts.Desc.Digest) 389 if cbkt != nil { 390 // Add content to lease to prevent other reference removals 391 // from effecting this object during a provided lease 392 if err := addContentLease(ctx, tx, wOpts.Desc.Digest); err != nil { 393 return errors.Wrap(err, "unable to lease content") 394 } 395 // Return error outside of transaction to ensure 396 // commit succeeds with the lease. 397 exists = true 398 return nil 399 } 400 401 if cs.shared { 402 if st, err := cs.Store.Info(ctx, wOpts.Desc.Digest); err == nil { 403 // Ensure the expected size is the same, it is likely 404 // an error if the size is mismatched but the caller 405 // must resolve this on commit 406 if wOpts.Desc.Size == 0 || wOpts.Desc.Size == st.Size { 407 shared = true 408 wOpts.Desc.Size = st.Size 409 } 410 } 411 } 412 } 413 414 bkt, err := createIngestBucket(tx, ns, wOpts.Ref) 415 if err != nil { 416 return err 417 } 418 419 leased, err := addIngestLease(ctx, tx, wOpts.Ref) 420 if err != nil { 421 return err 422 } 423 424 brefb := bkt.Get(bucketKeyRef) 425 if brefb == nil { 426 sid, err := bkt.NextSequence() 427 if err != nil { 428 return err 429 } 430 431 bref = createKey(sid, ns, wOpts.Ref) 432 if err := bkt.Put(bucketKeyRef, []byte(bref)); err != nil { 433 return err 434 } 435 } else { 436 bref = string(brefb) 437 } 438 if !leased { 439 // Add timestamp to allow aborting once stale 440 // When lease is set the ingest should be aborted 441 // after lease it belonged to is deleted. 442 // Expiration can be configurable in the future to 443 // give more control to the daemon, however leases 444 // already give users more control of expiration. 445 expireAt := time.Now().UTC().Add(24 * time.Hour) 446 if err := writeExpireAt(expireAt, bkt); err != nil { 447 return err 448 } 449 } 450 451 if shared { 452 if err := bkt.Put(bucketKeyExpected, []byte(wOpts.Desc.Digest)); err != nil { 453 return err 454 } 455 } else { 456 // Do not use the passed in expected value here since it was 457 // already checked against the user metadata. The content must 458 // be committed in the namespace before it will be seen as 459 // available in the current namespace. 460 desc := wOpts.Desc 461 desc.Digest = "" 462 w, err = cs.Store.Writer(ctx, content.WithRef(bref), content.WithDescriptor(desc)) 463 } 464 return err 465 }); err != nil { 466 return nil, err 467 } 468 if exists { 469 return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", wOpts.Desc.Digest) 470 } 471 472 return &namespacedWriter{ 473 ctx: ctx, 474 ref: wOpts.Ref, 475 namespace: ns, 476 db: cs.db, 477 provider: cs.Store, 478 l: &cs.l, 479 w: w, 480 bref: bref, 481 started: time.Now(), 482 desc: wOpts.Desc, 483 }, nil 484 } 485 486 type namespacedWriter struct { 487 ctx context.Context 488 ref string 489 namespace string 490 db transactor 491 provider interface { 492 content.Provider 493 content.Ingester 494 } 495 l *sync.RWMutex 496 497 w content.Writer 498 499 bref string 500 started time.Time 501 desc ocispec.Descriptor 502 } 503 504 func (nw *namespacedWriter) Close() error { 505 if nw.w != nil { 506 return nw.w.Close() 507 } 508 return nil 509 } 510 511 func (nw *namespacedWriter) Write(p []byte) (int, error) { 512 // if no writer, first copy and unshare before performing write 513 if nw.w == nil { 514 if len(p) == 0 { 515 return 0, nil 516 } 517 518 if err := nw.createAndCopy(nw.ctx, nw.desc); err != nil { 519 return 0, err 520 } 521 } 522 523 return nw.w.Write(p) 524 } 525 526 func (nw *namespacedWriter) Digest() digest.Digest { 527 if nw.w != nil { 528 return nw.w.Digest() 529 } 530 return nw.desc.Digest 531 } 532 533 func (nw *namespacedWriter) Truncate(size int64) error { 534 if nw.w != nil { 535 return nw.w.Truncate(size) 536 } 537 desc := nw.desc 538 desc.Size = size 539 desc.Digest = "" 540 return nw.createAndCopy(nw.ctx, desc) 541 } 542 543 func (nw *namespacedWriter) createAndCopy(ctx context.Context, desc ocispec.Descriptor) error { 544 nwDescWithoutDigest := desc 545 nwDescWithoutDigest.Digest = "" 546 w, err := nw.provider.Writer(ctx, content.WithRef(nw.bref), content.WithDescriptor(nwDescWithoutDigest)) 547 if err != nil { 548 return err 549 } 550 551 if desc.Size > 0 { 552 ra, err := nw.provider.ReaderAt(ctx, nw.desc) 553 if err != nil { 554 return err 555 } 556 defer ra.Close() 557 558 if err := content.CopyReaderAt(w, ra, desc.Size); err != nil { 559 nw.w.Close() 560 nw.w = nil 561 return err 562 } 563 } 564 nw.w = w 565 566 return nil 567 } 568 569 func (nw *namespacedWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error { 570 ctx = namespaces.WithNamespace(ctx, nw.namespace) 571 572 nw.l.RLock() 573 defer nw.l.RUnlock() 574 575 var innerErr error 576 577 if err := update(ctx, nw.db, func(tx *bolt.Tx) error { 578 dgst, err := nw.commit(ctx, tx, size, expected, opts...) 579 if err != nil { 580 if !errdefs.IsAlreadyExists(err) { 581 return err 582 } 583 innerErr = err 584 } 585 bkt := getIngestsBucket(tx, nw.namespace) 586 if bkt != nil { 587 if err := bkt.DeleteBucket([]byte(nw.ref)); err != nil && err != bolt.ErrBucketNotFound { 588 return err 589 } 590 } 591 if err := removeIngestLease(ctx, tx, nw.ref); err != nil { 592 return err 593 } 594 return addContentLease(ctx, tx, dgst) 595 }); err != nil { 596 return err 597 } 598 599 return innerErr 600 } 601 602 func (nw *namespacedWriter) commit(ctx context.Context, tx *bolt.Tx, size int64, expected digest.Digest, opts ...content.Opt) (digest.Digest, error) { 603 var base content.Info 604 for _, opt := range opts { 605 if err := opt(&base); err != nil { 606 if nw.w != nil { 607 nw.w.Close() 608 } 609 return "", err 610 } 611 } 612 if err := validateInfo(&base); err != nil { 613 if nw.w != nil { 614 nw.w.Close() 615 } 616 return "", err 617 } 618 619 var actual digest.Digest 620 if nw.w == nil { 621 if size != 0 && size != nw.desc.Size { 622 return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "%q failed size validation: %v != %v", nw.ref, nw.desc.Size, size) 623 } 624 if expected != "" && expected != nw.desc.Digest { 625 return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "%q unexpected digest", nw.ref) 626 } 627 size = nw.desc.Size 628 actual = nw.desc.Digest 629 } else { 630 status, err := nw.w.Status() 631 if err != nil { 632 nw.w.Close() 633 return "", err 634 } 635 if size != 0 && size != status.Offset { 636 nw.w.Close() 637 return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "%q failed size validation: %v != %v", nw.ref, status.Offset, size) 638 } 639 size = status.Offset 640 641 if err := nw.w.Commit(ctx, size, expected); err != nil && !errdefs.IsAlreadyExists(err) { 642 return "", err 643 } 644 actual = nw.w.Digest() 645 } 646 647 bkt, err := createBlobBucket(tx, nw.namespace, actual) 648 if err != nil { 649 if err == bolt.ErrBucketExists { 650 return actual, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", actual) 651 } 652 return "", err 653 } 654 655 commitTime := time.Now().UTC() 656 657 sizeEncoded, err := encodeInt(size) 658 if err != nil { 659 return "", err 660 } 661 662 if err := boltutil.WriteTimestamps(bkt, commitTime, commitTime); err != nil { 663 return "", err 664 } 665 if err := boltutil.WriteLabels(bkt, base.Labels); err != nil { 666 return "", err 667 } 668 return actual, bkt.Put(bucketKeySize, sizeEncoded) 669 } 670 671 func (nw *namespacedWriter) Status() (st content.Status, err error) { 672 if nw.w != nil { 673 st, err = nw.w.Status() 674 } else { 675 st.Offset = nw.desc.Size 676 st.Total = nw.desc.Size 677 st.StartedAt = nw.started 678 st.UpdatedAt = nw.started 679 st.Expected = nw.desc.Digest 680 } 681 if err == nil { 682 st.Ref = nw.ref 683 } 684 return 685 } 686 687 func (cs *contentStore) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) { 688 if err := cs.checkAccess(ctx, desc.Digest); err != nil { 689 return nil, err 690 } 691 return cs.Store.ReaderAt(ctx, desc) 692 } 693 694 func (cs *contentStore) checkAccess(ctx context.Context, dgst digest.Digest) error { 695 ns, err := namespaces.NamespaceRequired(ctx) 696 if err != nil { 697 return err 698 } 699 700 return view(ctx, cs.db, func(tx *bolt.Tx) error { 701 bkt := getBlobBucket(tx, ns, dgst) 702 if bkt == nil { 703 return errors.Wrapf(errdefs.ErrNotFound, "content digest %v", dgst) 704 } 705 return nil 706 }) 707 } 708 709 func validateInfo(info *content.Info) error { 710 for k, v := range info.Labels { 711 if err := labels.Validate(k, v); err == nil { 712 return errors.Wrapf(err, "info.Labels") 713 } 714 } 715 716 return nil 717 } 718 719 func readInfo(info *content.Info, bkt *bolt.Bucket) error { 720 if err := boltutil.ReadTimestamps(bkt, &info.CreatedAt, &info.UpdatedAt); err != nil { 721 return err 722 } 723 724 labels, err := boltutil.ReadLabels(bkt) 725 if err != nil { 726 return err 727 } 728 info.Labels = labels 729 730 if v := bkt.Get(bucketKeySize); len(v) > 0 { 731 info.Size, _ = binary.Varint(v) 732 } 733 734 return nil 735 } 736 737 func writeInfo(info *content.Info, bkt *bolt.Bucket) error { 738 if err := boltutil.WriteTimestamps(bkt, info.CreatedAt, info.UpdatedAt); err != nil { 739 return err 740 } 741 742 if err := boltutil.WriteLabels(bkt, info.Labels); err != nil { 743 return errors.Wrapf(err, "writing labels for info %v", info.Digest) 744 } 745 746 // Write size 747 sizeEncoded, err := encodeInt(info.Size) 748 if err != nil { 749 return err 750 } 751 752 return bkt.Put(bucketKeySize, sizeEncoded) 753 } 754 755 func readExpireAt(bkt *bolt.Bucket) (*time.Time, error) { 756 v := bkt.Get(bucketKeyExpireAt) 757 if v == nil { 758 return nil, nil 759 } 760 t := &time.Time{} 761 if err := t.UnmarshalBinary(v); err != nil { 762 return nil, err 763 } 764 return t, nil 765 } 766 767 func writeExpireAt(expire time.Time, bkt *bolt.Bucket) error { 768 expireAt, err := expire.MarshalBinary() 769 if err != nil { 770 return err 771 } 772 return bkt.Put(bucketKeyExpireAt, expireAt) 773 } 774 775 func (cs *contentStore) garbageCollect(ctx context.Context) (d time.Duration, err error) { 776 cs.l.Lock() 777 t1 := time.Now() 778 defer func() { 779 if err == nil { 780 d = time.Since(t1) 781 } 782 cs.l.Unlock() 783 }() 784 785 contentSeen := map[string]struct{}{} 786 ingestSeen := map[string]struct{}{} 787 if err := cs.db.View(func(tx *bolt.Tx) error { 788 v1bkt := tx.Bucket(bucketKeyVersion) 789 if v1bkt == nil { 790 return nil 791 } 792 793 // iterate through each namespace 794 v1c := v1bkt.Cursor() 795 796 for k, v := v1c.First(); k != nil; k, v = v1c.Next() { 797 if v != nil { 798 continue 799 } 800 801 cbkt := v1bkt.Bucket(k).Bucket(bucketKeyObjectContent) 802 if cbkt == nil { 803 continue 804 } 805 bbkt := cbkt.Bucket(bucketKeyObjectBlob) 806 if bbkt != nil { 807 if err := bbkt.ForEach(func(ck, cv []byte) error { 808 if cv == nil { 809 contentSeen[string(ck)] = struct{}{} 810 } 811 return nil 812 }); err != nil { 813 return err 814 } 815 } 816 817 ibkt := cbkt.Bucket(bucketKeyObjectIngests) 818 if ibkt != nil { 819 if err := ibkt.ForEach(func(ref, v []byte) error { 820 if v == nil { 821 bkt := ibkt.Bucket(ref) 822 // expected here may be from a different namespace 823 // so much be explicitly retained from the ingest 824 // in case it was removed from the other namespace 825 expected := bkt.Get(bucketKeyExpected) 826 if len(expected) > 0 { 827 contentSeen[string(expected)] = struct{}{} 828 } 829 bref := bkt.Get(bucketKeyRef) 830 if len(bref) > 0 { 831 ingestSeen[string(bref)] = struct{}{} 832 } 833 } 834 return nil 835 }); err != nil { 836 return err 837 } 838 } 839 } 840 841 return nil 842 }); err != nil { 843 return 0, err 844 } 845 846 err = cs.Store.Walk(ctx, func(info content.Info) error { 847 if _, ok := contentSeen[info.Digest.String()]; !ok { 848 if err := cs.Store.Delete(ctx, info.Digest); err != nil { 849 return err 850 } 851 log.G(ctx).WithField("digest", info.Digest).Debug("removed content") 852 } 853 return nil 854 }) 855 if err != nil { 856 return 857 } 858 859 // If the content store has implemented a more efficient walk function 860 // then use that else fallback to reading all statuses which may 861 // cause reading of unneeded metadata. 862 type statusWalker interface { 863 WalkStatusRefs(context.Context, func(string) error) error 864 } 865 if w, ok := cs.Store.(statusWalker); ok { 866 err = w.WalkStatusRefs(ctx, func(ref string) error { 867 if _, ok := ingestSeen[ref]; !ok { 868 if err := cs.Store.Abort(ctx, ref); err != nil { 869 return err 870 } 871 log.G(ctx).WithField("ref", ref).Debug("cleanup aborting ingest") 872 } 873 return nil 874 }) 875 } else { 876 var statuses []content.Status 877 statuses, err = cs.Store.ListStatuses(ctx) 878 if err != nil { 879 return 0, err 880 } 881 for _, status := range statuses { 882 if _, ok := ingestSeen[status.Ref]; !ok { 883 if err = cs.Store.Abort(ctx, status.Ref); err != nil { 884 return 885 } 886 log.G(ctx).WithField("ref", status.Ref).Debug("cleanup aborting ingest") 887 } 888 } 889 } 890 return 891 }