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
+320
View File
@@ -0,0 +1,320 @@
package container
import (
"context"
"errors"
"math"
"sort"
"strconv"
"strings"
"time"
)
const (
ContractVersion = "v1"
DefaultMaxContainers = 250
)
type Limits struct {
MaxContainers, MaxPorts, MaxVolumes, MaxNetworks, MaxLabels, MaxPageSize int
FreshnessMaxAge time.Duration
}
func (l Limits) withDefaults() Limits {
if l.MaxContainers == 0 {
// The v1 performance target remains 150 containers. Bounded growth margin
// keeps a modestly larger host from invalidating the complete snapshot.
l.MaxContainers = DefaultMaxContainers
}
if l.MaxPorts == 0 {
l.MaxPorts = 32
}
if l.MaxVolumes == 0 {
l.MaxVolumes = 32
}
if l.MaxNetworks == 0 {
l.MaxNetworks = 32
}
if l.MaxLabels == 0 {
l.MaxLabels = 64
}
if l.MaxPageSize == 0 {
l.MaxPageSize = 100
}
if l.FreshnessMaxAge == 0 {
l.FreshnessMaxAge = 60 * time.Second
}
return l
}
func (l Limits) Validate() error {
if l.MaxContainers < 1 || l.MaxContainers > 1000 || l.MaxPorts < 1 || l.MaxPorts > 128 || l.MaxVolumes < 1 || l.MaxVolumes > 128 || l.MaxNetworks < 1 || l.MaxNetworks > 128 || l.MaxLabels < 1 || l.MaxLabels > 256 || l.MaxPageSize < 1 || l.MaxPageSize > 500 || l.FreshnessMaxAge <= 0 || l.FreshnessMaxAge > 24*time.Hour {
return errors.New("container limits are outside safe bounds")
}
return nil
}
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 Port struct {
ContainerPort int `json:"containerPort"`
HostPort int `json:"hostPort,omitempty"`
Protocol string `json:"protocol"`
}
type RawContainer struct {
ID, Name, Image, ImageDigest, State, Health string
IntentionalStop bool
MetricsAvailable, LifecycleAvailable bool
UptimeSeconds float64
RestartCount int
ExitCode int
CPUPercent float64
MemoryBytes, MemoryLimitBytes uint64
NetworkRxBytes, NetworkTxBytes uint64
BlockReadBytes, BlockWriteBytes uint64
Ports []Port
Volumes, Networks []string
Project string
Labels map[string]string
}
type Container struct {
ID string `json:"id"`
Name string `json:"name"`
Image string `json:"image,omitempty"`
ImageDigest string `json:"imageDigest,omitempty"`
State string `json:"state"`
Health string `json:"health"`
IntentionalStop bool `json:"intentionalStop"`
MetricsAvailable bool `json:"metricsAvailable"`
LifecycleAvailable bool `json:"lifecycleAvailable"`
UptimeSeconds float64 `json:"uptimeSeconds"`
RestartCount int `json:"restartCount"`
ExitCode int `json:"exitCode"`
CPUPercent float64 `json:"cpuPercent"`
MemoryBytes uint64 `json:"memoryBytes"`
MemoryLimitBytes uint64 `json:"memoryLimitBytes"`
NetworkRxBytes uint64 `json:"networkRxBytes"`
NetworkTxBytes uint64 `json:"networkTxBytes"`
BlockReadBytes uint64 `json:"blockReadBytes"`
BlockWriteBytes uint64 `json:"blockWriteBytes"`
Ports []Port `json:"ports"`
Volumes []string `json:"volumes"`
Networks []string `json:"networks"`
Project string `json:"project,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
type RawSnapshot struct {
Source Source
Containers []RawContainer
ObservedAt, ReceivedAt time.Time
}
type Snapshot struct {
ContractVersion string `json:"contractVersion"`
Source Source `json:"source"`
Containers []Container `json:"containers"`
Total int `json:"total"`
NextCursor string `json:"nextCursor,omitempty"`
}
type Provider interface {
Snapshot(context.Context) (Snapshot, error)
}
type Adapter struct {
Source interface {
Snapshot(context.Context) (RawSnapshot, error)
}
Limits Limits
Now func() time.Time
}
func (a Adapter) Snapshot(ctx context.Context) (Snapshot, error) {
if err := ctx.Err(); err != nil {
return Snapshot{}, err
}
if a.Source == nil {
return UnknownSnapshot(time.Now().UTC(), "container", "agent", "source_unavailable"), nil
}
raw, err := a.Source.Snapshot(ctx)
if err != nil {
return Snapshot{}, err
}
now := time.Now().UTC()
if a.Now != nil {
now = a.Now()
}
return Normalize(raw, now, a.Limits)
}
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, Freshness: "unavailable", State: "unknown", Reason: reason}, Containers: []Container{}, Total: 0}
}
func Normalize(raw RawSnapshot, now time.Time, limits Limits) (Snapshot, error) {
limits = limits.withDefaults()
if err := limits.Validate(); err != nil {
return Snapshot{}, err
}
if now.IsZero() {
now = time.Now().UTC()
}
if raw.ReceivedAt.IsZero() {
raw.ReceivedAt = now
}
if raw.ObservedAt.IsZero() {
raw.ObservedAt = raw.ReceivedAt
}
if raw.ObservedAt.After(now.Add(time.Minute)) {
return Snapshot{}, errors.New("container observation is materially in the future")
}
if len(raw.Containers) > limits.MaxContainers {
return Snapshot{}, errors.New("container count exceeds bounds")
}
source := raw.Source
if source.ID == "" {
source.ID = "container"
}
if source.Type == "" {
source.Type = "agent"
}
source.ObservedAt = raw.ObservedAt.UTC()
source.ReceivedAt = raw.ReceivedAt.UTC()
source.Freshness = "fresh"
source.State = "healthy"
if now.Sub(raw.ObservedAt) > limits.FreshnessMaxAge {
source.Freshness = "stale"
source.State = "unknown"
source.Reason = "stale_source"
}
items := make([]Container, 0, len(raw.Containers))
for _, r := range raw.Containers {
if strings.TrimSpace(r.ID) == "" || strings.TrimSpace(r.Name) == "" || len(r.Name) > 255 || r.RestartCount < 0 || r.UptimeSeconds < 0 || r.CPUPercent < 0 || r.CPUPercent > 10000 || math.IsNaN(r.CPUPercent) || math.IsInf(r.CPUPercent, 0) {
return Snapshot{}, errors.New("invalid container identity or metrics")
}
if len(r.Ports) > limits.MaxPorts || len(r.Volumes) > limits.MaxVolumes || len(r.Networks) > limits.MaxNetworks || len(r.Labels) > limits.MaxLabels {
return Snapshot{}, errors.New("container detail exceeds bounds")
}
ports := append([]Port(nil), r.Ports...)
sort.Slice(ports, func(i, j int) bool {
if ports[i].ContainerPort != ports[j].ContainerPort {
return ports[i].ContainerPort < ports[j].ContainerPort
}
return ports[i].Protocol < ports[j].Protocol
})
volumes := sortedStrings(r.Volumes)
networks := sortedStrings(r.Networks)
labels := make(map[string]string, len(r.Labels))
for k, v := range r.Labels {
if len(k) <= 128 && len(v) <= 512 {
labels[k] = v
}
}
items = append(items, Container{ID: r.ID, Name: r.Name, Image: r.Image, ImageDigest: r.ImageDigest, State: normalizeRuntimeState(r.State), Health: normalizeHealth(r.Health), IntentionalStop: r.IntentionalStop, MetricsAvailable: r.MetricsAvailable, LifecycleAvailable: r.LifecycleAvailable, UptimeSeconds: r.UptimeSeconds, RestartCount: r.RestartCount, ExitCode: r.ExitCode, CPUPercent: r.CPUPercent, MemoryBytes: r.MemoryBytes, MemoryLimitBytes: r.MemoryLimitBytes, NetworkRxBytes: r.NetworkRxBytes, NetworkTxBytes: r.NetworkTxBytes, BlockReadBytes: r.BlockReadBytes, BlockWriteBytes: r.BlockWriteBytes, Ports: ports, Volumes: volumes, Networks: networks, Project: r.Project, Labels: labels})
}
sort.Slice(items, func(i, j int) bool {
if items[i].Name != items[j].Name {
return items[i].Name < items[j].Name
}
return items[i].ID < items[j].ID
})
return Snapshot{ContractVersion: ContractVersion, Source: source, Containers: items, Total: len(items)}, nil
}
func Page(snapshot Snapshot, limit int, after string, limits Limits) (Snapshot, error) {
return FilteredPage(snapshot, limit, after, limits, "", "", "", "name")
}
func FilteredPage(snapshot Snapshot, limit int, after string, limits Limits, query, state, health, order string) (Snapshot, error) {
limits = limits.withDefaults()
if err := limits.Validate(); err != nil {
return Snapshot{}, err
}
if limit < 1 || limit > limits.MaxPageSize {
return Snapshot{}, errors.New("container page limit is outside bounds")
}
query, state, health, order = strings.ToLower(strings.TrimSpace(query)), strings.ToLower(strings.TrimSpace(state)), strings.ToLower(strings.TrimSpace(health)), strings.ToLower(strings.TrimSpace(order))
if len(query) > 100 || (state != "" && normalizeRuntimeState(state) != state) || (health != "" && normalizeHealth(health) != health) || (order != "name" && order != "cpu" && order != "memory" && order != "state") {
return Snapshot{}, errors.New("container filters are invalid")
}
items := make([]Container, 0, len(snapshot.Containers))
for _, item := range snapshot.Containers {
if query != "" && !strings.Contains(strings.ToLower(item.Name+" "+item.Project+" "+item.Image), query) {
continue
}
if state != "" && item.State != state {
continue
}
if health != "" && item.Health != health {
continue
}
items = append(items, item)
}
sort.SliceStable(items, func(i, j int) bool {
switch order {
case "cpu":
if items[i].CPUPercent != items[j].CPUPercent {
return items[i].CPUPercent > items[j].CPUPercent
}
case "memory":
if items[i].MemoryBytes != items[j].MemoryBytes {
return items[i].MemoryBytes > items[j].MemoryBytes
}
case "state":
if items[i].State != items[j].State {
return items[i].State < items[j].State
}
}
if items[i].Name != items[j].Name {
return items[i].Name < items[j].Name
}
return items[i].ID < items[j].ID
})
start := 0
if after != "" {
n, err := strconv.Atoi(after)
if err != nil || n < 0 || n > len(items) {
return Snapshot{}, errors.New("invalid container cursor")
}
start = n
}
end := start + limit
if end > len(items) {
end = len(items)
}
result := snapshot
result.Containers = items[start:end]
result.Total = len(items)
result.NextCursor = ""
if end < len(items) {
result.NextCursor = strconv.Itoa(end)
}
return result, nil
}
func sortedStrings(values []string) []string {
r := append([]string(nil), values...)
sort.Strings(r)
return r
}
func normalizeRuntimeState(value string) string {
value = strings.ToLower(strings.TrimSpace(value))
switch value {
case "running", "restarting", "paused", "exited", "dead", "stopped", "created", "removing":
return value
default:
return "unknown"
}
}
func normalizeHealth(value string) string {
value = strings.ToLower(strings.TrimSpace(value))
switch value {
case "healthy", "unhealthy", "starting":
return value
default:
return "unknown"
}
}
+117
View File
@@ -0,0 +1,117 @@
package container
import (
"context"
"errors"
"fmt"
"testing"
"time"
)
func TestNormalizeAcceptsBoundedOperationalContainerHeadroom(t *testing.T) {
now := time.Date(2026, 8, 21, 12, 0, 0, 0, time.UTC)
raw := RawSnapshot{Source: Source{ID: "agent"}, ObservedAt: now, ReceivedAt: now}
for index := 0; index < DefaultMaxContainers; index++ {
raw.Containers = append(raw.Containers, RawContainer{ID: fmt.Sprintf("id-%03d", index), Name: fmt.Sprintf("container-%03d", index), State: "running"})
}
if snapshot, err := Normalize(raw, now, Limits{}); err != nil || snapshot.Total != DefaultMaxContainers {
t.Fatalf("bounded headroom snapshot total=%d err=%v", snapshot.Total, err)
}
raw.Containers = append(raw.Containers, RawContainer{ID: "overflow", Name: "overflow", State: "running"})
if _, err := Normalize(raw, now, Limits{}); err == nil {
t.Fatal("container inventory beyond bounded headroom was accepted")
}
}
func TestNormalize150ContainerFixtureAndSeparateStateHealth(t *testing.T) {
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
raw := RawSnapshot{Source: Source{ID: "agent-1"}, ObservedAt: now, ReceivedAt: now}
for i := 0; i < 150; i++ {
raw.Containers = append(raw.Containers, RawContainer{ID: string(rune('a'+i/26)) + string(rune('a'+i%26)), Name: "container-" + string(rune('a'+i%26)) + "-" + string(rune('0'+i/26)), State: "running", Health: "unhealthy", CPUPercent: 1})
}
raw.Containers[0].IntentionalStop = true
raw.Containers[0].State = "exited"
raw.Containers[0].Health = "healthy"
snapshot, err := Normalize(raw, now, Limits{})
if err != nil {
t.Fatal(err)
}
if snapshot.Total != 150 || len(snapshot.Containers) != 150 || snapshot.Containers[0].State == snapshot.Containers[0].Health {
t.Fatalf("container state and health were conflated: %+v", snapshot.Containers[0])
}
foundStopped := false
for _, item := range snapshot.Containers {
if item.IntentionalStop {
foundStopped = item.State == "exited" && item.Health == "healthy"
}
}
if !foundStopped {
t.Fatal("intentional stop was not preserved")
}
}
func TestPageIsBoundedAndDeterministic(t *testing.T) {
now := time.Now().UTC()
raw := RawSnapshot{Source: Source{ID: "agent"}, ObservedAt: now, ReceivedAt: now, Containers: []RawContainer{{ID: "b", Name: "zeta", State: "running"}, {ID: "a", Name: "alpha", State: "running"}}}
snapshot, err := Normalize(raw, now, Limits{})
if err != nil {
t.Fatal(err)
}
page, err := Page(snapshot, 1, "", Limits{})
if err != nil || len(page.Containers) != 1 || page.Containers[0].Name != "alpha" || page.NextCursor != "1" {
t.Fatalf("page=%+v err=%v", page, err)
}
if _, err := Page(snapshot, 101, "", Limits{}); err == nil {
t.Fatal("oversized page accepted")
}
}
func TestFilteredPageFiltersBeforeCursorAndSortsDeterministically(t *testing.T) {
snapshot := Snapshot{Containers: []Container{{ID: "b", Name: "Beta", State: "running", Health: "healthy", CPUPercent: 5}, {ID: "a", Name: "Alpha", State: "running", Health: "healthy", CPUPercent: 10}, {ID: "c", Name: "Other", State: "exited", Health: "unknown"}}}
page, err := FilteredPage(snapshot, 1, "", Limits{}, "a", "running", "healthy", "cpu")
if err != nil || page.Total != 2 || len(page.Containers) != 1 || page.Containers[0].ID != "a" || page.NextCursor != "1" {
t.Fatalf("filtered page=%+v err=%v", page, err)
}
next, err := FilteredPage(snapshot, 1, page.NextCursor, Limits{}, "a", "running", "healthy", "cpu")
if err != nil || len(next.Containers) != 1 || next.Containers[0].ID != "b" {
t.Fatalf("next filtered page=%+v err=%v", next, err)
}
}
func TestStaleSourceAndContextCancellation(t *testing.T) {
now := time.Now().UTC()
raw := RawSnapshot{Source: Source{ID: "agent"}, ObservedAt: now.Add(-2 * time.Minute), ReceivedAt: now, Containers: []RawContainer{{ID: "a", Name: "alpha", State: "running"}}}
snapshot, err := Normalize(raw, now, Limits{FreshnessMaxAge: time.Minute})
if err != nil {
t.Fatal(err)
}
if snapshot.Source.State != "unknown" || snapshot.Source.Freshness != "stale" {
t.Fatalf("source=%+v", snapshot.Source)
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err = (Adapter{}).Snapshot(ctx)
if !errors.Is(err, context.Canceled) {
t.Fatalf("err=%v", err)
}
}
func TestNormalizeCanonicalizesRuntimeAndPreservesAvailability(t *testing.T) {
now := time.Date(2026, 8, 12, 0, 0, 0, 0, time.UTC)
snapshot, err := Normalize(RawSnapshot{
Source: Source{ID: "agent"}, ObservedAt: now, ReceivedAt: now,
Containers: []RawContainer{
{ID: "a", Name: "alpha", State: " RUNNING ", Health: " HEALTHY ", MetricsAvailable: true, LifecycleAvailable: true},
{ID: "b", Name: "beta", State: "RUNNING", Health: "Up 34 hours"},
},
}, now, Limits{})
if err != nil {
t.Fatal(err)
}
if snapshot.Containers[0].State != "running" || snapshot.Containers[0].Health != "healthy" || !snapshot.Containers[0].MetricsAvailable || !snapshot.Containers[0].LifecycleAvailable {
t.Fatalf("canonical container = %+v", snapshot.Containers[0])
}
if snapshot.Containers[1].State != "running" || snapshot.Containers[1].Health != "unknown" || snapshot.Containers[1].MetricsAvailable || snapshot.Containers[1].LifecycleAvailable {
t.Fatalf("missing telemetry was fabricated: %+v", snapshot.Containers[1])
}
}