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 8.3 kB

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
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
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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "encoding/json"
  7. "errors"
  8. "io/ioutil"
  9. "time"
  10. "github.com/gogits/gogs/modules/httplib"
  11. "github.com/gogits/gogs/modules/log"
  12. "github.com/gogits/gogs/modules/setting"
  13. "github.com/gogits/gogs/modules/uuid"
  14. )
  15. var (
  16. ErrWebhookNotExist = errors.New("Webhook does not exist")
  17. )
  18. type HookContentType int
  19. const (
  20. JSON HookContentType = iota + 1
  21. FORM
  22. )
  23. func (t HookContentType) Name() string {
  24. switch t {
  25. case JSON:
  26. return "json"
  27. case FORM:
  28. return "form"
  29. }
  30. return ""
  31. }
  32. // HookEvent represents events that will delivery hook.
  33. type HookEvent struct {
  34. PushOnly bool `json:"push_only"`
  35. }
  36. // Webhook represents a web hook object.
  37. type Webhook struct {
  38. Id int64
  39. RepoId int64
  40. Url string `xorm:"TEXT"`
  41. ContentType HookContentType
  42. Secret string `xorm:"TEXT"`
  43. Events string `xorm:"TEXT"`
  44. *HookEvent `xorm:"-"`
  45. IsSsl bool
  46. IsActive bool
  47. HookTaskType HookTaskType
  48. Meta string `xorm:"TEXT"` // store hook-specific attributes
  49. OrgId int64
  50. }
  51. // GetEvent handles conversion from Events to HookEvent.
  52. func (w *Webhook) GetEvent() {
  53. w.HookEvent = &HookEvent{}
  54. if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
  55. log.Error(4, "webhook.GetEvent(%d): %v", w.Id, err)
  56. }
  57. }
  58. func (w *Webhook) GetSlackHook() *Slack {
  59. s := &Slack{}
  60. if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
  61. log.Error(4, "webhook.GetSlackHook(%d): %v", w.Id, err)
  62. }
  63. return s
  64. }
  65. // UpdateEvent handles conversion from HookEvent to Events.
  66. func (w *Webhook) UpdateEvent() error {
  67. data, err := json.Marshal(w.HookEvent)
  68. w.Events = string(data)
  69. return err
  70. }
  71. // HasPushEvent returns true if hook enbaled push event.
  72. func (w *Webhook) HasPushEvent() bool {
  73. if w.PushOnly {
  74. return true
  75. }
  76. return false
  77. }
  78. // CreateWebhook creates a new web hook.
  79. func CreateWebhook(w *Webhook) error {
  80. _, err := x.Insert(w)
  81. return err
  82. }
  83. // GetWebhookById returns webhook by given ID.
  84. func GetWebhookById(hookId int64) (*Webhook, error) {
  85. w := &Webhook{Id: hookId}
  86. has, err := x.Get(w)
  87. if err != nil {
  88. return nil, err
  89. } else if !has {
  90. return nil, ErrWebhookNotExist
  91. }
  92. return w, nil
  93. }
  94. // GetActiveWebhooksByRepoId returns all active webhooks of repository.
  95. func GetActiveWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) {
  96. err = x.Where("repo_id=?", repoId).And("is_active=?", true).Find(&ws)
  97. return ws, err
  98. }
  99. // GetWebhooksByRepoId returns all webhooks of repository.
  100. func GetWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) {
  101. err = x.Find(&ws, &Webhook{RepoId: repoId})
  102. return ws, err
  103. }
  104. // UpdateWebhook updates information of webhook.
  105. func UpdateWebhook(w *Webhook) error {
  106. _, err := x.Id(w.Id).AllCols().Update(w)
  107. return err
  108. }
  109. // DeleteWebhook deletes webhook of repository.
  110. func DeleteWebhook(hookId int64) error {
  111. _, err := x.Delete(&Webhook{Id: hookId})
  112. return err
  113. }
  114. // GetWebhooksByOrgId returns all webhooks for an organization.
  115. func GetWebhooksByOrgId(orgId int64) (ws []*Webhook, err error) {
  116. err = x.Find(&ws, &Webhook{OrgId: orgId})
  117. return ws, err
  118. }
  119. // GetActiveWebhooksByOrgId returns all active webhooks for an organization.
  120. func GetActiveWebhooksByOrgId(orgId int64) (ws []*Webhook, err error) {
  121. err = x.Where("org_id=?", orgId).And("is_active=?", true).Find(&ws)
  122. return ws, err
  123. }
  124. // ___ ___ __ ___________ __
  125. // / | \ ____ ____ | | _\__ ___/____ _____| | __
  126. // / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ /
  127. // \ Y ( <_> | <_> ) < | | / __ \_\___ \| <
  128. // \___|_ / \____/ \____/|__|_ \ |____| (____ /____ >__|_ \
  129. // \/ \/ \/ \/ \/
  130. type HookTaskType int
  131. const (
  132. GOGS HookTaskType = iota + 1
  133. SLACK
  134. )
  135. func (t HookTaskType) Name() string {
  136. switch t {
  137. case GOGS:
  138. return "gogs"
  139. case SLACK:
  140. return "slack"
  141. }
  142. return ""
  143. }
  144. type HookEventType string
  145. const (
  146. PUSH HookEventType = "push"
  147. )
  148. type PayloadAuthor struct {
  149. Name string `json:"name"`
  150. Email string `json:"email"`
  151. UserName string `json:"username"`
  152. }
  153. type PayloadCommit struct {
  154. Id string `json:"id"`
  155. Message string `json:"message"`
  156. Url string `json:"url"`
  157. Author *PayloadAuthor `json:"author"`
  158. }
  159. type PayloadRepo struct {
  160. Id int64 `json:"id"`
  161. Name string `json:"name"`
  162. Url string `json:"url"`
  163. Description string `json:"description"`
  164. Website string `json:"website"`
  165. Watchers int `json:"watchers"`
  166. Owner *PayloadAuthor `json:"owner"`
  167. Private bool `json:"private"`
  168. }
  169. type BasePayload interface {
  170. GetJSONPayload() ([]byte, error)
  171. }
  172. // Payload represents a payload information of hook.
  173. type Payload struct {
  174. Secret string `json:"secret"`
  175. Ref string `json:"ref"`
  176. Commits []*PayloadCommit `json:"commits"`
  177. Repo *PayloadRepo `json:"repository"`
  178. Pusher *PayloadAuthor `json:"pusher"`
  179. Before string `json:"before"`
  180. After string `json:"after"`
  181. CompareUrl string `json:"compare_url"`
  182. }
  183. func (p Payload) GetJSONPayload() ([]byte, error) {
  184. data, err := json.Marshal(p)
  185. if err != nil {
  186. return []byte{}, err
  187. }
  188. return data, nil
  189. }
  190. // HookTask represents a hook task.
  191. type HookTask struct {
  192. Id int64
  193. Uuid string
  194. Type HookTaskType
  195. Url string
  196. BasePayload `xorm:"-"`
  197. PayloadContent string `xorm:"TEXT"`
  198. ContentType HookContentType
  199. EventType HookEventType
  200. IsSsl bool
  201. IsDelivered bool
  202. IsSucceed bool
  203. }
  204. // CreateHookTask creates a new hook task,
  205. // it handles conversion from Payload to PayloadContent.
  206. func CreateHookTask(t *HookTask) error {
  207. data, err := t.BasePayload.GetJSONPayload()
  208. if err != nil {
  209. return err
  210. }
  211. t.Uuid = uuid.NewV4().String()
  212. t.PayloadContent = string(data)
  213. _, err = x.Insert(t)
  214. return err
  215. }
  216. // UpdateHookTask updates information of hook task.
  217. func UpdateHookTask(t *HookTask) error {
  218. _, err := x.Id(t.Id).AllCols().Update(t)
  219. return err
  220. }
  221. var (
  222. // Prevent duplicate deliveries.
  223. // This happens with massive hook tasks cannot finish delivering
  224. // before next shooting starts.
  225. isShooting = false
  226. )
  227. // DeliverHooks checks and delivers undelivered hooks.
  228. // FIXME: maybe can use goroutine to shoot a number of them at same time?
  229. func DeliverHooks() {
  230. if isShooting {
  231. return
  232. }
  233. isShooting = true
  234. defer func() { isShooting = false }()
  235. tasks := make([]*HookTask, 0, 10)
  236. timeout := time.Duration(setting.WebhookDeliverTimeout) * time.Second
  237. x.Where("is_delivered=?", false).Iterate(new(HookTask),
  238. func(idx int, bean interface{}) error {
  239. t := bean.(*HookTask)
  240. req := httplib.Post(t.Url).SetTimeout(timeout, timeout).
  241. Header("X-Gogs-Delivery", t.Uuid).
  242. Header("X-Gogs-Event", string(t.EventType))
  243. switch t.ContentType {
  244. case JSON:
  245. req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)
  246. case FORM:
  247. req.Param("payload", t.PayloadContent)
  248. }
  249. t.IsDelivered = true
  250. // FIXME: record response.
  251. switch t.Type {
  252. case GOGS:
  253. {
  254. if _, err := req.Response(); err != nil {
  255. log.Error(4, "Delivery: %v", err)
  256. } else {
  257. t.IsSucceed = true
  258. }
  259. }
  260. case SLACK:
  261. {
  262. if res, err := req.Response(); err != nil {
  263. log.Error(4, "Delivery: %v", err)
  264. } else {
  265. defer res.Body.Close()
  266. contents, err := ioutil.ReadAll(res.Body)
  267. if err != nil {
  268. log.Error(4, "%s", err)
  269. } else {
  270. if string(contents) != "ok" {
  271. log.Error(4, "slack failed with: %s", string(contents))
  272. } else {
  273. t.IsSucceed = true
  274. }
  275. }
  276. }
  277. }
  278. }
  279. tasks = append(tasks, t)
  280. if t.IsSucceed {
  281. log.Trace("Hook delivered(%s): %s", t.Uuid, t.PayloadContent)
  282. }
  283. return nil
  284. })
  285. // Update hook task status.
  286. for _, t := range tasks {
  287. if err := UpdateHookTask(t); err != nil {
  288. log.Error(4, "UpdateHookTask(%d): %v", t.Id, err)
  289. }
  290. }
  291. }