Skip to content

Instantly share code, notes, and snippets.

@jyotty
Created August 31, 2017 22:08
Show Gist options
  • Save jyotty/71378dbca31f805180283b620fd2cd94 to your computer and use it in GitHub Desktop.
Save jyotty/71378dbca31f805180283b620fd2cd94 to your computer and use it in GitHub Desktop.
tiny flapjack webhook receiver for end-to-end testing
package main
import (
"encoding/json"
"fmt"
"github.com/PagerDuty/go-pagerduty"
"github.com/go-martini/martini"
"gopkg.in/alecthomas/kingpin.v2"
"io/ioutil"
"log"
"net/http"
"sync"
"time"
)
var (
listenIP = kingpin.Flag("ip", "server address").Default("127.0.0.1").String()
port = kingpin.Flag("port", "Address to bind HTTP server (default 3901)").Default("3901").OverrideDefaultFromEnvar("PORT").String()
timeout = kingpin.Flag("timeout", "How long to wait until a check is considered dead, in seconds (default 600)").Default("600").Int64()
pdkey = kingpin.Flag("pd-service-key", "PagerDuty service key").String()
)
func timeoutAlert(entity *Entity, pages chan pagerduty.Event, now int64) {
entity.Lock()
// XXX break out so we aren't eight goddamn tabs deep
for name, last := range entity.last_event {
delta := now - last
if key, ok := entity.incident_key[name]; ok {
// already an incident
if delta < *timeout {
pages <- pagerduty.Event{
ServiceKey: *pdkey,
Description: fmt.Sprintf("%s has reported (%d seconds ago)", name, delta),
Type: "resolve",
Client: "flapjack etet",
IncidentKey: key,
}
// clear the incident
delete(entity.incident_key, name)
}
} else {
if delta > *timeout {
pages <- pagerduty.Event{
ServiceKey: *pdkey,
Description: fmt.Sprintf("%s has not reported in %d seconds", name, delta),
Type: "trigger",
Client: "flapjack etet",
Details: map[string]string{
"entity": name,
},
}
}
}
}
entity.Unlock()
}
func pagerdutyEvents(entity *Entity, pages chan pagerduty.Event) {
for event := range pages {
res, err := pagerduty.CreateEvent(event)
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Status)
fmt.Println(res.Message)
fmt.Println(res.IncidentKey)
if event.Type == "trigger" {
name := event.Details.(map[string]string)["entity"]
entity.Lock()
entity.incident_key[name] = res.IncidentKey
entity.Unlock()
}
}
}
type Alert struct {
Entity string `json:"entity"`
Time int64 `json:"time"`
}
type Event struct {
Al Alert `json:"alert"`
Id string `json:"id"`
Type string `json:"type"`
}
type Entity struct {
sync.RWMutex
last_event map[string]int64
incident_key map[string]string
}
func updateEntity(entity *Entity, w http.ResponseWriter, r *http.Request) {
var event Event
body, err := ioutil.ReadAll(r.Body)
if err != nil {
message := "Error: Couldn't read request body: %s\n"
log.Printf(message, err)
fmt.Fprintf(w, message, err)
return
}
err = json.Unmarshal(body, &event)
if err != nil {
message := "Error: Couldn't read request body: %s\n"
log.Println(message, err)
fmt.Fprintf(w, message, err)
return
}
entity.Lock()
defer entity.Unlock()
entity.last_event[event.Al.Entity] = event.Al.Time
}
func main() {
kingpin.Parse()
entity := &Entity{
last_event: make(map[string]int64),
incident_key: make(map[string]string),
}
pages := make(chan pagerduty.Event)
// check for timeouts once a minute
go func() {
c := time.Tick(1 * time.Minute)
for now := range c {
timeoutAlert(entity, pages, int64(now.Unix()))
}
}()
go pagerdutyEvents(entity, pages)
m := martini.Classic()
m.Delete("/entities/:entity", func(params martini.Params) (int, string) {
entity.Lock()
defer entity.Unlock()
if _, exists := entity.last_event[params["entity"]]; exists {
delete(entity.last_event, params["entity"])
delete(entity.incident_key, params["entity"])
return 200, "OK"
} else {
return 404, "No such entity"
}
})
m.Get("/entities", func() string {
entity.RLock()
res, _ := json.MarshalIndent(entity.last_event, "", " ")
entity.RUnlock()
return string(res)
})
m.Post("/check", func(res http.ResponseWriter, req *http.Request) {
updateEntity(entity, res, req)
})
addr := fmt.Sprintf("%s:%s", *listenIP, *port)
log.Fatal(http.ListenAndServe(addr, m))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment