Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

commit_status.go 4.7KB

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