- Rename Go module switchboard-core → armature (155+ files) - Rename Docker image → gobha/armature - Rename K8s resources, secrets, deployments - Rename Prometheus metrics switchboard_* → armature_* - Rename env vars SWITCHBOARD_ADMIN_* → ARMATURE_ADMIN_* - Rename DB names switchboard_core* → armature* - Update all frontend branding, notification templates, docs - Update CI scripts, e2e tests, Keycloak realm, nginx conf - Rename scripts/switchboard-ca.sh → scripts/armature-ca.sh - Rename k8s/switchboard.yaml → k8s/armature.yaml - Rename chart alerting/dashboard files - Fix: DockerHub push uses env: binding for secret injection - Helm chart updated (name, labels, template functions, dashboard, alerting) - Replace favicon/icon assets with Armature brand No functional changes. Pure mechanical rename + CI fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
193 lines
4.8 KiB
Go
193 lines
4.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"log"
|
|
|
|
"armature/models"
|
|
"armature/store"
|
|
"armature/triggers"
|
|
)
|
|
|
|
// manifestTrigger is the manifest.json trigger declaration shape.
|
|
type manifestTrigger struct {
|
|
Type string `json:"type"` // "event" or "webhook"
|
|
Pattern string `json:"pattern"` // event pattern (for type=event)
|
|
Slug string `json:"slug"` // webhook slug (for type=webhook)
|
|
EntryPoint string `json:"entry_point"` // Starlark function name
|
|
}
|
|
|
|
// SyncManifestTriggers parses the "triggers" array from a package manifest
|
|
// and syncs them with the trigger store. Removes triggers no longer in the
|
|
// manifest (declarative sync). For webhooks, generates an HMAC secret on
|
|
// first create.
|
|
//
|
|
// Called from admin install and package install.
|
|
func SyncManifestTriggers(ctx context.Context, stores store.Stores, engine *triggers.Engine, pkgID string, manifest map[string]any) {
|
|
if stores.Triggers == nil {
|
|
return
|
|
}
|
|
|
|
// Parse triggers from manifest
|
|
triggersRaw, ok := manifest["triggers"]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
data, err := json.Marshal(triggersRaw)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var declared []manifestTrigger
|
|
if err := json.Unmarshal(data, &declared); err != nil {
|
|
log.Printf(" ⚠️ trigger-sync[%s]: invalid triggers in manifest: %v", pkgID, err)
|
|
return
|
|
}
|
|
|
|
if len(declared) == 0 {
|
|
return
|
|
}
|
|
|
|
// Get existing triggers for this package
|
|
existing, err := stores.Triggers.ListByPackage(ctx, pkgID)
|
|
if err != nil {
|
|
log.Printf(" ⚠️ trigger-sync[%s]: failed to list existing: %v", pkgID, err)
|
|
return
|
|
}
|
|
|
|
// Build lookup maps
|
|
existingByKey := make(map[string]*models.Trigger, len(existing))
|
|
for i := range existing {
|
|
key := triggerKey(&existing[i])
|
|
existingByKey[key] = &existing[i]
|
|
}
|
|
|
|
declaredKeys := make(map[string]bool, len(declared))
|
|
created, updated := 0, 0
|
|
|
|
for _, dt := range declared {
|
|
if dt.Type != models.TriggerTypeEvent && dt.Type != models.TriggerTypeWebhook {
|
|
log.Printf(" ⚠️ trigger-sync[%s]: unknown trigger type %q, skipping", pkgID, dt.Type)
|
|
continue
|
|
}
|
|
if dt.EntryPoint == "" {
|
|
continue
|
|
}
|
|
|
|
key := dt.Type + ":" + triggerIdentifier(dt)
|
|
declaredKeys[key] = true
|
|
|
|
if ex, ok := existingByKey[key]; ok {
|
|
// Update if entry_point changed
|
|
if ex.EntryPoint != dt.EntryPoint {
|
|
ex.EntryPoint = dt.EntryPoint
|
|
if err := stores.Triggers.Update(ctx, ex); err != nil {
|
|
log.Printf(" ⚠️ trigger-sync[%s]: update failed: %v", pkgID, err)
|
|
} else {
|
|
updated++
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Create new trigger
|
|
t := &models.Trigger{
|
|
PackageID: pkgID,
|
|
Type: dt.Type,
|
|
Enabled: true,
|
|
EntryPoint: dt.EntryPoint,
|
|
Config: json.RawMessage("{}"),
|
|
}
|
|
|
|
switch dt.Type {
|
|
case models.TriggerTypeEvent:
|
|
t.EventPattern = dt.Pattern
|
|
case models.TriggerTypeWebhook:
|
|
t.Slug = dt.Slug
|
|
t.Secret = generateSecret()
|
|
}
|
|
|
|
if err := stores.Triggers.Create(ctx, t); err != nil {
|
|
log.Printf(" ⚠️ trigger-sync[%s]: create failed: %v", pkgID, err)
|
|
continue
|
|
}
|
|
created++
|
|
|
|
// Register with engine
|
|
if engine != nil {
|
|
// Re-fetch to get the generated ID
|
|
switch dt.Type {
|
|
case models.TriggerTypeWebhook:
|
|
if wh, _ := stores.Triggers.GetWebhook(ctx, pkgID, dt.Slug); wh != nil {
|
|
engine.RegisterTrigger(wh)
|
|
}
|
|
case models.TriggerTypeEvent:
|
|
// Need to fetch by package to find the new one
|
|
triggers, _ := stores.Triggers.ListByPackage(ctx, pkgID)
|
|
for i := range triggers {
|
|
if triggers[i].EventPattern == dt.Pattern {
|
|
engine.RegisterTrigger(&triggers[i])
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove triggers no longer in manifest
|
|
removed := 0
|
|
for key, ex := range existingByKey {
|
|
if !declaredKeys[key] {
|
|
if engine != nil {
|
|
engine.UnregisterTrigger(ex.ID)
|
|
}
|
|
if err := stores.Triggers.Delete(ctx, ex.ID); err != nil {
|
|
log.Printf(" ⚠️ trigger-sync[%s]: delete failed: %v", pkgID, err)
|
|
} else {
|
|
removed++
|
|
}
|
|
}
|
|
}
|
|
|
|
if created+updated+removed > 0 {
|
|
log.Printf(" 🔔 trigger-sync[%s]: %d created, %d updated, %d removed",
|
|
pkgID, created, updated, removed)
|
|
}
|
|
}
|
|
|
|
// triggerKey returns a unique key for a trigger within a package.
|
|
func triggerKey(t *models.Trigger) string {
|
|
return t.Type + ":" + triggerIdentifierFromModel(t)
|
|
}
|
|
|
|
func triggerIdentifier(dt manifestTrigger) string {
|
|
switch dt.Type {
|
|
case models.TriggerTypeEvent:
|
|
return dt.Pattern
|
|
case models.TriggerTypeWebhook:
|
|
return dt.Slug
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func triggerIdentifierFromModel(t *models.Trigger) string {
|
|
switch t.Type {
|
|
case models.TriggerTypeEvent:
|
|
return t.EventPattern
|
|
case models.TriggerTypeWebhook:
|
|
return t.Slug
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// generateSecret creates a random 32-byte hex-encoded secret for webhook HMAC.
|
|
func generateSecret() string {
|
|
b := make([]byte, 32)
|
|
_, _ = rand.Read(b)
|
|
return hex.EncodeToString(b)
|
|
}
|