xmpp-webhook

webhook to xmpp gateway (clone of https://github.com/opthomas-prime/xmpp-webhook/ with my own mods)
git clone https://git.e1e0.net/xmpp-webhook.git
Log | Files | Refs | README | LICENSE

commit 3e30ee275f8d050d6deb3b1866ebfefe52d3ac8d
parent edced6e71a9ca83fd275e2b6dd0af64cf146cd5f
Author: Thomas Maier <contact@thomas-maier.net>
Date:   Tue, 26 Sep 2017 14:39:19 +0200

refactoring

Diffstat:
Dgrafana.go | 50--------------------------------------------------
Agrafana.json | 17+++++++++++++++++
Ahandler.go | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmain.go | 112++++++++++++++++++++++++++++---------------------------------------------------
4 files changed, 120 insertions(+), 122 deletions(-)

diff --git a/grafana.go b/grafana.go @@ -1,50 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" -) - -/* -{ - "title": "My alert", - "ruleId": 1, - "ruleName": "Load peaking!", - "ruleUrl": "http://url.to.grafana/db/dashboard/my_dashboard?panelId=2", - "state": "alerting", - "imageUrl": "http://s3.image.url", - "message": "Load is peaking. Make sure the traffic is real and spin up more webfronts", - "evalMatches": [ - { - "metric": "requests", - "tags": {}, - "value": 122 - } - ] -} -*/ - -type GrafanaAlert struct { - Title string `json:"title"` - RuleName string `json:"ruleName"` - RuleUrl string `json:"ruleUrl"` - State string `json:"state"` - Message string `json:"message"` -} - -func makeGrafanaHandler(messages chan<- string) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - fmt.Printf("%v", string(body)) - if err == nil { - var alert GrafanaAlert - err = json.Unmarshal(body, &alert) - if err == nil { - message := alert.State + ": " + alert.Title + "/" + alert.Message + "(" + alert.RuleUrl + ")" - messages <- message - } - } - } -} diff --git a/grafana.json b/grafana.json @@ -0,0 +1,17 @@ +{ + "title": "My alert", + "ruleId": 1, + "ruleName": "Load peaking!", + "ruleUrl": "http://url.to.grafana/db/dashboard/my_dashboard?panelId=2", + "state": "alerting", + "imageUrl": "http://s3.image.url", + "message": "Load is peaking. Make sure the traffic is real and spin up more webfronts", + "evalMatches": [ + { + "metric": "requests", + "tags": {}, + "value": 122 + } + ] +} + diff --git a/handler.go b/handler.go @@ -0,0 +1,63 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "net/http" +) + +// interface for parser functions (grafana, prometheus, ...) +type parserFunc func(*http.Request) (string, error) + +type messageHandler struct { + messages chan<- string // chan to xmpp client + parserFunc parserFunc +} + +// http request handler +func (h *messageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // parse/generate message from http request + m, err := h.parserFunc(r) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } + // send message to xmpp client + h.messages <- m + w.WriteHeader(http.StatusNoContent) +} + +// returns new handler with a given parser function +func newMessageHandler(m chan<- string, f parserFunc) *messageHandler { + return &messageHandler{ + messages: m, + parserFunc: f, + } +} + +/************* +GRAFANA PARSER +*************/ +func grafanaParserFunc(r *http.Request) (string, error) { + // get alert data from request + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return "", err + } + + // grafana alert struct + alert := &struct { + Title string `json:"title"` + RuleURL string `json:"ruleUrl"` + State string `json:"state"` + Message string `json:"message"` + }{} + + // parse body into the alert struct + err = json.Unmarshal(body, &alert) + if err != nil { + return "", err + } + + // contruct and return alert message + return alert.State + ": " + alert.Title + "/" + alert.Message + "(" + alert.RuleURL + ")", nil +} diff --git a/main.go b/main.go @@ -5,23 +5,10 @@ import ( "net/http" "os" "strings" - "time" "github.com/emgee/go-xmpp/src/xmpp" ) -const ( - envXMPPID = "XMPP_ID" - envXMPPPASS = "XMPP_PASS" - envXMPPReceivers = "XMPP_RECEIVERS" - errWrongArgs = "XMPP_ID, XMPP_PASS or XMPP_RECEIVERS not set" - xmppBotAnswer = "im a dumb bot" - xmppConnErr = "failed to connect " - xmppOfflineErr = "not connected to XMPP server, dropped message" - xmppFailedPause = 30 - webHookAddr = ":4321" -) - // starts xmpp session and returns the xmpp client func xmppLogin(id string, pass string) (*xmpp.XMPP, error) { // parse jid structure @@ -51,84 +38,65 @@ func xmppLogin(id string, pass string) (*xmpp.XMPP, error) { return client, nil } -// creates MessageBody slice suitable for xmpp.Message -func xmppBodyCreate(message string) []xmpp.MessageBody { - return []xmpp.MessageBody{ - xmpp.MessageBody{ - Value: message, - }, - } -} - -// handles incoming stanzas -func handleXMPPStanza(in <-chan interface{}, out chan<- interface{}) { - for stanza := range in { - // check if stanza is a message - message, ok := stanza.(*xmpp.Message) - if ok && len(message.Body) > 0 { - // send constant as answer - out <- xmpp.Message{ - To: message.From, - Body: xmppBodyCreate(xmppBotAnswer), - } - } - } - // func returns when in chan is closed (server terminated stream) -} - func main() { - // get xmpp credentials from ENV - xi := os.Getenv(envXMPPID) - xp := os.Getenv(envXMPPPASS) - xr := os.Getenv(envXMPPReceivers) + // get xmpp credentials and message receivers from env + xi := os.Getenv("XMPP_ID") + xp := os.Getenv("XMPP_PASS") + xr := os.Getenv("XMPP_RECEIVERS") // check if xmpp credentials and receiver list are supplied if len(xi) < 1 || len(xp) < 1 || len(xr) < 1 { - log.Fatal(errWrongArgs) + log.Fatal("XMPP_ID, XMPP_PASS or XMPP_RECEIVERS not set") } - // connect xmpp client and observe connection - reconnect if needed - var xc *xmpp.XMPP + // connect to xmpp server + xc, err := xmppLogin(xi, xp) + if err != nil { + log.Fatal(err) + } + + // announce initial presence + xc.Out <- xmpp.Presence{} + + // listen for incoming xmpp stanzas go func() { - for { - // try to connect to xmpp server - var err error - xc, err = xmppLogin(xi, xp) - if err != nil { - // report failure and wait - log.Print(xmppConnErr, err) - time.Sleep(time.Second * time.Duration(xmppFailedPause)) - } else { - // send initial presence and dispatch channels to handler for incoming messages - xc.Out <- xmpp.Presence{} - handleXMPPStanza(xc.In, xc.Out) + for stanza := range xc.In { + // check if stanza is a message + m, ok := stanza.(*xmpp.Message) + if ok && len(m.Body) > 0 { + // echo the message + xc.Out <- xmpp.Message{ + To: m.From, + Body: m.Body, + } } } + // xc.In is closed when the server closes the stream + log.Fatal("connection lost") }() - // create channel for alerts (Webhook -> XMPP) - alertChan := make(chan string) + // create chan for messages (webhooks -> xmpp) + messages := make(chan string) - // create handler for outgoing XMPP messages + // wait for messages from the webhooks and send them to all receivers go func() { - for message := range alertChan { + for m := range messages { for _, r := range strings.Split(xr, ",") { - if xc != nil { - xc.Out <- xmpp.Message{ - To: r, - Body: xmppBodyCreate(message), - } - } else { - log.Print(xmppOfflineErr) + xc.Out <- xmpp.Message{ + To: r, + Body: []xmpp.MessageBody{ + xmpp.MessageBody{ + Value: m, + }, + }, } } } }() - // initialize HTTP handlers with chan for alerts - grafanaHandler := makeGrafanaHandler(alertChan) - http.HandleFunc("/grafana", grafanaHandler) + // initialize handler for grafana alerts + http.Handle("/grafana", newMessageHandler(messages, grafanaParserFunc)) // listen for requests - http.ListenAndServe(webHookAddr, nil) + http.ListenAndServe(":4321", nil) }