9bb4b09a04
CI / Lint + build + test (push) Has been cancelled
Post-repair hardware validation pipeline for Proxmox cluster hosts. Go orchestrator + in-image agent + mkosi live image + bundled dnsmasq PXE + SQLite + HTMX/SSE UI + notify registry + janitor + full docs.
88 lines
2.1 KiB
Go
88 lines
2.1 KiB
Go
package notify
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// DiscordNotifier posts to a Discord incoming webhook. Body is rendered
|
|
// as a single embed so Discord shows a colored sidebar matching event
|
|
// severity. Discord rejects empty content+embeds; we always include the
|
|
// embed so that never happens.
|
|
type DiscordNotifier struct {
|
|
NameStr string
|
|
WebhookURL string
|
|
HTTP *http.Client
|
|
}
|
|
|
|
func NewDiscord(name, webhookURL string) *DiscordNotifier {
|
|
return &DiscordNotifier{
|
|
NameStr: name,
|
|
WebhookURL: webhookURL,
|
|
HTTP: &http.Client{Timeout: 10 * time.Second},
|
|
}
|
|
}
|
|
|
|
func (d *DiscordNotifier) Name() string { return d.NameStr }
|
|
|
|
type discordPayload struct {
|
|
Embeds []discordEmbed `json:"embeds"`
|
|
}
|
|
|
|
type discordEmbed struct {
|
|
Title string `json:"title,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
URL string `json:"url,omitempty"`
|
|
Color int `json:"color,omitempty"`
|
|
}
|
|
|
|
func (d *DiscordNotifier) Send(ctx context.Context, ev Event) error {
|
|
if d.WebhookURL == "" {
|
|
return fmt.Errorf("discord: no webhook_url configured")
|
|
}
|
|
payload := discordPayload{Embeds: []discordEmbed{{
|
|
Title: ev.Title,
|
|
Description: ev.Body,
|
|
URL: ev.URL,
|
|
Color: discordColor(ev.Severity),
|
|
}}}
|
|
buf, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, d.WebhookURL, bytes.NewReader(buf))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := d.HTTP.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
if resp.StatusCode >= 300 {
|
|
b, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("discord: %d: %s", resp.StatusCode, strings.TrimSpace(string(b)))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// discordColor returns the embed sidebar color for each severity.
|
|
// Values are standard Discord decimal color codes.
|
|
func discordColor(s Severity) int {
|
|
switch s {
|
|
case SeverityCritical:
|
|
return 0xE74C3C // red
|
|
case SeverityWarning:
|
|
return 0xF1C40F // yellow
|
|
default:
|
|
return 0x2ECC71 // green
|
|
}
|
|
}
|