Browse Source

Merge pull request #24 from matrix-org/kegan/jira-webhook-2

Handle incoming JIRA webhook events
kegan/get-service
Kegsay 8 years ago
committed by GitHub
parent
commit
23df851d49
  1. 3
      src/github.com/matrix-org/go-neb/api.go
  2. 2
      src/github.com/matrix-org/go-neb/realms/jira/jira.go
  3. 68
      src/github.com/matrix-org/go-neb/services/jira/jira.go
  4. 45
      src/github.com/matrix-org/go-neb/services/jira/webhook/webhook.go

3
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)
}

2
src/github.com/matrix-org/go-neb/realms/jira/jira.go

@ -32,6 +32,7 @@ type JIRARealm struct {
ConsumerSecret string
PublicKeyPEM string // clobbered based on PrivateKeyPEM
PrivateKeyPEM string
HasWebhook bool // clobbered based on NEB
}
// JIRASession represents a single authentication session between a user and a JIRA endpoint.
@ -95,6 +96,7 @@ func (r *JIRARealm) Register() error {
if r.JIRAEndpoint == "" {
return errors.New("JIRAEndpoint must be specified")
}
r.HasWebhook = false // never let the user set this; only NEB can.
// Check to see if JIRA endpoint is valid by pinging an endpoint
cli, err := r.JIRAClient("", true)

68
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,47 @@ 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.WithField("project", eventProjectKey).Print("Unable to process event for project")
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")
}
}
}
}
w.WriteHeader(200)
}
func (s *jiraService) realmIDForProject(roomID, projectKey string) string {
@ -335,6 +376,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 <b>%s</b> - %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}

45
src/github.com/matrix-org/go-neb/services/jira/webhook/webhook.go

@ -1,13 +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 {
@ -20,6 +22,14 @@ type jiraWebhook struct {
Enabled bool `json:"enabled"`
}
// Event represents an incoming JIRA webhook event
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.
@ -72,8 +82,11 @@ func RegisterHook(jrealm *realms.JIRARealm, projects []string, userID, webhookEn
// All projects that wish to be tracked are public, but the user cannot create
// webhooks. The only way this will work is if we already have a webhook for this
// JIRA endpoint.
// TODO: Check for an existing webhook for this realm (flag on realm?)
return fmt.Errorf("Not supported yet")
if !jrealm.HasWebhook {
logger.Print("No webhook exists for this realm.")
return fmt.Errorf("Not authorised to create webhook: not an admin.")
}
return nil
}
// The user is probably an admin (can query webhooks endpoint)
@ -85,9 +98,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 {
@ -115,7 +142,11 @@ func createWebhook(jrealm *realms.JIRARealm, webhookEndpointURL, userID string)
"realm_id": jrealm.ID(),
"jira_url": jrealm.JIRAEndpoint,
}).Print("Created webhook")
return nil
// mark this on the realm and persist it.
jrealm.HasWebhook = true
_, err = database.GetServiceDB().StoreAuthRealm(jrealm)
return err
}
func getWebhook(cli *jira.Client, webhookEndpointURL string) (*jiraWebhook, *errors.HTTPError) {

Loading…
Cancel
Save