diff options
author | Unknwon <u@gogs.io> | 2015-08-27 23:06:14 +0800 |
---|---|---|
committer | Unknwon <u@gogs.io> | 2015-08-27 23:06:14 +0800 |
commit | 23f42d92c917564435a00e8e75633b8056bd7b0d (patch) | |
tree | 6fed7f6df4c6b039e4e43eaae2fcd5788ddba3a0 /models/webhook.go | |
parent | fc2d0e5470fa2fea260adba30866acda1aa945cb (diff) | |
download | gitea-23f42d92c917564435a00e8e75633b8056bd7b0d.tar.gz gitea-23f42d92c917564435a00e8e75633b8056bd7b0d.zip |
add webhook recent deliveries
Diffstat (limited to 'models/webhook.go')
-rw-r--r-- | models/webhook.go | 220 |
1 files changed, 161 insertions, 59 deletions
diff --git a/models/webhook.go b/models/webhook.go index 6211b215f2..debe46e5ec 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -7,21 +7,20 @@ package models import ( "crypto/tls" "encoding/json" - "errors" + "fmt" "io/ioutil" + "strings" "sync" "time" + "github.com/go-xorm/xorm" + "github.com/gogits/gogs/modules/httplib" "github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/uuid" ) -var ( - ErrWebhookNotExist = errors.New("Webhook does not exist") -) - type HookContentType int const ( @@ -103,6 +102,11 @@ func (w *Webhook) GetSlackHook() *Slack { return s } +// History returns history of webhook by given conditions. +func (w *Webhook) History(page int) ([]*HookTask, error) { + return HookTasks(w.ID, page) +} + // UpdateEvent handles conversion from HookEvent to Events. func (w *Webhook) UpdateEvent() error { data, err := json.Marshal(w.HookEvent) @@ -124,14 +128,14 @@ func CreateWebhook(w *Webhook) error { return err } -// GetWebhookById returns webhook by given ID. -func GetWebhookById(hookId int64) (*Webhook, error) { - w := &Webhook{ID: hookId} - has, err := x.Get(w) +// GetWebhookByID returns webhook by given ID. +func GetWebhookByID(id int64) (*Webhook, error) { + w := new(Webhook) + has, err := x.Id(id).Get(w) if err != nil { return nil, err } else if !has { - return nil, ErrWebhookNotExist + return nil, ErrWebhookNotExist{id} } return w, nil } @@ -271,29 +275,99 @@ type Payload struct { } func (p Payload) GetJSONPayload() ([]byte, error) { - data, err := json.Marshal(p) + data, err := json.MarshalIndent(p, "", " ") if err != nil { return []byte{}, err } return data, nil } +// HookRequest represents hook task request information. +type HookRequest struct { + Headers map[string]string `json:"headers"` +} + +// HookResponse represents hook task response information. +type HookResponse struct { + Status int `json:"status"` + Headers map[string]string `json:"headers"` + Body string `json:"body"` +} + // HookTask represents a hook task. type HookTask struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"INDEX"` - HookID int64 - Uuid string - Type HookTaskType - Url string - BasePayload `xorm:"-"` - PayloadContent string `xorm:"TEXT"` - ContentType HookContentType - EventType HookEventType - IsSsl bool - IsDelivered bool - Delivered int64 - IsSucceed bool + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX"` + HookID int64 + UUID string + Type HookTaskType + URL string + BasePayload `xorm:"-"` + PayloadContent string `xorm:"TEXT"` + ContentType HookContentType + EventType HookEventType + IsSSL bool + IsDelivered bool + Delivered int64 + DeliveredString string `xorm:"-"` + + // History info. + IsSucceed bool + RequestContent string `xorm:"TEXT"` + RequestInfo *HookRequest `xorm:"-"` + ResponseContent string `xorm:"TEXT"` + ResponseInfo *HookResponse `xorm:"-"` +} + +func (t *HookTask) BeforeUpdate() { + if t.RequestInfo != nil { + t.RequestContent = t.MarshalJSON(t.RequestInfo) + } + if t.ResponseInfo != nil { + t.ResponseContent = t.MarshalJSON(t.ResponseInfo) + } +} + +func (t *HookTask) AfterSet(colName string, _ xorm.Cell) { + var err error + switch colName { + case "delivered": + t.DeliveredString = time.Unix(0, t.Delivered).Format("2006-01-02 15:04:05 MST") + + case "request_content": + if len(t.RequestContent) == 0 { + return + } + + t.RequestInfo = &HookRequest{} + if err = json.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil { + log.Error(3, "Unmarshal[%d]: %v", t.ID, err) + } + + case "response_content": + if len(t.ResponseContent) == 0 { + return + } + + t.ResponseInfo = &HookResponse{} + if err = json.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil { + log.Error(3, "Unmarshal[%d]: %v", t.ID, err) + } + } +} + +func (t *HookTask) MarshalJSON(v interface{}) string { + p, err := json.Marshal(v) + if err != nil { + log.Error(3, "Marshal[%d]: %v", t.ID, err) + } + return string(p) +} + +// HookTasks returns a list of hook tasks by given conditions. +func HookTasks(hookID int64, page int) ([]*HookTask, error) { + tasks := make([]*HookTask, 0, setting.Webhook.PagingNum) + return tasks, x.Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum).Desc("id").Find(&tasks) } // CreateHookTask creates a new hook task, @@ -303,7 +377,7 @@ func CreateHookTask(t *HookTask) error { if err != nil { return err } - t.Uuid = uuid.NewV4().String() + t.UUID = uuid.NewV4().String() t.PayloadContent = string(data) _, err = x.Insert(t) return err @@ -348,9 +422,11 @@ func (q *hookQueue) AddRepoID(id int64) { var HookQueue *hookQueue func deliverHook(t *HookTask) { + t.IsDelivered = true + timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second - req := httplib.Post(t.Url).SetTimeout(timeout, timeout). - Header("X-Gogs-Delivery", t.Uuid). + 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}) @@ -361,42 +437,68 @@ func deliverHook(t *HookTask) { req.Param("payload", t.PayloadContent) } - t.IsDelivered = true + // Record delivery information. + t.RequestInfo = &HookRequest{ + Headers: map[string]string{}, + } + for k, vals := range req.Headers() { + t.RequestInfo.Headers[k] = strings.Join(vals, ",") + } - // 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 - } + t.ResponseInfo = &HookResponse{ + Headers: map[string]string{}, + } + + defer func() { + t.Delivered = time.Now().UTC().UnixNano() + if t.IsSucceed { + log.Trace("Hook delivered: %s", t.UUID) } - 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 - } - } - } + + // Update webhook last delivery status. + w, err := GetWebhookByID(t.HookID) + if err != nil { + log.Error(5, "GetWebhookByID: %v", err) + return + } + if t.IsSucceed { + w.LastStatus = HOOK_STATUS_SUCCEED + } else { + w.LastStatus = HOOK_STATUS_FAILED } + if err = UpdateWebhook(w); err != nil { + log.Error(5, "UpdateWebhook: %v", err) + return + } + }() + + resp, err := req.Response() + if err != nil { + t.ResponseInfo.Body = fmt.Sprintf("Delivery: %v", err) + return } + defer resp.Body.Close() - t.Delivered = time.Now().UTC().UnixNano() - if t.IsSucceed { - log.Trace("Hook delivered(%s): %s", t.Uuid, t.PayloadContent) + // Status code is 20x can be seen as succeed. + t.IsSucceed = resp.StatusCode/100 == 2 + t.ResponseInfo.Status = resp.StatusCode + for k, vals := range resp.Header { + t.ResponseInfo.Headers[k] = strings.Join(vals, ",") + } + + p, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.ResponseInfo.Body = fmt.Sprintf("read body: %s", err) + return + } + t.ResponseInfo.Body = string(p) + + switch t.Type { + case SLACK: + if t.ResponseInfo.Body != "ok" { + log.Error(5, "slack failed with: %s", t.ResponseInfo.Body) + t.IsSucceed = false + } } } |