Add Sandbar alert channel, tags for resources and sites, use Reef's secretbox
CI / test (push) Successful in 4m52s
Release / release (push) Successful in 5m24s

Sandbar channels raise and resolve alerts through its API, with the API key
sealed at rest and never returned to the browser. Resources and sites can be
tagged and filtered by tag; sites can be renamed. The secretbox package now
comes from Reef v0.1.9 instead of a private copy.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VeillaxandClaude Sonnet 5.5 committed 2026-10-27 03:32:43 -06:00
1 parent 38ab4f7db2
commit 227c890409
17 files changed
+386 -196

No files matched your search

+55 -1
View File
@@ -10,8 +10,8 @@ import (
"testing"
"time"
"git.veillax.com/Veillax/hammerhead/internal/secretbox"
"git.veillax.com/Veillax/reef/model"
"git.veillax.com/Veillax/reef/secretbox"
)
type hook struct {
@@ -487,3 +487,57 @@ func TestPromEscape(t *testing.T) {
t.Errorf("promEscape = %q", got)
}
}
func TestSandbarChannelRaisesAndResolves(t *testing.T) {
f := newFx(t)
h := newHook(t)
f.s.mu.Lock()
f.s.st.Channels = []*AlertChannel{{ID: "c2", OrgID: f.org, Name: "pager", Kind: "sandbar", Target: strings.TrimSuffix(h.URL, "/hook/secret-token"),
Token: "sb_secret", Policy: "pol1", AllowPrivate: true, Enabled: true}}
f.s.mu.Unlock()
f.eval(f.base)
f.setHealthy(false)
f.eval(f.base.Add(10 * time.Second))
f.eval(f.base.Add(3 * time.Minute))
var up map[string]string
if err := json.Unmarshal([]byte(h.next(2*time.Second)), &up); err != nil {
t.Fatal("no Sandbar request:", err)
}
if up["key"] != "hammerhead:target:m1:0" || up["policy"] != "pol1" || up["severity"] != "critical" || !strings.Contains(up["summary"], "Minecraft") {
t.Fatalf("unexpected alert sent to Sandbar: %v", up)
}
h.mu.Lock()
auth := h.hdrs[0].Get("Authorization")
h.mu.Unlock()
if auth != "Bearer sb_secret" {
t.Fatalf("API key not sent: %q", auth)
}
f.setHealthy(true)
f.eval(f.base.Add(4 * time.Minute))
var down map[string]string
if err := json.Unmarshal([]byte(h.next(2*time.Second)), &down); err != nil {
t.Fatal("no resolve request:", err)
}
if down["key"] != up["key"] || down["status"] != "resolved" {
t.Fatalf("recovery did not resolve the same alert: %v", down)
}
}
func TestSandbarChannelValidation(t *testing.T) {
f := newFx(t)
for _, ch := range []AlertChannel{
{Kind: "sandbar", Target: "not a url", Token: "k"},
{Kind: "sandbar", Target: "https://sandbar.example.com"}, // no API key
} {
ch := ch
if err := f.s.validateChannel(&ch, true); err == nil {
t.Fatalf("accepted %+v", ch)
}
}
ok := AlertChannel{Kind: "sandbar", Target: "https://sandbar.example.com", Token: " k ", Policy: " p "}
if err := f.s.validateChannel(&ok, true); err != nil || ok.Token != "k" || ok.Policy != "p" {
t.Fatalf("%v %+v", err, ok)
}
}