Skip to content

Commit

Permalink
Merge pull request #301 from ubclaunchpad/daemon/#133-webhooks
Browse files Browse the repository at this point in the history
Webhook rework and support for GitLab
  • Loading branch information
bobheadxi committed Jul 10, 2018
2 parents 14cf119 + c5fbe7a commit 5b10c12
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 69 deletions.
14 changes: 1 addition & 13 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@
name = "gopkg.in/src-d/go-git.v4"
version = "4.4.0"

[[constraint]]
name = "github.com/google/go-github"
version = "15.0"

[[constraint]]
name = "github.com/sirupsen/logrus"
version = "1.0.5"
Expand Down
2 changes: 1 addition & 1 deletion daemon/inertiad/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func run(host, port, version string) {
)

// GitHub webhook endpoint
handler.AttachPublicHandlerFunc("/webhook", gitHubWebHookHandler)
handler.AttachPublicHandlerFunc("/webhook", webhookHandler)

// CLI API endpoints
handler.AttachUserRestrictedHandlerFunc("/status", statusHandler)
Expand Down
79 changes: 28 additions & 51 deletions daemon/inertiad/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,67 @@ package main

import (
"fmt"
"io"
"net/http"
"os"

docker "github.com/docker/docker/client"
"github.com/google/go-github/github"
"github.com/ubclaunchpad/inertia/common"
"github.com/ubclaunchpad/inertia/daemon/inertiad/project"
"github.com/ubclaunchpad/inertia/daemon/inertiad/webhook"
)

var webhookSecret = "inertia"

// gitHubWebHookHandler writes a response to a request into the given ResponseWriter.
func gitHubWebHookHandler(w http.ResponseWriter, r *http.Request) {
// webhookHandler writes a response to a request into the given ResponseWriter.
func webhookHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, common.MsgDaemonOK)

payload, err := github.ValidatePayload(r, []byte(webhookSecret))
payload, err := webhook.Parse(r, os.Stdout)
if err != nil {
println(err.Error())
fmt.Fprintln(os.Stdout, err.Error())
return
}

event, err := github.ParseWebHook(github.WebHookType(r), payload)
if err != nil {
println(err.Error())
return
}

switch event := event.(type) {
case *github.PushEvent:
processPushEvent(event)
case *github.PullRequestEvent:
processPullRequestEvent(event)
switch event := payload.GetEventType(); event {
case webhook.PushEvent:
processPushEvent(payload, os.Stdout)
// case webhook.PullEvent:
// processPullRequestEvent(payload)
default:
println("Unrecognized event type")
fmt.Fprintln(os.Stdout, "Unrecognized event type")
}
}

