All checks were successful
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-frontend (pull_request) Has been skipped
CI/CD / test-runners (pull_request) Has been skipped
CI/CD / e2e-smoke (pull_request) Has been skipped
CI/CD / test-go-pg (pull_request) Successful in 2m43s
CI/CD / test-sqlite (pull_request) Successful in 2m52s
CI/CD / build-and-deploy (pull_request) Successful in 2m15s
Consolidate duplicate Go↔Starlark converters into sandbox/convert.go and snapshot parsers into models/snapshot.go. Standardize snapshot creation on wrapped format. Net -393 lines across 21 files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
126 lines
3.3 KiB
Go
126 lines
3.3 KiB
Go
package triggers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"log"
|
|
"time"
|
|
|
|
"go.starlark.net/starlark"
|
|
|
|
"armature/events"
|
|
"armature/models"
|
|
"armature/sandbox"
|
|
)
|
|
|
|
// wireEventTrigger subscribes to the bus pattern for an event trigger.
|
|
func (e *Engine) wireEventTrigger(t *models.Trigger) {
|
|
if t.EventPattern == "" {
|
|
return
|
|
}
|
|
|
|
triggerID := t.ID
|
|
packageID := t.PackageID
|
|
entryPoint := t.EntryPoint
|
|
pattern := t.EventPattern
|
|
|
|
unsub := e.bus.Subscribe(pattern, func(ev events.Event) {
|
|
// Fire asynchronously — never block the event bus
|
|
go e.fireEventTrigger(triggerID, packageID, entryPoint, ev)
|
|
})
|
|
|
|
e.mu.Lock()
|
|
e.unsubs[triggerID] = unsub
|
|
e.mu.Unlock()
|
|
}
|
|
|
|
// fireEventTrigger invokes the Starlark handler for an event trigger.
|
|
func (e *Engine) fireEventTrigger(triggerID, packageID, entryPoint string, ev events.Event) {
|
|
start := time.Now()
|
|
ctx := e.ctx
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
// Re-check trigger is still enabled
|
|
trigger, err := e.stores.Triggers.GetByID(ctx, triggerID)
|
|
if err != nil || trigger == nil || !trigger.Enabled {
|
|
return
|
|
}
|
|
|
|
// Load package
|
|
pkg, err := e.stores.Packages.Get(ctx, packageID)
|
|
if err != nil || pkg == nil || pkg.Status != models.PackageStatusActive {
|
|
return
|
|
}
|
|
|
|
// Check triggers.register permission
|
|
if !e.hasPermission(ctx, packageID, models.ExtPermTriggersRegister) {
|
|
return
|
|
}
|
|
|
|
// Build Starlark context dict
|
|
ctxDict := starlark.NewDict(6)
|
|
_ = ctxDict.SetKey(starlark.String("trigger_type"), starlark.String("event"))
|
|
_ = ctxDict.SetKey(starlark.String("trigger_id"), starlark.String(triggerID))
|
|
_ = ctxDict.SetKey(starlark.String("event_label"), starlark.String(ev.Label))
|
|
_ = ctxDict.SetKey(starlark.String("event_ts"), starlark.MakeInt64(ev.Ts))
|
|
if ev.Room != "" {
|
|
_ = ctxDict.SetKey(starlark.String("event_room"), starlark.String(ev.Room))
|
|
}
|
|
if len(ev.Payload) > 0 {
|
|
var payloadMap map[string]any
|
|
if json.Unmarshal(ev.Payload, &payloadMap) == nil {
|
|
_ = ctxDict.SetKey(starlark.String("event_payload"), sandbox.MapToDict(payloadMap))
|
|
} else {
|
|
_ = ctxDict.SetKey(starlark.String("event_payload"), starlark.String(string(ev.Payload)))
|
|
}
|
|
}
|
|
|
|
// Call entry point with full sandbox context
|
|
_, output, callErr := e.runner.CallEntryPoint(ctx, pkg, entryPoint,
|
|
starlark.Tuple{ctxDict}, nil, nil)
|
|
|
|
duration := int(time.Since(start).Milliseconds())
|
|
errStr := ""
|
|
if callErr != nil {
|
|
errStr = callErr.Error()
|
|
log.Printf(" ⚠️ trigger[event] %s/%s error: %v", packageID, entryPoint, callErr)
|
|
}
|
|
|
|
// Update fire state
|
|
_ = e.stores.Triggers.UpdateFireState(ctx, triggerID, start, errStr)
|
|
|
|
// Log execution
|
|
e.logExecution(triggerID, "", start, duration, callErr == nil, errStr, output)
|
|
e.publishEvent("trigger.fired", triggerID)
|
|
}
|
|
|
|
// hasPermission checks if a package has a specific granted permission.
|
|
func (e *Engine) hasPermission(ctx context.Context, packageID, permission string) bool {
|
|
if e.stores.ExtPermissions == nil {
|
|
return false
|
|
}
|
|
granted, err := e.stores.ExtPermissions.GrantedForPackage(ctx, packageID)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
for _, p := range granted {
|
|
if p == permission {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// RunContext builds a sandbox.RunContext for a trigger invocation.
|
|
func triggerRunContext(userID, teamID string) *sandbox.RunContext {
|
|
if userID == "" {
|
|
return nil
|
|
}
|
|
return &sandbox.RunContext{
|
|
UserID: userID,
|
|
TeamID: teamID,
|
|
}
|
|
}
|