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.

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