package core
import (
"fmt"
"strings"
"time"
log "github.com/sirupsen/logrus"
)
var Notifiers = map [ string ] AbstractNotifier {
"email" : EmailNotifier { } ,
"slack" : SlackNotifier { } ,
"xmpp" : XmppNotifier { } ,
}
type AbstractNotifier interface {
New ( config TransportConfig ) ( AbstractNotifier , error )
Submit ( notification Notification ) error
}
type Notification struct {
Status string // one of CheckOk | CheckErr
Body string
Subject string
}
//////
func ( box Box ) GetNotifier ( ) ( AbstractNotifier , error ) {
return GetNotifier ( & box . NotifyConf . Notifications )
}
func GetNotifier ( config * TransportConfig ) ( AbstractNotifier , error ) {
transport := config . Transport
if transport == "" {
return nil , fmt . Errorf ( "No notification transport provided" )
}
notifier := Notifiers [ transport ]
if notifier == nil {
return nil , fmt . Errorf ( "%s is not a supported notification transport" , transport )
}
return notifier . New ( * config )
}
func ( results BoxCheckResults ) SendNotifications ( notifyTypes [ ] string , useCache bool ) error {
if useCache {
results = results . filterChangedFromCache ( )
}
toCheck := results . Size ( notifyTypes )
if toCheck == 0 {
log . Info ( "No notifications due." )
} else {
log . Infof ( "Notifying for %v checks changing state to %v..." , toCheck , notifyTypes )
}
errs := [ ] string { }
for box , resultsBox := range results {
// only submit results which are errors
resultsDue := [ ] CheckResult { }
for _ , result := range resultsBox {
if result . HasStatus ( notifyTypes ) {
resultsDue = append ( resultsDue , result )
}
}
transport := box . NotifyConf . Notifications . Transport
notifyLog := log . WithFields ( log . Fields {
"boxId" : box . Id ,
"transport" : transport ,
} )
if len ( resultsDue ) != 0 {
notifier , err := box . GetNotifier ( )
if err != nil {
notifyLog . Error ( err )
errs = append ( errs , err . Error ( ) )
continue
}
notification := ComposeNotification ( box , resultsDue )
var submitErr error
submitErr = notifier . Submit ( notification )
for retry := 0 ; submitErr != nil && retry < 2 ; retry ++ {
notifyLog . Warnf ( "sending notification failed (retry %v): %v" , retry , submitErr )
time . Sleep ( 10 * time . Second )
submitErr = notifier . Submit ( notification )
}
if submitErr != nil {
notifyLog . Error ( submitErr )
errs = append ( errs , submitErr . Error ( ) )
continue
}
}
// update cache (with /all/ changed results to reset status)
if useCache {
notifyLog . Debug ( "updating cache" )
updateCache ( box , resultsBox )
}
if len ( resultsDue ) != 0 {
notifyLog . Infof ( "Sent notification for %s via %s with %v updated issues" , box . Name , transport , len ( resultsDue ) )
}
}
// persist changes to cache
if useCache {
err := writeCache ( )
if err != nil {
log . Error ( "could not write cache of notification results: " , err )
errs = append ( errs , err . Error ( ) )
}
}
if len ( errs ) != 0 {
return fmt . Errorf ( strings . Join ( errs , "\n" ) )
}
return nil
}
func ComposeNotification ( box * Box , checks [ ] CheckResult ) Notification {
errTexts := [ ] string { }
resolvedTexts := [ ] string { }
for _ , check := range checks {
if check . Status == CheckErr {
errTexts = append ( errTexts , check . String ( ) )
} else {
resolvedTexts = append ( resolvedTexts , check . String ( ) )
}
}
var (
resolved string
resolvedList string
errList string
status string
)
if len ( resolvedTexts ) != 0 {
resolvedList = fmt . Sprintf ( "Resolved issue(s):\n\n%s\n\n" , strings . Join ( resolvedTexts , "\n" ) )
}
if len ( errTexts ) != 0 {
errList = fmt . Sprintf ( "New issue(s):\n\n%s\n\n" , strings . Join ( errTexts , "\n" ) )
status = CheckErr
} else {
resolved = "resolved "
status = CheckOk
}
return Notification {
Status : status ,
Subject : fmt . Sprintf ( "Issues %swith your box \"%s\" on opensensemap.org!" , resolved , box . Name ) ,
Body : fmt . Sprintf ( "A check at %s identified the following updates for your box \"%s\":\n\n%s%sYou may visit https://opensensemap.org/explore/%s for more details." ,
time . Now ( ) . Round ( time . Minute ) , box . Name , errList , resolvedList , box . Id ) ,
}
}