Files
logpile/internal/parser/fru_manufactured.go
2026-03-15 23:27:32 +03:00

136 lines
3.4 KiB
Go

package parser
import (
"fmt"
"regexp"
"strings"
"time"
"git.mchus.pro/mchus/logpile/internal/models"
)
var manufacturedYearWeekPattern = regexp.MustCompile(`^\d{4}-W\d{2}$`)
// NormalizeManufacturedYearWeek converts common FRU manufacturing date formats
// into contract-compatible YYYY-Www values. Unknown or ambiguous inputs return "".
func NormalizeManufacturedYearWeek(raw string) string {
value := strings.TrimSpace(raw)
if value == "" {
return ""
}
upper := strings.ToUpper(value)
if manufacturedYearWeekPattern.MatchString(upper) {
return upper
}
layouts := []string{
time.RFC3339,
"2006-01-02T15:04:05",
"2006-01-02 15:04:05",
"2006-01-02",
"2006/01/02",
"01/02/2006 15:04:05",
"01/02/2006",
"01-02-2006",
"Mon Jan 2 15:04:05 2006",
"Mon Jan _2 15:04:05 2006",
"Jan 2 2006",
"Jan _2 2006",
}
for _, layout := range layouts {
if ts, err := time.Parse(layout, value); err == nil {
year, week := ts.ISOWeek()
return formatYearWeek(year, week)
}
}
return ""
}
func formatYearWeek(year, week int) string {
if year <= 0 || week <= 0 || week > 53 {
return ""
}
return fmt.Sprintf("%04d-W%02d", year, week)
}
// ApplyManufacturedYearWeekFromFRU attaches normalized manufactured_year_week to
// component details by exact serial-number match. Board-level FRU entries are not
// expanded to components.
func ApplyManufacturedYearWeekFromFRU(frus []models.FRUInfo, hw *models.HardwareConfig) {
if hw == nil || len(frus) == 0 {
return
}
bySerial := make(map[string]string, len(frus))
for _, fru := range frus {
serial := normalizeFRUSerial(fru.SerialNumber)
yearWeek := NormalizeManufacturedYearWeek(fru.MfgDate)
if serial == "" || yearWeek == "" {
continue
}
if _, exists := bySerial[serial]; exists {
continue
}
bySerial[serial] = yearWeek
}
if len(bySerial) == 0 {
return
}
for i := range hw.CPUs {
attachYearWeek(&hw.CPUs[i].Details, bySerial[normalizeFRUSerial(hw.CPUs[i].SerialNumber)])
}
for i := range hw.Memory {
attachYearWeek(&hw.Memory[i].Details, bySerial[normalizeFRUSerial(hw.Memory[i].SerialNumber)])
}
for i := range hw.Storage {
attachYearWeek(&hw.Storage[i].Details, bySerial[normalizeFRUSerial(hw.Storage[i].SerialNumber)])
}
for i := range hw.PCIeDevices {
attachYearWeek(&hw.PCIeDevices[i].Details, bySerial[normalizeFRUSerial(hw.PCIeDevices[i].SerialNumber)])
}
for i := range hw.GPUs {
attachYearWeek(&hw.GPUs[i].Details, bySerial[normalizeFRUSerial(hw.GPUs[i].SerialNumber)])
}
for i := range hw.NetworkAdapters {
attachYearWeek(&hw.NetworkAdapters[i].Details, bySerial[normalizeFRUSerial(hw.NetworkAdapters[i].SerialNumber)])
}
for i := range hw.PowerSupply {
attachYearWeek(&hw.PowerSupply[i].Details, bySerial[normalizeFRUSerial(hw.PowerSupply[i].SerialNumber)])
}
}
func attachYearWeek(details *map[string]any, yearWeek string) {
if yearWeek == "" {
return
}
if *details == nil {
*details = map[string]any{}
}
if existing, ok := (*details)["manufactured_year_week"]; ok && strings.TrimSpace(toString(existing)) != "" {
return
}
(*details)["manufactured_year_week"] = yearWeek
}
func normalizeFRUSerial(v string) string {
s := strings.TrimSpace(v)
if s == "" {
return ""
}
switch strings.ToUpper(s) {
case "N/A", "NA", "NULL", "UNKNOWN", "-", "0":
return ""
default:
return strings.ToUpper(s)
}
}
func toString(v any) string {
switch x := v.(type) {
case string:
return x
default:
return strings.TrimSpace(fmt.Sprint(v))
}
}