package ingest import ( "encoding/json" "fmt" "strings" "reanimator/internal/normalize" ) const ( statusUnknown = "UNKNOWN" statusEmpty = "EMPTY" statusOK = "OK" statusWarning = "WARNING" statusCritical = "CRITICAL" ) type HardwareIngestRequest struct { Filename *string `json:"filename"` SourceType *string `json:"source_type"` Protocol *string `json:"protocol"` TargetHost string `json:"target_host"` CollectedAt string `json:"collected_at"` Hardware HardwareSnapshot `json:"hardware"` } type HardwareSnapshot struct { Board HardwareBoard `json:"board"` Firmware []HardwareFirmwareRecord `json:"firmware,omitempty"` CPUs []HardwareCPU `json:"cpus,omitempty"` Memory []HardwareMemory `json:"memory,omitempty"` Storage []HardwareStorage `json:"storage,omitempty"` PCIeDevices []HardwarePCIeDevice `json:"pcie_devices,omitempty"` PowerSupplies []HardwarePowerSupply `json:"power_supplies,omitempty"` } type HardwareBoard struct { Manufacturer *string `json:"manufacturer"` ProductName *string `json:"product_name"` SerialNumber string `json:"serial_number"` PartNumber *string `json:"part_number"` UUID *string `json:"uuid"` } type HardwareFirmwareRecord struct { DeviceName string `json:"device_name"` Version string `json:"version"` } type HardwareCPU struct { Socket *int `json:"socket"` Model *string `json:"model"` Manufacturer *string `json:"manufacturer"` Status *string `json:"status"` StatusCheckedAt *string `json:"status_checked_at"` StatusChangedAt *string `json:"status_changed_at"` StatusHistory []HardwareStatusHistoryEntry `json:"status_history,omitempty"` ErrorDescription *string `json:"error_description"` Present *bool `json:"present"` SerialNumber *string `json:"serial_number"` Firmware *string `json:"firmware"` Cores *int `json:"cores"` Threads *int `json:"threads"` FrequencyMHz *int `json:"frequency_mhz"` MaxFrequencyMHz *int `json:"max_frequency_mhz"` } type HardwareMemory struct { Slot *string `json:"slot"` Location *string `json:"location"` Present *bool `json:"present"` SizeMB *int `json:"size_mb"` Type *string `json:"type"` MaxSpeedMHz *int `json:"max_speed_mhz"` CurrentSpeedMHz *int `json:"current_speed_mhz"` Manufacturer *string `json:"manufacturer"` SerialNumber *string `json:"serial_number"` PartNumber *string `json:"part_number"` Status *string `json:"status"` StatusCheckedAt *string `json:"status_checked_at"` StatusChangedAt *string `json:"status_changed_at"` StatusHistory []HardwareStatusHistoryEntry `json:"status_history,omitempty"` ErrorDescription *string `json:"error_description"` } type HardwareStorage struct { Slot *string `json:"slot"` Type *string `json:"type"` Model *string `json:"model"` SizeGB *int `json:"size_gb"` SerialNumber *string `json:"serial_number"` Manufacturer *string `json:"manufacturer"` Firmware *string `json:"firmware"` Interface *string `json:"interface"` Present *bool `json:"present"` Status *string `json:"status"` StatusCheckedAt *string `json:"status_checked_at"` StatusChangedAt *string `json:"status_changed_at"` StatusHistory []HardwareStatusHistoryEntry `json:"status_history,omitempty"` ErrorDescription *string `json:"error_description"` } type HardwarePCIeDevice struct { Slot *string `json:"slot"` VendorID *int `json:"vendor_id"` DeviceID *int `json:"device_id"` BDF *string `json:"bdf"` DeviceClass *string `json:"device_class"` Manufacturer *string `json:"manufacturer"` Model *string `json:"model"` LinkWidth *int `json:"link_width"` LinkSpeed *string `json:"link_speed"` MaxLinkWidth *int `json:"max_link_width"` MaxLinkSpeed *string `json:"max_link_speed"` SerialNumber *string `json:"serial_number"` Firmware *string `json:"firmware"` Present *bool `json:"present"` Status *string `json:"status"` StatusCheckedAt *string `json:"status_checked_at"` StatusChangedAt *string `json:"status_changed_at"` StatusHistory []HardwareStatusHistoryEntry `json:"status_history,omitempty"` ErrorDescription *string `json:"error_description"` } type HardwarePowerSupply struct { Slot *string `json:"slot"` Present *bool `json:"present"` Model *string `json:"model"` Vendor *string `json:"vendor"` WattageW *int `json:"wattage_w"` SerialNumber *string `json:"serial_number"` PartNumber *string `json:"part_number"` Firmware *string `json:"firmware"` Status *string `json:"status"` StatusCheckedAt *string `json:"status_checked_at"` StatusChangedAt *string `json:"status_changed_at"` StatusHistory []HardwareStatusHistoryEntry `json:"status_history,omitempty"` ErrorDescription *string `json:"error_description"` InputType *string `json:"input_type"` InputPowerW *float64 `json:"input_power_w"` OutputPowerW *float64 `json:"output_power_w"` InputVoltage *float64 `json:"input_voltage"` } type HardwareStatusHistoryEntry struct { Status string `json:"status"` ChangedAt string `json:"changed_at"` Details *string `json:"details,omitempty"` } type HardwareComponent struct { ComponentType string `json:"component_type"` VendorSerial string `json:"vendor_serial"` Vendor *string `json:"vendor,omitempty"` Model *string `json:"model,omitempty"` Firmware *string `json:"firmware,omitempty"` Status string `json:"status"` StatusCheckedAt *string `json:"status_checked_at,omitempty"` StatusChangedAt *string `json:"status_changed_at,omitempty"` StatusHistory []HardwareStatusHistoryEntry `json:"status_history,omitempty"` ErrorDescription *string `json:"error_description,omitempty"` Present *bool `json:"present,omitempty"` Slot *string `json:"slot,omitempty"` Attributes map[string]any `json:"attributes,omitempty"` Telemetry map[string]any `json:"telemetry,omitempty"` } func FlattenHardwareComponents(snapshot HardwareSnapshot) ([]HardwareComponent, []HardwareFirmwareRecord) { boardSerial := strings.TrimSpace(snapshot.Board.SerialNumber) if boardSerial == "" { return nil, normalizeFirmwareRecords(snapshot.Firmware) } components := make([]HardwareComponent, 0) components = append(components, flattenCPUs(boardSerial, snapshot.CPUs)...) components = append(components, flattenMemory(boardSerial, snapshot.Memory)...) components = append(components, flattenStorage(boardSerial, snapshot.Storage)...) components = append(components, flattenPCIe(boardSerial, snapshot.PCIeDevices)...) components = append(components, flattenPSUs(boardSerial, snapshot.PowerSupplies)...) firmware := normalizeFirmwareRecords(snapshot.Firmware) return components, firmware } func normalizeFirmwareRecords(entries []HardwareFirmwareRecord) []HardwareFirmwareRecord { normalized := make([]HardwareFirmwareRecord, 0, len(entries)) for _, entry := range entries { name := strings.TrimSpace(entry.DeviceName) version := strings.TrimSpace(entry.Version) if name == "" || version == "" { continue } normalized = append(normalized, HardwareFirmwareRecord{DeviceName: name, Version: version}) } return normalized } func flattenCPUs(boardSerial string, items []HardwareCPU) []HardwareComponent { result := make([]HardwareComponent, 0) for _, item := range items { status := normalizeStatus(item.Status) present := resolvePresent(item.Present, true) if !present || status == statusEmpty { continue } serial := normalizeSerial(item.SerialNumber) if serial == "" { serial = generateCPUVendorSerial(boardSerial, item.Socket) } if serial == "" { continue } comp := HardwareComponent{ ComponentType: "cpu", VendorSerial: serial, Vendor: normalize.VendorDisplayPtr(normalizeString(item.Manufacturer)), Model: normalizeString(item.Model), Firmware: normalizeString(item.Firmware), Status: status, StatusCheckedAt: normalizeString(item.StatusCheckedAt), StatusChangedAt: normalizeString(item.StatusChangedAt), StatusHistory: normalizeStatusHistory(item.StatusHistory), ErrorDescription: normalizeString(item.ErrorDescription), Present: boolPtr(present), Attributes: structToMap(item), Telemetry: extractTelemetry(structToMap(item), []string{"cores", "threads", "frequency_mhz", "max_frequency_mhz"}), } result = append(result, comp) } return result } func flattenMemory(boardSerial string, items []HardwareMemory) []HardwareComponent { result := make([]HardwareComponent, 0) for _, item := range items { status := normalizeStatus(item.Status) present := resolvePresent(item.Present, true) if !present || status == statusEmpty { continue } serial := normalizeSerial(item.SerialNumber) if serial == "" { continue } comp := HardwareComponent{ ComponentType: "memory", VendorSerial: serial, Vendor: normalize.VendorDisplayPtr(normalizeString(item.Manufacturer)), Model: normalizeString(item.PartNumber), Firmware: nil, Status: status, StatusCheckedAt: normalizeString(item.StatusCheckedAt), StatusChangedAt: normalizeString(item.StatusChangedAt), StatusHistory: normalizeStatusHistory(item.StatusHistory), ErrorDescription: normalizeString(item.ErrorDescription), Present: boolPtr(present), Slot: normalizeString(item.Slot), Attributes: structToMap(item), Telemetry: extractTelemetry(structToMap(item), []string{"size_mb", "max_speed_mhz", "current_speed_mhz"}), } result = append(result, comp) } return result } func flattenStorage(boardSerial string, items []HardwareStorage) []HardwareComponent { result := make([]HardwareComponent, 0) for _, item := range items { status := normalizeStatus(item.Status) present := resolvePresent(item.Present, true) if !present || status == statusEmpty { continue } serial := normalizeSerial(item.SerialNumber) if serial == "" { continue } comp := HardwareComponent{ ComponentType: "storage", VendorSerial: serial, Vendor: normalize.VendorDisplayPtr(normalizeString(item.Manufacturer)), Model: normalizeString(item.Model), Firmware: normalizeString(item.Firmware), Status: status, StatusCheckedAt: normalizeString(item.StatusCheckedAt), StatusChangedAt: normalizeString(item.StatusChangedAt), StatusHistory: normalizeStatusHistory(item.StatusHistory), ErrorDescription: normalizeString(item.ErrorDescription), Present: boolPtr(present), Slot: normalizeString(item.Slot), Attributes: structToMap(item), Telemetry: extractTelemetry(structToMap(item), []string{"size_gb"}), } result = append(result, comp) } return result } func flattenPCIe(boardSerial string, items []HardwarePCIeDevice) []HardwareComponent { result := make([]HardwareComponent, 0) for _, item := range items { status := normalizeStatus(item.Status) present := resolvePresent(item.Present, true) if !present || status == statusEmpty { continue } serial := normalizeSerial(item.SerialNumber) if serial == "" { serial = generatePCIEVendorSerial(boardSerial, item.Slot) } if serial == "" { continue } comp := HardwareComponent{ ComponentType: "pcie_device", VendorSerial: serial, Vendor: normalize.VendorDisplayPtr(normalizeString(item.Manufacturer)), Model: normalizeString(item.Model), Firmware: normalizeString(item.Firmware), Status: status, StatusCheckedAt: normalizeString(item.StatusCheckedAt), StatusChangedAt: normalizeString(item.StatusChangedAt), StatusHistory: normalizeStatusHistory(item.StatusHistory), ErrorDescription: normalizeString(item.ErrorDescription), Present: boolPtr(present), Slot: normalizeString(item.Slot), Attributes: structToMap(item), Telemetry: extractTelemetry(structToMap(item), []string{"vendor_id", "device_id"}), } result = append(result, comp) } return result } func flattenPSUs(boardSerial string, items []HardwarePowerSupply) []HardwareComponent { result := make([]HardwareComponent, 0) for _, item := range items { status := normalizeStatus(item.Status) present := resolvePresent(item.Present, true) if !present || status == statusEmpty { continue } serial := normalizeSerial(item.SerialNumber) if serial == "" { continue } comp := HardwareComponent{ ComponentType: "power_supply", VendorSerial: serial, Vendor: normalize.VendorDisplayPtr(normalizeString(item.Vendor)), Model: normalizeString(item.Model), Firmware: normalizeString(item.Firmware), Status: status, StatusCheckedAt: normalizeString(item.StatusCheckedAt), StatusChangedAt: normalizeString(item.StatusChangedAt), StatusHistory: normalizeStatusHistory(item.StatusHistory), ErrorDescription: normalizeString(item.ErrorDescription), Present: boolPtr(present), Slot: normalizeString(item.Slot), Attributes: structToMap(item), Telemetry: extractTelemetry(structToMap(item), []string{"wattage_w", "input_power_w", "output_power_w", "input_voltage"}), } result = append(result, comp) } return result } func normalizeSerial(value *string) string { if value == nil { return "" } trimmed := strings.TrimSpace(*value) if trimmed == "" { return "" } upper := strings.ToUpper(trimmed) if upper == "NULL" || upper == "N/A" { return "" } return trimmed } func generateCPUVendorSerial(boardSerial string, socket *int) string { if boardSerial == "" || socket == nil { return "" } return fmt.Sprintf("%s-CPU-%d", boardSerial, *socket) } func generatePCIEVendorSerial(boardSerial string, slot *string) string { if boardSerial == "" || slot == nil { return "" } trimmed := strings.TrimSpace(*slot) if trimmed == "" { return "" } return fmt.Sprintf("%s-PCIE-%s", boardSerial, trimmed) } func resolvePresent(value *bool, defaultValue bool) bool { if value == nil { return defaultValue } return *value } func normalizeStatus(value *string) string { if value == nil { return statusUnknown } normalized := strings.ToUpper(strings.TrimSpace(*value)) if normalized == "" { return statusUnknown } return normalized } func normalizeStatusHistory(entries []HardwareStatusHistoryEntry) []HardwareStatusHistoryEntry { if len(entries) == 0 { return nil } result := make([]HardwareStatusHistoryEntry, 0, len(entries)) for _, entry := range entries { status := normalizeStatus(&entry.Status) changedAt := strings.TrimSpace(entry.ChangedAt) if changedAt == "" { continue } result = append(result, HardwareStatusHistoryEntry{ Status: status, ChangedAt: changedAt, Details: normalizeString(entry.Details), }) } if len(result) == 0 { return nil } return result } func boolPtr(value bool) *bool { return &value } func normalizeString(value *string) *string { if value == nil { return nil } trimmed := strings.TrimSpace(*value) if trimmed == "" || strings.EqualFold(trimmed, "NULL") { return nil } return &trimmed } func structToMap(value any) map[string]any { data, err := json.Marshal(value) if err != nil { return nil } var result map[string]any if err := json.Unmarshal(data, &result); err != nil { return nil } return result } func extractTelemetry(attributes map[string]any, keys []string) map[string]any { if len(attributes) == 0 { return nil } telemetry := make(map[string]any) for _, key := range keys { if v, ok := attributes[key]; ok { switch v.(type) { case float64, int, int64, float32: telemetry[key] = v } } } if len(telemetry) == 0 { return nil } return telemetry }