Browse Source

Add Service.PostRegister(oldService) and implement GH webhook creation

- `PostRegister` is called after the new Service is stored in the database.
  It exists so Services can do post-creation actions like hit remote services
  using information from old services, if any.
- The `GithubService` uses the `PostRegister()` function to remove old webhooks.
pull/13/head
Kegan Dougal 8 years ago
parent
commit
33b5abb84b
  1. 2
      src/github.com/matrix-org/go-neb/api.go
  2. 11
      src/github.com/matrix-org/go-neb/services/echo/echo.go
  3. 143
      src/github.com/matrix-org/go-neb/services/github/github.go
  4. 4
      src/github.com/matrix-org/go-neb/services/github/webhook/webhook.go
  5. 1
      src/github.com/matrix-org/go-neb/types/types.go

2
src/github.com/matrix-org/go-neb/api.go

@ -213,6 +213,8 @@ func (s *configureServiceHandler) OnIncomingRequest(req *http.Request) (interfac
return nil, &errors.HTTPError{err, "Error storing service", 500}
}
service.PostRegister(oldService)
return &struct {
ID string
Type string

11
src/github.com/matrix-org/go-neb/services/echo/echo.go

@ -14,11 +14,12 @@ type echoService struct {
Rooms []string
}
func (e *echoService) ServiceUserID() string { return e.UserID }
func (e *echoService) ServiceID() string { return e.id }
func (e *echoService) ServiceType() string { return "echo" }
func (e *echoService) RoomIDs() []string { return e.Rooms }
func (e *echoService) Register() error { return nil }
func (e *echoService) ServiceUserID() string { return e.UserID }
func (e *echoService) ServiceID() string { return e.id }
func (e *echoService) ServiceType() string { return "echo" }
func (e *echoService) RoomIDs() []string { return e.Rooms }
func (e *echoService) Register() error { return nil }
func (e *echoService) PostRegister(old types.Service) {}
func (e *echoService) Plugin(roomID string) plugin.Plugin {
return plugin.Plugin{
Commands: []plugin.Command{

143
src/github.com/matrix-org/go-neb/services/github/github.go

@ -22,12 +22,14 @@ import (
var ownerRepoIssueRegex = regexp.MustCompile("([A-z0-9-_]+)/([A-z0-9-_]+)#([0-9]+)")
type githubService struct {
id string
BotUserID string
GithubUserID string
RealmID string
Rooms map[string]struct { // room_id => {}
OwnerRepo map[string]struct { // owner/repo => { events: ["push","issue","pull_request"] }
id string
BotUserID string
ClientUserID string
RealmID string
SecretToken string
WebhookBaseURI string
Rooms map[string]struct { // room_id => {}
Repos map[string]struct { // owner/repo => { events: ["push","issue","pull_request"] }
Events []string
}
}
@ -128,15 +130,15 @@ func (s *githubService) Plugin(roomID string) plugin.Plugin {
}
}
func (s *githubService) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *matrix.Client) {
evType, repo, msg, err := webhook.OnReceiveRequest(req, "")
evType, repo, msg, err := webhook.OnReceiveRequest(req, s.SecretToken)
if err != nil {
w.WriteHeader(err.Code)
return
}
for roomID, roomConfig := range s.Rooms {
for ownerRepo, repoConfig := range roomConfig.OwnerRepo {
if *repo.FullName != ownerRepo {
for ownerRepo, repoConfig := range roomConfig.Repos {
if !strings.EqualFold(*repo.FullName, ownerRepo) {
continue
}
@ -165,8 +167,8 @@ func (s *githubService) OnReceiveWebhook(w http.ResponseWriter, req *http.Reques
w.WriteHeader(200)
}
func (s *githubService) Register() error {
if s.RealmID == "" || s.BotUserID == "" {
return fmt.Errorf("RealmID and BotUserID are required")
if s.RealmID == "" || s.ClientUserID == "" || s.BotUserID == "" {
return fmt.Errorf("RealmID, BotUserID and ClientUserID are required")
}
// check realm exists
realm, err := database.GetServiceDB().LoadAuthRealm(s.RealmID)
@ -178,9 +180,90 @@ func (s *githubService) Register() error {
return fmt.Errorf("Realm is of type '%s', not 'github'", realm.Type())
}
// In order to register the GH service, you must have authed with GH.
cli := s.githubClientFor(s.ClientUserID, false)
if cli == nil {
return fmt.Errorf("User %s does not have a Github auth session.", s.ClientUserID)
}
return nil
}
func (s *githubService) PostRegister(oldService types.Service) {
cli := s.githubClientFor(s.ClientUserID, false)
if cli == nil {
log.Errorf("PostRegister: %s does not have a github session", s.ClientUserID)
return
}
old, ok := oldService.(*githubService)
if !ok {
log.Error("PostRegister: Provided old service is not of type GithubService")
return
}
// remove any existing webhooks this service created on the user's behalf
modifyWebhooks(old, cli, true)
// make new webhooks according to service config
modifyWebhooks(s, cli, false)
}
func modifyWebhooks(s *githubService, cli *github.Client, removeHooks bool) {
// TODO: This makes assumptions about how Go-NEB maps services to webhook endpoints.
// We should factor this out to a function called GetWebhookEndpoint(Service) or something.
trailingSlash := ""
if !strings.HasSuffix(s.WebhookBaseURI, "/") {
trailingSlash = "/"
}
webhookEndpointURL := s.WebhookBaseURI + trailingSlash + "services/hooks/" + s.id
ownerRepoSet := make(map[string]bool)
for _, roomCfg := range s.Rooms {
for ownerRepo := range roomCfg.Repos {
// sanity check that it looks like 'owner/repo' as we'll split on / later
if strings.Count(ownerRepo, "/") != 1 {
log.WithField("owner_repo", ownerRepo).Print("Bad owner/repo value.")
continue
}
ownerRepoSet[ownerRepo] = true
}
}
for ownerRepo := range ownerRepoSet {
o := strings.Split(ownerRepo, "/")
owner := o[0]
repo := o[1]
logger := log.WithFields(log.Fields{
"owner": owner,
"repo": repo,
})
if removeHooks {
removeHook(logger, cli, owner, repo, webhookEndpointURL)
} else {
// make a hook for all GH events since we'll filter it when we receive webhook requests
name := "web" // https://developer.github.com/v3/repos/hooks/#create-a-hook
cfg := map[string]interface{}{
"content_type": "json",
"url": webhookEndpointURL,
}
if s.SecretToken != "" {
cfg["secret"] = s.SecretToken
}
events := []string{"push", "pull_request", "issues", "issue_comment", "pull_request_review_comment"}
_, _, err := cli.Repositories.CreateHook(owner, repo, &github.Hook{
Name: &name,
Config: cfg,
Events: events,
})
if err != nil {
logger.WithError(err).Print("Failed to create webhook")
// continue as others may succeed
}
}
}
}
func (s *githubService) githubClientFor(userID string, allowUnauth bool) *github.Client {
token, err := getTokenForUser(s.RealmID, userID)
if err != nil {
@ -217,6 +300,9 @@ func getTokenForUser(realmID, userID string) (string, error) {
if !ok {
return "", fmt.Errorf("Session is not a github session: %s", session.ID())
}
if ghSession.AccessToken == "" {
return "", fmt.Errorf("Github auth session for %s has not been completed.", userID)
}
return ghSession.AccessToken, nil
}
@ -249,6 +335,41 @@ func ownerRepoNumberFromText(ownerRepoNumberText string) (string, string, int, e
return groups[1], groups[2], num, nil
}
func removeHook(logger *log.Entry, cli *github.Client, owner, repo, webhookEndpointURL string) {
// Get a list of webhooks for this owner/repo and find the one which has the
// same endpoint URL which is what github uses to determine equivalence.
hooks, _, err := cli.Repositories.ListHooks(owner, repo, nil)
if err != nil {
logger.WithError(err).Print("Failed to list hooks")
return
}
var hook *github.Hook
for _, h := range hooks {
if h.Config["url"] == nil {
logger.Print("Ignoring nil config.url")
continue
}
hookURL, ok := h.Config["url"].(string)
if !ok {
logger.Print("Ignoring non-string config.url")
continue
}
if hookURL == webhookEndpointURL {
hook = h
break
}
}
if hook == nil {
return // couldn't find it
}
_, err = cli.Repositories.DeleteHook(owner, repo, *hook.ID)
if err != nil {
logger.WithError(err).Print("Failed to delete hook")
// continue as others may succeed
}
}
func init() {
types.RegisterService(func(serviceID string) types.Service {
return &githubService{id: serviceID}

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

@ -53,6 +53,10 @@ func OnReceiveRequest(r *http.Request, secretToken string) (string, *github.Repo
"signature": signatureSHA1,
}).Print("Received Github event")
if eventType == "ping" {
return "", nil, nil, &errors.HTTPError{nil, "pong", 200}
}
htmlStr, repo, err := parseGithubEvent(eventType, content)
if err != nil {
log.WithError(err).Print("Failed to parse github event")

1
src/github.com/matrix-org/go-neb/types/types.go

@ -36,6 +36,7 @@ type Service interface {
Plugin(roomID string) plugin.Plugin
OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *matrix.Client)
Register() error
PostRegister(oldService Service)
}
var servicesByType = map[string]func(string) Service{}

Loading…
Cancel
Save