You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

runner.go 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package actions
  4. import (
  5. "context"
  6. "fmt"
  7. "strings"
  8. "time"
  9. "code.gitea.io/gitea/models/db"
  10. repo_model "code.gitea.io/gitea/models/repo"
  11. "code.gitea.io/gitea/models/shared/types"
  12. user_model "code.gitea.io/gitea/models/user"
  13. "code.gitea.io/gitea/modules/optional"
  14. "code.gitea.io/gitea/modules/timeutil"
  15. "code.gitea.io/gitea/modules/translation"
  16. "code.gitea.io/gitea/modules/util"
  17. runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
  18. "xorm.io/builder"
  19. )
  20. // ActionRunner represents runner machines
  21. type ActionRunner struct {
  22. ID int64
  23. UUID string `xorm:"CHAR(36) UNIQUE"`
  24. Name string `xorm:"VARCHAR(255)"`
  25. Version string `xorm:"VARCHAR(64)"`
  26. OwnerID int64 `xorm:"index"` // org level runner, 0 means system
  27. Owner *user_model.User `xorm:"-"`
  28. RepoID int64 `xorm:"index"` // repo level runner, if OwnerID also is zero, then it's a global
  29. Repo *repo_model.Repository `xorm:"-"`
  30. Description string `xorm:"TEXT"`
  31. Base int // 0 native 1 docker 2 virtual machine
  32. RepoRange string // glob match which repositories could use this runner
  33. Token string `xorm:"-"`
  34. TokenHash string `xorm:"UNIQUE"` // sha256 of token
  35. TokenSalt string
  36. // TokenLastEight string `xorm:"token_last_eight"` // it's unnecessary because we don't find runners by token
  37. LastOnline timeutil.TimeStamp `xorm:"index"`
  38. LastActive timeutil.TimeStamp `xorm:"index"`
  39. // Store labels defined in state file (default: .runner file) of `act_runner`
  40. AgentLabels []string `xorm:"TEXT"`
  41. Created timeutil.TimeStamp `xorm:"created"`
  42. Updated timeutil.TimeStamp `xorm:"updated"`
  43. Deleted timeutil.TimeStamp `xorm:"deleted"`
  44. }
  45. const (
  46. RunnerOfflineTime = time.Minute
  47. RunnerIdleTime = 10 * time.Second
  48. )
  49. // BelongsToOwnerName before calling, should guarantee that all attributes are loaded
  50. func (r *ActionRunner) BelongsToOwnerName() string {
  51. if r.RepoID != 0 {
  52. return r.Repo.FullName()
  53. }
  54. if r.OwnerID != 0 {
  55. return r.Owner.Name
  56. }
  57. return ""
  58. }
  59. func (r *ActionRunner) BelongsToOwnerType() types.OwnerType {
  60. if r.RepoID != 0 {
  61. return types.OwnerTypeRepository
  62. }
  63. if r.OwnerID != 0 {
  64. if r.Owner.Type == user_model.UserTypeOrganization {
  65. return types.OwnerTypeOrganization
  66. } else if r.Owner.Type == user_model.UserTypeIndividual {
  67. return types.OwnerTypeIndividual
  68. }
  69. }
  70. return types.OwnerTypeSystemGlobal
  71. }
  72. // if the logic here changed, you should also modify FindRunnerOptions.ToCond
  73. func (r *ActionRunner) Status() runnerv1.RunnerStatus {
  74. if time.Since(r.LastOnline.AsTime()) > RunnerOfflineTime {
  75. return runnerv1.RunnerStatus_RUNNER_STATUS_OFFLINE
  76. }
  77. if time.Since(r.LastActive.AsTime()) > RunnerIdleTime {
  78. return runnerv1.RunnerStatus_RUNNER_STATUS_IDLE
  79. }
  80. return runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE
  81. }
  82. func (r *ActionRunner) StatusName() string {
  83. return strings.ToLower(strings.TrimPrefix(r.Status().String(), "RUNNER_STATUS_"))
  84. }
  85. func (r *ActionRunner) StatusLocaleName(lang translation.Locale) string {
  86. return lang.TrString("actions.runners.status." + r.StatusName())
  87. }
  88. func (r *ActionRunner) IsOnline() bool {
  89. status := r.Status()
  90. if status == runnerv1.RunnerStatus_RUNNER_STATUS_IDLE || status == runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE {
  91. return true
  92. }
  93. return false
  94. }
  95. // Editable checks if the runner is editable by the user
  96. func (r *ActionRunner) Editable(ownerID, repoID int64) bool {
  97. if ownerID == 0 && repoID == 0 {
  98. return true
  99. }
  100. if ownerID > 0 && r.OwnerID == ownerID {
  101. return true
  102. }
  103. return repoID > 0 && r.RepoID == repoID
  104. }
  105. // LoadAttributes loads the attributes of the runner
  106. func (r *ActionRunner) LoadAttributes(ctx context.Context) error {
  107. if r.OwnerID > 0 {
  108. var user user_model.User
  109. has, err := db.GetEngine(ctx).ID(r.OwnerID).Get(&user)
  110. if err != nil {
  111. return err
  112. }
  113. if has {
  114. r.Owner = &user
  115. }
  116. }
  117. if r.RepoID > 0 {
  118. var repo repo_model.Repository
  119. has, err := db.GetEngine(ctx).ID(r.RepoID).Get(&repo)
  120. if err != nil {
  121. return err
  122. }
  123. if has {
  124. r.Repo = &repo
  125. }
  126. }
  127. return nil
  128. }
  129. func (r *ActionRunner) GenerateToken() (err error) {
  130. r.Token, r.TokenSalt, r.TokenHash, _, err = generateSaltedToken()
  131. return err
  132. }
  133. func init() {
  134. db.RegisterModel(&ActionRunner{})
  135. }
  136. type FindRunnerOptions struct {
  137. db.ListOptions
  138. RepoID int64
  139. OwnerID int64
  140. Sort string
  141. Filter string
  142. IsOnline optional.Option[bool]
  143. WithAvailable bool // not only runners belong to, but also runners can be used
  144. }
  145. func (opts FindRunnerOptions) ToConds() builder.Cond {
  146. cond := builder.NewCond()
  147. if opts.RepoID > 0 {
  148. c := builder.NewCond().And(builder.Eq{"repo_id": opts.RepoID})
  149. if opts.WithAvailable {
  150. c = c.Or(builder.Eq{"owner_id": builder.Select("owner_id").From("repository").Where(builder.Eq{"id": opts.RepoID})})
  151. c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
  152. }
  153. cond = cond.And(c)
  154. }
  155. if opts.OwnerID > 0 {
  156. c := builder.NewCond().And(builder.Eq{"owner_id": opts.OwnerID})
  157. if opts.WithAvailable {
  158. c = c.Or(builder.Eq{"repo_id": 0, "owner_id": 0})
  159. }
  160. cond = cond.And(c)
  161. }
  162. if opts.Filter != "" {
  163. cond = cond.And(builder.Like{"name", opts.Filter})
  164. }
  165. if opts.IsOnline.Has() {
  166. if opts.IsOnline.Value() {
  167. cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
  168. } else {
  169. cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
  170. }
  171. }
  172. return cond
  173. }
  174. func (opts FindRunnerOptions) ToOrders() string {
  175. switch opts.Sort {
  176. case "online":
  177. return "last_online DESC"
  178. case "offline":
  179. return "last_online ASC"
  180. case "alphabetically":
  181. return "name ASC"
  182. case "reversealphabetically":
  183. return "name DESC"
  184. case "newest":
  185. return "id DESC"
  186. case "oldest":
  187. return "id ASC"
  188. }
  189. return "last_online DESC"
  190. }
  191. // GetRunnerByUUID returns a runner via uuid
  192. func GetRunnerByUUID(ctx context.Context, uuid string) (*ActionRunner, error) {
  193. var runner ActionRunner
  194. has, err := db.GetEngine(ctx).Where("uuid=?", uuid).Get(&runner)
  195. if err != nil {
  196. return nil, err
  197. } else if !has {
  198. return nil, fmt.Errorf("runner with uuid %s: %w", uuid, util.ErrNotExist)
  199. }
  200. return &runner, nil
  201. }
  202. // GetRunnerByID returns a runner via id
  203. func GetRunnerByID(ctx context.Context, id int64) (*ActionRunner, error) {
  204. var runner ActionRunner
  205. has, err := db.GetEngine(ctx).Where("id=?", id).Get(&runner)
  206. if err != nil {
  207. return nil, err
  208. } else if !has {
  209. return nil, fmt.Errorf("runner with id %d: %w", id, util.ErrNotExist)
  210. }
  211. return &runner, nil
  212. }
  213. // UpdateRunner updates runner's information.
  214. func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
  215. e := db.GetEngine(ctx)
  216. var err error
  217. if len(cols) == 0 {
  218. _, err = e.ID(r.ID).AllCols().Update(r)
  219. } else {
  220. _, err = e.ID(r.ID).Cols(cols...).Update(r)
  221. }
  222. return err
  223. }
  224. // DeleteRunner deletes a runner by given ID.
  225. func DeleteRunner(ctx context.Context, id int64) error {
  226. if _, err := GetRunnerByID(ctx, id); err != nil {
  227. return err
  228. }
  229. _, err := db.DeleteByID[ActionRunner](ctx, id)
  230. return err
  231. }
  232. // CreateRunner creates new runner.
  233. func CreateRunner(ctx context.Context, t *ActionRunner) error {
  234. return db.Insert(ctx, t)
  235. }
  236. func CountRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
  237. // Only affect action runners were a owner ID is set, as actions runners
  238. // could also be created on a repository.
  239. return db.GetEngine(ctx).Table("action_runner").
  240. Join("LEFT", "user", "`action_runner`.owner_id = `user`.id").
  241. Where("`action_runner`.owner_id != ?", 0).
  242. And(builder.IsNull{"`user`.id"}).
  243. Count(new(ActionRunner))
  244. }
  245. func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) {
  246. subQuery := builder.Select("`action_runner`.id").
  247. From("`action_runner`").
  248. Join("LEFT", "user", "`action_runner`.owner_id = `user`.id").
  249. Where(builder.Neq{"`action_runner`.owner_id": 0}).
  250. And(builder.IsNull{"`user`.id"})
  251. b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`")
  252. res, err := db.GetEngine(ctx).Exec(b)
  253. if err != nil {
  254. return 0, err
  255. }
  256. return res.RowsAffected()
  257. }