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.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user