gitea源码

commit_status.go 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package actions
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "path"
  9. actions_model "code.gitea.io/gitea/models/actions"
  10. "code.gitea.io/gitea/models/db"
  11. git_model "code.gitea.io/gitea/models/git"
  12. user_model "code.gitea.io/gitea/models/user"
  13. actions_module "code.gitea.io/gitea/modules/actions"
  14. "code.gitea.io/gitea/modules/commitstatus"
  15. git "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/log"
  17. webhook_module "code.gitea.io/gitea/modules/webhook"
  18. commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus"
  19. "github.com/nektos/act/pkg/jobparser"
  20. )
  21. // CreateCommitStatus creates a commit status for the given job.
  22. // It won't return an error failed, but will log it, because it's not critical.
  23. func CreateCommitStatus(ctx context.Context, jobs ...*actions_model.ActionRunJob) {
  24. for _, job := range jobs {
  25. if err := createCommitStatus(ctx, job); err != nil {
  26. log.Error("Failed to create commit status for job %d: %v", job.ID, err)
  27. }
  28. }
  29. }
  30. func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) error {
  31. if err := job.LoadAttributes(ctx); err != nil {
  32. return fmt.Errorf("load run: %w", err)
  33. }
  34. run := job.Run
  35. var (
  36. sha string
  37. event string
  38. )
  39. switch run.Event {
  40. case webhook_module.HookEventPush:
  41. event = "push"
  42. payload, err := run.GetPushEventPayload()
  43. if err != nil {
  44. return fmt.Errorf("GetPushEventPayload: %w", err)
  45. }
  46. if payload.HeadCommit == nil {
  47. return errors.New("head commit is missing in event payload")
  48. }
  49. sha = payload.HeadCommit.ID
  50. case // pull_request
  51. webhook_module.HookEventPullRequest,
  52. webhook_module.HookEventPullRequestSync,
  53. webhook_module.HookEventPullRequestAssign,
  54. webhook_module.HookEventPullRequestLabel,
  55. webhook_module.HookEventPullRequestReviewRequest,
  56. webhook_module.HookEventPullRequestMilestone:
  57. if run.TriggerEvent == actions_module.GithubEventPullRequestTarget {
  58. event = "pull_request_target"
  59. } else {
  60. event = "pull_request"
  61. }
  62. payload, err := run.GetPullRequestEventPayload()
  63. if err != nil {
  64. return fmt.Errorf("GetPullRequestEventPayload: %w", err)
  65. }
  66. if payload.PullRequest == nil {
  67. return errors.New("pull request is missing in event payload")
  68. } else if payload.PullRequest.Head == nil {
  69. return errors.New("head of pull request is missing in event payload")
  70. }
  71. sha = payload.PullRequest.Head.Sha
  72. case webhook_module.HookEventRelease:
  73. event = string(run.Event)
  74. sha = run.CommitSHA
  75. default:
  76. return nil
  77. }
  78. repo := run.Repo
  79. // TODO: store workflow name as a field in ActionRun to avoid parsing
  80. runName := path.Base(run.WorkflowID)
  81. if wfs, err := jobparser.Parse(job.WorkflowPayload); err == nil && len(wfs) > 0 {
  82. runName = wfs[0].Name
  83. }
  84. ctxname := fmt.Sprintf("%s / %s (%s)", runName, job.Name, event)
  85. state := toCommitStatus(job.Status)
  86. if statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll); err == nil {
  87. for _, v := range statuses {
  88. if v.Context == ctxname {
  89. if v.State == state {
  90. // no need to update
  91. return nil
  92. }
  93. break
  94. }
  95. }
  96. } else {
  97. return fmt.Errorf("GetLatestCommitStatus: %w", err)
  98. }
  99. description := ""
  100. switch job.Status {
  101. // TODO: if we want support description in different languages, we need to support i18n placeholders in it
  102. case actions_model.StatusSuccess:
  103. description = fmt.Sprintf("Successful in %s", job.Duration())
  104. case actions_model.StatusFailure:
  105. description = fmt.Sprintf("Failing after %s", job.Duration())
  106. case actions_model.StatusCancelled:
  107. description = "Has been cancelled"
  108. case actions_model.StatusSkipped:
  109. description = "Has been skipped"
  110. case actions_model.StatusRunning:
  111. description = "Has started running"
  112. case actions_model.StatusWaiting:
  113. description = "Waiting to run"
  114. case actions_model.StatusBlocked:
  115. description = "Blocked by required conditions"
  116. }
  117. index, err := getIndexOfJob(ctx, job)
  118. if err != nil {
  119. return fmt.Errorf("getIndexOfJob: %w", err)
  120. }
  121. creator := user_model.NewActionsUser()
  122. commitID, err := git.NewIDFromString(sha)
  123. if err != nil {
  124. return fmt.Errorf("HashTypeInterfaceFromHashString: %w", err)
  125. }
  126. status := git_model.CommitStatus{
  127. SHA: sha,
  128. TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), index),
  129. Description: description,
  130. Context: ctxname,
  131. CreatorID: creator.ID,
  132. State: state,
  133. }
  134. return commitstatus_service.CreateCommitStatus(ctx, repo, creator, commitID.String(), &status)
  135. }
  136. func toCommitStatus(status actions_model.Status) commitstatus.CommitStatusState {
  137. switch status {
  138. case actions_model.StatusSuccess:
  139. return commitstatus.CommitStatusSuccess
  140. case actions_model.StatusFailure, actions_model.StatusCancelled:
  141. return commitstatus.CommitStatusFailure
  142. case actions_model.StatusWaiting, actions_model.StatusBlocked, actions_model.StatusRunning:
  143. return commitstatus.CommitStatusPending
  144. case actions_model.StatusSkipped:
  145. return commitstatus.CommitStatusSkipped
  146. default:
  147. return commitstatus.CommitStatusError
  148. }
  149. }
  150. func getIndexOfJob(ctx context.Context, job *actions_model.ActionRunJob) (int, error) {
  151. // TODO: store job index as a field in ActionRunJob to avoid this
  152. jobs, err := actions_model.GetRunJobsByRunID(ctx, job.RunID)
  153. if err != nil {
  154. return 0, err
  155. }
  156. for i, v := range jobs {
  157. if v.ID == job.ID {
  158. return i, nil
  159. }
  160. }
  161. return 0, nil
  162. }