[TASK] add webhook support

This commit is contained in:
Martin/Geno 2018-09-05 21:00:02 +02:00
parent a9039fa290
commit 65047a6c8b
No known key found for this signature in database
GPG Key ID: 9D7D3C6BFF600C6A
15 changed files with 378 additions and 27 deletions

View File

@ -166,14 +166,14 @@ func (b *Bot) listMaxfilter(answer func(string), from string, params []string) {
if len(params) > 0 { if len(params) > 0 {
of = params[0] of = params[0]
} }
if filter, ok := b.db.NotifiesByAddress[of]; ok { if notify, ok := b.db.NotifiesByAddress[of]; ok {
msg = fmt.Sprintf("%s of %s is %s", msg, of, filter) msg = fmt.Sprintf("%s %s is %s", msg, of, notify.MaxPrioIn.String())
} }
} }
answer(msg) answer(msg)
} }
// set a filter to a mix // set a filter to a max
func (b *Bot) setMaxfilter(answer func(string), from string, params []string) { func (b *Bot) setMaxfilter(answer func(string), from string, params []string) {
if len(params) < 1 { if len(params) < 1 {
answer("invalid: CMD Priority\n or\n CMD Channel Priority") answer("invalid: CMD Priority\n or\n CMD Channel Priority")

View File

@ -66,7 +66,7 @@ var serverCmd = &cobra.Command{
go db.Alert(config.AlertCheck.Duration, out.Send) go db.Alert(config.AlertCheck.Duration, out.Send)
} }
log.Info("starting logmania") log.WithField("defaults", len(db.DefaultNotify)).Info("starting logmania")
if config.HTTPAddress != "" { if config.HTTPAddress != "" {
if config.Webroot != "" { if config.Webroot != "" {

View File

@ -89,6 +89,7 @@ func (db *DB) NewHost(addr string) *Host {
h := &Host{ h := &Host{
Address: addr, Address: addr,
NotifiesByAddress: make(map[string]*Notify), NotifiesByAddress: make(map[string]*Notify),
Lastseen: time.Now(),
} }
db.AddHost(h) db.AddHost(h)
return h return h

View File

@ -18,15 +18,15 @@ func Init(configInterface interface{}, exportChannel chan *log.Entry) input.Inpu
for inputType, init := range input.Register { for inputType, init := range input.Register {
configForItem := config[inputType] configForItem := config[inputType]
if configForItem == nil { if configForItem == nil {
log.Warnf("the input type '%s' has no configuration\n", inputType) log.Warnf("the input type '%s' has no configuration", inputType)
continue continue
} }
input := init(configForItem, exportChannel) in := init(configForItem, exportChannel)
if input == nil { if in == nil {
continue continue
} }
list = append(list, input) list = append(list, in)
} }
return &Input{ return &Input{
list: list, list: list,

View File

@ -4,4 +4,8 @@ import (
_ "dev.sum7.eu/genofire/logmania/input/journald_json" _ "dev.sum7.eu/genofire/logmania/input/journald_json"
_ "dev.sum7.eu/genofire/logmania/input/logrus" _ "dev.sum7.eu/genofire/logmania/input/logrus"
_ "dev.sum7.eu/genofire/logmania/input/syslog" _ "dev.sum7.eu/genofire/logmania/input/syslog"
_ "dev.sum7.eu/genofire/logmania/input/webhook"
_ "dev.sum7.eu/genofire/logmania/input/webhook/circleci"
_ "dev.sum7.eu/genofire/logmania/input/webhook/git"
_ "dev.sum7.eu/genofire/logmania/input/webhook/grafana"
) )

View File

@ -27,7 +27,7 @@ func Init(config interface{}, exportChannel chan *log.Entry) input.Input {
http.HandleFunc("/input/"+inputType, ws.Handler) http.HandleFunc("/input/"+inputType, ws.Handler)
input := &Input{ in := &Input{
input: inputMsg, input: inputMsg,
serverSocket: ws, serverSocket: ws,
exportChannel: exportChannel, exportChannel: exportChannel,
@ -35,7 +35,7 @@ func Init(config interface{}, exportChannel chan *log.Entry) input.Input {
logger.Info("init") logger.Info("init")
return input return in
} }
func (in *Input) Listen() { func (in *Input) Listen() {

View File

@ -37,14 +37,14 @@ func Init(configInterface interface{}, exportChannel chan *log.Entry) input.Inpu
logger.Error("init ", err) logger.Error("init ", err)
return nil return nil
} }
input := &Input{ in := &Input{
serverSocket: ln, serverSocket: ln,
exportChannel: exportChannel, exportChannel: exportChannel,
} }
logger.Info("init") logger.Info("init")
return input return in
} }
const maxDataGramSize = 8192 const maxDataGramSize = 8192

View File

@ -0,0 +1,55 @@
package circleci
import (
"fmt"
"net/http"
"time"
"github.com/mitchellh/mapstructure"
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/logmania/input/webhook"
)
type requestBody struct {
Payload struct {
VCSURL string `mapstructure:"vcs_url"`
Status string `mapstructure:"status"`
BuildNum float64 `mapstructure:"build_num"`
BuildURL string `mapstructure:"build_url"`
BuildTime float64 `mapstructure:"build_time_millis"`
Subject string `mapstructure:"subject"`
} `mapstructure:"payload"`
}
const webhookType = "circleci"
var HookstatusMap = map[string]log.Level{
"failed": log.ErrorLevel,
"success": log.InfoLevel,
}
var logger = log.WithField("input", webhook.InputType).WithField("hook", webhookType)
func handler(_ http.Header, body interface{}) *log.Entry {
var request requestBody
if err := mapstructure.Decode(body, &request); err != nil {
logger.Warnf("not able to decode data: %s", err)
return nil
}
if request.Payload.VCSURL == "" {
return nil
}
entry := log.NewEntry(nil)
entry = entry.WithField("hostname", request.Payload.VCSURL)
entry.Time = time.Now()
entry.Level = HookstatusMap[request.Payload.Status]
entry.Message = fmt.Sprintf("#%0.f (%0.fs): %s - %s", request.Payload.BuildNum, request.Payload.BuildTime/1000, request.Payload.Subject, request.Payload.BuildURL)
return entry
}
func init() {
webhook.AddHandler(webhookType, handler)
}

81
input/webhook/git/main.go Normal file
View File

@ -0,0 +1,81 @@
package git
import (
"net/http"
"time"
"github.com/mitchellh/mapstructure"
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/logmania/input/webhook"
)
type requestBody struct {
Repository struct {
HTMLURL string `mapstructure:"html_url"`
FullName string `mapstructure:"full_name"`
} `mapstructure:"repository"`
//push
Pusher struct {
Name string `mapstructure:"name"`
} `mapstructure:"pusher"`
Commits []struct {
Added []interface{} `mapstructure:"added"`
Removed []interface{} `mapstructure:"removed"`
Modified []interface{} `mapstructure:"modified"`
} `mapstructure:"commits"`
Compare string `mapstructure:"compare"`
Ref string `mapstructure:"ref"`
// issue + fallback
Sender struct {
Login string `mapstructure:"login"`
} `mapstructure:"sender"`
// issue
Action string `mapstructure:"action"`
Issue struct {
HTMLURL string `mapstructure:"html_url"`
Number float64 `mapstructure:"number"`
Title string `mapstructure:"title"`
} `mapstructure:"issue"`
}
const webhookType = "git"
var eventHeader = []string{"X-GitHub-Event", "X-Gogs-Event"}
var logger = log.WithField("input", webhook.InputType).WithField("hook", webhookType)
func handler(header http.Header, body interface{}) *log.Entry {
event := ""
for _, head := range eventHeader {
event = header.Get(head)
if event != "" {
break
}
}
if event == "status" {
return nil
}
var request requestBody
if err := mapstructure.Decode(body, &request); err != nil {
logger.Warnf("not able to decode data: %s", err)
return nil
}
if request.Repository.HTMLURL == "" {
return nil
}
entry := log.NewEntry(nil)
entry = entry.WithField("hostname", request.Repository.HTMLURL)
entry.Time = time.Now()
entry.Level = log.InfoLevel
entry.Message = RequestToString(event, request)
return entry
}
func init() {
webhook.AddHandler(webhookType, handler)
}

75
input/webhook/git/msg.go Normal file
View File

@ -0,0 +1,75 @@
package git
import (
"fmt"
"strings"
)
var eventMsg = map[string]string{
"commit_comment_created": "Commit comment",
"status_error": "Commit status: error",
"status_failure": "Commit status: failure",
"status_pending": "Commit status: pending",
"status_success": "Commit status: success",
"create_branch": "Create branch",
"create_tag": "Create tag",
"delete_branch": "Delete branch",
"delete_tag": "Delete tag",
"issue_comment_created": "Issue comment",
"issue_comment_deleted": "Issue comment: deleted",
"issue_comment_edited": "Issue comment: edited",
"issue_assigned": "Issue: assigned",
"issue_closed": "Issue: closed",
"issue_edited": "Issue: edited",
"issue_labeled": "Issue: labeled",
"issue_opened": "Issue: opened",
"issue_reopened": "Issue: reopened",
"issue_unassigned": "Issue: unassigned",
"issue_unlabeled": "Issue: unlabeled",
"pr_review_created": "Pull request review comment",
"pr_review_deleted": "Pull request review comment: deleted",
"pr_review_edited": "Pull request review comment: edited",
"pr_assigned": "Pull request: assigned",
"pr_closed": "Pull request: closed",
"pr_edited": "Pull request: edited",
"pr_labeled": "Pull request: labeled",
"pr_opened": "Pull request: opened",
"pr_reopened": "Pull request: reopened",
"pr_synchronize": "Pull request: synchronize",
"pr_unassigned": "Pull request: unassigned",
"pr_unlabeled": "Pull request: unlabeled",
"push": "Push",
"release_published": "Release published",
"member_added": "Repo: added collaborator",
"team_add": "Repo: added to a team",
"fork": "Repo: forked",
"public": "Repo: made public",
"watch_started": "Repo: starred",
"gollum_created": "Wiki: created page",
"gollum_edited": "Wiki: edited page",
}
func RequestToString(event string, request requestBody) string {
msg := fmt.Sprintf("[%s]", request.Repository.FullName)
if event == "push" {
added := 0
removed := 0
modified := 0
for _, commit := range request.Commits {
added += len(commit.Added)
removed += len(commit.Removed)
modified += len(commit.Modified)
}
msg = fmt.Sprintf("%s %s - pushed %d commit(s) to %s [+%d/-%d/\u00B1%d]: %s", msg, request.Pusher.Name, len(request.Commits), strings.TrimLeft(request.Ref, "refs/heads/"), added, removed, modified, request.Compare)
} else if event == "issues" || event == "issue_comment" {
msg = fmt.Sprintf("%s %s - %s action #%.0f: %s - %s", msg, request.Sender.Login, request.Action, request.Issue.Number, request.Issue.Title, request.Issue.HTMLURL)
} else {
text := eventMsg[event]
if text == "" {
text = event
}
msg = fmt.Sprintf("%s %s - %s", msg, request.Sender.Login, text)
}
return msg
}

View File

@ -0,0 +1,67 @@
package grafana
import (
"fmt"
"net/http"
"time"
"github.com/mitchellh/mapstructure"
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/logmania/input/webhook"
)
type evalMatch struct {
Tags map[string]string `mapstructure:"tags,omitempty"`
Metric string `mapstructure:"metric"`
Value float64 `mapstructure:"value"`
}
type requestBody struct {
Title string `mapstructure:"title"`
State string `mapstructure:"state"`
RuleID int64 `mapstructure:"ruleId"`
RuleName string `mapstructure:"ruleName"`
RuleURL string `mapstructure:"ruleUrl"`
EvalMatches []evalMatch `mapstructure:"evalMatches"`
ImageURL string `mapstructure:"imageUrl"`
Message string `mapstructure:"message"`
}
const webhookType = "grafana"
var HookstateMap = map[string]log.Level{
"no_data": log.ErrorLevel,
"paused": log.InfoLevel,
"alerting": log.ErrorLevel,
"ok": log.InfoLevel,
"pending": log.WarnLevel,
}
var logger = log.WithField("input", webhook.InputType).WithField("hook", webhookType)
func handler(_ http.Header, body interface{}) *log.Entry {
var request requestBody
if err := mapstructure.Decode(body, &request); err != nil {
logger.Warnf("not able to decode data: %s", err)
return nil
}
if request.RuleURL == "" {
return nil
}
entry := log.NewEntry(nil)
entry = entry.WithField("hostname", request.RuleURL)
entry.Time = time.Now()
entry.Level = HookstateMap[request.State]
if request.Message == "" {
entry.Message = fmt.Sprintf("%s - %s: %s", request.Title, request.State, request.RuleURL)
} else {
entry.Message = fmt.Sprintf("%s - %s: %s %s", request.Title, request.State, request.Message, request.RuleURL)
}
return entry
}
func init() {
webhook.AddHandler(webhookType, handler)
}

15
input/webhook/internal.go Normal file
View File

@ -0,0 +1,15 @@
package webhook
import (
"net/http"
log "github.com/sirupsen/logrus"
)
type WebhookHandler func(http.Header, interface{}) *log.Entry
var handlers = make(map[string]WebhookHandler)
func AddHandler(name string, f WebhookHandler) {
handlers[name] = f
}

56
input/webhook/main.go Normal file
View File

@ -0,0 +1,56 @@
package webhook
import (
"fmt"
"net/http"
libHTTP "github.com/genofire/golang-lib/http"
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/logmania/input"
)
const InputType = "webhook"
var logger = log.WithField("input", InputType)
type Input struct {
input.Input
exportChannel chan *log.Entry
}
func Init(config interface{}, exportChannel chan *log.Entry) input.Input {
logger.Info("init")
return &Input{
exportChannel: exportChannel,
}
}
func (in *Input) getHTTPHandler(name string, h WebhookHandler) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var body interface{}
libHTTP.Read(r, &body)
e := h(r.Header, body)
if e == nil {
http.Error(w, fmt.Sprintf("no able to generate log for handler-request %s", name), http.StatusInternalServerError)
return
}
in.exportChannel <- e
http.Error(w, fmt.Sprintf("handler-request %s - ok", name), http.StatusOK)
}
}
func (in *Input) Listen() {
for name, h := range handlers {
http.HandleFunc("/input/"+InputType+"/"+name, in.getHTTPHandler(name, h))
}
}
func (in *Input) Close() {
}
func init() {
input.Add(InputType, Init)
}

View File

@ -21,7 +21,7 @@ func Init(configInterface interface{}, db *database.DB, bot *bot.Bot) output.Out
config := configInterface.(map[string]interface{}) config := configInterface.(map[string]interface{})
var list []output.Output var list []output.Output
var defaults []*database.Notify
for outputType, init := range output.Register { for outputType, init := range output.Register {
configForItem := config[outputType] configForItem := config[outputType]
if configForItem == nil { if configForItem == nil {
@ -35,14 +35,12 @@ func Init(configInterface interface{}, db *database.DB, bot *bot.Bot) output.Out
} }
list = append(list, notify) list = append(list, notify)
def := notify.Default() def := notify.Default()
if def != nil { if def == nil {
continue continue
} }
defaults = append(defaults, def...) db.DefaultNotify = append(db.DefaultNotify, def...)
} }
db.DefaultNotify = defaults
out := &Output{ out := &Output{
db: db, db: db,
list: list, list: list,

View File

@ -143,7 +143,13 @@ func Init(configInterface interface{}, db *database.DB, bot *bot.Bot) output.Out
logger.WithField("jid", config.JID).Info("startup") logger.WithField("jid", config.JID).Info("startup")
var defaults []*database.Notify out := &Output{
channels: channels,
client: client,
formatter: &log.TextFormatter{
DisableTimestamp: true,
},
}
for to, muc := range config.Defaults { for to, muc := range config.Defaults {
def := &database.Notify{ def := &database.Notify{
Protocol: proto, Protocol: proto,
@ -152,16 +158,9 @@ func Init(configInterface interface{}, db *database.DB, bot *bot.Bot) output.Out
if muc { if muc {
def.Protocol = protoGroup def.Protocol = protoGroup
} }
defaults = append(defaults, def) out.defaults = append(out.defaults, def)
}
return &Output{
channels: channels,
defaults: defaults,
client: client,
formatter: &log.TextFormatter{
DisableTimestamp: true,
},
} }
return out
} }
func (out *Output) Default() []*database.Notify { func (out *Output) Default() []*database.Notify {