diff --git a/content/oci/oci.go b/content/oci/oci.go index 659a2b0e..85da676c 100644 --- a/content/oci/oci.go +++ b/content/oci/oci.go @@ -36,6 +36,7 @@ import ( "oras.land/oras-go/v2/internal/descriptor" "oras.land/oras-go/v2/internal/graph" "oras.land/oras-go/v2/internal/resolver" + "oras.land/oras-go/v2/registry" ) // Store implements `oras.Target`, and represents a content store @@ -61,6 +62,11 @@ type Store struct { // - Default value: true. AutoGC bool + // AutoRemoveReferrers controls if the OCI store will automatically delete its + // referrers when a manifest is deleted. + // - Default value: true. + AutoRemoveReferrers bool + root string indexPath string index *ocispec.Index @@ -93,13 +99,14 @@ func NewWithContext(ctx context.Context, root string) (*Store, error) { } store := &Store{ - AutoSaveIndex: true, - AutoGC: true, - root: rootAbs, - indexPath: filepath.Join(rootAbs, ocispec.ImageIndexFile), - storage: storage, - tagResolver: resolver.NewMemory(), - graph: graph.NewMemory(), + AutoSaveIndex: true, + AutoGC: true, + AutoRemoveReferrers: true, + root: rootAbs, + indexPath: filepath.Join(rootAbs, ocispec.ImageIndexFile), + storage: storage, + tagResolver: resolver.NewMemory(), + graph: graph.NewMemory(), } if err := ensureDir(filepath.Join(rootAbs, ocispec.ImageBlobsDir)); err != nil { @@ -155,6 +162,16 @@ func (s *Store) Exists(ctx context.Context, target ocispec.Descriptor) (bool, er // fail on certain systems (i.e. NTFS), if there is a process (i.e. an unclosed // Reader) using target. func (s *Store) Delete(ctx context.Context, target ocispec.Descriptor) error { + // get referrers first to avoid deadlock + var referrers []ocispec.Descriptor + if descriptor.IsManifest(target) && s.AutoRemoveReferrers { + res, err := registry.Referrers(ctx, s, target, "") + referrers = append(referrers, res...) + if err != nil { + return err + } + } + s.sync.Lock() defer s.sync.Unlock() @@ -166,8 +183,24 @@ func (s *Store) Delete(ctx context.Context, target ocispec.Descriptor) error { // delete the dangling nodes caused by the delete if s.AutoGC { - return s.cleanGraphs(ctx, danglings) + if err := s.cleanGraphs(ctx, danglings); err != nil { + return err + } } + + // delete the referrers + for _, desc := range referrers { + danglings, err := s.delete(ctx, desc) + if err != nil { + return err + } + if s.AutoGC { + if err := s.cleanGraphs(ctx, danglings); err != nil { + return err + } + } + } + return nil } @@ -195,8 +228,7 @@ func (s *Store) delete(ctx context.Context, target ocispec.Descriptor) ([]ocispe } func (s *Store) cleanGraphs(ctx context.Context, danglings []ocispec.Descriptor) error { - // for each item in dangling, if it exists and it is dangling, remove it and - // add new dangling nodes into dangling + // for each item in danglings, remove it and add new dangling nodes into danglings for len(danglings) > 0 { node := danglings[0] danglings = danglings[1:] diff --git a/content/oci/oci_test.go b/content/oci/oci_test.go index 956724da..53a7f5ce 100644 --- a/content/oci/oci_test.go +++ b/content/oci/oci_test.go @@ -2375,13 +2375,13 @@ func TestStore_DeleteWithGarbageCollection(t *testing.T) { } // blob 1, 3, 4, 6 and 8 are now deleted, and other blobs are still present - notPresent = []ocispec.Descriptor{descs[1], descs[3], descs[4], descs[6], descs[8]} + notPresent = []ocispec.Descriptor{descs[1], descs[3], descs[4], descs[6], descs[8], descs[9]} for _, node := range notPresent { if exists, _ := s.Exists(egCtx, node); exists { t.Errorf("%v should not exist in store", node) } } - stillPresent = []ocispec.Descriptor{descs[0], descs[2], descs[5], descs[7], descs[9]} + stillPresent = []ocispec.Descriptor{descs[0], descs[2], descs[5], descs[7]} for _, node := range stillPresent { if exists, _ := s.Exists(egCtx, node); !exists { t.Errorf("%v should exist in store", node) @@ -2390,16 +2390,16 @@ func TestStore_DeleteWithGarbageCollection(t *testing.T) { // verify predecessors information wants := [][]ocispec.Descriptor{ - {descs[5], descs[9]}, // Blob 0 - nil, // Blob 1 - {descs[5], descs[9]}, // Blob 2 - nil, // Blob 3 - {descs[7]}, // Blob 4's predecessor is descs[7], even though blob 4 no longer exist - {descs[7]}, // Blob 5 - {descs[9]}, // Blob 6's predecessor is descs[9], even though blob 6 no longer exist - nil, // Blob 7 - nil, // Blob 8 - nil, // Blob 9 + {descs[5]}, // Blob 0 + nil, // Blob 1 + {descs[5]}, // Blob 2 + nil, // Blob 3 + {descs[7]}, // Blob 4's predecessor is descs[7], even though blob 4 no longer exist + {descs[7]}, // Blob 5 + nil, // Blob 6 + nil, // Blob 7 + nil, // Blob 8 + nil, // Blob 9 } for i, want := range wants { predecessors, err := s.Predecessors(ctx, descs[i])