|
@@ -9,6 +9,7 @@ import (
|
|
|
"encoding/json"
|
|
|
"errors"
|
|
|
"io/ioutil"
|
|
|
+ "sync"
|
|
|
"time"
|
|
|
|
|
|
"github.com/gogits/gogs/modules/httplib"
|
|
@@ -259,7 +260,9 @@ func (p Payload) GetJSONPayload() ([]byte, error) {
|
|
|
|
|
|
// HookTask represents a hook task.
|
|
|
type HookTask struct {
|
|
|
- Id int64
|
|
|
+ ID int64 `xorm:"pk autoincr"`
|
|
|
+ RepoID int64 `xorm:"INDEX"`
|
|
|
+ HookID int64
|
|
|
Uuid string
|
|
|
Type HookTaskType
|
|
|
Url string
|
|
@@ -269,6 +272,7 @@ type HookTask struct {
|
|
|
EventType HookEventType
|
|
|
IsSsl bool
|
|
|
IsDelivered bool
|
|
|
+ Delivered int64
|
|
|
IsSucceed bool
|
|
|
}
|
|
|
|
|
@@ -287,87 +291,137 @@ func CreateHookTask(t *HookTask) error {
|
|
|
|
|
|
// UpdateHookTask updates information of hook task.
|
|
|
func UpdateHookTask(t *HookTask) error {
|
|
|
- _, err := x.Id(t.Id).AllCols().Update(t)
|
|
|
+ _, err := x.Id(t.ID).AllCols().Update(t)
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
-var (
|
|
|
- // Prevent duplicate deliveries.
|
|
|
- // This happens with massive hook tasks cannot finish delivering
|
|
|
- // before next shooting starts.
|
|
|
- isShooting = false
|
|
|
-)
|
|
|
+type hookQueue struct {
|
|
|
+ // Make sure one repository only occur once in the queue.
|
|
|
+ lock sync.Mutex
|
|
|
+ repoIDs map[int64]bool
|
|
|
|
|
|
-// DeliverHooks checks and delivers undelivered hooks.
|
|
|
-// FIXME: maybe can use goroutine to shoot a number of them at same time?
|
|
|
-func DeliverHooks() {
|
|
|
- if isShooting {
|
|
|
+ queue chan int64
|
|
|
+}
|
|
|
+
|
|
|
+func (q *hookQueue) removeRepoID(id int64) {
|
|
|
+ q.lock.Lock()
|
|
|
+ defer q.lock.Unlock()
|
|
|
+ delete(q.repoIDs, id)
|
|
|
+}
|
|
|
+
|
|
|
+func (q *hookQueue) addRepoID(id int64) {
|
|
|
+ q.lock.Lock()
|
|
|
+ if q.repoIDs[id] {
|
|
|
+ q.lock.Unlock()
|
|
|
return
|
|
|
}
|
|
|
- isShooting = true
|
|
|
- defer func() { isShooting = false }()
|
|
|
+ q.repoIDs[id] = true
|
|
|
+ q.lock.Unlock()
|
|
|
+ q.queue <- id
|
|
|
+}
|
|
|
|
|
|
- tasks := make([]*HookTask, 0, 10)
|
|
|
+// AddRepoID adds repository ID to hook delivery queue.
|
|
|
+func (q *hookQueue) AddRepoID(id int64) {
|
|
|
+ go q.addRepoID(id)
|
|
|
+}
|
|
|
+
|
|
|
+var HookQueue *hookQueue
|
|
|
+
|
|
|
+func deliverHook(t *HookTask) {
|
|
|
timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
|
|
|
- x.Where("is_delivered=?", false).Iterate(new(HookTask),
|
|
|
- func(idx int, bean interface{}) error {
|
|
|
- t := bean.(*HookTask)
|
|
|
- req := httplib.Post(t.Url).SetTimeout(timeout, timeout).
|
|
|
- Header("X-Gogs-Delivery", t.Uuid).
|
|
|
- Header("X-Gogs-Event", string(t.EventType)).
|
|
|
- SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify})
|
|
|
-
|
|
|
- switch t.ContentType {
|
|
|
- case JSON:
|
|
|
- req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)
|
|
|
- case FORM:
|
|
|
- req.Param("payload", t.PayloadContent)
|
|
|
- }
|
|
|
+ req := httplib.Post(t.Url).SetTimeout(timeout, timeout).
|
|
|
+ Header("X-Gogs-Delivery", t.Uuid).
|
|
|
+ Header("X-Gogs-Event", string(t.EventType)).
|
|
|
+ SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify})
|
|
|
|
|
|
- t.IsDelivered = true
|
|
|
+ switch t.ContentType {
|
|
|
+ case JSON:
|
|
|
+ req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)
|
|
|
+ case FORM:
|
|
|
+ req.Param("payload", t.PayloadContent)
|
|
|
+ }
|
|
|
|
|
|
- // FIXME: record response.
|
|
|
- switch t.Type {
|
|
|
- case GOGS:
|
|
|
- {
|
|
|
- if _, err := req.Response(); err != nil {
|
|
|
- log.Error(5, "Delivery: %v", err)
|
|
|
+ t.IsDelivered = true
|
|
|
+
|
|
|
+ // FIXME: record response.
|
|
|
+ switch t.Type {
|
|
|
+ case GOGS:
|
|
|
+ {
|
|
|
+ if resp, err := req.Response(); err != nil {
|
|
|
+ log.Error(5, "Delivery: %v", err)
|
|
|
+ } else {
|
|
|
+ resp.Body.Close()
|
|
|
+ t.IsSucceed = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ case SLACK:
|
|
|
+ {
|
|
|
+ if resp, err := req.Response(); err != nil {
|
|
|
+ log.Error(5, "Delivery: %v", err)
|
|
|
+ } else {
|
|
|
+ defer resp.Body.Close()
|
|
|
+ contents, err := ioutil.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ log.Error(5, "%s", err)
|
|
|
+ } else {
|
|
|
+ if string(contents) != "ok" {
|
|
|
+ log.Error(5, "slack failed with: %s", string(contents))
|
|
|
} else {
|
|
|
t.IsSucceed = true
|
|
|
}
|
|
|
}
|
|
|
- case SLACK:
|
|
|
- {
|
|
|
- if res, err := req.Response(); err != nil {
|
|
|
- log.Error(5, "Delivery: %v", err)
|
|
|
- } else {
|
|
|
- defer res.Body.Close()
|
|
|
- contents, err := ioutil.ReadAll(res.Body)
|
|
|
- if err != nil {
|
|
|
- log.Error(5, "%s", err)
|
|
|
- } else {
|
|
|
- if string(contents) != "ok" {
|
|
|
- log.Error(5, "slack failed with: %s", string(contents))
|
|
|
- } else {
|
|
|
- t.IsSucceed = true
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
}
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- tasks = append(tasks, t)
|
|
|
+ t.Delivered = time.Now().UTC().UnixNano()
|
|
|
+ if t.IsSucceed {
|
|
|
+ log.Trace("Hook delivered(%s): %s", t.Uuid, t.PayloadContent)
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- if t.IsSucceed {
|
|
|
- log.Trace("Hook delivered(%s): %s", t.Uuid, t.PayloadContent)
|
|
|
- }
|
|
|
+// DeliverHooks checks and delivers undelivered hooks.
|
|
|
+func DeliverHooks() {
|
|
|
+ tasks := make([]*HookTask, 0, 10)
|
|
|
+ x.Where("is_delivered=?", false).Iterate(new(HookTask),
|
|
|
+ func(idx int, bean interface{}) error {
|
|
|
+ t := bean.(*HookTask)
|
|
|
+ deliverHook(t)
|
|
|
+ tasks = append(tasks, t)
|
|
|
return nil
|
|
|
})
|
|
|
|
|
|
// Update hook task status.
|
|
|
for _, t := range tasks {
|
|
|
if err := UpdateHookTask(t); err != nil {
|
|
|
- log.Error(4, "UpdateHookTask(%d): %v", t.Id, err)
|
|
|
+ log.Error(4, "UpdateHookTask(%d): %v", t.ID, err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ HookQueue = &hookQueue{
|
|
|
+ lock: sync.Mutex{},
|
|
|
+ repoIDs: make(map[int64]bool),
|
|
|
+ queue: make(chan int64, setting.Webhook.QueueLength),
|
|
|
+ }
|
|
|
+
|
|
|
+ // Start listening on new hook requests.
|
|
|
+ for repoID := range HookQueue.queue {
|
|
|
+ HookQueue.removeRepoID(repoID)
|
|
|
+
|
|
|
+ tasks = make([]*HookTask, 0, 5)
|
|
|
+ if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil {
|
|
|
+ log.Error(4, "Get repository(%d) hook tasks: %v", repoID, err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ for _, t := range tasks {
|
|
|
+ deliverHook(t)
|
|
|
+ if err := UpdateHookTask(t); err != nil {
|
|
|
+ log.Error(4, "UpdateHookTask(%d): %v", t.ID, err)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+func InitDeliverHooks() {
|
|
|
+ go DeliverHooks()
|
|
|
+}
|