You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
4.5 KiB
Go

package core
import (
"fmt"
"strings"
"time"
log "github.com/sirupsen/logrus"
)
var Notifiers = map[string]AbstractNotifier{
"email": EmailNotifier{},
"slack": SlackNotifier{},
"discord": DiscordNotifier{},
"xmpp": XmppNotifier{},
}
type AbstractNotifier interface {
New(config TransportConfig) (AbstractNotifier, error)
Submit(notification Notification) error
}
// per-target configuration for a notifier
type NotifierOpts struct {
Recipients []string `mapstructure:"recipients"`
}
type Notification struct {
Status string // one of CheckOk | CheckErr
Body string
Subject string
}
//////
func (t Target) GetNotifier() (AbstractNotifier, error) {
return GetNotifier(&t.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 TargetCheckResults) SendNotifications(notifyTypes []string, useCache bool) error {
if useCache {
results.checkStaleness()
results = results.filterUnnotifiedFromCache()
}
if toCheck := results.Size(notifyTypes); toCheck == 0 {
log.Info("No notifications due.")
return nil
} else {
log.Infof("Notifying for %v checks with state '%v'...", toCheck, strings.Join(notifyTypes, "' or '"))
}
errs := []string{}
for target, resultsBox := range results {
// only submit results which are errors
resultsDue := []*CheckResult{}
for _, result := range resultsBox {
if result.HasStatus(notifyTypes) {
resultsDue = append(resultsDue, result)
}
}
transport := target.Notifications.Transport
notifyLog := log.WithFields(log.Fields{
"targetID": target.ID,
"transport": transport,
})
if len(resultsDue) != 0 {
notifier, err := target.GetNotifier()
if err != nil {
notifyLog.Error(err)
errs = append(errs, err.Error())
continue
}
notification := ComposeNotification(target, 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(target, resultsBox)
}
if len(resultsDue) != 0 {
notifyLog.Infof("Sent notification for %s via %s with %v updated issues", target.ID, 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 {
err := fmt.Errorf(strings.Join(errs, "\n"))
// done <- err
return err
}
// done <- nil
return nil
}
func ComposeNotification(t *Target, checks []*CheckResult) Notification {
staleTexts := []string{}
errTexts := []string{}
resolvedTexts := []string{}
for _, check := range checks {
if check.Status == CheckErr && check.Stale != 0 {
staleTexts = append(staleTexts, check.String())
} else if check.Status == CheckErr {
errTexts = append(errTexts, check.String())
} else {
resolvedTexts = append(resolvedTexts, check.String())
}
}
var (
kinds []string
resolvedList string
errList string
staleList string
status string
)
if len(resolvedTexts) != 0 {
kinds = append(kinds, "resolved")
resolvedList = fmt.Sprintf("Resolved issue(s):\n\n%s\n\n", strings.Join(resolvedTexts, "\n"))
}
if len(errTexts) != 0 {
kinds = append(kinds, "new")
errList = fmt.Sprintf("New issue(s):\n\n%s\n\n", strings.Join(errTexts, "\n"))
}
if len(staleTexts) != 0 {
kinds = append(kinds, "stale")
staleList = fmt.Sprintf("Stale issue(s):\n\n%s\n\n", strings.Join(staleTexts, "\n"))
}
if errList != "" || staleList != "" {
status = CheckErr
} else {
status = CheckOk
}
return Notification{
Status: status,
Subject: fmt.Sprintf("%s issues on '%s'!", strings.Join(kinds, ", "), t.ID),
Body: fmt.Sprintf("A check at %s identified the following updates for your target \"%s\":\n\n%s%s%s",
time.Now().Round(time.Minute), t.ID, errList, resolvedList, staleList),
}
}