You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
scrutiny/webapp/backend/pkg/notify/notify.go

162 lines
4.8 KiB

package notify
import (
"bytes"
"encoding/json"
"fmt"
"github.com/AnalogJ/go-util/utils"
"github.com/analogj/scrutiny/webapp/backend/pkg/config"
"github.com/containrrr/shoutrrr"
log "github.com/sirupsen/logrus"
"net/http"
"os"
"strings"
"sync"
"time"
)
type Payload struct {
Mailer string `json:"mailer"`
Subject string `json:"subject"`
Date string `json:"date"`
FailureType string `json:"failure_type"`
Device string `json:"device"`
DeviceType string `json:"device_type"`
DeviceString string `json:"device_string"`
Message string `json:"message"`
}
type Notify struct {
Config config.Interface
Payload Payload
}
func (n *Notify) Send(level string, payload interface{}) error {
//validate that the Payload is populated
sendDate := time.Now()
n.Payload.Date = sendDate.Format(time.RFC3339)
//retrieve list of notification endpoints from config file
configUrls := n.Config.GetStringSlice("notify.urls")
//remove http:// https:// and script:// prefixed urls
notifyWebhooks := []string{}
notifyScripts := []string{}
notifyShoutrrr := []string{}
for _, url := range configUrls {
if strings.HasPrefix(url, "https://") || strings.HasPrefix(url, "http://") {
notifyWebhooks = append(notifyWebhooks, url)
} else if strings.HasPrefix(url, "script://") {
notifyScripts = append(notifyScripts, url)
} else {
notifyShoutrrr = append(notifyShoutrrr, url)
}
}
//run all scripts, webhooks and shoutrr commands in parallel
var wg sync.WaitGroup
for _, notifyWebhook := range notifyWebhooks {
// execute collection in parallel go-routines
wg.Add(1)
go n.SendWebhookNotification(&wg, notifyWebhook)
}
for _, notifyScript := range notifyScripts {
// execute collection in parallel go-routines
wg.Add(1)
go n.SendScriptNotification(&wg, notifyScript)
}
if len(notifyScripts) > 0 {
wg.Add(1)
go n.SendShoutrrrNotification(&wg, notifyShoutrrr)
}
//and wait for completion, error or timeout.
if waitTimeout(&wg, time.Minute) { //wait for 1 minute
fmt.Println("Timed out while sending notifications")
} else {
fmt.Println("Sent notifications. Check logs for more information.")
}
return nil
}
func (n *Notify) SendWebhookNotification(wg *sync.WaitGroup, webhookUrl string) {
defer wg.Done()
log.Infof("Sending Webhook to %s", webhookUrl)
requestBody, err := json.Marshal(n.Payload)
if err != nil {
log.Errorf("An error occurred while sending Webhook to %s: %v", webhookUrl, err)
return
}
resp, err := http.Post(webhookUrl, "application/json", bytes.NewBuffer(requestBody))
if err != nil {
log.Errorf("An error occurred while sending Webhook to %s: %v", webhookUrl, err)
return
}
defer resp.Body.Close()
//we don't care about resp body content, but maybe we should log it?
}
func (n *Notify) SendScriptNotification(wg *sync.WaitGroup, scriptUrl string) {
defer wg.Done()
//check if the script exists.
scriptPath := strings.TrimPrefix(scriptUrl, "script://")
log.Infof("Executing Script %s", scriptPath)
if !utils.FileExists(scriptPath) {
log.Errorf("Script does not exist: %s", scriptPath)
return
}
copyEnv := os.Environ()
copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_MAILER=%s", n.Payload.Mailer))
copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_SUBJECT=%s", n.Payload.Subject))
copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_DATE=%s", n.Payload.Date))
copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_FAILURE_TYPE=%s", n.Payload.FailureType))
copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_DEVICE=%s", n.Payload.Device))
copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_DEVICE_TYPE=%s", n.Payload.DeviceType))
copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_DEVICE_STRING=%s", n.Payload.DeviceString))
copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_MESSAGE=%s", n.Payload.Message))
err := utils.CmdExec(scriptPath, []string{}, "", copyEnv, "")
if err != nil {
log.Errorf("An error occurred while executing script %s: %v", scriptPath, err)
}
return
}
func (n *Notify) SendShoutrrrNotification(wg *sync.WaitGroup, shoutrrrUrls []string) {
log.Infof("Sending notifications to %v", shoutrrrUrls)
defer wg.Done()
sender, err := shoutrrr.CreateSender(shoutrrrUrls...)
if err != nil {
log.Errorf("An error occurred while sending notifications %v: %v", shoutrrrUrls, err)
return
}
errs := sender.Send(n.Payload.Subject, nil) //structs.Map(n.Payload).())
if len(errs) > 0 {
log.Errorf("One or more errors occurred occurred while sending notifications %v:\n %v", shoutrrrUrls, errs)
}
}
//utility functions
// waitTimeout waits for the waitgroup for the specified max timeout.
// Returns true if waiting timed out.
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
c := make(chan struct{})
go func() {
defer close(c)
wg.Wait()
}()
select {
case <-c:
return false // completed normally
case <-time.After(timeout):
return true // timed out
}
}