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.

webhook.go 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2017 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package models
  6. import (
  7. "context"
  8. "fmt"
  9. "strings"
  10. "time"
  11. "code.gitea.io/gitea/models/db"
  12. "code.gitea.io/gitea/modules/json"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/setting"
  15. api "code.gitea.io/gitea/modules/structs"
  16. "code.gitea.io/gitea/modules/timeutil"
  17. "code.gitea.io/gitea/modules/util"
  18. gouuid "github.com/google/uuid"
  19. "xorm.io/builder"
  20. )
  21. // HookContentType is the content type of a web hook
  22. type HookContentType int
  23. const (
  24. // ContentTypeJSON is a JSON payload for web hooks
  25. ContentTypeJSON HookContentType = iota + 1
  26. // ContentTypeForm is an url-encoded form payload for web hook
  27. ContentTypeForm
  28. )
  29. var hookContentTypes = map[string]HookContentType{
  30. "json": ContentTypeJSON,
  31. "form": ContentTypeForm,
  32. }
  33. // ToHookContentType returns HookContentType by given name.
  34. func ToHookContentType(name string) HookContentType {
  35. return hookContentTypes[name]
  36. }
  37. // HookTaskCleanupType is the type of cleanup to perform on hook_task
  38. type HookTaskCleanupType int
  39. const (
  40. // OlderThan hook_task rows will be cleaned up by the age of the row
  41. OlderThan HookTaskCleanupType = iota
  42. // PerWebhook hook_task rows will be cleaned up by leaving the most recent deliveries for each webhook
  43. PerWebhook
  44. )
  45. var hookTaskCleanupTypes = map[string]HookTaskCleanupType{
  46. "OlderThan": OlderThan,
  47. "PerWebhook": PerWebhook,
  48. }
  49. // ToHookTaskCleanupType returns HookTaskCleanupType by given name.
  50. func ToHookTaskCleanupType(name string) HookTaskCleanupType {
  51. return hookTaskCleanupTypes[name]
  52. }
  53. // Name returns the name of a given web hook's content type
  54. func (t HookContentType) Name() string {
  55. switch t {
  56. case ContentTypeJSON:
  57. return "json"
  58. case ContentTypeForm:
  59. return "form"
  60. }
  61. return ""
  62. }
  63. // IsValidHookContentType returns true if given name is a valid hook content type.
  64. func IsValidHookContentType(name string) bool {
  65. _, ok := hookContentTypes[name]
  66. return ok
  67. }
  68. // HookEvents is a set of web hook events
  69. type HookEvents struct {
  70. Create bool `json:"create"`
  71. Delete bool `json:"delete"`
  72. Fork bool `json:"fork"`
  73. Issues bool `json:"issues"`
  74. IssueAssign bool `json:"issue_assign"`
  75. IssueLabel bool `json:"issue_label"`
  76. IssueMilestone bool `json:"issue_milestone"`
  77. IssueComment bool `json:"issue_comment"`
  78. Push bool `json:"push"`
  79. PullRequest bool `json:"pull_request"`
  80. PullRequestAssign bool `json:"pull_request_assign"`
  81. PullRequestLabel bool `json:"pull_request_label"`
  82. PullRequestMilestone bool `json:"pull_request_milestone"`
  83. PullRequestComment bool `json:"pull_request_comment"`
  84. PullRequestReview bool `json:"pull_request_review"`
  85. PullRequestSync bool `json:"pull_request_sync"`
  86. Repository bool `json:"repository"`
  87. Release bool `json:"release"`
  88. }
  89. // HookEvent represents events that will delivery hook.
  90. type HookEvent struct {
  91. PushOnly bool `json:"push_only"`
  92. SendEverything bool `json:"send_everything"`
  93. ChooseEvents bool `json:"choose_events"`
  94. BranchFilter string `json:"branch_filter"`
  95. HookEvents `json:"events"`
  96. }
  97. // HookType is the type of a webhook
  98. type HookType = string
  99. // Types of webhooks
  100. const (
  101. GITEA HookType = "gitea"
  102. GOGS HookType = "gogs"
  103. SLACK HookType = "slack"
  104. DISCORD HookType = "discord"
  105. DINGTALK HookType = "dingtalk"
  106. TELEGRAM HookType = "telegram"
  107. MSTEAMS HookType = "msteams"
  108. FEISHU HookType = "feishu"
  109. MATRIX HookType = "matrix"
  110. WECHATWORK HookType = "wechatwork"
  111. )
  112. // HookStatus is the status of a web hook
  113. type HookStatus int
  114. // Possible statuses of a web hook
  115. const (
  116. HookStatusNone = iota
  117. HookStatusSucceed
  118. HookStatusFail
  119. )
  120. // Webhook represents a web hook object.
  121. type Webhook struct {
  122. ID int64 `xorm:"pk autoincr"`
  123. RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
  124. OrgID int64 `xorm:"INDEX"`
  125. IsSystemWebhook bool
  126. URL string `xorm:"url TEXT"`
  127. HTTPMethod string `xorm:"http_method"`
  128. ContentType HookContentType
  129. Secret string `xorm:"TEXT"`
  130. Events string `xorm:"TEXT"`
  131. *HookEvent `xorm:"-"`
  132. IsActive bool `xorm:"INDEX"`
  133. Type HookType `xorm:"VARCHAR(16) 'type'"`
  134. Meta string `xorm:"TEXT"` // store hook-specific attributes
  135. LastStatus HookStatus // Last delivery status
  136. CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
  137. UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
  138. }
  139. func init() {
  140. db.RegisterModel(new(Webhook))
  141. db.RegisterModel(new(HookTask))
  142. }
  143. // AfterLoad updates the webhook object upon setting a column
  144. func (w *Webhook) AfterLoad() {
  145. w.HookEvent = &HookEvent{}
  146. if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
  147. log.Error("Unmarshal[%d]: %v", w.ID, err)
  148. }
  149. }
  150. // History returns history of webhook by given conditions.
  151. func (w *Webhook) History(page int) ([]*HookTask, error) {
  152. return HookTasks(w.ID, page)
  153. }
  154. // UpdateEvent handles conversion from HookEvent to Events.
  155. func (w *Webhook) UpdateEvent() error {
  156. data, err := json.Marshal(w.HookEvent)
  157. w.Events = string(data)
  158. return err
  159. }
  160. // HasCreateEvent returns true if hook enabled create event.
  161. func (w *Webhook) HasCreateEvent() bool {
  162. return w.SendEverything ||
  163. (w.ChooseEvents && w.HookEvents.Create)
  164. }
  165. // HasDeleteEvent returns true if hook enabled delete event.
  166. func (w *Webhook) HasDeleteEvent() bool {
  167. return w.SendEverything ||
  168. (w.ChooseEvents && w.HookEvents.Delete)
  169. }
  170. // HasForkEvent returns true if hook enabled fork event.
  171. func (w *Webhook) HasForkEvent() bool {
  172. return w.SendEverything ||
  173. (w.ChooseEvents && w.HookEvents.Fork)
  174. }
  175. // HasIssuesEvent returns true if hook enabled issues event.
  176. func (w *Webhook) HasIssuesEvent() bool {
  177. return w.SendEverything ||
  178. (w.ChooseEvents && w.HookEvents.Issues)
  179. }
  180. // HasIssuesAssignEvent returns true if hook enabled issues assign event.
  181. func (w *Webhook) HasIssuesAssignEvent() bool {
  182. return w.SendEverything ||
  183. (w.ChooseEvents && w.HookEvents.IssueAssign)
  184. }
  185. // HasIssuesLabelEvent returns true if hook enabled issues label event.
  186. func (w *Webhook) HasIssuesLabelEvent() bool {
  187. return w.SendEverything ||
  188. (w.ChooseEvents && w.HookEvents.IssueLabel)
  189. }
  190. // HasIssuesMilestoneEvent returns true if hook enabled issues milestone event.
  191. func (w *Webhook) HasIssuesMilestoneEvent() bool {
  192. return w.SendEverything ||
  193. (w.ChooseEvents && w.HookEvents.IssueMilestone)
  194. }
  195. // HasIssueCommentEvent returns true if hook enabled issue_comment event.
  196. func (w *Webhook) HasIssueCommentEvent() bool {
  197. return w.SendEverything ||
  198. (w.ChooseEvents && w.HookEvents.IssueComment)
  199. }
  200. // HasPushEvent returns true if hook enabled push event.
  201. func (w *Webhook) HasPushEvent() bool {
  202. return w.PushOnly || w.SendEverything ||
  203. (w.ChooseEvents && w.HookEvents.Push)
  204. }
  205. // HasPullRequestEvent returns true if hook enabled pull request event.
  206. func (w *Webhook) HasPullRequestEvent() bool {
  207. return w.SendEverything ||
  208. (w.ChooseEvents && w.HookEvents.PullRequest)
  209. }
  210. // HasPullRequestAssignEvent returns true if hook enabled pull request assign event.
  211. func (w *Webhook) HasPullRequestAssignEvent() bool {
  212. return w.SendEverything ||
  213. (w.ChooseEvents && w.HookEvents.PullRequestAssign)
  214. }
  215. // HasPullRequestLabelEvent returns true if hook enabled pull request label event.
  216. func (w *Webhook) HasPullRequestLabelEvent() bool {
  217. return w.SendEverything ||
  218. (w.ChooseEvents && w.HookEvents.PullRequestLabel)
  219. }
  220. // HasPullRequestMilestoneEvent returns true if hook enabled pull request milestone event.
  221. func (w *Webhook) HasPullRequestMilestoneEvent() bool {
  222. return w.SendEverything ||
  223. (w.ChooseEvents && w.HookEvents.PullRequestMilestone)
  224. }
  225. // HasPullRequestCommentEvent returns true if hook enabled pull_request_comment event.
  226. func (w *Webhook) HasPullRequestCommentEvent() bool {
  227. return w.SendEverything ||
  228. (w.ChooseEvents && w.HookEvents.PullRequestComment)
  229. }
  230. // HasPullRequestApprovedEvent returns true if hook enabled pull request review event.
  231. func (w *Webhook) HasPullRequestApprovedEvent() bool {
  232. return w.SendEverything ||
  233. (w.ChooseEvents && w.HookEvents.PullRequestReview)
  234. }
  235. // HasPullRequestRejectedEvent returns true if hook enabled pull request review event.
  236. func (w *Webhook) HasPullRequestRejectedEvent() bool {
  237. return w.SendEverything ||
  238. (w.ChooseEvents && w.HookEvents.PullRequestReview)
  239. }
  240. // HasPullRequestReviewCommentEvent returns true if hook enabled pull request review event.
  241. func (w *Webhook) HasPullRequestReviewCommentEvent() bool {
  242. return w.SendEverything ||
  243. (w.ChooseEvents && w.HookEvents.PullRequestReview)
  244. }
  245. // HasPullRequestSyncEvent returns true if hook enabled pull request sync event.
  246. func (w *Webhook) HasPullRequestSyncEvent() bool {
  247. return w.SendEverything ||
  248. (w.ChooseEvents && w.HookEvents.PullRequestSync)
  249. }
  250. // HasReleaseEvent returns if hook enabled release event.
  251. func (w *Webhook) HasReleaseEvent() bool {
  252. return w.SendEverything ||
  253. (w.ChooseEvents && w.HookEvents.Release)
  254. }
  255. // HasRepositoryEvent returns if hook enabled repository event.
  256. func (w *Webhook) HasRepositoryEvent() bool {
  257. return w.SendEverything ||
  258. (w.ChooseEvents && w.HookEvents.Repository)
  259. }
  260. // EventCheckers returns event checkers
  261. func (w *Webhook) EventCheckers() []struct {
  262. Has func() bool
  263. Type HookEventType
  264. } {
  265. return []struct {
  266. Has func() bool
  267. Type HookEventType
  268. }{
  269. {w.HasCreateEvent, HookEventCreate},
  270. {w.HasDeleteEvent, HookEventDelete},
  271. {w.HasForkEvent, HookEventFork},
  272. {w.HasPushEvent, HookEventPush},
  273. {w.HasIssuesEvent, HookEventIssues},
  274. {w.HasIssuesAssignEvent, HookEventIssueAssign},
  275. {w.HasIssuesLabelEvent, HookEventIssueLabel},
  276. {w.HasIssuesMilestoneEvent, HookEventIssueMilestone},
  277. {w.HasIssueCommentEvent, HookEventIssueComment},
  278. {w.HasPullRequestEvent, HookEventPullRequest},
  279. {w.HasPullRequestAssignEvent, HookEventPullRequestAssign},
  280. {w.HasPullRequestLabelEvent, HookEventPullRequestLabel},
  281. {w.HasPullRequestMilestoneEvent, HookEventPullRequestMilestone},
  282. {w.HasPullRequestCommentEvent, HookEventPullRequestComment},
  283. {w.HasPullRequestApprovedEvent, HookEventPullRequestReviewApproved},
  284. {w.HasPullRequestRejectedEvent, HookEventPullRequestReviewRejected},
  285. {w.HasPullRequestCommentEvent, HookEventPullRequestReviewComment},
  286. {w.HasPullRequestSyncEvent, HookEventPullRequestSync},
  287. {w.HasRepositoryEvent, HookEventRepository},
  288. {w.HasReleaseEvent, HookEventRelease},
  289. }
  290. }
  291. // EventsArray returns an array of hook events
  292. func (w *Webhook) EventsArray() []string {
  293. events := make([]string, 0, 7)
  294. for _, c := range w.EventCheckers() {
  295. if c.Has() {
  296. events = append(events, string(c.Type))
  297. }
  298. }
  299. return events
  300. }
  301. // CreateWebhook creates a new web hook.
  302. func CreateWebhook(w *Webhook) error {
  303. return createWebhook(db.GetEngine(db.DefaultContext), w)
  304. }
  305. func createWebhook(e db.Engine, w *Webhook) error {
  306. w.Type = strings.TrimSpace(w.Type)
  307. _, err := e.Insert(w)
  308. return err
  309. }
  310. // getWebhook uses argument bean as query condition,
  311. // ID must be specified and do not assign unnecessary fields.
  312. func getWebhook(bean *Webhook) (*Webhook, error) {
  313. has, err := db.GetEngine(db.DefaultContext).Get(bean)
  314. if err != nil {
  315. return nil, err
  316. } else if !has {
  317. return nil, ErrWebhookNotExist{bean.ID}
  318. }
  319. return bean, nil
  320. }
  321. // GetWebhookByID returns webhook of repository by given ID.
  322. func GetWebhookByID(id int64) (*Webhook, error) {
  323. return getWebhook(&Webhook{
  324. ID: id,
  325. })
  326. }
  327. // GetWebhookByRepoID returns webhook of repository by given ID.
  328. func GetWebhookByRepoID(repoID, id int64) (*Webhook, error) {
  329. return getWebhook(&Webhook{
  330. ID: id,
  331. RepoID: repoID,
  332. })
  333. }
  334. // GetWebhookByOrgID returns webhook of organization by given ID.
  335. func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) {
  336. return getWebhook(&Webhook{
  337. ID: id,
  338. OrgID: orgID,
  339. })
  340. }
  341. // ListWebhookOptions are options to filter webhooks on ListWebhooksByOpts
  342. type ListWebhookOptions struct {
  343. db.ListOptions
  344. RepoID int64
  345. OrgID int64
  346. IsActive util.OptionalBool
  347. }
  348. func (opts *ListWebhookOptions) toCond() builder.Cond {
  349. cond := builder.NewCond()
  350. if opts.RepoID != 0 {
  351. cond = cond.And(builder.Eq{"webhook.repo_id": opts.RepoID})
  352. }
  353. if opts.OrgID != 0 {
  354. cond = cond.And(builder.Eq{"webhook.org_id": opts.OrgID})
  355. }
  356. if !opts.IsActive.IsNone() {
  357. cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.IsTrue()})
  358. }
  359. return cond
  360. }
  361. func listWebhooksByOpts(e db.Engine, opts *ListWebhookOptions) ([]*Webhook, error) {
  362. sess := e.Where(opts.toCond())
  363. if opts.Page != 0 {
  364. sess = db.SetSessionPagination(sess, opts)
  365. webhooks := make([]*Webhook, 0, opts.PageSize)
  366. err := sess.Find(&webhooks)
  367. return webhooks, err
  368. }
  369. webhooks := make([]*Webhook, 0, 10)
  370. err := sess.Find(&webhooks)
  371. return webhooks, err
  372. }
  373. // ListWebhooksByOpts return webhooks based on options
  374. func ListWebhooksByOpts(opts *ListWebhookOptions) ([]*Webhook, error) {
  375. return listWebhooksByOpts(db.GetEngine(db.DefaultContext), opts)
  376. }
  377. // CountWebhooksByOpts count webhooks based on options and ignore pagination
  378. func CountWebhooksByOpts(opts *ListWebhookOptions) (int64, error) {
  379. return db.GetEngine(db.DefaultContext).Where(opts.toCond()).Count(&Webhook{})
  380. }
  381. // GetDefaultWebhooks returns all admin-default webhooks.
  382. func GetDefaultWebhooks() ([]*Webhook, error) {
  383. return getDefaultWebhooks(db.GetEngine(db.DefaultContext))
  384. }
  385. func getDefaultWebhooks(e db.Engine) ([]*Webhook, error) {
  386. webhooks := make([]*Webhook, 0, 5)
  387. return webhooks, e.
  388. Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false).
  389. Find(&webhooks)
  390. }
  391. // GetSystemOrDefaultWebhook returns admin system or default webhook by given ID.
  392. func GetSystemOrDefaultWebhook(id int64) (*Webhook, error) {
  393. webhook := &Webhook{ID: id}
  394. has, err := db.GetEngine(db.DefaultContext).
  395. Where("repo_id=? AND org_id=?", 0, 0).
  396. Get(webhook)
  397. if err != nil {
  398. return nil, err
  399. } else if !has {
  400. return nil, ErrWebhookNotExist{id}
  401. }
  402. return webhook, nil
  403. }
  404. // GetSystemWebhooks returns all admin system webhooks.
  405. func GetSystemWebhooks() ([]*Webhook, error) {
  406. return getSystemWebhooks(db.GetEngine(db.DefaultContext))
  407. }
  408. func getSystemWebhooks(e db.Engine) ([]*Webhook, error) {
  409. webhooks := make([]*Webhook, 0, 5)
  410. return webhooks, e.
  411. Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true).
  412. Find(&webhooks)
  413. }
  414. // UpdateWebhook updates information of webhook.
  415. func UpdateWebhook(w *Webhook) error {
  416. _, err := db.GetEngine(db.DefaultContext).ID(w.ID).AllCols().Update(w)
  417. return err
  418. }
  419. // UpdateWebhookLastStatus updates last status of webhook.
  420. func UpdateWebhookLastStatus(w *Webhook) error {
  421. _, err := db.GetEngine(db.DefaultContext).ID(w.ID).Cols("last_status").Update(w)
  422. return err
  423. }
  424. // deleteWebhook uses argument bean as query condition,
  425. // ID must be specified and do not assign unnecessary fields.
  426. func deleteWebhook(bean *Webhook) (err error) {
  427. sess := db.NewSession(db.DefaultContext)
  428. defer sess.Close()
  429. if err = sess.Begin(); err != nil {
  430. return err
  431. }
  432. if count, err := sess.Delete(bean); err != nil {
  433. return err
  434. } else if count == 0 {
  435. return ErrWebhookNotExist{ID: bean.ID}
  436. } else if _, err = sess.Delete(&HookTask{HookID: bean.ID}); err != nil {
  437. return err
  438. }
  439. return sess.Commit()
  440. }
  441. // DeleteWebhookByRepoID deletes webhook of repository by given ID.
  442. func DeleteWebhookByRepoID(repoID, id int64) error {
  443. return deleteWebhook(&Webhook{
  444. ID: id,
  445. RepoID: repoID,
  446. })
  447. }
  448. // DeleteWebhookByOrgID deletes webhook of organization by given ID.
  449. func DeleteWebhookByOrgID(orgID, id int64) error {
  450. return deleteWebhook(&Webhook{
  451. ID: id,
  452. OrgID: orgID,
  453. })
  454. }
  455. // DeleteDefaultSystemWebhook deletes an admin-configured default or system webhook (where Org and Repo ID both 0)
  456. func DeleteDefaultSystemWebhook(id int64) error {
  457. sess := db.NewSession(db.DefaultContext)
  458. defer sess.Close()
  459. if err := sess.Begin(); err != nil {
  460. return err
  461. }
  462. count, err := sess.
  463. Where("repo_id=? AND org_id=?", 0, 0).
  464. Delete(&Webhook{ID: id})
  465. if err != nil {
  466. return err
  467. } else if count == 0 {
  468. return ErrWebhookNotExist{ID: id}
  469. }
  470. if _, err := sess.Delete(&HookTask{HookID: id}); err != nil {
  471. return err
  472. }
  473. return sess.Commit()
  474. }
  475. // copyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo
  476. func copyDefaultWebhooksToRepo(e db.Engine, repoID int64) error {
  477. ws, err := getDefaultWebhooks(e)
  478. if err != nil {
  479. return fmt.Errorf("GetDefaultWebhooks: %v", err)
  480. }
  481. for _, w := range ws {
  482. w.ID = 0
  483. w.RepoID = repoID
  484. if err := createWebhook(e, w); err != nil {
  485. return fmt.Errorf("CreateWebhook: %v", err)
  486. }
  487. }
  488. return nil
  489. }
  490. // ___ ___ __ ___________ __
  491. // / | \ ____ ____ | | _\__ ___/____ _____| | __
  492. // / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ /
  493. // \ Y ( <_> | <_> ) < | | / __ \_\___ \| <
  494. // \___|_ / \____/ \____/|__|_ \ |____| (____ /____ >__|_ \
  495. // \/ \/ \/ \/ \/
  496. // HookEventType is the type of an hook event
  497. type HookEventType string
  498. // Types of hook events
  499. const (
  500. HookEventCreate HookEventType = "create"
  501. HookEventDelete HookEventType = "delete"
  502. HookEventFork HookEventType = "fork"
  503. HookEventPush HookEventType = "push"
  504. HookEventIssues HookEventType = "issues"
  505. HookEventIssueAssign HookEventType = "issue_assign"
  506. HookEventIssueLabel HookEventType = "issue_label"
  507. HookEventIssueMilestone HookEventType = "issue_milestone"
  508. HookEventIssueComment HookEventType = "issue_comment"
  509. HookEventPullRequest HookEventType = "pull_request"
  510. HookEventPullRequestAssign HookEventType = "pull_request_assign"
  511. HookEventPullRequestLabel HookEventType = "pull_request_label"
  512. HookEventPullRequestMilestone HookEventType = "pull_request_milestone"
  513. HookEventPullRequestComment HookEventType = "pull_request_comment"
  514. HookEventPullRequestReviewApproved HookEventType = "pull_request_review_approved"
  515. HookEventPullRequestReviewRejected HookEventType = "pull_request_review_rejected"
  516. HookEventPullRequestReviewComment HookEventType = "pull_request_review_comment"
  517. HookEventPullRequestSync HookEventType = "pull_request_sync"
  518. HookEventRepository HookEventType = "repository"
  519. HookEventRelease HookEventType = "release"
  520. )
  521. // Event returns the HookEventType as an event string
  522. func (h HookEventType) Event() string {
  523. switch h {
  524. case HookEventCreate:
  525. return "create"
  526. case HookEventDelete:
  527. return "delete"
  528. case HookEventFork:
  529. return "fork"
  530. case HookEventPush:
  531. return "push"
  532. case HookEventIssues, HookEventIssueAssign, HookEventIssueLabel, HookEventIssueMilestone:
  533. return "issues"
  534. case HookEventPullRequest, HookEventPullRequestAssign, HookEventPullRequestLabel, HookEventPullRequestMilestone,
  535. HookEventPullRequestSync:
  536. return "pull_request"
  537. case HookEventIssueComment, HookEventPullRequestComment:
  538. return "issue_comment"
  539. case HookEventPullRequestReviewApproved:
  540. return "pull_request_approved"
  541. case HookEventPullRequestReviewRejected:
  542. return "pull_request_rejected"
  543. case HookEventPullRequestReviewComment:
  544. return "pull_request_comment"
  545. case HookEventRepository:
  546. return "repository"
  547. case HookEventRelease:
  548. return "release"
  549. }
  550. return ""
  551. }
  552. // HookRequest represents hook task request information.
  553. type HookRequest struct {
  554. URL string `json:"url"`
  555. HTTPMethod string `json:"http_method"`
  556. Headers map[string]string `json:"headers"`
  557. }
  558. // HookResponse represents hook task response information.
  559. type HookResponse struct {
  560. Status int `json:"status"`
  561. Headers map[string]string `json:"headers"`
  562. Body string `json:"body"`
  563. }
  564. // HookTask represents a hook task.
  565. type HookTask struct {
  566. ID int64 `xorm:"pk autoincr"`
  567. RepoID int64 `xorm:"INDEX"`
  568. HookID int64
  569. UUID string
  570. api.Payloader `xorm:"-"`
  571. PayloadContent string `xorm:"TEXT"`
  572. EventType HookEventType
  573. IsDelivered bool
  574. Delivered int64
  575. DeliveredString string `xorm:"-"`
  576. // History info.
  577. IsSucceed bool
  578. RequestContent string `xorm:"TEXT"`
  579. RequestInfo *HookRequest `xorm:"-"`
  580. ResponseContent string `xorm:"TEXT"`
  581. ResponseInfo *HookResponse `xorm:"-"`
  582. }
  583. // BeforeUpdate will be invoked by XORM before updating a record
  584. // representing this object
  585. func (t *HookTask) BeforeUpdate() {
  586. if t.RequestInfo != nil {
  587. t.RequestContent = t.simpleMarshalJSON(t.RequestInfo)
  588. }
  589. if t.ResponseInfo != nil {
  590. t.ResponseContent = t.simpleMarshalJSON(t.ResponseInfo)
  591. }
  592. }
  593. // AfterLoad updates the webhook object upon setting a column
  594. func (t *HookTask) AfterLoad() {
  595. t.DeliveredString = time.Unix(0, t.Delivered).Format("2006-01-02 15:04:05 MST")
  596. if len(t.RequestContent) == 0 {
  597. return
  598. }
  599. t.RequestInfo = &HookRequest{}
  600. if err := json.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil {
  601. log.Error("Unmarshal RequestContent[%d]: %v", t.ID, err)
  602. }
  603. if len(t.ResponseContent) > 0 {
  604. t.ResponseInfo = &HookResponse{}
  605. if err := json.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil {
  606. log.Error("Unmarshal ResponseContent[%d]: %v", t.ID, err)
  607. }
  608. }
  609. }
  610. func (t *HookTask) simpleMarshalJSON(v interface{}) string {
  611. p, err := json.Marshal(v)
  612. if err != nil {
  613. log.Error("Marshal [%d]: %v", t.ID, err)
  614. }
  615. return string(p)
  616. }
  617. // HookTasks returns a list of hook tasks by given conditions.
  618. func HookTasks(hookID int64, page int) ([]*HookTask, error) {
  619. tasks := make([]*HookTask, 0, setting.Webhook.PagingNum)
  620. return tasks, db.GetEngine(db.DefaultContext).
  621. Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum).
  622. Where("hook_id=?", hookID).
  623. Desc("id").
  624. Find(&tasks)
  625. }
  626. // CreateHookTask creates a new hook task,
  627. // it handles conversion from Payload to PayloadContent.
  628. func CreateHookTask(t *HookTask) error {
  629. return createHookTask(db.GetEngine(db.DefaultContext), t)
  630. }
  631. func createHookTask(e db.Engine, t *HookTask) error {
  632. data, err := t.Payloader.JSONPayload()
  633. if err != nil {
  634. return err
  635. }
  636. t.UUID = gouuid.New().String()
  637. t.PayloadContent = string(data)
  638. _, err = e.Insert(t)
  639. return err
  640. }
  641. // UpdateHookTask updates information of hook task.
  642. func UpdateHookTask(t *HookTask) error {
  643. _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t)
  644. return err
  645. }
  646. // FindUndeliveredHookTasks represents find the undelivered hook tasks
  647. func FindUndeliveredHookTasks() ([]*HookTask, error) {
  648. tasks := make([]*HookTask, 0, 10)
  649. if err := db.GetEngine(db.DefaultContext).Where("is_delivered=?", false).Find(&tasks); err != nil {
  650. return nil, err
  651. }
  652. return tasks, nil
  653. }
  654. // FindRepoUndeliveredHookTasks represents find the undelivered hook tasks of one repository
  655. func FindRepoUndeliveredHookTasks(repoID int64) ([]*HookTask, error) {
  656. tasks := make([]*HookTask, 0, 5)
  657. if err := db.GetEngine(db.DefaultContext).Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil {
  658. return nil, err
  659. }
  660. return tasks, nil
  661. }
  662. // CleanupHookTaskTable deletes rows from hook_task as needed.
  663. func CleanupHookTaskTable(ctx context.Context, cleanupType HookTaskCleanupType, olderThan time.Duration, numberToKeep int) error {
  664. log.Trace("Doing: CleanupHookTaskTable")
  665. if cleanupType == OlderThan {
  666. deleteOlderThan := time.Now().Add(-olderThan).UnixNano()
  667. deletes, err := db.GetEngine(db.DefaultContext).
  668. Where("is_delivered = ? and delivered < ?", true, deleteOlderThan).
  669. Delete(new(HookTask))
  670. if err != nil {
  671. return err
  672. }
  673. log.Trace("Deleted %d rows from hook_task", deletes)
  674. } else if cleanupType == PerWebhook {
  675. hookIDs := make([]int64, 0, 10)
  676. err := db.GetEngine(db.DefaultContext).Table("webhook").
  677. Where("id > 0").
  678. Cols("id").
  679. Find(&hookIDs)
  680. if err != nil {
  681. return err
  682. }
  683. for _, hookID := range hookIDs {
  684. select {
  685. case <-ctx.Done():
  686. return ErrCancelledf("Before deleting hook_task records for hook id %d", hookID)
  687. default:
  688. }
  689. if err = deleteDeliveredHookTasksByWebhook(hookID, numberToKeep); err != nil {
  690. return err
  691. }
  692. }
  693. }
  694. log.Trace("Finished: CleanupHookTaskTable")
  695. return nil
  696. }
  697. func deleteDeliveredHookTasksByWebhook(hookID int64, numberDeliveriesToKeep int) error {
  698. log.Trace("Deleting hook_task rows for webhook %d, keeping the most recent %d deliveries", hookID, numberDeliveriesToKeep)
  699. deliveryDates := make([]int64, 0, 10)
  700. err := db.GetEngine(db.DefaultContext).Table("hook_task").
  701. Where("hook_task.hook_id = ? AND hook_task.is_delivered = ? AND hook_task.delivered is not null", hookID, true).
  702. Cols("hook_task.delivered").
  703. Join("INNER", "webhook", "hook_task.hook_id = webhook.id").
  704. OrderBy("hook_task.delivered desc").
  705. Limit(1, int(numberDeliveriesToKeep)).
  706. Find(&deliveryDates)
  707. if err != nil {
  708. return err
  709. }
  710. if len(deliveryDates) > 0 {
  711. deletes, err := db.GetEngine(db.DefaultContext).
  712. Where("hook_id = ? and is_delivered = ? and delivered <= ?", hookID, true, deliveryDates[0]).
  713. Delete(new(HookTask))
  714. if err != nil {
  715. return err
  716. }
  717. log.Trace("Deleted %d hook_task rows for webhook %d", deletes, hookID)
  718. } else {
  719. log.Trace("No hook_task rows to delete for webhook %d", hookID)
  720. }
  721. return nil
  722. }