Skip to content

Instantly share code, notes, and snippets.

@groob
Last active December 21, 2022 03:39
Show Gist options
  • Save groob/a70af7e7ed3567eb6b6801f65c7a1393 to your computer and use it in GitHub Desktop.
Save groob/a70af7e7ed3567eb6b6801f65c7a1393 to your computer and use it in GitHub Desktop.
/*
HookHandler - listen for github webhooks, sending updates on channel.
DeploymentMonitor select update type based on channel and call deployment script
*/
package main
import (
"fmt"
"html/template"
"io/ioutil"
"log"
"net/http"
"github.com/google/go-github/github"
"github.com/nlopes/slack"
)
// HookHandler parses GitHub webhooks and sends an update to DeploymentMonitor.
func HookHandler(prUp chan<- PullUpdate, cUp chan<- CommitUpdate, brUp chan<- BranchUpdate) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
payload, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("error reading request body: err=%s\n", err)
return
}
defer r.Body.Close()
event, err := github.ParseWebHook(github.WebHookType(r), payload)
if err != nil {
log.Printf("could not parse webhook: err=%s\n", err)
return
}
// send PR or Branch updates to the DeploymentMonitor
// send commit status (from CircleCI) to DeploymentMonitor
switch e := event.(type) {
case *github.StatusEvent:
var commitMessage string
if e.Commit != nil {
if e.Commit.Commit != nil {
commitMessage = *e.Commit.Commit.Message
}
}
cUp <- CommitUpdate{status: *e.State, sha: *e.SHA, message: commitMessage}
return
case *github.PullRequestEvent:
prUp <- PullUpdate{
pr: PullRequest{
Number: *e.Number,
SHA: *e.PullRequest.Head.SHA,
},
action: *e.Action,
}
return
case *github.PushEvent:
ref := *e.Ref
branch := ref[len("refs/heads/"):]
if branch == "master" {
brUp <- BranchUpdate{
Name: branch,
SHA: *e.After,
}
}
return
default:
log.Printf("unknown WebHookType: %s, webhook-id: %s skipping\n", github.WebHookType(r), r.Header.Get("X-GitHub-Delivery"))
return
}
}
}
// DeploymentMonitor receives updates when
// a pull request is opened/updated/closed
// a branch receives a new push(merge to master is a push event)
// the pullUpdate and branchUpdate channels will update a branch or PR SHA
// to the current one.
// Later, a commit status will come through. Deploment Monitor will find which branch
// The commit belongs to, and deploy that pull request.
func DeploymentMonitor(dm deployer, botUpdates chan<- botEvent) (chan<- PullUpdate, chan<- CommitUpdate, chan<- BranchUpdate) {
prUp := make(chan PullUpdate)
cUp := make(chan CommitUpdate)
brUp := make(chan BranchUpdate)
pulls := make(map[int]PullRequest)
// map[branchName]commitSHA
branches := make(map[string]string)
// map[commitSHA]status
// tracking commits to avoid duplicates
commits := make(map[string]string)
// load templates
tmpl, err := template.ParseFiles(
"templates/branch-deployment.template",
"templates/branch-service.template",
"templates/pr-deployment.template",
"templates/pr-service.template",
)
if err != nil {
log.Fatalf("failed to parse template files, err: %s\n", err)
}
go func() {
for {
select {
case p := <-prUp:
pulls[p.pr.Number] = p.pr
commits[p.pr.SHA] = "pending"
log.Printf("updated pr: %d to commit: %s, action=%s\n", p.pr.Number, p.pr.SHA, p.action)
// TODO: if action = closed, teardown a deployment?
case br := <-brUp:
branches[br.Name] = br.SHA
commits[br.SHA] = "pending"
log.Printf("updated branch: %s to commit: %s", br.Name, br.SHA)
case c := <-cUp:
// check pull requests
for _, d := range pulls {
if d.SHA == c.sha && c.status == "success" {
if status, ok := commits[c.sha]; ok && status == "deployed" {
continue
}
fmt.Printf("deploying pr=%d, commit=%s\n", d.Number, d.SHA)
if err := dm.deployPR(d.Number, d.SHA, tmpl); err != nil {
log.Println(err)
break
}
botUpdates <- botEvent{
attachments: []slack.Attachment{slack.Attachment{
Fallback: "Pull Request Deployment",
Color: "#36a64f",
AuthorName: "Github Discussion",
AuthorLink: fmt.Sprintf("https://github.com/acme/acme-ose/pull/%d", d.Number),
Title: fmt.Sprintf("%d.pr.acme.net", d.Number),
TitleLink: fmt.Sprintf("https://%d.pr.acme.net", d.Number),
Pretext: fmt.Sprintf("Deployed PR %d, commit %s", d.Number, d.SHA),
Text: c.message,
}},
}
commits[c.sha] = "deployed"
break
}
}
// check branches
for branch, sha := range branches {
if sha == c.sha && c.status == "success" {
// check if commit already marked as deployed
// to avoid duplicates
if status, ok := commits[sha]; ok && status == "deployed" {
continue
}
fmt.Printf("deploying branch=%s, commit=%s\n", branch, sha)
if err := dm.deployBranch(branch, sha, tmpl); err != nil {
log.Println(err)
break
}
// send update to slack
botUpdates <- botEvent{
attachments: []slack.Attachment{slack.Attachment{
Fallback: "Branch Deployment",
Color: "#36a64f",
Title: "acme.acme.net",
TitleLink: "https://acme.acme.net",
Pretext: fmt.Sprintf("Deployed Branch %s, commit %s", branch, sha),
Text: c.message,
}},
}
commits[sha] = "deployed"
break
}
}
}
}
}()
return prUp, cUp, brUp
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment