Created
August 31, 2017 22:08
-
-
Save jyotty/71378dbca31f805180283b620fd2cd94 to your computer and use it in GitHub Desktop.
tiny flapjack webhook receiver for end-to-end testing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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