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.

371 lines
9.0 KiB

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