2026-03-31 14:52:50 -03:00
|
|
|
package facts
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"metis/pkg/inventory"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ClassSummary captures aggregated sentinel facts per class.
|
|
|
|
|
type ClassSummary struct {
|
2026-04-11 01:08:08 -03:00
|
|
|
Class string `json:"class"`
|
|
|
|
|
Nodes []string `json:"nodes"`
|
|
|
|
|
Kernels map[string]int `json:"kernels,omitempty"`
|
|
|
|
|
OSImages map[string]int `json:"os_images,omitempty"`
|
|
|
|
|
Containerd map[string]int `json:"containerd,omitempty"`
|
|
|
|
|
K3sVersions map[string]int `json:"k3s_versions,omitempty"`
|
|
|
|
|
PackageStats map[string]map[string]int `json:"package_stats,omitempty"` // pkg -> version -> count
|
|
|
|
|
USBMountHealth map[string]int `json:"usb_mount_health,omitempty"`
|
|
|
|
|
USBUUIDHealth map[string]int `json:"usb_uuid_health,omitempty"`
|
|
|
|
|
USBLabelHealth map[string]int `json:"usb_label_health,omitempty"`
|
|
|
|
|
USBBindHealth map[string]int `json:"usb_bind_health,omitempty"`
|
2026-03-31 14:52:50 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Aggregate groups snapshots by inventory class and tallies version drift.
|
|
|
|
|
func Aggregate(inv *inventory.Inventory, snaps []Snapshot) map[string]*ClassSummary {
|
|
|
|
|
result := map[string]*ClassSummary{}
|
|
|
|
|
for _, s := range snaps {
|
|
|
|
|
class := "unknown"
|
2026-04-11 01:08:08 -03:00
|
|
|
var scratch *inventory.USBScratchDisk
|
2026-03-31 14:52:50 -03:00
|
|
|
if inv != nil {
|
2026-04-11 01:08:08 -03:00
|
|
|
if node, cls, err := inv.FindNode(s.Hostname); node != nil && cls != nil && err == nil {
|
2026-03-31 14:52:50 -03:00
|
|
|
class = cls.Name
|
2026-04-11 01:08:08 -03:00
|
|
|
scratch = node.USBScratch
|
|
|
|
|
} else if node != nil {
|
|
|
|
|
scratch = node.USBScratch
|
2026-03-31 14:52:50 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sum, ok := result[class]
|
|
|
|
|
if !ok {
|
|
|
|
|
sum = &ClassSummary{
|
2026-04-11 01:08:08 -03:00
|
|
|
Class: class,
|
|
|
|
|
Kernels: map[string]int{},
|
|
|
|
|
OSImages: map[string]int{},
|
|
|
|
|
Containerd: map[string]int{},
|
|
|
|
|
K3sVersions: map[string]int{},
|
|
|
|
|
PackageStats: map[string]map[string]int{},
|
|
|
|
|
USBMountHealth: map[string]int{},
|
|
|
|
|
USBUUIDHealth: map[string]int{},
|
|
|
|
|
USBLabelHealth: map[string]int{},
|
|
|
|
|
USBBindHealth: map[string]int{},
|
2026-03-31 14:52:50 -03:00
|
|
|
}
|
|
|
|
|
result[class] = sum
|
|
|
|
|
}
|
|
|
|
|
sum.Nodes = append(sum.Nodes, s.Hostname)
|
|
|
|
|
if s.Kernel != "" {
|
|
|
|
|
sum.Kernels[s.Kernel]++
|
|
|
|
|
}
|
|
|
|
|
if s.OSImage != "" {
|
|
|
|
|
sum.OSImages[s.OSImage]++
|
|
|
|
|
}
|
|
|
|
|
if s.Containerd != "" {
|
|
|
|
|
sum.Containerd[s.Containerd]++
|
|
|
|
|
}
|
|
|
|
|
if s.K3sVersion != "" {
|
|
|
|
|
sum.K3sVersions[s.K3sVersion]++
|
|
|
|
|
}
|
|
|
|
|
for pkg, ver := range s.PackageSample {
|
|
|
|
|
if sum.PackageStats[pkg] == nil {
|
|
|
|
|
sum.PackageStats[pkg] = map[string]int{}
|
|
|
|
|
}
|
|
|
|
|
if ver != "" {
|
|
|
|
|
sum.PackageStats[pkg][ver]++
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-11 01:08:08 -03:00
|
|
|
addUSBHealth(sum, scratch, s.USBScratch)
|
2026-03-31 14:52:50 -03:00
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
2026-04-11 01:08:08 -03:00
|
|
|
|
|
|
|
|
func addUSBHealth(sum *ClassSummary, desired *inventory.USBScratchDisk, observed *USBScratch) {
|
|
|
|
|
if desired == nil || sum == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if desired.Mountpoint != "" {
|
|
|
|
|
sum.USBMountHealth[usbStatus(observed, observed != nil && observed.MountHealthy)]++
|
|
|
|
|
}
|
|
|
|
|
if desired.UUID != "" {
|
|
|
|
|
sum.USBUUIDHealth[usbStatus(observed, observed != nil && observed.UUIDHealthy)]++
|
|
|
|
|
}
|
|
|
|
|
if desired.Label != "" {
|
|
|
|
|
sum.USBLabelHealth[usbStatus(observed, observed != nil && observed.LabelHealthy)]++
|
|
|
|
|
}
|
|
|
|
|
if len(desired.BindTargets) > 0 {
|
|
|
|
|
sum.USBBindHealth[usbStatus(observed, observed != nil && observed.BindHealthy)]++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func usbStatus(observed *USBScratch, ok bool) string {
|
|
|
|
|
if observed == nil {
|
|
|
|
|
return "missing"
|
|
|
|
|
}
|
|
|
|
|
if ok {
|
|
|
|
|
return "ok"
|
|
|
|
|
}
|
|
|
|
|
return "bad"
|
|
|
|
|
}
|