// processPushEvent prints information about the given PushEvent.
func processPushEvent(event *github.PushEvent) {
repo := event.GetRepo()
branch := common.GetBranchFromRef(event.GetRef())
println("Received PushEvent")
println(fmt.Sprintf("Repository Name: %s", *repo.Name))
println(fmt.Sprintf("Repository Git URL: %s", *repo.GitURL))
println(fmt.Sprintf("Branch: %s", branch))
func processPushEvent(payload webhook.Payload, out io.Writer) {
branch := common.GetBranchFromRef(payload.GetRef())

fmt.Fprintln(out, "Received PushEvent")
fmt.Fprintln(out, fmt.Sprintf("Repository Name: %s", payload.GetRepoName()))
fmt.Fprintln(out, fmt.Sprintf("Repository Git URL: %s", payload.GetGitURL()))
fmt.Fprintln(out, fmt.Sprintf("Branch: %s", branch))

// Ignore event if repository not set up yet, otherwise
// let deploy() handle the update.
if deployment == nil {
println("No deployment detected - try running 'inertia $REMOTE up'")
fmt.Fprintln(out, "No deployment detected - try running 'inertia $REMOTE up'")
return
}

// Check for matching remotes
err := deployment.CompareRemotes(common.GetSSHRemoteURL(repo.GetGitURL()))
err := deployment.CompareRemotes(payload.GetSSHURL())
if err != nil {
println(err.Error())
fmt.Fprintln(out, err.Error())
return
}

// If branches match, deploy, otherwise ignore the event.
if deployment.GetBranch() == branch {
println("Event branch matches deployed branch " + branch)
fmt.Fprintln(out, "Event branch matches deployed branch "+branch)
cli, err := docker.NewEnvClient()
if err != nil {
println(err.Error())
fmt.Fprintln(out, err.Error())
return
}
defer cli.Close()
Expand All @@ -77,30 +72,12 @@ func processPushEvent(event *github.PushEvent) {
SkipUpdate: false,
})
if err != nil {
println(err.Error())
fmt.Fprintln(out, err.Error())
}
} else {
println(
"Event branch " + branch + " does not match deployed branch " +
deployment.GetBranch() + " - ignoring event.",
fmt.Fprintln(out,
"Event branch "+branch+" does not match deployed branch "+
deployment.GetBranch()+" - ignoring event.",
)
}
}

// processPullRequestEvent prints information about the given PullRequestEvent.
// Handling PRs is unnecessary because merging one will trigger a PushEvent.
// For now, simply logs events - may in the future do something configured
// by the user.
func processPullRequestEvent(event *github.PullRequestEvent) {
repo := event.GetRepo()
pr := event.GetPullRequest()
merged := "false"
if *pr.Merged {
merged = "true"
}
println("Received PullRequestEvent")
println(fmt.Sprintf("Repository Name: %s", *repo.Name))
println(fmt.Sprintf("Repository Git URL: %s", *repo.GitURL))
println(fmt.Sprintf("Ref: %s", pr.GetBase().GetRef()))
println(fmt.Sprintf("Merge status: %v", merged))
}
2 changes: 2 additions & 0 deletions daemon/inertiad/webhook/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package webhook contains Inertia's webhook parsing code
package webhook
71 changes: 71 additions & 0 deletions daemon/inertiad/webhook/github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package webhook

import (
"encoding/json"
"errors"
"net/http"
)

// x-github-event header values
var (
GithubPushHeader = "push"
// GithubPullHeader = "pull"
)

// GithubPushEvent represents a push to a Github repository
// see https://developer.github.com/v3/activity/events/types/#pushevent
type GithubPushEvent struct {
eventType string
Ref string `json:"ref"`
Repo GithubPushEventRepository `json:"repository"`
}

// GithubPushEventRepository represents the repository object in a Github PushEvent
// see https://developer.github.com/v3/activity/events/types/#pushevent
type GithubPushEventRepository struct {
FullName string `json:"full_name"`
GitURL string `json:"clone_url"`
SSHURL string `json:"ssh_url"`
}

func parseGithubEvent(r *http.Request, event string) (Payload, error) {
dec := json.NewDecoder(r.Body)

switch event {
case GithubPushHeader:
payload := GithubPushEvent{eventType: PushEvent}

if err := dec.Decode(&payload); err != nil {
return nil, errors.New("Error parsing PushEvent")
}

return payload, nil
default:
return nil, errors.New("Unsupported Github event")
}
}

// GetEventType returns the event type of the webhook
func (g GithubPushEvent) GetEventType() string {
return g.eventType
}

// GetRepoName returns the full repo name
func (g GithubPushEvent) GetRepoName() string {
return g.Repo.FullName
}

// GetRef returns the full ref
func (g GithubPushEvent) GetRef() string {
return g.Ref
}

// GetGitURL returns the git clone URL
func (g GithubPushEvent) GetGitURL() string {
return g.Repo.GitURL
}

// GetSSHURL returns the ssh URL
func (g GithubPushEvent) GetSSHURL() string {
return g.Repo.SSHURL
}
70 changes: 70 additions & 0 deletions daemon/inertiad/webhook/gitlab.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package webhook

import (
"encoding/json"
"errors"
"net/http"
)

// x-gitlab-event header values
var (
GitlabPushHeader = "Push Hook"
)

// GitlabPushEvent represents a push to a Gitlab repository
// see https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#push-events
type GitlabPushEvent struct {
eventType string
Ref string `json:"ref"`
Repo GitlabPushEventRepository `json:"repository"`
}

// GitlabPushEventRepository represents the repository object in a Gitlab PushEvent
// see https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#push-events
type GitlabPushEventRepository struct {
Name string `json:"name"`
GitURL string `json:"git_http_url"`
SSHURL string `json:"git_ssh_url"`
}

func parseGitlabEvent(r *http.Request, event string) (Payload, error) {
dec := json.NewDecoder(r.Body)

switch event {
case GitlabPushHeader:
payload := GitlabPushEvent{eventType: PushEvent}

if err := dec.Decode(&payload); err != nil {
return nil, errors.New("Error parsing PushEvent")
}

return payload, nil
default:
return nil, errors.New("Unsupported Gitlab event")
}
}

// GetEventType returns the event type of the webhook
func (g GitlabPushEvent) GetEventType() string {
return g.eventType
}

// GetRepoName returns the repo name
func (g GitlabPushEvent) GetRepoName() string {
return g.Repo.Name
}

// GetRef returns the full ref
func (g GitlabPushEvent) GetRef() string {
return g.Ref
}

// GetGitURL returns the git clone URL
func (g GitlabPushEvent) GetGitURL() string {
return g.Repo.GitURL
}

// GetSSHURL returns the ssh URL
func (g GitlabPushEvent) GetSSHURL() string {
return g.Repo.SSHURL
}
54 changes: 54 additions & 0 deletions daemon/inertiad/webhook/webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package webhook

import (
"errors"
"fmt"
"io"
"net/http"
"strings"
)

// Constants for the generic webhook interface
var (
PushEvent = "push"
// PullEvent = "pull"
)

// Payload represents a generic webhook payload
type Payload interface {
GetEventType() string
GetRepoName() string
GetRef() string
GetGitURL() string
GetSSHURL() string
}

// Parse takes in a webhook request and parses it into one of the supported types
func Parse(r *http.Request, out io.Writer) (Payload, error) {
if r.Header.Get("content-type") != "application/json" {
return nil, errors.New("Content-Type must be JSON")
}

// Try Github
githubEventHeader := r.Header.Get("x-github-event")
if len(githubEventHeader) > 0 {
fmt.Fprintln(out, "Github webhook detected")
return parseGithubEvent(r, githubEventHeader)
}

// Try Gitlab
gitlabEventHeader := r.Header.Get("x-gitlab-event")
if len(gitlabEventHeader) > 0 {
fmt.Fprintln(out, "Gitlab webhook detected")
return parseGitlabEvent(r, gitlabEventHeader)
}

// Try Bitbucket
userAgent := r.Header.Get("user-agent")
if strings.Contains(userAgent, "Bitbucket") {
fmt.Fprintln(out, "Bitbucket webhook detected")
return nil, errors.New("Unsupported webhook received")
}

return nil, errors.New("Unsupported webhook received")
}

0 comments on commit 5b10c12

Please sign in to comment.