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.

notifier_helper.go 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package actions
  4. import (
  5. "context"
  6. "fmt"
  7. "strings"
  8. actions_model "code.gitea.io/gitea/models/actions"
  9. issues_model "code.gitea.io/gitea/models/issues"
  10. packages_model "code.gitea.io/gitea/models/packages"
  11. access_model "code.gitea.io/gitea/models/perm/access"
  12. repo_model "code.gitea.io/gitea/models/repo"
  13. unit_model "code.gitea.io/gitea/models/unit"
  14. user_model "code.gitea.io/gitea/models/user"
  15. actions_module "code.gitea.io/gitea/modules/actions"
  16. "code.gitea.io/gitea/modules/git"
  17. "code.gitea.io/gitea/modules/json"
  18. "code.gitea.io/gitea/modules/log"
  19. api "code.gitea.io/gitea/modules/structs"
  20. webhook_module "code.gitea.io/gitea/modules/webhook"
  21. "code.gitea.io/gitea/services/convert"
  22. "github.com/nektos/act/pkg/jobparser"
  23. )
  24. var methodCtxKey struct{}
  25. // withMethod sets the notification method that this context currently executes.
  26. // Used for debugging/ troubleshooting purposes.
  27. func withMethod(ctx context.Context, method string) context.Context {
  28. // don't overwrite
  29. if v := ctx.Value(methodCtxKey); v != nil {
  30. if _, ok := v.(string); ok {
  31. return ctx
  32. }
  33. }
  34. return context.WithValue(ctx, methodCtxKey, method)
  35. }
  36. // getMethod gets the notification method that this context currently executes.
  37. // Default: "notify"
  38. // Used for debugging/ troubleshooting purposes.
  39. func getMethod(ctx context.Context) string {
  40. if v := ctx.Value(methodCtxKey); v != nil {
  41. if s, ok := v.(string); ok {
  42. return s
  43. }
  44. }
  45. return "notify"
  46. }
  47. type notifyInput struct {
  48. // required
  49. Repo *repo_model.Repository
  50. Doer *user_model.User
  51. Event webhook_module.HookEventType
  52. // optional
  53. Ref string
  54. Payload api.Payloader
  55. PullRequest *issues_model.PullRequest
  56. }
  57. func newNotifyInput(repo *repo_model.Repository, doer *user_model.User, event webhook_module.HookEventType) *notifyInput {
  58. return &notifyInput{
  59. Repo: repo,
  60. Doer: doer,
  61. Event: event,
  62. }
  63. }
  64. func (input *notifyInput) WithDoer(doer *user_model.User) *notifyInput {
  65. input.Doer = doer
  66. return input
  67. }
  68. func (input *notifyInput) WithRef(ref string) *notifyInput {
  69. input.Ref = ref
  70. return input
  71. }
  72. func (input *notifyInput) WithPayload(payload api.Payloader) *notifyInput {
  73. input.Payload = payload
  74. return input
  75. }
  76. func (input *notifyInput) WithPullRequest(pr *issues_model.PullRequest) *notifyInput {
  77. input.PullRequest = pr
  78. if input.Ref == "" {
  79. input.Ref = pr.GetGitRefName()
  80. }
  81. return input
  82. }
  83. func (input *notifyInput) Notify(ctx context.Context) {
  84. log.Trace("execute %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
  85. if err := notify(ctx, input); err != nil {
  86. log.Error("an error occurred while executing the %s actions method: %v", getMethod(ctx), err)
  87. }
  88. }
  89. func notify(ctx context.Context, input *notifyInput) error {
  90. if input.Doer.IsActions() {
  91. // avoiding triggering cyclically, for example:
  92. // a comment of an issue will trigger the runner to add a new comment as reply,
  93. // and the new comment will trigger the runner again.
  94. log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
  95. return nil
  96. }
  97. if unit_model.TypeActions.UnitGlobalDisabled() {
  98. return nil
  99. }
  100. if err := input.Repo.LoadUnits(ctx); err != nil {
  101. return fmt.Errorf("repo.LoadUnits: %w", err)
  102. } else if !input.Repo.UnitEnabled(ctx, unit_model.TypeActions) {
  103. return nil
  104. }
  105. gitRepo, err := git.OpenRepository(context.Background(), input.Repo.RepoPath())
  106. if err != nil {
  107. return fmt.Errorf("git.OpenRepository: %w", err)
  108. }
  109. defer gitRepo.Close()
  110. ref := input.Ref
  111. if ref == "" {
  112. ref = input.Repo.DefaultBranch
  113. }
  114. // Get the commit object for the ref
  115. commit, err := gitRepo.GetCommit(ref)
  116. if err != nil {
  117. return fmt.Errorf("gitRepo.GetCommit: %w", err)
  118. }
  119. workflows, err := actions_module.DetectWorkflows(commit, input.Event, input.Payload)
  120. if err != nil {
  121. return fmt.Errorf("DetectWorkflows: %w", err)
  122. }
  123. if len(workflows) == 0 {
  124. log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RepoPath(), commit.ID)
  125. return nil
  126. }
  127. p, err := json.Marshal(input.Payload)
  128. if err != nil {
  129. return fmt.Errorf("json.Marshal: %w", err)
  130. }
  131. for id, content := range workflows {
  132. run := actions_model.ActionRun{
  133. Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
  134. RepoID: input.Repo.ID,
  135. OwnerID: input.Repo.OwnerID,
  136. WorkflowID: id,
  137. TriggerUserID: input.Doer.ID,
  138. Ref: ref,
  139. CommitSHA: commit.ID.String(),
  140. IsForkPullRequest: input.PullRequest != nil && input.PullRequest.IsFromFork(),
  141. Event: input.Event,
  142. EventPayload: string(p),
  143. Status: actions_model.StatusWaiting,
  144. }
  145. jobs, err := jobparser.Parse(content)
  146. if err != nil {
  147. log.Error("jobparser.Parse: %v", err)
  148. continue
  149. }
  150. if err := actions_model.InsertRun(ctx, &run, jobs); err != nil {
  151. log.Error("InsertRun: %v", err)
  152. continue
  153. }
  154. if jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: run.ID}); err != nil {
  155. log.Error("FindRunJobs: %v", err)
  156. } else {
  157. for _, job := range jobs {
  158. if err := CreateCommitStatus(ctx, job); err != nil {
  159. log.Error("CreateCommitStatus: %v", err)
  160. }
  161. }
  162. }
  163. }
  164. return nil
  165. }
  166. func newNotifyInputFromIssue(issue *issues_model.Issue, event webhook_module.HookEventType) *notifyInput {
  167. return newNotifyInput(issue.Repo, issue.Poster, event)
  168. }
  169. func notifyRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release, ref string, action api.HookReleaseAction) {
  170. if err := rel.LoadAttributes(ctx); err != nil {
  171. log.Error("LoadAttributes: %v", err)
  172. return
  173. }
  174. mode, _ := access_model.AccessLevel(ctx, doer, rel.Repo)
  175. newNotifyInput(rel.Repo, doer, webhook_module.HookEventRelease).
  176. WithRef(ref).
  177. WithPayload(&api.ReleasePayload{
  178. Action: action,
  179. Release: convert.ToRelease(rel),
  180. Repository: convert.ToRepo(ctx, rel.Repo, mode),
  181. Sender: convert.ToUser(doer, nil),
  182. }).
  183. Notify(ctx)
  184. }
  185. func notifyPackage(ctx context.Context, sender *user_model.User, pd *packages_model.PackageDescriptor, action api.HookPackageAction) {
  186. if pd.Repository == nil {
  187. // When a package is uploaded to an organization, it could trigger an event to notify.
  188. // So the repository could be nil, however, actions can't support that yet.
  189. // See https://github.com/go-gitea/gitea/pull/17940
  190. return
  191. }
  192. apiPackage, err := convert.ToPackage(ctx, pd, sender)
  193. if err != nil {
  194. log.Error("Error converting package: %v", err)
  195. return
  196. }
  197. newNotifyInput(pd.Repository, sender, webhook_module.HookEventPackage).
  198. WithPayload(&api.PackagePayload{
  199. Action: action,
  200. Package: apiPackage,
  201. Sender: convert.ToUser(sender, nil),
  202. }).
  203. Notify(ctx)
  204. }