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.

run.go 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package actions
  4. import (
  5. "context"
  6. "fmt"
  7. "time"
  8. "code.gitea.io/gitea/models/db"
  9. repo_model "code.gitea.io/gitea/models/repo"
  10. user_model "code.gitea.io/gitea/models/user"
  11. "code.gitea.io/gitea/modules/json"
  12. api "code.gitea.io/gitea/modules/structs"
  13. "code.gitea.io/gitea/modules/timeutil"
  14. "code.gitea.io/gitea/modules/util"
  15. webhook_module "code.gitea.io/gitea/modules/webhook"
  16. "github.com/nektos/act/pkg/jobparser"
  17. "xorm.io/builder"
  18. )
  19. // ActionRun represents a run of a workflow file
  20. type ActionRun struct {
  21. ID int64
  22. Title string
  23. RepoID int64 `xorm:"index unique(repo_index)"`
  24. Repo *repo_model.Repository `xorm:"-"`
  25. OwnerID int64 `xorm:"index"`
  26. WorkflowID string `xorm:"index"` // the name of workflow file
  27. Index int64 `xorm:"index unique(repo_index)"` // a unique number for each run of a repository
  28. TriggerUserID int64
  29. TriggerUser *user_model.User `xorm:"-"`
  30. Ref string
  31. CommitSHA string
  32. IsForkPullRequest bool
  33. Event webhook_module.HookEventType
  34. EventPayload string `xorm:"LONGTEXT"`
  35. Status Status `xorm:"index"`
  36. Started timeutil.TimeStamp
  37. Stopped timeutil.TimeStamp
  38. Created timeutil.TimeStamp `xorm:"created"`
  39. Updated timeutil.TimeStamp `xorm:"updated"`
  40. }
  41. func init() {
  42. db.RegisterModel(new(ActionRun))
  43. db.RegisterModel(new(ActionRunIndex))
  44. }
  45. func (run *ActionRun) HTMLURL() string {
  46. if run.Repo == nil {
  47. return ""
  48. }
  49. return fmt.Sprintf("%s/actions/runs/%d", run.Repo.HTMLURL(), run.Index)
  50. }
  51. func (run *ActionRun) Link() string {
  52. if run.Repo == nil {
  53. return ""
  54. }
  55. return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index)
  56. }
  57. // LoadAttributes load Repo TriggerUser if not loaded
  58. func (run *ActionRun) LoadAttributes(ctx context.Context) error {
  59. if run == nil {
  60. return nil
  61. }
  62. if run.Repo == nil {
  63. repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
  64. if err != nil {
  65. return err
  66. }
  67. run.Repo = repo
  68. }
  69. if err := run.Repo.LoadAttributes(ctx); err != nil {
  70. return err
  71. }
  72. if run.TriggerUser == nil {
  73. u, err := user_model.GetPossibleUserByID(ctx, run.TriggerUserID)
  74. if err != nil {
  75. return err
  76. }
  77. run.TriggerUser = u
  78. }
  79. return nil
  80. }
  81. func (run *ActionRun) Duration() time.Duration {
  82. return calculateDuration(run.Started, run.Stopped, run.Status)
  83. }
  84. func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) {
  85. if run.Event == webhook_module.HookEventPush {
  86. var payload api.PushPayload
  87. if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
  88. return nil, err
  89. }
  90. return &payload, nil
  91. }
  92. return nil, fmt.Errorf("event %s is not a push event", run.Event)
  93. }
  94. func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
  95. _, err := db.GetEngine(ctx).ID(repo.ID).
  96. SetExpr("num_action_runs",
  97. builder.Select("count(*)").From("action_run").
  98. Where(builder.Eq{"repo_id": repo.ID}),
  99. ).
  100. SetExpr("num_closed_action_runs",
  101. builder.Select("count(*)").From("action_run").
  102. Where(builder.Eq{
  103. "repo_id": repo.ID,
  104. }.And(
  105. builder.In("status",
  106. StatusSuccess,
  107. StatusFailure,
  108. StatusCancelled,
  109. StatusSkipped,
  110. ),
  111. ),
  112. ),
  113. ).
  114. Update(repo)
  115. return err
  116. }
  117. // InsertRun inserts a run
  118. func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
  119. ctx, commiter, err := db.TxContext(ctx)
  120. if err != nil {
  121. return err
  122. }
  123. defer commiter.Close()
  124. index, err := db.GetNextResourceIndex(ctx, "action_run_index", run.RepoID)
  125. if err != nil {
  126. return err
  127. }
  128. run.Index = index
  129. if run.Status.IsUnknown() {
  130. run.Status = StatusWaiting
  131. }
  132. if err := db.Insert(ctx, run); err != nil {
  133. return err
  134. }
  135. if run.Repo == nil {
  136. repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
  137. if err != nil {
  138. return err
  139. }
  140. run.Repo = repo
  141. }
  142. if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
  143. return err
  144. }
  145. runJobs := make([]*ActionRunJob, 0, len(jobs))
  146. for _, v := range jobs {
  147. id, job := v.Job()
  148. needs := job.Needs()
  149. job.EraseNeeds()
  150. payload, _ := v.Marshal()
  151. status := StatusWaiting
  152. if len(needs) > 0 {
  153. status = StatusBlocked
  154. }
  155. runJobs = append(runJobs, &ActionRunJob{
  156. RunID: run.ID,
  157. RepoID: run.RepoID,
  158. OwnerID: run.OwnerID,
  159. CommitSHA: run.CommitSHA,
  160. IsForkPullRequest: run.IsForkPullRequest,
  161. Name: job.Name,
  162. WorkflowPayload: payload,
  163. JobID: id,
  164. Needs: needs,
  165. RunsOn: job.RunsOn(),
  166. Status: status,
  167. })
  168. }
  169. if err := db.Insert(ctx, runJobs); err != nil {
  170. return err
  171. }
  172. return commiter.Commit()
  173. }
  174. func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) {
  175. var run ActionRun
  176. has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run)
  177. if err != nil {
  178. return nil, err
  179. } else if !has {
  180. return nil, fmt.Errorf("run with id %d: %w", id, util.ErrNotExist)
  181. }
  182. return &run, nil
  183. }
  184. func GetRunByIndex(ctx context.Context, repoID, index int64) (*ActionRun, error) {
  185. run := &ActionRun{
  186. RepoID: repoID,
  187. Index: index,
  188. }
  189. has, err := db.GetEngine(ctx).Get(run)
  190. if err != nil {
  191. return nil, err
  192. } else if !has {
  193. return nil, fmt.Errorf("run with index %d %d: %w", repoID, index, util.ErrNotExist)
  194. }
  195. return run, nil
  196. }
  197. func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
  198. sess := db.GetEngine(ctx).ID(run.ID)
  199. if len(cols) > 0 {
  200. sess.Cols(cols...)
  201. }
  202. _, err := sess.Update(run)
  203. if run.Status != 0 || util.SliceContains(cols, "status") {
  204. if run.RepoID == 0 {
  205. run, err = GetRunByID(ctx, run.ID)
  206. if err != nil {
  207. return err
  208. }
  209. }
  210. if run.Repo == nil {
  211. repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
  212. if err != nil {
  213. return err
  214. }
  215. run.Repo = repo
  216. }
  217. if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
  218. return err
  219. }
  220. }
  221. return err
  222. }
  223. type ActionRunIndex db.ResourceIndex