forked from wrenn/wrenn
Co-authored-by: Tasnim Kabir Sadik <tksadik@omukk.dev> Reviewed-on: wrenn/wrenn#50
73 lines
2.5 KiB
Go
73 lines
2.5 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
// buildStreamChannelPrefix is the Redis pub/sub channel prefix for live build
|
|
// events. One channel per build: wrenn:build:{buildID}.
|
|
const buildStreamChannelPrefix = "wrenn:build:"
|
|
|
|
func buildStreamChannel(buildID string) string {
|
|
return buildStreamChannelPrefix + buildID
|
|
}
|
|
|
|
// BuildStreamEvent is one event in a build's live stream. The same struct is
|
|
// published to Redis by the build worker and forwarded verbatim to admin
|
|
// WebSocket clients, so its JSON shape is the wire contract for both.
|
|
//
|
|
// Type discriminates the payload:
|
|
// - "step-start": Step, Phase, Cmd set.
|
|
// - "output": Step, Data (base64 PTY bytes) set.
|
|
// - "step-end": Step, Phase, Cmd, Exit, Ok, ElapsedMs set.
|
|
// - "build-status": Status, CurrentStep, TotalSteps, Error set.
|
|
type BuildStreamEvent struct {
|
|
Type string `json:"type"`
|
|
Step int `json:"step,omitempty"`
|
|
Phase string `json:"phase,omitempty"`
|
|
Cmd string `json:"cmd,omitempty"`
|
|
Data string `json:"data,omitempty"` // base64-encoded PTY output bytes
|
|
Exit int32 `json:"exit,omitempty"`
|
|
Ok bool `json:"ok,omitempty"`
|
|
ElapsedMs int64 `json:"elapsed_ms,omitempty"`
|
|
Status string `json:"status,omitempty"`
|
|
CurrentStep int32 `json:"current_step,omitempty"`
|
|
TotalSteps int32 `json:"total_steps,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
T int64 `json:"t"` // unix milliseconds, set at publish time
|
|
}
|
|
|
|
// IsTerminalBuildStatus reports whether a build status is final (the worker
|
|
// will publish no further events for it).
|
|
func IsTerminalBuildStatus(status string) bool {
|
|
switch status {
|
|
case "success", "failed", "cancelled":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// publishBuildEvent fire-and-forget publishes one event to a build's Redis
|
|
// channel. A missing/closed Redis connection only drops live events; the WS
|
|
// client always has the DB log history to fall back on.
|
|
func publishBuildEvent(ctx context.Context, rdb *redis.Client, buildID string, ev BuildStreamEvent) {
|
|
if rdb == nil {
|
|
return
|
|
}
|
|
ev.T = time.Now().UnixMilli()
|
|
payload, err := json.Marshal(ev)
|
|
if err != nil {
|
|
slog.Warn("build event marshal failed", "build_id", buildID, "error", err)
|
|
return
|
|
}
|
|
if err := rdb.Publish(ctx, buildStreamChannel(buildID), payload).Err(); err != nil {
|
|
slog.Debug("build event publish failed", "build_id", buildID, "error", err)
|
|
}
|
|
}
|