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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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. Ref: repo.DefaultBranch,
  61. Doer: doer,
  62. Event: event,
  63. }
  64. }
  65. func (input *notifyInput) WithDoer(doer *user_model.User) *notifyInput {
  66. input.Doer = doer
  67. return input
  68. }
  69. func (input *notifyInput) WithRef(ref string) *notifyInput {
  70. input.Ref = ref
  71. return input
  72. }
  73. func (input *notifyInput) WithPayload(payload api.Payloader) *notifyInput {
  74. input.Payload = payload
  75. return input
  76. }
  77. func (input *notifyInput) WithPullRequest(pr *issues_model.PullRequest) *notifyInput {
  78. input.PullRequest = pr
  79. return input
  80. }
  81. func (input *notifyInput) Notify(ctx context.Context) {
  82. log.Trace("execute %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
  83. if err := notify(ctx, input); err != nil {
  84. log.Error("an error occurred while executing the %s actions method: %v", getMethod(ctx), err)
  85. }
  86. }
  87. func notify(ctx context.Context, input *notifyInput) error {
  88. if input.Doer.IsActions() {
  89. // avoiding triggering cyclically, for example:
  90. // a comment of an issue will trigger the runner to add a new comment as reply,
  91. // and the new comment will trigger the runner again.
  92. log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
  93. return nil
  94. }
  95. if unit_model.TypeActions.UnitGlobalDisabled() {
  96. return nil
  97. }
  98. if err := input.Repo.LoadUnits(ctx); err != nil {
  99. return fmt.Errorf("repo.LoadUnits: %w", err)
  100. } else if !input.Repo.UnitEnabled(ctx, unit_model.TypeActions) {
  101. return nil
  102. }
  103. gitRepo, err := git.OpenRepository(context.Background(), input.Repo.RepoPath())
  104. if err != nil {
  105. return fmt.Errorf("git.OpenRepository: %w", err)
  106. }
  107. defer gitRepo.Close()
  108. // Get the commit object for the ref
  109. commit, err := gitRepo.GetCommit(input.Ref)
  110. if err != nil {
  111. return fmt.Errorf("gitRepo.GetCommit: %w", err)
  112. }
  113. workflows, err := actions_module.DetectWorkflows(commit, input.Event)
  114. if err != nil {
  115. return fmt.Errorf("DetectWorkflows: %w", err)
  116. }
  117. if len(workflows) == 0 {
  118. log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RepoPath(), commit.ID)
  119. return nil
  120. }
  121. p, err := json.Marshal(input.Payload)
  122. if err != nil {
  123. return fmt.Errorf("json.Marshal: %w", err)
  124. }
  125. for id, content := range workflows {
  126. run := actions_model.ActionRun{
  127. Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
  128. RepoID: input.Repo.ID,
  129. OwnerID: input.Repo.OwnerID,
  130. WorkflowID: id,
  131. TriggerUserID: input.Doer.ID,
  132. Ref: input.Ref,
  133. CommitSHA: commit.ID.String(),
  134. IsForkPullRequest: input.PullRequest != nil && input.PullRequest.IsFromFork(),
  135. Event: input.Event,
  136. EventPayload: string(p),
  137. Status: actions_model.StatusWaiting,
  138. }
  139. jobs, err := jobparser.Parse(content)
  140. if err != nil {
  141. log.Error("jobparser.Parse: %v", err)
  142. continue
  143. }
  144. if err := actions_model.InsertRun(ctx, &run, jobs); err != nil {
  145. log.Error("InsertRun: %v", err)
  146. continue
  147. }
  148. if jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: run.ID}); err != nil {
  149. log.Error("FindRunJobs: %v", err)
  150. } else {
  151. for _, job := range jobs {
  152. if err := CreateCommitStatus(ctx, job); err != nil {
  153. log.Error("CreateCommitStatus: %v", err)
  154. }
  155. }
  156. }
  157. }
  158. return nil
  159. }
  160. func newNotifyInputFromIssue(issue *issues_model.Issue, event webhook_module.HookEventType) *notifyInput {
  161. return newNotifyInput(issue.Repo, issue.Poster, event)
  162. }
  163. func notifyRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release, ref string, action api.HookReleaseAction) {
  164. if err := rel.LoadAttributes(ctx); err != nil {
  165. log.Error("LoadAttributes: %v", err)
  166. return
  167. }
  168. mode, _ := access_model.AccessLevel(ctx, doer, rel.Repo)
  169. newNotifyInput(rel.Repo, doer, webhook_module.HookEventRelease).
  170. WithRef(ref).
  171. WithPayload(&api.ReleasePayload{
  172. Action: action,
  173. Release: convert.ToRelease(rel),
  174. Repository: convert.ToRepo(ctx, rel.Repo, mode),
  175. Sender: convert.ToUser(doer, nil),
  176. }).
  177. Notify(ctx)
  178. }
  179. func notifyPackage(ctx context.Context, sender *user_model.User, pd *packages_model.PackageDescriptor, action api.HookPackageAction) {
  180. if pd.Repository == nil {
  181. // When a package is uploaded to an organization, it could trigger an event to notify.
  182. // So the repository could be nil, however, actions can't support that yet.
  183. // See https://github.com/go-gitea/gitea/pull/17940
  184. return
  185. }
  186. apiPackage, err := convert.ToPackage(ctx, pd, sender)
  187. if err != nil {
  188. log.Error("Error converting package: %v", err)
  189. return
  190. }
  191. newNotifyInput(pd.Repository, sender, webhook_module.HookEventPackage).
  192. WithPayload(&api.PackagePayload{
  193. Action: action,
  194. Package: apiPackage,
  195. Sender: convert.ToUser(sender, nil),
  196. }).
  197. Notify(ctx)
  198. }