Manifest and layer soft deletion.
Implement the delete API by implementing soft delete for layers and blobs by removing link files and updating the blob descriptor cache. Deletion is configurable - if it is disabled API calls will return an unsupported error. We invalidate the blob descriptor cache by changing the linkedBlobStore's blobStatter to a blobDescriptorService and naming it blobAccessController. Delete() is added throughout the relevant API to support this functionality. Signed-off-by: Richard Scothern <richard.scothern@gmail.com>
This commit is contained in:
parent
7dbe35176d
commit
390bb97a88
21 changed files with 816 additions and 92 deletions
|
@ -354,7 +354,7 @@ func (ms *manifests) Delete(dgst digest.Digest) error {
|
|||
defer resp.Body.Close()
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
case http.StatusAccepted:
|
||||
return nil
|
||||
default:
|
||||
return handleErrorResponse(resp)
|
||||
|
@ -366,7 +366,8 @@ type blobs struct {
|
|||
ub *v2.URLBuilder
|
||||
client *http.Client
|
||||
|
||||
statter distribution.BlobStatter
|
||||
statter distribution.BlobDescriptorService
|
||||
distribution.BlobDeleter
|
||||
}
|
||||
|
||||
func sanitizeLocation(location, source string) (string, error) {
|
||||
|
@ -484,6 +485,10 @@ func (bs *blobs) Resume(ctx context.Context, id string) (distribution.BlobWriter
|
|||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (bs *blobs) Delete(ctx context.Context, dgst digest.Digest) error {
|
||||
return bs.statter.Clear(ctx, dgst)
|
||||
}
|
||||
|
||||
type blobStatter struct {
|
||||
name string
|
||||
ub *v2.URLBuilder
|
||||
|
@ -535,3 +540,32 @@ func buildCatalogValues(maxEntries int, last string) url.Values {
|
|||
|
||||
return values
|
||||
}
|
||||
|
||||
func (bs *blobStatter) Clear(ctx context.Context, dgst digest.Digest) error {
|
||||
blobURL, err := bs.ub.BuildBlobURL(bs.name, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", blobURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := bs.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusAccepted:
|
||||
return nil
|
||||
default:
|
||||
return handleErrorResponse(resp)
|
||||
}
|
||||
}
|
||||
|
||||
func (bs *blobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -101,6 +101,39 @@ func addTestCatalog(route string, content []byte, link string, m *testutil.Reque
|
|||
})
|
||||
}
|
||||
|
||||
func TestBlobDelete(t *testing.T) {
|
||||
dgst, _ := newRandomBlob(1024)
|
||||
var m testutil.RequestResponseMap
|
||||
repo := "test.example.com/repo1"
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "DELETE",
|
||||
Route: "/v2/" + repo + "/blobs/" + dgst.String(),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Content-Length": {"0"},
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
e, c := testServer(m)
|
||||
defer c()
|
||||
|
||||
ctx := context.Background()
|
||||
r, err := NewRepository(ctx, repo, e, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
l := r.Blobs(ctx)
|
||||
err = l.Delete(ctx, dgst)
|
||||
if err != nil {
|
||||
t.Errorf("Error deleting blob: %s", err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestBlobFetch(t *testing.T) {
|
||||
d1, b1 := newRandomBlob(1024)
|
||||
var m testutil.RequestResponseMap
|
||||
|
@ -590,7 +623,7 @@ func TestManifestDelete(t *testing.T) {
|
|||
Route: "/v2/" + repo + "/manifests/" + dgst1.String(),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
StatusCode: http.StatusAccepted,
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Content-Length": {"0"},
|
||||
}),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue