gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package actions
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "strings"
  9. "time"
  10. "code.gitea.io/gitea/models/db"
  11. repo_model "code.gitea.io/gitea/models/repo"
  12. "code.gitea.io/gitea/models/shared/types"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/optional"
  15. "code.gitea.io/gitea/modules/setting"
  16. "code.gitea.io/gitea/modules/timeutil"
  17. "code.gitea.io/gitea/modules/translation"
  18. "code.gitea.io/gitea/modules/util"
  19. runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
  20. "xorm.io/builder"
  21. )
  22. // ActionRunner represents runner machines
  23. //
  24. // It can be:
  25. // 1. global runner, OwnerID is 0 and RepoID is 0
  26. // 2. org/user level runner, OwnerID is org/user ID and RepoID is 0
  27. // 3. repo level runner, OwnerID is 0 and RepoID is repo ID
  28. //
  29. // Please note that it's not acceptable to have both OwnerID and RepoID to be non-zero,
  30. // or it will be complicated to find runners belonging to a specific owner.
  31. // For example, conditions like `OwnerID = 1` will also return runner {OwnerID: 1, RepoID: 1},
  32. // but it's a repo level runner, not an org/user level runner.
  33. // To avoid this, make it clear with {OwnerID: 0, RepoID: 1} for repo level runners.
  34. type ActionRunner struct {
  35. ID int64
  36. UUID string `xorm:"CHAR(36) UNIQUE"`
  37. Name string `xorm:"VARCHAR(255)"`
  38. Version string `xorm:"VARCHAR(64)"`
  39. OwnerID int64 `xorm:"index"`
  40. Owner *user_model.User `xorm:"-"`
  41. RepoID int64 `xorm:"index"`
  42. Repo *repo_model.Repository `xorm:"-"`
  43. Description string `xorm:"TEXT"`
  44. Base int // 0 native 1 docker 2 virtual machine
  45. RepoRange string // glob match which repositories could use this runner
  46. Token string `xorm:"-"`
  47. TokenHash string `xorm:"UNIQUE"` // sha256 of token
  48. TokenSalt string
  49. // TokenLastEight string `xorm:"token_last_eight"` // it's unnecessary because we don't find runners by token
  50. LastOnline timeutil.TimeStamp `xorm:"index"`
  51. LastActive timeutil.TimeStamp `xorm:"index"`
  52. // Store labels defined in state file (default: .runner file) of `act_runner`
  53. AgentLabels []string `xorm:"TEXT"`
  54. // Store if this is a runner that only ever get one single job assigned
  55. Ephemeral bool `xorm:"ephemeral NOT NULL DEFAULT false"`
  56. Created timeutil.TimeStamp `xorm:"created"`
  57. Updated timeutil.TimeStamp `xorm:"updated"`
  58. Deleted timeutil.TimeStamp `xorm:"deleted"`
  59. }
  60. const (
  61. RunnerOfflineTime = time.Minute
  62. RunnerIdleTime = 10 * time.Second
  63. )
  64. // BelongsToOwnerName before calling, should guarantee that all attributes are loaded
  65. func (r *ActionRunner) BelongsToOwnerName() string {
  66. if r.RepoID != 0 {
  67. return r.Repo.FullName()
  68. }
  69. if r.OwnerID != 0 {
  70. return r.Owner.Name
  71. }
  72. return ""
  73. }
  74. func (r *ActionRunner) BelongsToOwnerType() types.OwnerType {
  75. if r.RepoID != 0 {
  76. return types.OwnerTypeRepository
  77. }
  78. if r.OwnerID != 0 {
  79. switch r.Owner.Type {
  80. case user_model.UserTypeOrganization:
  81. return types.OwnerTypeOrganization
  82. case user_model.UserTypeIndividual:
  83. return types.OwnerTypeIndividual
  84. }
  85. }
  86. return types.OwnerTypeSystemGlobal
  87. }
  88. // if the logic here changed, you should also modify FindRunnerOptions.ToCond
  89. func (r *ActionRunner) Status() runnerv1.RunnerStatus {
  90. if time.Since(r.LastOnline.AsTime()) > RunnerOfflineTime {
  91. return runnerv1.RunnerStatus_RUNNER_STATUS_OFFLINE
  92. }
  93. if time.Since(r.LastActive.AsTime()) > RunnerIdleTime {
  94. return runnerv1.RunnerStatus_RUNNER_STATUS_IDLE
  95. }
  96. return runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE
  97. }
  98. func (r *ActionRunner) StatusName() string {
  99. return strings.ToLower(strings.TrimPrefix(r.Status().String(), "RUNNER_STATUS_"))
  100. }
  101. func (r *ActionRunner) StatusLocaleName(lang translation.Locale) string {
  102. return lang.TrString("actions.runners.status." + r.StatusName())
  103. }
  104. func (r *ActionRunner) IsOnline() bool {
  105. status := r.Status()
  106. if status == runnerv1.RunnerStatus_RUNNER_STATUS_IDLE || status == runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE {
  107. return true
  108. }
  109. return false
  110. }
  111. // EditableInContext checks if the runner is editable by the "context" owner/repo
  112. // ownerID == 0 and repoID == 0 means "admin" context, any runner including global runners could be edited
  113. // ownerID == 0 and repoID != 0 means "repo" context, any runner belonging to the given repo could be edited
  114. // ownerID != 0 and repoID == 0 means "owner(org/user)" context, any runner belonging to the given user/org could be edited
  115. // ownerID != 0 and repoID != 0 means "owner" OR "repo" context, legacy behavior, but we should forbid using it
  116. func (r *ActionRunner) EditableInContext(ownerID, repoID int64) bool {
  117. if ownerID != 0 && repoID != 0 {
  118. setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
  119. }
  120. if ownerID == 0 && repoID == 0 {
  121. return true
  122. }
  123. if ownerID > 0 && r.OwnerID == ownerID {
  124. return true
  125. }
  126. return repoID > 0 && r.RepoID == repoID
  127. }
  128. // LoadAttributes loads the attributes of the runner
  129. func (r *ActionRunner) LoadAttributes(ctx context.Context) error {
  130. if r.OwnerID > 0 {
  131. var user user_model.User
  132. has, err := db.GetEngine(ctx).ID(r.OwnerID).Get(&user)
  133. if err != nil {
  134. return err
  135. }
  136. if has {
  137. r.Owner = &user
  138. }
  139. }
  140. if r.RepoID > 0 {
  141. var repo repo_model.Repository
  142. has, err := db.GetEngine(ctx).ID(r.RepoID).Get(&repo)
  143. if err != nil {
  144. return err
  145. }
  146. if has {
  147. r.Repo = &repo
  148. }
  149. }
  150. return nil
  151. }
  152. func (r *ActionRunner) GenerateToken() (err error) {
  153. r.Token, r.TokenSalt, r.TokenHash, _, err = generateSaltedToken()
  154. return err
  155. }
  156. func init() {
  157. db.RegisterModel(&ActionRunner{})
  158. }
  159. // FindRunnerOptions
  160. // ownerID == 0 and repoID == 0 means any runner including global runners
  161. // repoID != 0 and WithAvailable == false means any runner for the given repo
  162. // repoID != 0 and WithAvailable == true means any runner for the given repo, parent user/org, and global runners
  163. // ownerID != 0 and repoID == 0 and WithAvailable == false means any runner for the given user/org
  164. // ownerID != 0 and repoID == 0 and WithAvailable == true means any runner for the given user/org and global runners
  165. type FindRunnerOptions struct {
  166. db.ListOptions
  167. IDs []int64
  168. RepoID int64
  169. OwnerID int64 // it will be ignored if RepoID is set
  170. Sort string
  171. Filter string
  172. IsOnline optional.Option[bool]
  173. WithAvailable bool // not only runners belong to, but also runners can be used
  174. }
  175. func (opts FindRunnerOptions) ToConds() builder.Cond {
  176. cond := builder.NewCond()
  177. if len(opts.IDs) > 0 {
  178. if len(opts.IDs) == 1 {
  179. cond = cond.And(builder.Eq{"id": opts.IDs[0]})
  180. } else {
  181. cond = cond.And(builder.In("id", opts.IDs))
  182. }
  183. }
  184. if opts.RepoID > 0 {
  185. c := builder.NewCond().And(builder.Eq{"repo_id": opts.RepoID})
  186. if opts.WithAvailable {
  187. c = c.Or(builder.Eq{"owner_id": builder.Select("owner_id").From("repository").Where(builder.Eq{"id": opts.RepoID})})
  188. c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
  189. }
  190. cond = cond.And(c)
  191. } else if opts.OwnerID > 0 { // OwnerID is ignored if RepoID is set
  192. c := builder.NewCond().And(builder.Eq{"owner_id": opts.OwnerID})
  193. if opts.WithAvailable {
  194. c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
  195. }
  196. cond = cond.And(c)
  197. }
  198. if opts.Filter != "" {
  199. cond = cond.And(builder.Like{"name", opts.Filter})
  200. }
  201. if opts.IsOnline.Has() {
  202. if opts.IsOnline.Value() {
  203. cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
  204. } else {
  205. cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
  206. }
  207. }
  208. return cond
  209. }
  210. func (opts FindRunnerOptions) ToOrders() string {
  211. switch opts.Sort {
  212. case "online":
  213. return "last_online DESC"
  214. case "offline":
  215. return "last_online ASC"
  216. case "alphabetically":
  217. return "name ASC"
  218. case "reversealphabetically":
  219. return "name DESC"
  220. case "newest":
  221. return "id DESC"
  222. case "oldest":
  223. return "id ASC"
  224. }
  225. return "last_online DESC"
  226. }
  227. // GetRunnerByUUID returns a runner via uuid
  228. func GetRunnerByUUID(ctx context.Context, uuid string) (*ActionRunner, error) {
  229. var runner ActionRunner
  230. has, err := db.GetEngine(ctx).Where("uuid=?", uuid).Get(&runner)
  231. if err != nil {
  232. return nil, err
  233. } else if !has {
  234. return nil, fmt.Errorf("runner with uuid %s: %w", uuid, util.ErrNotExist)
  235. }
  236. return &runner, nil
  237. }
  238. // GetRunnerByID returns a runner via id
  239. func GetRunnerByID(ctx context.Context, id int64) (*ActionRunner, error) {
  240. var runner ActionRunner
  241. has, err := db.GetEngine(ctx).Where("id=?", id).Get(&runner)
  242. if err != nil {
  243. return nil, err
  244. } else if !has {
  245. return nil, fmt.Errorf("runner with id %d: %w", id, util.ErrNotExist)
  246. }
  247. return &runner, nil
  248. }
  249. // UpdateRunner updates runner's information.
  250. func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
  251. e := db.GetEngine(ctx)
  252. r.Name = util.EllipsisDisplayString(r.Name, 255)
  253. var err error
  254. if len(cols) == 0 {
  255. _, err = e.ID(r.ID).AllCols().Update(r)
  256. } else {
  257. _, err = e.ID(r.ID).Cols(cols...).Update(r)
  258. }
  259. return err
  260. }
  261. // DeleteRunner deletes a runner by given ID.
  262. func DeleteRunner(ctx context.Context, id int64) error {
  263. if _, err := GetRunnerByID(ctx, id); err != nil {
  264. return err
  265. }
  266. _, err := db.DeleteByID[ActionRunner](ctx, id)
  267. return err
  268. }
  269. // DeleteEphemeralRunner deletes a ephemeral runner by given ID.
  270. func DeleteEphemeralRunner(ctx context.Context, id int64) error {
  271. runner, err := GetRunnerByID(ctx, id)
  272. if err != nil {
  273. if errors.Is(err, util.ErrNotExist) {
  274. return nil
  275. }
  276. return err
  277. }
  278. if !runner.Ephemeral {
  279. return nil
  280. }
  281. _, err = db.DeleteByID[ActionRunner](ctx, id)
  282. return err
  283. }
  284. // CreateRunner creates new runner.
  285. func CreateRunner(ctx context.Context, t *ActionRunner) error {
  286. if t.OwnerID != 0 && t.RepoID != 0 {
  287. // It's trying to create a runner that belongs to a repository, but OwnerID has been set accidentally.
  288. // Remove OwnerID to avoid confusion; it's not worth returning an error here.
  289. t.OwnerID = 0
  290. }
  291. t.Name = util.EllipsisDisplayString(t.Name, 255)
  292. return db.Insert(ctx, t)
  293. }
  294. func CountRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
  295. // Only affect action runners were a owner ID is set, as actions runners
  296. // could also be created on a repository.
  297. return db.GetEngine(ctx).Table("action_runner").
  298. Join("LEFT", "`user`", "`action_runner`.owner_id = `user`.id").
  299. Where("`action_runner`.owner_id != ?", 0).
  300. And(builder.IsNull{"`user`.id"}).
  301. Count(new(ActionRunner))
  302. }
  303. func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
  304. subQuery := builder.Select("`action_runner`.id").
  305. From("`action_runner`").
  306. Join("LEFT", "`user`", "`action_runner`.owner_id = `user`.id").
  307. Where(builder.Neq{"`action_runner`.owner_id": 0}).
  308. And(builder.IsNull{"`user`.id"})
  309. b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`")
  310. res, err := db.GetEngine(ctx).Exec(b)
  311. if err != nil {
  312. return 0, err
  313. }
  314. return res.RowsAffected()
  315. }
  316. func CountRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) {
  317. return db.GetEngine(ctx).Table("action_runner").
  318. Join("LEFT", "`repository`", "`action_runner`.repo_id = `repository`.id").
  319. Where("`action_runner`.repo_id != ?", 0).
  320. And(builder.IsNull{"`repository`.id"}).
  321. Count(new(ActionRunner))
  322. }
  323. func FixRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) {
  324. subQuery := builder.Select("`action_runner`.id").
  325. From("`action_runner`").
  326. Join("LEFT", "`repository`", "`action_runner`.repo_id = `repository`.id").
  327. Where(builder.Neq{"`action_runner`.repo_id": 0}).
  328. And(builder.IsNull{"`repository`.id"})
  329. b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`")
  330. res, err := db.GetEngine(ctx).Exec(b)
  331. if err != nil {
  332. return 0, err
  333. }
  334. return res.RowsAffected()
  335. }
  336. func CountWrongRepoLevelRunners(ctx context.Context) (int64, error) {
  337. var result int64
  338. _, err := db.GetEngine(ctx).SQL("SELECT count(`id`) FROM `action_runner` WHERE `repo_id` > 0 AND `owner_id` > 0").Get(&result)
  339. return result, err
  340. }
  341. func UpdateWrongRepoLevelRunners(ctx context.Context) (int64, error) {
  342. result, err := db.GetEngine(ctx).Exec("UPDATE `action_runner` SET `owner_id` = 0 WHERE `repo_id` > 0 AND `owner_id` > 0")
  343. if err != nil {
  344. return 0, err
  345. }
  346. return result.RowsAffected()
  347. }