mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-20 01:04:15 +00:00
fix tag and manifest deletion
This commit is contained in:
@@ -535,10 +535,16 @@ func DeleteTag(db *sql.DB, did, repository, tag string) error {
|
||||
}
|
||||
|
||||
// DeleteManifest deletes a manifest and its associated layers
|
||||
// If repository is empty, deletes all manifests matching did and digest
|
||||
func DeleteManifest(db *sql.DB, did, repository, digest string) error {
|
||||
_, err := db.Exec(`
|
||||
DELETE FROM manifests WHERE did = ? AND repository = ? AND digest = ?
|
||||
`, did, repository, digest)
|
||||
var err error
|
||||
if repository == "" {
|
||||
// Delete by DID + digest only (used when repository is unknown, e.g., Jetstream DELETE events)
|
||||
_, err = db.Exec(`DELETE FROM manifests WHERE did = ? AND digest = ?`, did, digest)
|
||||
} else {
|
||||
// Delete specific manifest
|
||||
_, err = db.Exec(`DELETE FROM manifests WHERE did = ? AND repository = ? AND digest = ?`, did, repository, digest)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1017,6 +1023,21 @@ func GetStarsForDID(db *sql.DB, starrerDID string) (map[string]time.Time, error)
|
||||
return stars, rows.Err()
|
||||
}
|
||||
|
||||
// CleanupOrphanedTags removes tags whose manifest digest no longer exists
|
||||
// This handles cases where manifests were deleted but tags pointing to them remain
|
||||
func CleanupOrphanedTags(db *sql.DB, did string) error {
|
||||
_, err := db.Exec(`
|
||||
DELETE FROM tags
|
||||
WHERE did = ?
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM manifests
|
||||
WHERE manifests.did = tags.did
|
||||
AND manifests.digest = tags.digest
|
||||
)
|
||||
`, did)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteStarsNotInList deletes stars from the database that are not in the provided list
|
||||
// This is used during backfill reconciliation to remove stars that no longer exist on PDS
|
||||
func DeleteStarsNotInList(db *sql.DB, starrerDID string, foundStars map[string]time.Time) error {
|
||||
|
||||
@@ -198,6 +198,13 @@ func (b *BackfillWorker) backfillRepo(ctx context.Context, did, collection strin
|
||||
fmt.Printf("WARNING: Failed to reconcile deletions for %s: %v\n", did, err)
|
||||
}
|
||||
|
||||
// After processing manifests, clean up orphaned tags (tags pointing to non-existent manifests)
|
||||
if collection == atproto.ManifestCollection {
|
||||
if err := db.CleanupOrphanedTags(b.db, did); err != nil {
|
||||
fmt.Printf("WARNING: Failed to cleanup orphaned tags for %s: %v\n", did, err)
|
||||
}
|
||||
}
|
||||
|
||||
return recordCount, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -418,10 +417,13 @@ func (w *Worker) processManifest(commit *CommitEvent) error {
|
||||
}
|
||||
|
||||
if commit.Operation == "delete" {
|
||||
// Delete manifest
|
||||
repo := extractRepoFromRKey(commit.RKey)
|
||||
// Delete manifest - rkey is just the digest, repository is not encoded
|
||||
digest := commit.RKey
|
||||
return db.DeleteManifest(w.db, commit.DID, repo, digest)
|
||||
if err := db.DeleteManifest(w.db, commit.DID, "", digest); err != nil {
|
||||
return err
|
||||
}
|
||||
// Clean up any orphaned tags pointing to this manifest
|
||||
return db.CleanupOrphanedTags(w.db, commit.DID)
|
||||
}
|
||||
|
||||
// Parse manifest record
|
||||
@@ -497,13 +499,8 @@ func (w *Worker) processTag(commit *CommitEvent) error {
|
||||
}
|
||||
|
||||
if commit.Operation == "delete" {
|
||||
// Delete tag
|
||||
parts := strings.Split(commit.RKey, "/")
|
||||
if len(parts) < 2 {
|
||||
return fmt.Errorf("invalid tag rkey: %s", commit.RKey)
|
||||
}
|
||||
repo := strings.Join(parts[:len(parts)-1], "/")
|
||||
tag := parts[len(parts)-1]
|
||||
// Delete tag - decode rkey back to repository and tag
|
||||
repo, tag := atproto.RKeyToRepositoryTag(commit.RKey)
|
||||
return db.DeleteTag(w.db, commit.DID, repo, tag)
|
||||
}
|
||||
|
||||
@@ -606,23 +603,3 @@ type AccountInfo struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func extractRepoFromRKey(rkey string) string {
|
||||
// RKey format: <digest> or <repo>/<digest>
|
||||
// For manifest, it's just the digest
|
||||
parts := strings.Split(rkey, "/")
|
||||
if len(parts) > 1 {
|
||||
return parts[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func calculateManifestSize(manifest *atproto.ManifestRecord) int64 {
|
||||
var total int64
|
||||
total += manifest.Config.Size
|
||||
for _, layer := range manifest.Layers {
|
||||
total += layer.Size
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
@@ -207,6 +207,26 @@ func repositoryTagToRKey(repository, tag string) string {
|
||||
return key
|
||||
}
|
||||
|
||||
// RKeyToRepositoryTag converts an ATProto record key back to repository and tag
|
||||
// This is the inverse of repositoryTagToRKey
|
||||
// Note: If the tag contains underscores, this will split on the LAST underscore
|
||||
func RKeyToRepositoryTag(rkey string) (repository, tag string) {
|
||||
// Find the last underscore to split repository and tag
|
||||
lastUnderscore := strings.LastIndex(rkey, "_")
|
||||
if lastUnderscore == -1 {
|
||||
// No underscore found - treat entire string as tag with empty repository
|
||||
return "", rkey
|
||||
}
|
||||
|
||||
repository = rkey[:lastUnderscore]
|
||||
tag = rkey[lastUnderscore+1:]
|
||||
|
||||
// Convert dashes back to slashes in repository
|
||||
repository = strings.ReplaceAll(repository, "-", "/")
|
||||
|
||||
return repository, tag
|
||||
}
|
||||
|
||||
// GetLastFetchedHoldEndpoint returns the hold endpoint from the most recently fetched manifest
|
||||
// This is used by the routing repository to cache the hold for blob requests
|
||||
func (s *ManifestStore) GetLastFetchedHoldEndpoint() string {
|
||||
|
||||
Reference in New Issue
Block a user