You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

webhook.go 18 kB

11 years ago
11 years ago
9 years ago
9 years ago
9 years ago
11 years ago
11 years ago
10 years ago
11 years ago
10 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
10 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  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. "crypto/tls"
  8. "encoding/json"
  9. "fmt"
  10. "io/ioutil"
  11. "strings"
  12. "time"
  13. "code.gitea.io/gitea/modules/httplib"
  14. "code.gitea.io/gitea/modules/log"
  15. "code.gitea.io/gitea/modules/setting"
  16. "code.gitea.io/gitea/modules/sync"
  17. api "code.gitea.io/sdk/gitea"
  18. "github.com/go-xorm/xorm"
  19. gouuid "github.com/satori/go.uuid"
  20. )
  21. // HookQueue is a global queue of web hooks
  22. var HookQueue = sync.NewUniqueQueue(setting.Webhook.QueueLength)
  23. // HookContentType is the content type of a web hook
  24. type HookContentType int
  25. const (
  26. // ContentTypeJSON is a JSON payload for web hooks
  27. ContentTypeJSON HookContentType = iota + 1
  28. // ContentTypeForm is an url-encoded form payload for web hook
  29. ContentTypeForm
  30. )
  31. var hookContentTypes = map[string]HookContentType{
  32. "json": ContentTypeJSON,
  33. "form": ContentTypeForm,
  34. }
  35. // ToHookContentType returns HookContentType by given name.
  36. func ToHookContentType(name string) HookContentType {
  37. return hookContentTypes[name]
  38. }
  39. // Name returns the name of a given web hook's content type
  40. func (t HookContentType) Name() string {
  41. switch t {
  42. case ContentTypeJSON:
  43. return "json"
  44. case ContentTypeForm:
  45. return "form"
  46. }
  47. return ""
  48. }
  49. // IsValidHookContentType returns true if given name is a valid hook content type.
  50. func IsValidHookContentType(name string) bool {
  51. _, ok := hookContentTypes[name]
  52. return ok
  53. }
  54. // HookEvents is a set of web hook events
  55. type HookEvents struct {
  56. Create bool `json:"create"`
  57. Push bool `json:"push"`
  58. PullRequest bool `json:"pull_request"`
  59. }
  60. // HookEvent represents events that will delivery hook.
  61. type HookEvent struct {
  62. PushOnly bool `json:"push_only"`
  63. SendEverything bool `json:"send_everything"`
  64. ChooseEvents bool `json:"choose_events"`
  65. HookEvents `json:"events"`
  66. }
  67. // HookStatus is the status of a web hook
  68. type HookStatus int
  69. // Possible statuses of a web hook
  70. const (
  71. HookStatusNone = iota
  72. HookStatusSucceed
  73. HookStatusFail
  74. )
  75. // Webhook represents a web hook object.
  76. type Webhook struct {
  77. ID int64 `xorm:"pk autoincr"`
  78. RepoID int64 `xorm:"INDEX"`
  79. OrgID int64 `xorm:"INDEX"`
  80. URL string `xorm:"url TEXT"`
  81. ContentType HookContentType
  82. Secret string `xorm:"TEXT"`
  83. Events string `xorm:"TEXT"`
  84. *HookEvent `xorm:"-"`
  85. IsSSL bool `xorm:"is_ssl"`
  86. IsActive bool `xorm:"INDEX"`
  87. HookTaskType HookTaskType
  88. Meta string `xorm:"TEXT"` // store hook-specific attributes
  89. LastStatus HookStatus // Last delivery status
  90. Created time.Time `xorm:"-"`
  91. CreatedUnix int64 `xorm:"INDEX"`
  92. Updated time.Time `xorm:"-"`
  93. UpdatedUnix int64 `xorm:"INDEX"`
  94. }
  95. // BeforeInsert will be invoked by XORM before inserting a record
  96. // representing this object
  97. func (w *Webhook) BeforeInsert() {
  98. w.CreatedUnix = time.Now().Unix()
  99. w.UpdatedUnix = w.CreatedUnix
  100. }
  101. // BeforeUpdate will be invoked by XORM before updating a record
  102. // representing this object
  103. func (w *Webhook) BeforeUpdate() {
  104. w.UpdatedUnix = time.Now().Unix()
  105. }
  106. // AfterSet updates the webhook object upon setting a column
  107. func (w *Webhook) AfterSet(colName string, _ xorm.Cell) {
  108. var err error
  109. switch colName {
  110. case "events":
  111. w.HookEvent = &HookEvent{}
  112. if err = json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
  113. log.Error(3, "Unmarshal[%d]: %v", w.ID, err)
  114. }
  115. case "created_unix":
  116. w.Created = time.Unix(w.CreatedUnix, 0).Local()
  117. case "updated_unix":
  118. w.Updated = time.Unix(w.UpdatedUnix, 0).Local()
  119. }
  120. }
  121. // GetSlackHook returns slack metadata
  122. func (w *Webhook) GetSlackHook() *SlackMeta {
  123. s := &SlackMeta{}
  124. if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
  125. log.Error(4, "webhook.GetSlackHook(%d): %v", w.ID, err)
  126. }
  127. return s
  128. }
  129. // GetDiscordHook returns discord metadata
  130. func (w *Webhook) GetDiscordHook() *DiscordMeta {
  131. s := &DiscordMeta{}
  132. if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
  133. log.Error(4, "webhook.GetDiscordHook(%d): %v", w.ID, err)
  134. }
  135. return s
  136. }
  137. // History returns history of webhook by given conditions.
  138. func (w *Webhook) History(page int) ([]*HookTask, error) {
  139. return HookTasks(w.ID, page)
  140. }
  141. // UpdateEvent handles conversion from HookEvent to Events.
  142. func (w *Webhook) UpdateEvent() error {
  143. data, err := json.Marshal(w.HookEvent)
  144. w.Events = string(data)
  145. return err
  146. }
  147. // HasCreateEvent returns true if hook enabled create event.
  148. func (w *Webhook) HasCreateEvent() bool {
  149. return w.SendEverything ||
  150. (w.ChooseEvents && w.HookEvents.Create)
  151. }
  152. // HasPushEvent returns true if hook enabled push event.
  153. func (w *Webhook) HasPushEvent() bool {
  154. return w.PushOnly || w.SendEverything ||
  155. (w.ChooseEvents && w.HookEvents.Push)
  156. }
  157. // HasPullRequestEvent returns true if hook enabled pull request event.
  158. func (w *Webhook) HasPullRequestEvent() bool {
  159. return w.SendEverything ||
  160. (w.ChooseEvents && w.HookEvents.PullRequest)
  161. }
  162. // EventsArray returns an array of hook events
  163. func (w *Webhook) EventsArray() []string {
  164. events := make([]string, 0, 3)
  165. if w.HasCreateEvent() {
  166. events = append(events, "create")
  167. }
  168. if w.HasPushEvent() {
  169. events = append(events, "push")
  170. }
  171. if w.HasPullRequestEvent() {
  172. events = append(events, "pull_request")
  173. }
  174. return events
  175. }
  176. // CreateWebhook creates a new web hook.
  177. func CreateWebhook(w *Webhook) error {
  178. _, err := x.Insert(w)
  179. return err
  180. }
  181. // getWebhook uses argument bean as query condition,
  182. // ID must be specified and do not assign unnecessary fields.
  183. func getWebhook(bean *Webhook) (*Webhook, error) {
  184. has, err := x.Get(bean)
  185. if err != nil {
  186. return nil, err
  187. } else if !has {
  188. return nil, ErrWebhookNotExist{bean.ID}
  189. }
  190. return bean, nil
  191. }
  192. // GetWebhookByRepoID returns webhook of repository by given ID.
  193. func GetWebhookByRepoID(repoID, id int64) (*Webhook, error) {
  194. return getWebhook(&Webhook{
  195. ID: id,
  196. RepoID: repoID,
  197. })
  198. }
  199. // GetWebhookByOrgID returns webhook of organization by given ID.
  200. func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) {
  201. return getWebhook(&Webhook{
  202. ID: id,
  203. OrgID: orgID,
  204. })
  205. }
  206. // GetActiveWebhooksByRepoID returns all active webhooks of repository.
  207. func GetActiveWebhooksByRepoID(repoID int64) ([]*Webhook, error) {
  208. webhooks := make([]*Webhook, 0, 5)
  209. return webhooks, x.Where("is_active=?", true).
  210. Find(&webhooks, &Webhook{RepoID: repoID})
  211. }
  212. // GetWebhooksByRepoID returns all webhooks of a repository.
  213. func GetWebhooksByRepoID(repoID int64) ([]*Webhook, error) {
  214. webhooks := make([]*Webhook, 0, 5)
  215. return webhooks, x.Find(&webhooks, &Webhook{RepoID: repoID})
  216. }
  217. // GetActiveWebhooksByOrgID returns all active webhooks for an organization.
  218. func GetActiveWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
  219. err = x.
  220. Where("org_id=?", orgID).
  221. And("is_active=?", true).
  222. Find(&ws)
  223. return ws, err
  224. }
  225. // GetWebhooksByOrgID returns all webhooks for an organization.
  226. func GetWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
  227. err = x.Find(&ws, &Webhook{OrgID: orgID})
  228. return ws, err
  229. }
  230. // UpdateWebhook updates information of webhook.
  231. func UpdateWebhook(w *Webhook) error {
  232. _, err := x.Id(w.ID).AllCols().Update(w)
  233. return err
  234. }
  235. // deleteWebhook uses argument bean as query condition,
  236. // ID must be specified and do not assign unnecessary fields.
  237. func deleteWebhook(bean *Webhook) (err error) {
  238. sess := x.NewSession()
  239. defer sess.Close()
  240. if err = sess.Begin(); err != nil {
  241. return err
  242. }
  243. if count, err := sess.Delete(bean); err != nil {
  244. return err
  245. } else if count == 0 {
  246. return ErrWebhookNotExist{ID: bean.ID}
  247. } else if _, err = sess.Delete(&HookTask{HookID: bean.ID}); err != nil {
  248. return err
  249. }
  250. return sess.Commit()
  251. }
  252. // DeleteWebhookByRepoID deletes webhook of repository by given ID.
  253. func DeleteWebhookByRepoID(repoID, id int64) error {
  254. return deleteWebhook(&Webhook{
  255. ID: id,
  256. RepoID: repoID,
  257. })
  258. }
  259. // DeleteWebhookByOrgID deletes webhook of organization by given ID.
  260. func DeleteWebhookByOrgID(orgID, id int64) error {
  261. return deleteWebhook(&Webhook{
  262. ID: id,
  263. OrgID: orgID,
  264. })
  265. }
  266. // ___ ___ __ ___________ __
  267. // / | \ ____ ____ | | _\__ ___/____ _____| | __
  268. // / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ /
  269. // \ Y ( <_> | <_> ) < | | / __ \_\___ \| <
  270. // \___|_ / \____/ \____/|__|_ \ |____| (____ /____ >__|_ \
  271. // \/ \/ \/ \/ \/
  272. // HookTaskType is the type of an hook task
  273. type HookTaskType int
  274. // Types of hook tasks
  275. const (
  276. GOGS HookTaskType = iota + 1
  277. SLACK
  278. GITEA
  279. DISCORD
  280. )
  281. var hookTaskTypes = map[string]HookTaskType{
  282. "gitea": GITEA,
  283. "gogs": GOGS,
  284. "slack": SLACK,
  285. "discord": DISCORD,
  286. }
  287. // ToHookTaskType returns HookTaskType by given name.
  288. func ToHookTaskType(name string) HookTaskType {
  289. return hookTaskTypes[name]
  290. }
  291. // Name returns the name of an hook task type
  292. func (t HookTaskType) Name() string {
  293. switch t {
  294. case GITEA:
  295. return "gitea"
  296. case GOGS:
  297. return "gogs"
  298. case SLACK:
  299. return "slack"
  300. case DISCORD:
  301. return "discord"
  302. }
  303. return ""
  304. }
  305. // IsValidHookTaskType returns true if given name is a valid hook task type.
  306. func IsValidHookTaskType(name string) bool {
  307. _, ok := hookTaskTypes[name]
  308. return ok
  309. }
  310. // HookEventType is the type of an hook event
  311. type HookEventType string
  312. // Types of hook events
  313. const (
  314. HookEventCreate HookEventType = "create"
  315. HookEventPush HookEventType = "push"
  316. HookEventPullRequest HookEventType = "pull_request"
  317. )
  318. // HookRequest represents hook task request information.
  319. type HookRequest struct {
  320. Headers map[string]string `json:"headers"`
  321. }
  322. // HookResponse represents hook task response information.
  323. type HookResponse struct {
  324. Status int `json:"status"`
  325. Headers map[string]string `json:"headers"`
  326. Body string `json:"body"`
  327. }
  328. // HookTask represents a hook task.
  329. type HookTask struct {
  330. ID int64 `xorm:"pk autoincr"`
  331. RepoID int64 `xorm:"INDEX"`
  332. HookID int64
  333. UUID string
  334. Type HookTaskType
  335. URL string `xorm:"TEXT"`
  336. api.Payloader `xorm:"-"`
  337. PayloadContent string `xorm:"TEXT"`
  338. ContentType HookContentType
  339. EventType HookEventType
  340. IsSSL bool
  341. IsDelivered bool
  342. Delivered int64
  343. DeliveredString string `xorm:"-"`
  344. // History info.
  345. IsSucceed bool
  346. RequestContent string `xorm:"TEXT"`
  347. RequestInfo *HookRequest `xorm:"-"`
  348. ResponseContent string `xorm:"TEXT"`
  349. ResponseInfo *HookResponse `xorm:"-"`
  350. }
  351. // BeforeUpdate will be invoked by XORM before updating a record
  352. // representing this object
  353. func (t *HookTask) BeforeUpdate() {
  354. if t.RequestInfo != nil {
  355. t.RequestContent = t.simpleMarshalJSON(t.RequestInfo)
  356. }
  357. if t.ResponseInfo != nil {
  358. t.ResponseContent = t.simpleMarshalJSON(t.ResponseInfo)
  359. }
  360. }
  361. // AfterSet updates the webhook object upon setting a column
  362. func (t *HookTask) AfterSet(colName string, _ xorm.Cell) {
  363. var err error
  364. switch colName {
  365. case "delivered":
  366. t.DeliveredString = time.Unix(0, t.Delivered).Format("2006-01-02 15:04:05 MST")
  367. case "request_content":
  368. if len(t.RequestContent) == 0 {
  369. return
  370. }
  371. t.RequestInfo = &HookRequest{}
  372. if err = json.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil {
  373. log.Error(3, "Unmarshal[%d]: %v", t.ID, err)
  374. }
  375. case "response_content":
  376. if len(t.ResponseContent) == 0 {
  377. return
  378. }
  379. t.ResponseInfo = &HookResponse{}
  380. if err = json.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil {
  381. log.Error(3, "Unmarshal [%d]: %v", t.ID, err)
  382. }
  383. }
  384. }
  385. func (t *HookTask) simpleMarshalJSON(v interface{}) string {
  386. p, err := json.Marshal(v)
  387. if err != nil {
  388. log.Error(3, "Marshal [%d]: %v", t.ID, err)
  389. }
  390. return string(p)
  391. }
  392. // HookTasks returns a list of hook tasks by given conditions.
  393. func HookTasks(hookID int64, page int) ([]*HookTask, error) {
  394. tasks := make([]*HookTask, 0, setting.Webhook.PagingNum)
  395. return tasks, x.
  396. Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum).
  397. Where("hook_id=?", hookID).
  398. Desc("id").
  399. Find(&tasks)
  400. }
  401. // CreateHookTask creates a new hook task,
  402. // it handles conversion from Payload to PayloadContent.
  403. func CreateHookTask(t *HookTask) error {
  404. data, err := t.Payloader.JSONPayload()
  405. if err != nil {
  406. return err
  407. }
  408. t.UUID = gouuid.NewV4().String()
  409. t.PayloadContent = string(data)
  410. _, err = x.Insert(t)
  411. return err
  412. }
  413. // UpdateHookTask updates information of hook task.
  414. func UpdateHookTask(t *HookTask) error {
  415. _, err := x.Id(t.ID).AllCols().Update(t)
  416. return err
  417. }
  418. // PrepareWebhooks adds new webhooks to task queue for given payload.
  419. func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) error {
  420. ws, err := GetActiveWebhooksByRepoID(repo.ID)
  421. if err != nil {
  422. return fmt.Errorf("GetActiveWebhooksByRepoID: %v", err)
  423. }
  424. // check if repo belongs to org and append additional webhooks
  425. if repo.MustOwner().IsOrganization() {
  426. // get hooks for org
  427. orgHooks, err := GetActiveWebhooksByOrgID(repo.OwnerID)
  428. if err != nil {
  429. return fmt.Errorf("GetActiveWebhooksByOrgID: %v", err)
  430. }
  431. ws = append(ws, orgHooks...)
  432. }
  433. if len(ws) == 0 {
  434. return nil
  435. }
  436. var payloader api.Payloader
  437. for _, w := range ws {
  438. switch event {
  439. case HookEventCreate:
  440. if !w.HasCreateEvent() {
  441. continue
  442. }
  443. case HookEventPush:
  444. if !w.HasPushEvent() {
  445. continue
  446. }
  447. case HookEventPullRequest:
  448. if !w.HasPullRequestEvent() {
  449. continue
  450. }
  451. }
  452. // Use separate objects so modifications won't be made on payload on non-Gogs/Gitea type hooks.
  453. switch w.HookTaskType {
  454. case SLACK:
  455. payloader, err = GetSlackPayload(p, event, w.Meta)
  456. if err != nil {
  457. return fmt.Errorf("GetSlackPayload: %v", err)
  458. }
  459. case DISCORD:
  460. payloader, err = GetDiscordPayload(p, event, w.Meta)
  461. if err != nil {
  462. return fmt.Errorf("GetDiscordPayload: %v", err)
  463. }
  464. default:
  465. p.SetSecret(w.Secret)
  466. payloader = p
  467. }
  468. if err = CreateHookTask(&HookTask{
  469. RepoID: repo.ID,
  470. HookID: w.ID,
  471. Type: w.HookTaskType,
  472. URL: w.URL,
  473. Payloader: payloader,
  474. ContentType: w.ContentType,
  475. EventType: event,
  476. IsSSL: w.IsSSL,
  477. }); err != nil {
  478. return fmt.Errorf("CreateHookTask: %v", err)
  479. }
  480. }
  481. return nil
  482. }
  483. func (t *HookTask) deliver() {
  484. t.IsDelivered = true
  485. timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
  486. req := httplib.Post(t.URL).SetTimeout(timeout, timeout).
  487. Header("X-Gitea-Delivery", t.UUID).
  488. Header("X-Gitea-Event", string(t.EventType)).
  489. Header("X-Gogs-Delivery", t.UUID).
  490. Header("X-Gogs-Event", string(t.EventType)).
  491. Header("X-GitHub-Delivery", t.UUID).
  492. Header("X-GitHub-Event", string(t.EventType)).
  493. SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify})
  494. switch t.ContentType {
  495. case ContentTypeJSON:
  496. req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)
  497. case ContentTypeForm:
  498. req.Param("payload", t.PayloadContent)
  499. }
  500. // Record delivery information.
  501. t.RequestInfo = &HookRequest{
  502. Headers: map[string]string{},
  503. }
  504. for k, vals := range req.Headers() {
  505. t.RequestInfo.Headers[k] = strings.Join(vals, ",")
  506. }
  507. t.ResponseInfo = &HookResponse{
  508. Headers: map[string]string{},
  509. }
  510. defer func() {
  511. t.Delivered = time.Now().UnixNano()
  512. if t.IsSucceed {
  513. log.Trace("Hook delivered: %s", t.UUID)
  514. } else {
  515. log.Trace("Hook delivery failed: %s", t.UUID)
  516. }
  517. // Update webhook last delivery status.
  518. w, err := GetWebhookByRepoID(t.RepoID, t.HookID)
  519. if err != nil {
  520. log.Error(5, "GetWebhookByID: %v", err)
  521. return
  522. }
  523. if t.IsSucceed {
  524. w.LastStatus = HookStatusSucceed
  525. } else {
  526. w.LastStatus = HookStatusFail
  527. }
  528. if err = UpdateWebhook(w); err != nil {
  529. log.Error(5, "UpdateWebhook: %v", err)
  530. return
  531. }
  532. }()
  533. resp, err := req.Response()
  534. if err != nil {
  535. t.ResponseInfo.Body = fmt.Sprintf("Delivery: %v", err)
  536. return
  537. }
  538. defer resp.Body.Close()
  539. // Status code is 20x can be seen as succeed.
  540. t.IsSucceed = resp.StatusCode/100 == 2
  541. t.ResponseInfo.Status = resp.StatusCode
  542. for k, vals := range resp.Header {
  543. t.ResponseInfo.Headers[k] = strings.Join(vals, ",")
  544. }
  545. p, err := ioutil.ReadAll(resp.Body)
  546. if err != nil {
  547. t.ResponseInfo.Body = fmt.Sprintf("read body: %s", err)
  548. return
  549. }
  550. t.ResponseInfo.Body = string(p)
  551. }
  552. // DeliverHooks checks and delivers undelivered hooks.
  553. // TODO: shoot more hooks at same time.
  554. func DeliverHooks() {
  555. tasks := make([]*HookTask, 0, 10)
  556. err := x.Where("is_delivered=?", false).Find(&tasks)
  557. if err != nil {
  558. log.Error(4, "DeliverHooks: %v", err)
  559. return
  560. }
  561. // Update hook task status.
  562. for _, t := range tasks {
  563. t.deliver()
  564. if err := UpdateHookTask(t); err != nil {
  565. log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
  566. }
  567. }
  568. // Start listening on new hook requests.
  569. for repoID := range HookQueue.Queue() {
  570. log.Trace("DeliverHooks [repo_id: %v]", repoID)
  571. HookQueue.Remove(repoID)
  572. tasks = make([]*HookTask, 0, 5)
  573. if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil {
  574. log.Error(4, "Get repository [%s] hook tasks: %v", repoID, err)
  575. continue
  576. }
  577. for _, t := range tasks {
  578. t.deliver()
  579. if err := UpdateHookTask(t); err != nil {
  580. log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
  581. continue
  582. }
  583. }
  584. }
  585. }
  586. // InitDeliverHooks starts the hooks delivery thread
  587. func InitDeliverHooks() {
  588. go DeliverHooks()
  589. }