From ddf64b84e45e8c3fa47981577c8676f2865e4cb6 Mon Sep 17 00:00:00 2001
From: Zettat123 <zettat123@gmail.com>
Date: Wed, 24 Apr 2024 08:08:06 +0800
Subject: [PATCH] Add a db consistency check to remove runners that do not
 belong to a repository (#30614) (#30653)

Backport #30614
---
 models/actions/runner.go        | 26 ++++++++++++++++++++++++--
 modules/doctor/dbconsistency.go |  6 ++++++
 2 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/models/actions/runner.go b/models/actions/runner.go
index aaeb80ce63..70400df015 100644
--- a/models/actions/runner.go
+++ b/models/actions/runner.go
@@ -284,7 +284,7 @@ func CountRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
 	// Only affect action runners were a owner ID is set, as actions runners
 	// could also be created on a repository.
 	return db.GetEngine(ctx).Table("action_runner").
-		Join("LEFT", "user", "`action_runner`.owner_id = `user`.id").
+		Join("LEFT", "`user`", "`action_runner`.owner_id = `user`.id").
 		Where("`action_runner`.owner_id != ?", 0).
 		And(builder.IsNull{"`user`.id"}).
 		Count(new(ActionRunner))
@@ -293,7 +293,7 @@ func CountRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
 func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
 	subQuery := builder.Select("`action_runner`.id").
 		From("`action_runner`").
-		Join("LEFT", "user", "`action_runner`.owner_id = `user`.id").
+		Join("LEFT", "`user`", "`action_runner`.owner_id = `user`.id").
 		Where(builder.Neq{"`action_runner`.owner_id": 0}).
 		And(builder.IsNull{"`user`.id"})
 	b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`")
@@ -303,3 +303,25 @@ func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
 	}
 	return res.RowsAffected()
 }
+
+func CountRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) {
+	return db.GetEngine(ctx).Table("action_runner").
+		Join("LEFT", "`repository`", "`action_runner`.repo_id = `repository`.id").
+		Where("`action_runner`.repo_id != ?", 0).
+		And(builder.IsNull{"`repository`.id"}).
+		Count(new(ActionRunner))
+}
+
+func FixRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) {
+	subQuery := builder.Select("`action_runner`.id").
+		From("`action_runner`").
+		Join("LEFT", "`repository`", "`action_runner`.repo_id = `repository`.id").
+		Where(builder.Neq{"`action_runner`.repo_id": 0}).
+		And(builder.IsNull{"`repository`.id"})
+	b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`")
+	res, err := db.GetEngine(ctx).Exec(b)
+	if err != nil {
+		return 0, err
+	}
+	return res.RowsAffected()
+}
diff --git a/modules/doctor/dbconsistency.go b/modules/doctor/dbconsistency.go
index 455fbb5c76..89c90367d2 100644
--- a/modules/doctor/dbconsistency.go
+++ b/modules/doctor/dbconsistency.go
@@ -158,6 +158,12 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
 			Fixer:        actions_model.FixRunnersWithoutBelongingOwner,
 			FixedMessage: "Removed",
 		},
+		{
+			Name:         "Action Runners without existing repository",
+			Counter:      actions_model.CountRunnersWithoutBelongingRepo,
+			Fixer:        actions_model.FixRunnersWithoutBelongingRepo,
+			FixedMessage: "Removed",
+		},
 	}
 
 	// TODO: function to recalc all counters