Publish ITWorx Pulse source
Public source validation / validate (push) Failing after 3m8s

This commit is contained in:
ITWorx Pulse release export
2026-09-03 02:09:19 +02:00
commit bd774932d5
614 changed files with 77116 additions and 0 deletions
+244
View File
@@ -0,0 +1,244 @@
package application
import (
"context"
"errors"
"sort"
"strings"
"time"
"github.com/itworx/pulse/internal/reconciliation"
)
const ContractVersion = "v1"
// SourceID is the stable logical source used for derived application IDs. It
// is deliberately not a database datasource UUID: the observations remain
// owned by the underlying container datasource while IDs stay stable across
// API and worker projections.
const SourceID = "applications"
type State string
const (
StateHealthy State = "healthy"
StateDegraded State = "degraded"
StateUnknown State = "unknown"
StateDown State = "down"
)
type Source struct {
ID string `json:"id"`
Type string `json:"type"`
ObservedAt time.Time `json:"observedAt"`
ReceivedAt time.Time `json:"receivedAt"`
Freshness string `json:"freshness"`
State string `json:"state"`
Reason string `json:"reason,omitempty"`
}
type ComponentInput struct {
ID string
Name string
Kind string
ContainerState State
ServiceState State
Critical bool
}
type DiscoveredApplication struct {
ID string
Name string
Components []ComponentInput
}
type ApplicationOverride struct {
ApplicationID string
Name string
CriticalByComponent map[string]bool
}
type Reason struct {
Code string `json:"code"`
Message string `json:"message"`
ComponentID string `json:"componentId,omitempty"`
Critical bool `json:"critical"`
}
type Component struct {
ID string `json:"id"`
Name string `json:"name"`
Kind string `json:"kind"`
Critical bool `json:"critical"`
ContainerState State `json:"containerState"`
ServiceState State `json:"serviceState"`
Status State `json:"status"`
Reason string `json:"reason,omitempty"`
}
type Application struct {
ID string `json:"id"`
Name string `json:"name"`
Status State `json:"status"`
Overridden bool `json:"overridden"`
Components []Component `json:"components"`
Reasons []Reason `json:"reasons,omitempty"`
}
type Snapshot struct {
ContractVersion string `json:"contractVersion"`
Source Source `json:"source"`
Applications []Application `json:"applications"`
Total int `json:"total"`
}
type Provider interface {
Snapshot(context.Context) (Snapshot, error)
}
func UnknownSnapshot(now time.Time, id, typ, reason string) Snapshot {
if now.IsZero() {
now = time.Now().UTC()
}
return Snapshot{ContractVersion: ContractVersion, Source: Source{ID: id, Type: typ, ReceivedAt: now.UTC(), Freshness: "unavailable", State: "unknown", Reason: reason}, Applications: []Application{}}
}
func StableApplicationID(sourceID, groupKey string) string {
return reconciliation.StableEntityID(sourceID, "application", groupKey)
}
func BuildSnapshot(source Source, discovered []DiscoveredApplication, overrides []ApplicationOverride, now time.Time) (Snapshot, error) {
if now.IsZero() {
now = time.Now().UTC()
}
now = now.UTC()
if strings.TrimSpace(source.ID) == "" {
return Snapshot{}, errors.New("application source id is required")
}
if len(discovered) > 150 {
return Snapshot{}, errors.New("application count exceeds bounds")
}
if source.ReceivedAt.IsZero() {
source.ReceivedAt = now
}
if source.ObservedAt.IsZero() {
source.ObservedAt = source.ReceivedAt
}
source.ObservedAt, source.ReceivedAt = source.ObservedAt.UTC(), source.ReceivedAt.UTC()
if source.ObservedAt.After(now.Add(time.Minute)) {
return Snapshot{}, errors.New("application observation is materially in the future")
}
source.Freshness, source.State = "fresh", "healthy"
if now.Sub(source.ObservedAt) > time.Minute {
source.Freshness, source.State, source.Reason = "stale", "unknown", "stale_source"
}
overrideByID := make(map[string]ApplicationOverride, len(overrides))
for _, override := range overrides {
if strings.TrimSpace(override.ApplicationID) == "" {
return Snapshot{}, errors.New("application override id is required")
}
if _, exists := overrideByID[override.ApplicationID]; exists {
return Snapshot{}, errors.New("duplicate application override")
}
overrideByID[override.ApplicationID] = override
}
apps := make([]Application, 0, len(discovered))
seenApps := make(map[string]struct{}, len(discovered))
componentCount := 0
for _, group := range discovered {
if strings.TrimSpace(group.ID) == "" || strings.TrimSpace(group.Name) == "" {
return Snapshot{}, errors.New("application identity is required")
}
if _, exists := seenApps[group.ID]; exists {
return Snapshot{}, errors.New("duplicate application identity")
}
seenApps[group.ID] = struct{}{}
componentCount += len(group.Components)
if componentCount > 1000 {
return Snapshot{}, errors.New("application component count exceeds bounds")
}
override, overridden := overrideByID[group.ID]
name := group.Name
if strings.TrimSpace(override.Name) != "" {
name = strings.TrimSpace(override.Name)
}
components := make([]Component, 0, len(group.Components))
seenComponents := make(map[string]struct{}, len(group.Components))
for _, input := range group.Components {
if strings.TrimSpace(input.ID) == "" || strings.TrimSpace(input.Name) == "" {
return Snapshot{}, errors.New("application component identity is required")
}
if _, exists := seenComponents[input.ID]; exists {
return Snapshot{}, errors.New("duplicate application component")
}
seenComponents[input.ID] = struct{}{}
critical := input.Critical
if value, ok := override.CriticalByComponent[input.ID]; ok {
critical = value
}
components = append(components, evaluateComponent(input, critical))
}
sort.Slice(components, func(i, j int) bool { return components[i].ID < components[j].ID })
status, reasons := aggregate(components)
apps = append(apps, Application{ID: group.ID, Name: name, Status: status, Overridden: overridden, Components: components, Reasons: reasons})
}
sort.Slice(apps, func(i, j int) bool { return apps[i].ID < apps[j].ID })
if source.State == "unknown" {
for i := range apps {
apps[i].Status = StateUnknown
apps[i].Reasons = append(apps[i].Reasons, Reason{Code: "source_unknown", Message: "Applicatiebron is onbekend.", Critical: true})
}
}
return Snapshot{ContractVersion: ContractVersion, Source: source, Applications: apps, Total: len(apps)}, nil
}
func evaluateComponent(input ComponentInput, critical bool) Component {
container, service := normalizeState(input.ContainerState), normalizeState(input.ServiceState)
status, reason := container, ""
if container == StateHealthy && service != "" {
status = service
}
if status != StateHealthy {
if service != "" && service != StateHealthy && container == StateHealthy {
reason = "service_" + string(service)
} else {
reason = "container_" + string(status)
}
}
return Component{ID: input.ID, Name: input.Name, Kind: input.Kind, Critical: critical, ContainerState: container, ServiceState: service, Status: status, Reason: reason}
}
func aggregate(components []Component) (State, []Reason) {
reasons := make([]Reason, 0)
criticalFailure, criticalUnknown, optionalFailure := false, false, false
for _, component := range components {
if component.Status == StateHealthy {
continue
}
reasons = append(reasons, Reason{Code: component.Reason, Message: component.Name + " is " + string(component.Status) + ".", ComponentID: component.ID, Critical: component.Critical})
if component.Critical {
if component.Status == StateUnknown {
criticalUnknown = true
} else {
criticalFailure = true
}
} else {
optionalFailure = true
}
}
sort.Slice(reasons, func(i, j int) bool {
if reasons[i].ComponentID != reasons[j].ComponentID {
return reasons[i].ComponentID < reasons[j].ComponentID
}
return reasons[i].Code < reasons[j].Code
})
if criticalFailure {
return StateDegraded, reasons
}
if criticalUnknown {
return StateUnknown, reasons
}
if optionalFailure {
return StateDegraded, reasons
}
return StateHealthy, reasons
}
func normalizeState(state State) State {
state = State(strings.ToLower(strings.TrimSpace(string(state))))
switch state {
case StateHealthy, StateDegraded, StateUnknown, StateDown:
return state
default:
return StateUnknown
}
}
+81
View File
@@ -0,0 +1,81 @@
package application
import (
"testing"
"time"
)
func appSource(now time.Time) Source {
return Source{ID: "agent", Type: "agent", ObservedAt: now, ReceivedAt: now}
}
func TestCriticalAndOptionalAggregationAndReasons(t *testing.T) {
now := time.Date(2026, 8, 1, 22, 0, 0, 0, time.UTC)
snapshot, err := BuildSnapshot(appSource(now), []DiscoveredApplication{{ID: "app-1", Name: "Pulse", Components: []ComponentInput{
{ID: "db", Name: "Database", Kind: "container", ContainerState: StateHealthy, ServiceState: StateHealthy, Critical: true},
{ID: "worker", Name: "Worker", Kind: "container", ContainerState: StateDown, ServiceState: StateDown, Critical: false},
}}}, nil, now)
if err != nil {
t.Fatal(err)
}
if snapshot.Applications[0].Status != StateDegraded || len(snapshot.Applications[0].Reasons) != 1 || snapshot.Applications[0].Reasons[0].Critical {
t.Fatalf("unexpected optional aggregation: %+v", snapshot.Applications[0])
}
}
func TestServiceDownWhileContainerRunningDegradesCriticalApplication(t *testing.T) {
now := time.Date(2026, 8, 1, 22, 0, 0, 0, time.UTC)
snapshot, err := BuildSnapshot(appSource(now), []DiscoveredApplication{{ID: "app-1", Name: "Pulse", Components: []ComponentInput{
{ID: "api", Name: "API", Kind: "container", ContainerState: StateHealthy, ServiceState: StateDown, Critical: true},
}}}, nil, now)
if err != nil {
t.Fatal(err)
}
app := snapshot.Applications[0]
if app.Status != StateDegraded || len(app.Reasons) != 1 || app.Reasons[0].Code != "service_down" || !app.Reasons[0].Critical {
t.Fatalf("service failure not explained: %+v", app)
}
}
func TestUnknownCriticalIsNotHealthy(t *testing.T) {
now := time.Date(2026, 8, 1, 22, 0, 0, 0, time.UTC)
snapshot, err := BuildSnapshot(appSource(now), []DiscoveredApplication{{ID: "app-1", Name: "Pulse", Components: []ComponentInput{{ID: "db", Name: "Database", Critical: true, ContainerState: StateUnknown}}}}, nil, now)
if err != nil {
t.Fatal(err)
}
if snapshot.Applications[0].Status != StateUnknown {
t.Fatalf("unknown critical became healthy: %+v", snapshot.Applications[0])
}
}
func TestStateNormalizationIsCaseInsensitive(t *testing.T) {
now := time.Date(2026, 8, 1, 22, 0, 0, 0, time.UTC)
snapshot, err := BuildSnapshot(appSource(now), []DiscoveredApplication{{ID: "app-1", Name: "Pulse", Components: []ComponentInput{{ID: "api", Name: "API", Critical: true, ContainerState: "HEALTHY", ServiceState: "HEALTHY"}}}}, nil, now)
if err != nil {
t.Fatal(err)
}
if snapshot.Applications[0].Status != StateHealthy {
t.Fatalf("uppercase healthy state was not normalized: %+v", snapshot.Applications[0])
}
}
func TestUserOverridePersistsAcrossDiscoveryProjection(t *testing.T) {
now := time.Date(2026, 8, 1, 22, 0, 0, 0, time.UTC)
discovered := []DiscoveredApplication{{ID: "app-1", Name: "Discovered name", Components: []ComponentInput{{ID: "worker", Name: "Worker", Critical: true, ContainerState: StateHealthy}}}}
override := []ApplicationOverride{{ApplicationID: "app-1", Name: "Handmatige naam", CriticalByComponent: map[string]bool{"worker": false}}}
first, err := BuildSnapshot(appSource(now), discovered, override, now)
if err != nil {
t.Fatal(err)
}
second, err := BuildSnapshot(appSource(now.Add(time.Minute)), discovered, override, now.Add(time.Minute))
if err != nil {
t.Fatal(err)
}
for _, snapshot := range []Snapshot{first, second} {
if snapshot.Applications[0].Name != "Handmatige naam" || !snapshot.Applications[0].Overridden || snapshot.Applications[0].Components[0].Critical {
t.Fatalf("override not retained: %+v", snapshot.Applications[0])
}
}
}
func TestStableApplicationIDIsDeterministic(t *testing.T) {
if StableApplicationID("agent", "pulse") != StableApplicationID("agent", "pulse") {
t.Fatal("stable application id changed")
}
}