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.5KB

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