Skip to content

Commit d57c2d1

Browse files
committed
feat: extend oci.Store with DeleteTree().
Signed-off-by: Francis Laniel <flaniel@linux.microsoft.com>
1 parent 3776676 commit d57c2d1

2 files changed

Lines changed: 134 additions & 0 deletions

File tree

content/oci/oci.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,43 @@ func (s *Store) Delete(ctx context.Context, target ocispec.Descriptor) error {
168168
return s.storage.Delete(ctx, target)
169169
}
170170

171+
// DeleteTree deletes the content matching the descriptor as well as its
172+
// children from the store.
173+
// Children will be deleted only if they do not any other predecessors than the
174+
// given root.
175+
// DeleteTree may fail on certain systems (i.e. NTFS), if there is a process
176+
// (i.e. an unclosed Reader) using any of root children.
177+
func (s *Store) DeleteTree(ctx context.Context, root ocispec.Descriptor) error {
178+
predecessors, err := s.Predecessors(ctx, root)
179+
if err != nil {
180+
return fmt.Errorf("getting predecessors: %w", err)
181+
}
182+
183+
// We need to check if there are more than 1 predecessor as the current tree
184+
// is counted in.
185+
if len(predecessors) > 1 {
186+
return nil
187+
}
188+
189+
descriptors, err := content.Successors(ctx, s, root)
190+
if err != nil {
191+
return fmt.Errorf("getting successors: %w", err)
192+
}
193+
194+
for _, descriptor := range descriptors {
195+
if content.Equal(descriptor, ocispec.DescriptorEmptyJSON) {
196+
continue
197+
}
198+
199+
err := s.DeleteTree(ctx, descriptor)
200+
if err != nil {
201+
return err
202+
}
203+
}
204+
205+
return s.Delete(ctx, root)
206+
}
207+
171208
// Tag tags a descriptor with a reference string.
172209
// reference should be a valid tag (e.g. "latest").
173210
// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc5/image-layout.md#indexjson-file

content/oci/oci_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2237,6 +2237,103 @@ func TestStore_PredecessorsAndDelete(t *testing.T) {
22372237
}
22382238
}
22392239

2240+
func TestStore_PredecessorsAndDeleteTree(t *testing.T) {
2241+
tempDir := t.TempDir()
2242+
s, err := New(tempDir)
2243+
if err != nil {
2244+
t.Fatal("New() error =", err)
2245+
}
2246+
ctx := context.Background()
2247+
2248+
// generate test content
2249+
var blobs [][]byte
2250+
var descs []ocispec.Descriptor
2251+
appendBlob := func(mediaType string, blob []byte) {
2252+
blobs = append(blobs, blob)
2253+
descs = append(descs, ocispec.Descriptor{
2254+
MediaType: mediaType,
2255+
Digest: digest.FromBytes(blob),
2256+
Size: int64(len(blob)),
2257+
})
2258+
}
2259+
generateManifest := func(config ocispec.Descriptor, layers ...ocispec.Descriptor) {
2260+
manifest := ocispec.Manifest{
2261+
Config: config,
2262+
Layers: layers,
2263+
}
2264+
manifestJSON, err := json.Marshal(manifest)
2265+
if err != nil {
2266+
t.Fatal(err)
2267+
}
2268+
appendBlob(ocispec.MediaTypeImageManifest, manifestJSON)
2269+
}
2270+
generateIndex := func(manifests ...ocispec.Descriptor) {
2271+
index := ocispec.Index{
2272+
Manifests: manifests,
2273+
}
2274+
indexJSON, err := json.Marshal(index)
2275+
if err != nil {
2276+
t.Fatal(err)
2277+
}
2278+
appendBlob(ocispec.MediaTypeImageIndex, indexJSON)
2279+
}
2280+
2281+
appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 0
2282+
appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 1
2283+
generateManifest(ocispec.DescriptorEmptyJSON, descs[0]) // Blob 2
2284+
generateManifest(ocispec.DescriptorEmptyJSON, descs[1]) // Blob 3
2285+
generateIndex(descs[2:4]...) // Blob 4
2286+
2287+
eg, egCtx := errgroup.WithContext(ctx)
2288+
for i := range blobs {
2289+
eg.Go(func(i int) func() error {
2290+
return func() error {
2291+
err := s.Push(egCtx, descs[i], bytes.NewReader(blobs[i]))
2292+
if err != nil {
2293+
return fmt.Errorf("failed to push test content to src: %d: %v", i, err)
2294+
}
2295+
return nil
2296+
}
2297+
}(i))
2298+
}
2299+
if err := eg.Wait(); err != nil {
2300+
t.Fatal(err)
2301+
}
2302+
2303+
// verify predecessors
2304+
wants := [][]ocispec.Descriptor{
2305+
{descs[2]}, // Blob 0
2306+
{descs[3]}, // Blob 1
2307+
{descs[4]}, // Blob 2
2308+
{descs[4]}, // Blob 3
2309+
nil, // Blob 4
2310+
}
2311+
for i, want := range wants {
2312+
predecessors, err := s.Predecessors(ctx, descs[i])
2313+
if err != nil {
2314+
t.Errorf("Store.Predecessors(%d) error = %v", i, err)
2315+
}
2316+
if !equalDescriptorSet(predecessors, want) {
2317+
t.Errorf("Store.Predecessors(%d) = %v, want %v", i, predecessors, want)
2318+
}
2319+
}
2320+
2321+
// delete the tree and verify the result
2322+
err = s.DeleteTree(egCtx, descs[4])
2323+
if err != nil {
2324+
t.Errorf("failed deleting tree: %v", err)
2325+
}
2326+
for i, desc := range descs {
2327+
ok, err := s.Exists(ctx, desc)
2328+
if err != nil {
2329+
t.Errorf("failed testing Store.Exists(%d): %v", i, err)
2330+
}
2331+
if ok {
2332+
t.Errorf("Store.Exists(%d) should have been deleted", i)
2333+
}
2334+
}
2335+
}
2336+
22402337
func equalDescriptorSet(actual []ocispec.Descriptor, expected []ocispec.Descriptor) bool {
22412338
if len(actual) != len(expected) {
22422339
return false

0 commit comments

Comments
 (0)