package facts import ( "metis/pkg/inventory" ) // ClassSummary captures aggregated sentinel facts per class. type ClassSummary struct { 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"` } // 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" var scratch *inventory.USBScratchDisk if inv != nil { if node, cls, err := inv.FindNode(s.Hostname); node != nil && cls != nil && err == nil { class = cls.Name scratch = node.USBScratch } else if node != nil { scratch = node.USBScratch } } sum, ok := result[class] if !ok { sum = &ClassSummary{ 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{}, } 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]++ } } addUSBHealth(sum, scratch, s.USBScratch) } return result } 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" }