Add delete target to image remove

Adds atomicity to image delete when deleting from a list.

Signed-off-by: Derek McGowan <derek@mcg.dev>
This commit is contained in:
Derek McGowan
2023-08-18 16:04:09 -07:00
parent f8fb2dad39
commit 2ce971d890
5 changed files with 128 additions and 38 deletions

View File

@@ -266,6 +266,13 @@ func (s *imageStore) Delete(ctx context.Context, name string, opts ...images.Del
return err
}
var options images.DeleteOptions
for _, opt := range opts {
if err := opt(ctx, &options); err != nil {
return err
}
}
return update(ctx, s.db, func(tx *bolt.Tx) error {
bkt := getImagesBucket(tx, namespace)
if bkt == nil {
@@ -276,6 +283,22 @@ func (s *imageStore) Delete(ctx context.Context, name string, opts ...images.Del
return err
}
if options.Target != nil && options.Target.Digest != "" {
ibkt := bkt.Bucket([]byte(name))
if ibkt == nil {
return fmt.Errorf("image %q: %w", name, errdefs.ErrNotFound)
}
var check images.Image
if err := readImage(&check, ibkt); err != nil {
return fmt.Errorf("image %q: %w", name, err)
}
if check.Target.Digest != options.Target.Digest {
return fmt.Errorf("image %q has target %v, not %v: %w", name, check.Target.Digest, options.Target.Digest, errdefs.ErrNotFound)
}
}
if err = bkt.DeleteBucket([]byte(name)); err != nil {
if err == bolt.ErrBucketNotFound {
err = fmt.Errorf("image %q: %w", name, errdefs.ErrNotFound)

View File

@@ -154,10 +154,11 @@ func TestImagesCreateUpdateDelete(t *testing.T) {
name string
original images.Image
createerr error
input images.Image
input images.Image // Input target size determines target digest, base image uses 10
fieldpaths []string
expected images.Image
cause error
deleteerr error
}{
{
name: "Touch",
@@ -239,7 +240,7 @@ func TestImagesCreateUpdateDelete(t *testing.T) {
"boo": "boo",
},
Target: ocispec.Descriptor{
Size: 20, // ignored
Size: 10,
MediaType: "application/vnd.oci.blab+ignored", // make sure other stuff is ignored
Annotations: map[string]string{
"not": "bar",
@@ -272,7 +273,7 @@ func TestImagesCreateUpdateDelete(t *testing.T) {
"boo": "boo",
},
Target: ocispec.Descriptor{
Size: 20, // ignored
Size: 10,
MediaType: "application/vnd.oci.blab+ignored", // make sure other stuff is ignored
Annotations: map[string]string{
"foo": "boo",
@@ -303,7 +304,7 @@ func TestImagesCreateUpdateDelete(t *testing.T) {
"baz": "bunk",
},
Target: ocispec.Descriptor{
Size: 20, // ignored
Size: 10,
MediaType: "application/vnd.oci.blab+ignored", // make sure other stuff is ignored
Annotations: map[string]string{
"foo": "bar",
@@ -335,7 +336,7 @@ func TestImagesCreateUpdateDelete(t *testing.T) {
"baz": "bunk",
},
Target: ocispec.Descriptor{
Size: 20, // ignored
Size: 10,
MediaType: "application/vnd.oci.blab+ignored", // make sure other stuff is ignored
Annotations: map[string]string{
"foo": "baz",
@@ -360,7 +361,7 @@ func TestImagesCreateUpdateDelete(t *testing.T) {
},
},
{
name: "ReplaceTarget", // target must be updated as a unit
name: "ReplaceTargetTypeAndAnnotations", // target must be updated as a unit
original: imageBase(),
input: images.Image{
Target: ocispec.Descriptor{
@@ -386,6 +387,57 @@ func TestImagesCreateUpdateDelete(t *testing.T) {
},
},
},
{
name: "ReplaceTargetFieldpath", // target must be updated as a unit
original: imageBase(),
input: images.Image{
Target: ocispec.Descriptor{
Size: 20,
MediaType: "application/vnd.oci.blab+replaced",
Annotations: map[string]string{
"fox": "dog",
},
},
},
fieldpaths: []string{"target"},
expected: images.Image{
Labels: map[string]string{ // Labels not updated
"foo": "bar",
"baz": "boo",
},
Target: ocispec.Descriptor{
Size: 20,
MediaType: "application/vnd.oci.blab+replaced",
Annotations: map[string]string{
"fox": "dog",
},
},
},
deleteerr: errdefs.ErrNotFound,
},
{
name: "ReplaceTarget", // target must be updated as a unit
original: imageBase(),
input: images.Image{
Target: ocispec.Descriptor{
Size: 20,
MediaType: "application/vnd.oci.blab+replaced",
Annotations: map[string]string{
"fox": "dog",
},
},
},
expected: images.Image{
Target: ocispec.Descriptor{
Size: 20,
MediaType: "application/vnd.oci.blab+replaced",
Annotations: map[string]string{
"fox": "dog",
},
},
},
deleteerr: errdefs.ErrNotFound,
},
{
name: "EmptySize",
original: imageBase(),
@@ -486,11 +538,9 @@ func TestImagesCreateUpdateDelete(t *testing.T) {
}
testcase.expected.Name = testcase.name
if testcase.original.Target.Digest == "" {
testcase.original.Target.Digest = digest.FromString(testcase.name)
testcase.input.Target.Digest = testcase.original.Target.Digest
testcase.expected.Target.Digest = testcase.original.Target.Digest
}
testcase.original.Target.Digest = digest.FromString(fmt.Sprintf("%s-%d", testcase.name, testcase.original.Target.Size))
testcase.input.Target.Digest = digest.FromString(fmt.Sprintf("%s-%d", testcase.name, testcase.input.Target.Size))
testcase.expected.Target.Digest = testcase.input.Target.Digest
// Create
now := time.Now()
@@ -538,6 +588,22 @@ func TestImagesCreateUpdateDelete(t *testing.T) {
}
checkImagesEqual(t, &result, &testcase.expected, "get after failed to get expected result")
if testcase.original.Target.Digest != testcase.expected.Target.Digest {
t.Log("Delete should fail")
}
// Delete
err = store.Delete(ctx, testcase.original.Name, images.DeleteTarget(&testcase.original.Target))
if err != nil {
if testcase.deleteerr == nil {
t.Fatal(err)
}
if !errors.Is(err, testcase.deleteerr) {
t.Fatal("unexpected error", err, ", expected", testcase.deleteerr)
}
} else if testcase.deleteerr != nil {
t.Fatal("no error on deleted, expected", testcase.deleteerr)
}
})
}
}