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.

709 lines
18 KiB

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