func dbDeleteManifest()

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
}