in registry/handlers/manifests.go [1379:1460]
func dbDeleteManifest(ctx context.Context, db datastore.Handler, cache datastore.RepositoryCache, repoPath string, d digest.Digest) error {
l := log.GetLogger(log.WithContext(ctx)).WithFields(log.Fields{"repository": repoPath, "digest": d})
l.Debug("deleting manifest from repository in database")
var opts []datastore.RepositoryStoreOption
if cache != nil {
opts = append(opts, datastore.WithRepositoryCache(cache))
}
rStore := datastore.NewRepositoryStore(db, opts...)
r, err := rStore.FindByPath(ctx, repoPath)
if err != nil {
return err
}
if r == nil {
return fmt.Errorf("repository not found in database: %w", err)
}
// We need to find the manifest first and then lookup for any manifest it references (if it's a manifest list). This
// is needed to ensure we lock any related online GC tasks to prevent race conditions around the delete. See:
// https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/gitlab/online-garbage-collection.md#deleting-the-last-referencing-manifest-list
m, err := rStore.FindManifestByDigest(ctx, r, d)
if err != nil {
return err
}
if m == nil {
return datastore.ErrManifestNotFound
}
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("failed to create database transaction: %w", err)
}
defer tx.Rollback()
switch m.MediaType {
case manifestlist.MediaTypeManifestList, v1.MediaTypeImageIndex:
mStore := datastore.NewManifestStore(tx)
mm, err := mStore.References(ctx, m)
if err != nil {
return err
}
// This should never happen, as it's not possible to delete a child manifest if it's referenced by a list, which
// means that we'll always have at least one child manifest here. Nevertheless, log error if this ever happens.
if len(mm) == 0 {
l.Error("stored manifest list has no references")
break
}
ids := make([]int64, 0, len(mm))
for _, m := range mm {
ids = append(ids, m.ID)
}
// Prevent long running transactions by setting an upper limit of manifestDeleteGCLockTimeout. If the GC is
// holding the lock of a related review record, the processing there should be fast enough to avoid this.
// Regardless, we should not let transactions open (and clients waiting) for too long. If this sensible timeout
// is exceeded, abort the manifest delete and let the client retry. This will bubble up and lead to a 503
// Service Unavailable response.
ctx, cancel := context.WithTimeout(ctx, manifestDeleteGCLockTimeout)
defer cancel()
mts := datastore.NewGCManifestTaskStore(tx)
if _, err := mts.FindAndLockNBefore(ctx, r.NamespaceID, r.ID, ids, time.Now().Add(manifestDeleteGCReviewWindow)); err != nil {
return err
}
}
rStore = datastore.NewRepositoryStore(tx, opts...)
found, err := rStore.DeleteManifest(ctx, r, d)
if err != nil {
return err
}
if !found {
return datastore.ErrManifestNotFound
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("failed to commit database transaction: %w", err)
}
return nil
}