Files
pulse/internal/api/connections_alerts.go
T
2026-05-13 20:38:26 +01:00

121 lines
4.4 KiB
Go

package api
import (
"context"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/alerts"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
)
// buildAggregatorInputs gathers the same inputs the HTTP connections handler
// uses so the alerts pipeline can derive identical Connection rows without
// going through the HTTP layer. Returning an empty aggregatorInputs when a
// dependency is unavailable keeps the alerts loop a no-op rather than a hard
// failure.
func buildAggregatorInputs(ctx context.Context, cfg *config.Config, persistence *config.ConfigPersistence, monitor *monitoring.Monitor) aggregatorInputs {
inputs := aggregatorInputs{now: time.Now()}
if cfg != nil {
inputs.pveInstances = cfg.PVEInstances
inputs.pbsInstances = cfg.PBSInstances
inputs.pmgInstances = cfg.PMGInstances
}
if persistence != nil {
if vmw, err := persistence.LoadVMwareConfig(); err == nil {
inputs.vmwareInstances = vmw
}
if tn, err := persistence.LoadTrueNASConfig(); err == nil {
inputs.truenasInstances = tn
}
if availability, err := persistence.LoadAvailabilityTargets(); err == nil {
inputs.availabilityTargets = availability
}
}
if monitor != nil {
inputs.hosts = monitor.HostsSnapshot()
inputs.agentDesiredConfigs = connectionAgentDesiredConfigFingerprints(monitor, inputs.hosts)
inputs.instanceHealth = instanceHealthByKey(monitor.SchedulerHealth())
inputs.availabilityStatuses = monitor.AvailabilityStatusSnapshot()
} else {
inputs.hosts = []models.Host{}
inputs.instanceHealth = map[string]monitoring.InstanceHealth{}
inputs.availabilityStatuses = map[string]monitoring.AvailabilityProbeStatus{}
}
if mock.IsMockEnabled() {
mockTargets, mockStatuses := mockAvailabilityConnectionInputs()
inputs.availabilityTargets = mergeAvailabilityTargets(inputs.availabilityTargets, mockTargets)
inputs.availabilityStatuses = mergeAvailabilityStatuses(inputs.availabilityStatuses, mockStatuses)
}
inputs.expectedAgentVersion = currentAgentTargetVersion()
_ = ctx
return inputs
}
// connectionTypeForAlerts maps the wire ConnectionType to the alerts package's
// narrow platform set. Types that fall outside the platform set return false
// so the caller drops the row before invoking CheckConnection.
func connectionTypeForAlerts(t ConnectionType) (alerts.ConnectionType, bool) {
switch t {
case ConnectionTypePVE:
return alerts.ConnectionTypePVE, true
case ConnectionTypePBS:
return alerts.ConnectionTypePBS, true
case ConnectionTypePMG:
return alerts.ConnectionTypePMG, true
case ConnectionTypeVMware:
return alerts.ConnectionTypeVMware, true
case ConnectionTypeTrueNAS:
return alerts.ConnectionTypeTrueNAS, true
default:
return "", false
}
}
// snapshotConnectionsForAlerts translates the aggregator's rich Connection
// rows into the narrow alerts.ConnectionSnapshot view. Non-platform types
// (agent, availability, docker, kubernetes) are dropped because they have
// their own alert lifecycles.
func snapshotConnectionsForAlerts(connections []Connection) []alerts.ConnectionSnapshot {
out := make([]alerts.ConnectionSnapshot, 0, len(connections))
for _, conn := range connections {
alertType, ok := connectionTypeForAlerts(conn.Type)
if !ok {
continue
}
snap := alerts.ConnectionSnapshot{
ID: conn.ID,
Name: conn.Name,
Type: alertType,
State: alerts.ConnectionState(conn.State),
StateReason: conn.StateReason,
Enabled: conn.Enabled,
LastSeen: conn.LastSeen,
}
if conn.LastError != nil {
snap.LastError = &alerts.ConnectionErrorSnapshot{
At: conn.LastError.At,
Message: conn.LastError.Message,
Category: conn.LastError.Category,
}
}
out = append(out, snap)
}
return out
}
// BuildAlertConnectionSnapshots returns the platform-connection snapshots the
// alerts package consumes for the connection-degraded check. This is the
// monitor-loop counterpart to the HTTP connections handler — it runs the
// same derivation but skips the JSON envelope.
func BuildAlertConnectionSnapshots(ctx context.Context, cfg *config.Config, persistence *config.ConfigPersistence, monitor *monitoring.Monitor) []alerts.ConnectionSnapshot {
inputs := buildAggregatorInputs(ctx, cfg, persistence, monitor)
return snapshotConnectionsForAlerts(buildConnections(inputs))
}