Phase 7: OCI delete path for manifests and blobs

Manifest delete (DELETE /v2/<name>/manifests/<digest>): rejects tag
references with 405 UNSUPPORTED per OCI spec, cascades to tags and
manifest_blobs via ON DELETE CASCADE, returns 202 Accepted.

Blob delete (DELETE /v2/<name>/blobs/<digest>): removes manifest_blobs
associations only — blob row and file are preserved for GC to handle,
since other repos may reference the same content-addressed blob.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 20:23:47 -07:00
parent dddc66f31b
commit c01e7ffa30
9 changed files with 623 additions and 7 deletions

View File

@@ -207,6 +207,34 @@ func (f *fakeDB) DeleteUpload(uuid string) error {
return nil
}
func (f *fakeDB) DeleteManifest(repoID int64, digest string) error {
f.mu.Lock()
defer f.mu.Unlock()
key := manifestKey{repoID, digest}
if _, ok := f.manifests[key]; !ok {
return db.ErrManifestNotFound
}
delete(f.manifests, key)
// Also remove any tag entries pointing to this digest.
for k, m := range f.manifests {
if k.repoID == repoID && m.Digest == digest {
delete(f.manifests, k)
}
}
return nil
}
func (f *fakeDB) DeleteBlobFromRepo(repoID int64, digest string) error {
f.mu.Lock()
defer f.mu.Unlock()
digests, ok := f.blobs[repoID]
if !ok || !digests[digest] {
return db.ErrBlobNotFound
}
delete(digests, digest)
return nil
}
// addRepo adds a repo to the fakeDB and returns its ID.
func (f *fakeDB) addRepo(name string, id int64) {
f.repos[name] = id