package inspur import ( "bufio" "regexp" "strings" "git.mchus.pro/mchus/logpile/internal/models" ) var ( fruDeviceRegex = regexp.MustCompile(`^FRU Device Description\s*:\s*(.+)$`) fruFieldRegex = regexp.MustCompile(`^\s+(.+?)\s*:\s*(.*)$`) platformIdRegex = regexp.MustCompile(`(?i)PlatformId\s*=\s*(\S+)`) ) // ParseFRU parses BMC FRU (Field Replaceable Unit) output func ParseFRU(content []byte) []models.FRUInfo { var fruList []models.FRUInfo var current *models.FRUInfo scanner := bufio.NewScanner(strings.NewReader(string(content))) for scanner.Scan() { line := scanner.Text() // Check for new FRU device if matches := fruDeviceRegex.FindStringSubmatch(line); matches != nil { if current != nil && current.Description != "" { fruList = append(fruList, *current) } current = &models.FRUInfo{ Description: strings.TrimSpace(matches[1]), } continue } // Skip if no current FRU device if current == nil { continue } // Skip "Device not present" entries if strings.Contains(line, "Device not present") { current = nil continue } // Parse FRU fields if matches := fruFieldRegex.FindStringSubmatch(line); matches != nil { fieldName := strings.TrimSpace(matches[1]) fieldValue := strings.TrimSpace(matches[2]) switch fieldName { case "Chassis Type": current.ChassisType = fieldValue case "Chassis Part Number": if fieldValue != "0" { current.PartNumber = fieldValue } case "Chassis Serial": if fieldValue != "0" { current.SerialNumber = fieldValue } case "Board Mfg Date": current.MfgDate = fieldValue case "Board Mfg", "Product Manufacturer": if fieldValue != "NULL" { current.Manufacturer = fieldValue } case "Board Product", "Product Name": if fieldValue != "NULL" { current.ProductName = fieldValue } case "Board Serial", "Product Serial": current.SerialNumber = fieldValue case "Board Part Number", "Product Part Number": if fieldValue != "0" { current.PartNumber = fieldValue } case "Product Version": if fieldValue != "0" { current.Version = fieldValue } case "Product Asset Tag": if fieldValue != "NULL" { current.AssetTag = fieldValue } } } } // Don't forget the last one if current != nil && current.Description != "" { fruList = append(fruList, *current) } return fruList } // extractBoardInfo extracts main board/chassis information from FRU data func extractBoardInfo(fruList []models.FRUInfo, hw *models.HardwareConfig) { if hw == nil || len(fruList) == 0 { return } // Look for the main board/chassis FRU entry // Usually it's the first entry or one with "Builtin FRU" or containing board info for _, fru := range fruList { // Skip empty entries if fru.ProductName == "" && fru.SerialNumber == "" { continue } // Prioritize entries that look like main board info desc := strings.ToLower(fru.Description) isMainBoard := strings.Contains(desc, "builtin") || strings.Contains(desc, "fru device") || strings.Contains(desc, "chassis") || strings.Contains(desc, "board") // If we haven't set board info yet, or this is a main board entry if hw.BoardInfo.ProductName == "" || isMainBoard { if fru.ProductName != "" { hw.BoardInfo.ProductName = fru.ProductName } if fru.SerialNumber != "" { hw.BoardInfo.SerialNumber = fru.SerialNumber } if fru.Manufacturer != "" { hw.BoardInfo.Manufacturer = fru.Manufacturer } if fru.PartNumber != "" { hw.BoardInfo.PartNumber = fru.PartNumber } // If we found a main board entry, stop searching if isMainBoard && fru.ProductName != "" && fru.SerialNumber != "" { break } } } } // extractPlatformId extracts server model from ThermalConfig (PlatformId) func extractPlatformId(content []byte, hw *models.HardwareConfig) { if hw == nil { return } if match := platformIdRegex.FindSubmatch(content); match != nil { platformId := strings.TrimSpace(string(match[1])) if platformId != "" { // Set as ProductName (server model) - this takes priority over FRU data hw.BoardInfo.ProductName = platformId // Also set manufacturer as Inspur if not already set if hw.BoardInfo.Manufacturer == "" { hw.BoardInfo.Manufacturer = "Inspur" } } } }