summaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
authorChristopher Brickley <brickley@gmail.com>2014-08-24 08:59:47 -0400
committerChristopher Brickley <brickley@gmail.com>2014-08-31 12:01:59 -0400
commit2bce24068dc3c64ee5e501c48b7f080c48383970 (patch)
tree1ec707518cb37307cd05fa5cf6ef6bbf670caf9b /models
parent5e6091a30ae4befd68041aaff3f70d7334ce1b1c (diff)
downloadgitea-2bce24068dc3c64ee5e501c48b7f080c48383970.tar.gz
gitea-2bce24068dc3c64ee5e501c48b7f080c48383970.zip
add Slack API webhook support
Diffstat (limited to 'models')
-rw-r--r--models/action.go35
-rw-r--r--models/slack.go114
-rw-r--r--models/webhook.go86
3 files changed, 207 insertions, 28 deletions
diff --git a/models/action.go b/models/action.go
index b5f692c49f..d536c84dd0 100644
--- a/models/action.go
+++ b/models/action.go
@@ -266,14 +266,33 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
continue
}
- p.Secret = w.Secret
- CreateHookTask(&HookTask{
- Type: WEBHOOK,
- Url: w.Url,
- Payload: p,
- ContentType: w.ContentType,
- IsSsl: w.IsSsl,
- })
+ switch w.HookTaskType {
+ case SLACK:
+ {
+ s, err := GetSlackPayload(p, w.Meta)
+ if err != nil {
+ return errors.New("action.GetSlackPayload: " + err.Error())
+ }
+ CreateHookTask(&HookTask{
+ Type: w.HookTaskType,
+ Url: w.Url,
+ BasePayload: s,
+ ContentType: w.ContentType,
+ IsSsl: w.IsSsl,
+ })
+ }
+ default:
+ {
+ p.Secret = w.Secret
+ CreateHookTask(&HookTask{
+ Type: w.HookTaskType,
+ Url: w.Url,
+ BasePayload: p,
+ ContentType: w.ContentType,
+ IsSsl: w.IsSsl,
+ })
+ }
+ }
}
return nil
}
diff --git a/models/slack.go b/models/slack.go
new file mode 100644
index 0000000000..0a55740947
--- /dev/null
+++ b/models/slack.go
@@ -0,0 +1,114 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "strings"
+)
+
+const (
+ SLACK_COLOR string = "#dd4b39"
+)
+
+type Slack struct {
+ Domain string `json:"domain"`
+ Token string `json:"token"`
+ Channel string `json:"channel"`
+}
+
+type SlackPayload struct {
+ Channel string `json:"channel"`
+ Text string `json:"text"`
+ Username string `json:"username"`
+ IconUrl string `json:"icon_url"`
+ UnfurlLinks int `json:"unfurl_links"`
+ LinkNames int `json:"link_names"`
+ Attachments []SlackAttachment `json:"attachments"`
+}
+
+type SlackAttachment struct {
+ Color string `json:"color"`
+ Text string `json:"text"`
+}
+
+func GetSlackURL(domain string, token string) string {
+ return fmt.Sprintf(
+ "https://%s.slack.com/services/hooks/incoming-webhook?token=%s",
+ domain,
+ token,
+ )
+}
+
+func (p SlackPayload) GetJSONPayload() ([]byte, error) {
+ data, err := json.Marshal(p)
+ if err != nil {
+ return []byte{}, err
+ }
+ return data, nil
+}
+
+func GetSlackPayload(p *Payload, meta string) (*SlackPayload, error) {
+ slack := &Slack{}
+ slackPayload := &SlackPayload{}
+ if err := json.Unmarshal([]byte(meta), &slack); err != nil {
+ return slackPayload, errors.New("GetSlackPayload meta json:" + err.Error())
+ }
+
+ // TODO: handle different payload types: push, new branch, delete branch etc.
+ // when they are added to gogs. Only handles push now
+ return getSlackPushPayload(p, slack)
+}
+
+func getSlackPushPayload(p *Payload, slack *Slack) (*SlackPayload, error) {
+ // n new commits
+ refSplit := strings.Split(p.Ref, "/")
+ branchName := refSplit[len(refSplit)-1]
+ var commitString string
+
+ // TODO: add commit compare before/after link when gogs adds it
+ if len(p.Commits) == 1 {
+ commitString = "1 new commit"
+ } else {
+ commitString = fmt.Sprintf("%d new commits", len(p.Commits))
+ }
+
+ text := fmt.Sprintf("[%s:%s] %s pushed by %s", p.Repo.Name, branchName, commitString, p.Pusher.Name)
+ var attachmentText string
+
+ // for each commit, generate attachment text
+ for i, commit := range p.Commits {
+ attachmentText += fmt.Sprintf("<%s|%s>: %s - %s", commit.Url, commit.Id[:7], SlackFormatter(commit.Message), commit.Author.Name)
+ // add linebreak to each commit but the last
+ if i < len(p.Commits)-1 {
+ attachmentText += "\n"
+ }
+ }
+
+ slackAttachments := []SlackAttachment{{Color: SLACK_COLOR, Text: attachmentText}}
+
+ return &SlackPayload{
+ Channel: slack.Channel,
+ Text: text,
+ Username: "gogs",
+ IconUrl: "https://raw.githubusercontent.com/gogits/gogs/master/public/img/favicon.png",
+ UnfurlLinks: 0,
+ LinkNames: 0,
+ Attachments: slackAttachments,
+ }, nil
+}
+
+// see: https://api.slack.com/docs/formatting
+func SlackFormatter(s string) string {
+ // take only first line of commit
+ first := strings.Split(s, "\n")[0]
+ // replace & < >
+ first = strings.Replace(first, "&", "&amp;", -1)
+ first = strings.Replace(first, "<", "&lt;", -1)
+ first = strings.Replace(first, ">", "&gt;", -1)
+ return first
+}
diff --git a/models/webhook.go b/models/webhook.go
index ced7936646..55ed4844ed 100644
--- a/models/webhook.go
+++ b/models/webhook.go
@@ -7,6 +7,7 @@ package models
import (
"encoding/json"
"errors"
+ "io/ioutil"
"time"
"github.com/gogits/gogs/modules/httplib"
@@ -33,15 +34,17 @@ type HookEvent struct {
// Webhook represents a web hook object.
type Webhook struct {
- Id int64
- RepoId int64
- Url string `xorm:"TEXT"`
- ContentType HookContentType
- Secret string `xorm:"TEXT"`
- Events string `xorm:"TEXT"`
- *HookEvent `xorm:"-"`
- IsSsl bool
- IsActive bool
+ Id int64
+ RepoId int64
+ Url string `xorm:"TEXT"`
+ ContentType HookContentType
+ Secret string `xorm:"TEXT"`
+ Events string `xorm:"TEXT"`
+ *HookEvent `xorm:"-"`
+ IsSsl bool
+ IsActive bool
+ HookTaskType HookTaskType
+ Meta string `xorm:"TEXT"` // store hook-specific attributes
}
// GetEvent handles conversion from Events to HookEvent.
@@ -52,6 +55,14 @@ func (w *Webhook) GetEvent() {
}
}
+func (w *Webhook) GetSlackHook() *Slack {
+ s := &Slack{}
+ if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
+ log.Error(4, "webhook.GetSlackHook(%d): %v", w.Id, err)
+ }
+ return s
+}
+
// UpdateEvent handles conversion from HookEvent to Events.
func (w *Webhook) UpdateEvent() error {
data, err := json.Marshal(w.HookEvent)
@@ -119,8 +130,8 @@ func DeleteWebhook(hookId int64) error {
type HookTaskType int
const (
- WEBHOOK HookTaskType = iota + 1
- SERVICE
+ GOGS HookTaskType = iota + 1
+ SLACK
)
type HookEventType string
@@ -152,6 +163,10 @@ type PayloadRepo struct {
Private bool `json:"private"`
}
+type BasePayload interface {
+ GetJSONPayload() ([]byte, error)
+}
+
// Payload represents a payload information of hook.
type Payload struct {
Secret string `json:"secret"`
@@ -161,25 +176,33 @@ type Payload struct {
Pusher *PayloadAuthor `json:"pusher"`
}
+func (p Payload) GetJSONPayload() ([]byte, error) {
+ data, err := json.Marshal(p)
+ if err != nil {
+ return []byte{}, err
+ }
+ return data, nil
+}
+
// HookTask represents a hook task.
type HookTask struct {
Id int64
Uuid string
Type HookTaskType
Url string
- *Payload `xorm:"-"`
+ BasePayload `xorm:"-"`
PayloadContent string `xorm:"TEXT"`
ContentType HookContentType
EventType HookEventType
IsSsl bool
- IsDeliveried bool
+ IsDelivered bool
IsSucceed bool
}
// CreateHookTask creates a new hook task,
// it handles conversion from Payload to PayloadContent.
func CreateHookTask(t *HookTask) error {
- data, err := json.Marshal(t.Payload)
+ data, err := t.BasePayload.GetJSONPayload()
if err != nil {
return err
}
@@ -198,7 +221,7 @@ func UpdateHookTask(t *HookTask) error {
// DeliverHooks checks and delivers undelivered hooks.
func DeliverHooks() {
timeout := time.Duration(setting.WebhookDeliverTimeout) * time.Second
- x.Where("is_deliveried=?", false).Iterate(new(HookTask),
+ 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).
@@ -212,13 +235,36 @@ func DeliverHooks() {
req.Param("payload", t.PayloadContent)
}
- t.IsDeliveried = true
+ t.IsDelivered = true
// TODO: record response.
- if _, err := req.Response(); err != nil {
- log.Error(4, "Delivery: %v", err)
- } else {
- t.IsSucceed = true
+ switch t.Type {
+ case GOGS:
+ {
+ if _, err := req.Response(); err != nil {
+ log.Error(4, "Delivery: %v", err)
+ } else {
+ t.IsSucceed = true
+ }
+ }
+ case SLACK:
+ {
+ if res, err := req.Response(); err != nil {
+ log.Error(4, "Delivery: %v", err)
+ } else {
+ defer res.Body.Close()
+ contents, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ log.Error(4, "%s", err)
+ } else {
+ if string(contents) != "ok" {
+ log.Error(4, "slack failed with: %s", string(contents))
+ } else {
+ t.IsSucceed = true
+ }
+ }
+ }
+ }
}
if err := UpdateHookTask(t); err != nil {