Skip to content

Commit

Permalink
feat: extend oci.Store with DeleteTree().
Browse files Browse the repository at this point in the history
Signed-off-by: Francis Laniel <[email protected]>
  • Loading branch information
eiffel-fl committed Nov 21, 2023
1 parent 3776676 commit f77d4b2
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 0 deletions.
28 changes: 28 additions & 0 deletions content/oci/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,34 @@ func (s *Store) Delete(ctx context.Context, target ocispec.Descriptor) error {
return s.storage.Delete(ctx, target)
}

// DeleteTree deletes the content matching the descriptor as well as its
// children from the store.
// Children will be deleted only if they do not any other predecessors than the
// given root.
// DeleteTree may fail on certain systems (i.e. NTFS), if there is a process
// (i.e. an unclosed Reader) using any of root children.
func (s *Store) DeleteTree(ctx context.Context, root ocispec.Descriptor) error {
predecessors, err := s.Predecessors(ctx, root)
if err != nil {
return fmt.Errorf("getting predecessors: %w", err)
}

if len(predecessors) > 1 {
return nil
}

descriptors, err := content.Successors(ctx, s, root)
if err != nil {
return fmt.Errorf("getting successors: %w", err)
}

for _, descriptor := range descriptors {
s.DeleteTree(ctx, descriptor)
}

return s.Delete(ctx, root)
}

// Tag tags a descriptor with a reference string.
// reference should be a valid tag (e.g. "latest").
// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc5/image-layout.md#indexjson-file
Expand Down
94 changes: 94 additions & 0 deletions content/oci/oci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2237,6 +2237,100 @@ func TestStore_PredecessorsAndDelete(t *testing.T) {
}
}

func TestStore_PredecessorsAndDeleteTree(t *testing.T) {
tempDir := t.TempDir()
s, err := New(tempDir)
if err != nil {
t.Fatal("New() error =", err)
}
ctx := context.Background()

// generate test content
var blobs [][]byte
var descs []ocispec.Descriptor
appendBlob := func(mediaType string, blob []byte) {
blobs = append(blobs, blob)
descs = append(descs, ocispec.Descriptor{
MediaType: mediaType,
Digest: digest.FromBytes(blob),
Size: int64(len(blob)),
})
}
generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) {
manifest := ocispec.Manifest{
Config: config,
Layers: layers,
}
manifestJSON, err := json.Marshal(manifest)
if err != nil {
t.Fatal(err)
}
appendBlob(ocispec.MediaTypeImageManifest, manifestJSON)
}
generateIndex := func(manifests ...ocispec.Descriptor) {
index := ocispec.Index{
Manifests: manifests,
}
indexJSON, err := json.Marshal(index)
if err != nil {
t.Fatal(err)
}
appendBlob(ocispec.MediaTypeImageIndex, indexJSON)
}

appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 0
appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 1
generateManifest(ocispec.DescriptorEmptyJSON, descs[0]) // Blob 2
generateManifest(ocispec.DescriptorEmptyJSON, descs[1]) // Blob 3
generateIndex(descs[2:4]...) // Blob 4

eg, egCtx := errgroup.WithContext(ctx)
for i := range blobs {
eg.Go(func(i int) func() error {
return func() error {
err := s.Push(egCtx, descs[i], bytes.NewReader(blobs[i]))
if err != nil {
return fmt.Errorf("failed to push test content to src: %d: %v", i, err)
}
return nil
}
}(i))
}
if err := eg.Wait(); err != nil {
t.Fatal(err)
}

// verify predecessors
wants := [][]ocispec.Descriptor{
{descs[2]}, // Blob 0
{descs[3]}, // Blob 1
{descs[4]}, // Blob 2
{descs[4]}, // Blob 3
nil, // Blob 5
}
for i, want := range wants {
predecessors, err := s.Predecessors(ctx, descs[i])
if err != nil {
t.Errorf("Store.Predecessors(%d) error = %v", i, err)
}
if !equalDescriptorSet(predecessors, want) {
t.Errorf("Store.Predecessors(%d) = %v, want %v", i, predecessors, want)
}
}

// delete the tree and verify the result
s.DeleteTree(egCtx, descs[4])
for i, desc := range descs {
ok, err := s.Exists(ctx, desc)
if err != nil {
t.Errorf("failed testing Store.Exists(%d): %v", i, err)
}
if ok {
t.Errorf("Store.Exists(%d) should have been deleted", i)
}
}
}

func equalDescriptorSet(actual []ocispec.Descriptor, expected []ocispec.Descriptor) bool {
if len(actual) != len(expected) {
return false
Expand Down

0 comments on commit f77d4b2

Please sign in to comment.