diff --git a/bible-local/10-decisions.md b/bible-local/10-decisions.md index 9939792..c139262 100644 --- a/bible-local/10-decisions.md +++ b/bible-local/10-decisions.md @@ -1836,3 +1836,11 @@ combined-`component.log` onekeylog layout (classic `onekeylog/` root, has - Tests: `TestParseComponentLogFRU_BoardIdentityAndUUID`, `TestParseComponentLogFRU_AbsentSection`, `TestParseComponentLogSensors_FloatFansPower`. + +**Amendment (2026-09-01):** a second NF5280M6 pair showed iBMC `RESTful +version info` firmware versions carry a build-timestamp suffix +(`08.05.01 (02/21/2024 16:51:27)`) that the live-CD does not, churning a +FIRMWARE_CHANGED event per import. `cleanFirmwareVersion` now strips a trailing +` (...)` from those entries (BIOS then matches the live-CD exactly; BMC +`7.11.02` vs the live-CD's IPMI-truncated `7.11` is inherent and left as-is). +Test `TestExtractComponentFirmware_StripsBuildStamp`. diff --git a/internal/parser/vendors/inspur/component.go b/internal/parser/vendors/inspur/component.go index 0c005d6..95f40a6 100644 --- a/internal/parser/vendors/inspur/component.go +++ b/internal/parser/vendors/inspur/component.go @@ -1331,7 +1331,7 @@ func extractComponentFirmware(text string, hw *models.HardwareConfig) { if name == "" { continue } - version := strings.TrimSpace(e.DevVersion) + version := cleanFirmwareVersion(e.DevVersion) if version == "" { continue } @@ -1348,6 +1348,17 @@ func extractComponentFirmware(text string, hw *models.HardwareConfig) { } } +// firmwareBuildStampRe matches a trailing " (build date/time)" that iBMC appends +// to RESTful version info entries, e.g. "08.05.01 (02/21/2024 16:51:27)". +var firmwareBuildStampRe = regexp.MustCompile(`\s*\([^()]*\)\s*$`) + +// cleanFirmwareVersion drops the iBMC build-timestamp suffix so a BMC-dump +// firmware version matches the bare version a live-CD (dmidecode/redfish) reports +// for the same device, instead of churning a FIRMWARE_CHANGED event per import. +func cleanFirmwareVersion(v string) string { + return strings.TrimSpace(firmwareBuildStampRe.ReplaceAllString(strings.TrimSpace(v), "")) +} + // normalizeVersionInfoName converts RESTful version info dev_name to a clean label. // Returns "" for entries that should be skipped (inactive BMC, PSU slots). func normalizeVersionInfoName(name string) string { diff --git a/internal/parser/vendors/inspur/component_test.go b/internal/parser/vendors/inspur/component_test.go index 14a6ce5..b0571cc 100644 --- a/internal/parser/vendors/inspur/component_test.go +++ b/internal/parser/vendors/inspur/component_test.go @@ -310,6 +310,36 @@ BMC` } } +// TestExtractComponentFirmware_StripsBuildStamp guards against the iBMC +// "RESTful version info" build-timestamp suffix leaking into hardware.firmware, +// which made BIOS/BMC versions churn against the bare live-CD values. +func TestExtractComponentFirmware_StripsBuildStamp(t *testing.T) { + text := `RESTful version info: +[ { "id": 0, "dev_name": "Activate(BMC0)", "dev_version": "7.11.02 (2024-01-05 16:01:47)" }, + { "id": 1, "dev_name": "Inactivate(BMC1)", "dev_version": "7.11.02 (2024-01-05 16:01:47)" }, + { "id": 2, "dev_name": "BIOS", "dev_version": "08.05.01 (02/21/2024 16:51:27)" }, + { "id": 3, "dev_name": "ME", "dev_version": "4.4.4.500" } ] +RESTful CPU info: +{ }` + + hw := &models.HardwareConfig{} + extractComponentFirmware(text, hw) + + got := map[string]string{} + for _, fw := range hw.Firmware { + got[fw.DeviceName] = fw.Version + } + if got["BIOS"] != "08.05.01" { + t.Errorf("BIOS version = %q, want 08.05.01", got["BIOS"]) + } + if got["BMC"] != "7.11.02" { + t.Errorf("BMC version = %q, want 7.11.02", got["BMC"]) + } + if got["ME"] != "4.4.4.500" { + t.Errorf("ME version = %q, want 4.4.4.500 (untouched)", got["ME"]) + } +} + func TestParseHDDInfo_MergesIntoExistingStorage(t *testing.T) { text := `RESTful HDD info: [