From 5d08ba7368841ffa06a46437496d7b3a7eb60df6 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 12 Aug 2016 15:54:16 +0100 Subject: [PATCH] Handle incoming JIRA webhook requests --- src/github.com/matrix-org/go-neb/api.go | 3 + .../matrix-org/go-neb/services/jira/jira.go | 67 ++++++++++++++++++- .../go-neb/services/jira/webhook/webhook.go | 30 +++++++-- 3 files changed, 95 insertions(+), 5 deletions(-) diff --git a/src/github.com/matrix-org/go-neb/api.go b/src/github.com/matrix-org/go-neb/api.go index 146a820..ff1da59 100644 --- a/src/github.com/matrix-org/go-neb/api.go +++ b/src/github.com/matrix-org/go-neb/api.go @@ -140,6 +140,9 @@ func (wh *webhookHandler) handle(w http.ResponseWriter, req *http.Request) { w.WriteHeader(500) return } + log.WithFields(log.Fields{ + "service_id": service.ServiceID(), + }).Print("Incoming webhook") service.OnReceiveWebhook(w, req, cli) } diff --git a/src/github.com/matrix-org/go-neb/services/jira/jira.go b/src/github.com/matrix-org/go-neb/services/jira/jira.go index 22cd933..0faf466 100644 --- a/src/github.com/matrix-org/go-neb/services/jira/jira.go +++ b/src/github.com/matrix-org/go-neb/services/jira/jira.go @@ -10,6 +10,7 @@ import ( "github.com/matrix-org/go-neb/matrix" "github.com/matrix-org/go-neb/plugin" "github.com/matrix-org/go-neb/realms/jira" + "github.com/matrix-org/go-neb/realms/jira/urls" "github.com/matrix-org/go-neb/services/jira/webhook" "github.com/matrix-org/go-neb/types" "html" @@ -222,7 +223,46 @@ func (s *jiraService) Plugin(roomID string) plugin.Plugin { } func (s *jiraService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *matrix.Client) { - webhook.OnReceiveRequest(w, req, cli) + eventProjectKey, event, httpErr := webhook.OnReceiveRequest(req) + if httpErr != nil { + log.WithError(httpErr).Print("Failed to handle JIRA webhook") + w.WriteHeader(500) + return + } + // grab base jira url + jurl, err := urls.ParseJIRAURL(event.Issue.Self) + if err != nil { + log.WithError(err).Print("Failed to parse base JIRA URL") + w.WriteHeader(500) + return + } + // work out the HTML to send + htmlText := htmlForEvent(event, jurl.Base) + if htmlText == "" { + log.Print("Unable to process event") + w.WriteHeader(200) + return + } + // send message into each configured room + for roomID, roomConfig := range s.Rooms { + for _, realmConfig := range roomConfig.Realms { + for pkey, projectConfig := range realmConfig.Projects { + if pkey != eventProjectKey || !projectConfig.Track { + continue + } + _, msgErr := cli.SendMessageEvent( + roomID, "m.room.message", matrix.GetHTMLMessage("m.notice", htmlText), + ) + if msgErr != nil { + log.WithFields(log.Fields{ + log.ErrorKey: msgErr, + "project": pkey, + "room_id": roomID, + }).Print("Failed to send notice into room") + } + } + } + } } func (s *jiraService) realmIDForProject(roomID, projectKey string) string { @@ -335,6 +375,31 @@ func htmlSummaryForIssue(issue *jira.Issue) string { ) } +// htmlForEvent formats a webhook event as HTML. Returns an empty string if there is nothing to send/cannot +// be parsed. +func htmlForEvent(whe *webhook.Event, jiraBaseURL string) string { + action := "" + if whe.WebhookEvent == "jira:issue_updated" { + action = "updated" + } else if whe.WebhookEvent == "jira:issue_deleted" { + action = "deleted" + } else if whe.WebhookEvent == "jira:issue_created" { + action = "created" + } else { + return "" + } + + summaryHTML := htmlSummaryForIssue(&whe.Issue) + + return fmt.Sprintf("%s %s %s - %s %s", + html.EscapeString(whe.User.Name), + html.EscapeString(action), + html.EscapeString(whe.Issue.Key), + summaryHTML, + html.EscapeString(jiraBaseURL+"browse/"+whe.Issue.Key), + ) +} + func init() { types.RegisterService(func(serviceID, webhookEndpointURL string) types.Service { return &jiraService{id: serviceID, webhookEndpointURL: webhookEndpointURL} diff --git a/src/github.com/matrix-org/go-neb/services/jira/webhook/webhook.go b/src/github.com/matrix-org/go-neb/services/jira/webhook/webhook.go index 7a06a30..2ea562d 100644 --- a/src/github.com/matrix-org/go-neb/services/jira/webhook/webhook.go +++ b/src/github.com/matrix-org/go-neb/services/jira/webhook/webhook.go @@ -1,14 +1,15 @@ package webhook import ( + "encoding/json" "fmt" log "github.com/Sirupsen/logrus" "github.com/andygrunwald/go-jira" "github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/errors" - "github.com/matrix-org/go-neb/matrix" "github.com/matrix-org/go-neb/realms/jira" "net/http" + "strings" ) type jiraWebhook struct { @@ -21,6 +22,13 @@ type jiraWebhook struct { Enabled bool `json:"enabled"` } +type Event struct { + WebhookEvent string `json:"webhookEvent"` + Timestamp int64 `json:"timestamp"` + User jira.User `json:"user"` + Issue jira.Issue `json:"issue"` +} + // RegisterHook checks to see if this user is allowed to track the given projects and then tracks them. func RegisterHook(jrealm *realms.JIRARealm, projects []string, userID, webhookEndpointURL string) error { // Tracking means that a webhook may need to be created on the remote JIRA installation. @@ -89,9 +97,23 @@ func RegisterHook(jrealm *realms.JIRARealm, projects []string, userID, webhookEn return createWebhook(jrealm, webhookEndpointURL, userID) } -// OnReceiveRequest is called when JIRA hits NEB with an update -func OnReceiveRequest(w http.ResponseWriter, req *http.Request, cli *matrix.Client) { - w.WriteHeader(200) // Do nothing +// OnReceiveRequest is called when JIRA hits NEB with an update. +// Returns the project key and webhook event, or an error. +func OnReceiveRequest(req *http.Request) (string, *Event, *errors.HTTPError) { + // extract the JIRA webhook event JSON + defer req.Body.Close() + var whe Event + err := json.NewDecoder(req.Body).Decode(&whe) + if err != nil { + return "", nil, &errors.HTTPError{err, "Failed to parse request JSON", 400} + } + + if err != nil { + return "", nil, &errors.HTTPError{err, "Failed to parse JIRA URL", 400} + } + projKey := strings.Split(whe.Issue.Key, "-")[0] + projKey = strings.ToUpper(projKey) + return projKey, &whe, nil } func createWebhook(jrealm *realms.JIRARealm, webhookEndpointURL, userID string) error {