94 lines
2.6 KiB
Go
94 lines
2.6 KiB
Go
package alerts
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// AlwaysOnDisplay sends each alert to the session API used by the wall
|
|
// display. A session is created for the alert and then given a status and log
|
|
// entry so the display can show both the current incident and its details.
|
|
type AlwaysOnDisplay struct {
|
|
URL string
|
|
Client *http.Client
|
|
}
|
|
|
|
func (a *AlwaysOnDisplay) Name() string { return "always-on-display" }
|
|
|
|
type aodSession struct {
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
func (a *AlwaysOnDisplay) Send(ctx context.Context, alert Alert) error {
|
|
client := a.Client
|
|
if client == nil {
|
|
client = &http.Client{Timeout: 15 * time.Second}
|
|
}
|
|
base := strings.TrimRight(a.URL, "/")
|
|
|
|
var session aodSession
|
|
name := fmt.Sprintf("%s alert: %s", strings.ToUpper(string(alert.Priority())), alert.Spike.Service)
|
|
if alert.Test {
|
|
name = "TEST alert: " + alert.Spike.Service
|
|
}
|
|
if err := postJSONDecode(ctx, client, base+"/api/sessions", nil, map[string]string{"name": name}, &session); err != nil {
|
|
return err
|
|
}
|
|
if session.ID == "" {
|
|
return fmt.Errorf("always-on-display: session response did not include an id")
|
|
}
|
|
|
|
status := firstLine(alert.Text(""))
|
|
if err := postJSON(ctx, client, base+"/api/sessions/"+session.ID+"/status", nil, map[string]string{"status": status}); err != nil {
|
|
return err
|
|
}
|
|
level := "info"
|
|
if alert.Priority() == PriorityHigh {
|
|
level = "error"
|
|
} else if alert.Priority() == PriorityMedium {
|
|
level = "warning"
|
|
}
|
|
return postJSON(ctx, client, base+"/api/sessions/"+session.ID+"/log", nil, map[string]string{
|
|
"level": level,
|
|
"message": alert.Text(""),
|
|
})
|
|
}
|
|
|
|
func firstLine(s string) string {
|
|
if i := strings.IndexByte(s, '\n'); i >= 0 {
|
|
return s[:i]
|
|
}
|
|
return s
|
|
}
|
|
|
|
func postJSONDecode(ctx context.Context, client *http.Client, target string, headers map[string]string, body any, out any) error {
|
|
raw, err := json.Marshal(body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, target, strings.NewReader(string(raw)))
|
|
if err != nil {
|
|
return fmt.Errorf("always-on-display: invalid request")
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
for k, v := range headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("always-on-display: request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode/100 != 2 {
|
|
return fmt.Errorf("always-on-display: HTTP %d", resp.StatusCode)
|
|
}
|
|
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
|
|
return fmt.Errorf("always-on-display: invalid session response: %w", err)
|
|
}
|
|
return nil
|
|
}
|