feat(inspur_legacy): new parser for pre-Kaytus Inspur onekeylog

Older AMI-BMC Inspur onekeylog archives (NF5466M5 / NF5280M5 generation)
opened to an empty result: they carry none of the files the inspur parser
keys on (no asset.json, devicefrusdr.log, selelist.csv or component.log).

New package internal/parser/vendors/inspur_legacy (vendor id inspur_legacy),
separate from inspur:
- binary IPMI FRU decode (FRU.bin) -> board identity
- Inspur_AssetInfoInventory.log -> CPU / memory / PCIe / PSU inventory
- events from Inspur_<model>_<serial>_IDL, sel.log, blackbox.log,
  MegaRAID raid0.log, and the flat AMI <severity>.log files
- no live sensors in this archive class -> recorded as a collection error
- BMC clock timestamps before 2010 dropped as un-set (1970 / ~2005 RTC)

Registry: add optional PrioritizedParser { DetectPriority() int } so a
confidence tie is broken by specificity. inspur_legacy returns 10 and also
declines (Detect 0) when modern Kaytus markers are present, so the two
Inspur parsers never fight over a newer dump.

Docs: ADL-065, 06-parsers.md, releases/v1.32.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VL8wLGD6Lnp6hZCpqT6cZ
This commit is contained in:
Mikhail Chusavitin
2026-09-02 12:51:53 +03:00
co-authored by Claude Sonnet 5
parent be37f1852b
commit 3311bafd8e
18 changed files with 1827 additions and 5 deletions
+26 -3
View File
@@ -81,6 +81,29 @@ type DetectResult struct {
Confidence int
}
// PrioritizedParser is an optional interface a parser may implement to break
// confidence ties in detection. A more specific parser that overlaps with a
// broader one on the same archive class returns a higher priority so it is
// chosen when both report equal confidence. Parsers that do not implement it
// are treated as priority 0.
type PrioritizedParser interface {
DetectPriority() int
}
func detectPriority(p VendorParser) int {
if pp, ok := p.(PrioritizedParser); ok {
return pp.DetectPriority()
}
return 0
}
func lessByConfidenceThenPriority(a, b DetectResult) bool {
if a.Confidence != b.Confidence {
return a.Confidence > b.Confidence
}
return detectPriority(a.Parser) > detectPriority(b.Parser)
}
// DetectFormat tries to detect archive format and returns best matching parser
func DetectFormat(files []ExtractedFile) (VendorParser, error) {
registryLock.RLock()
@@ -102,9 +125,9 @@ func DetectFormat(files []ExtractedFile) (VendorParser, error) {
return nil, fmt.Errorf("no parser found for this archive format")
}
// Sort by confidence descending
// Sort by confidence descending, breaking ties by detect priority.
sort.Slice(results, func(i, j int) bool {
return results[i].Confidence > results[j].Confidence
return lessByConfidenceThenPriority(results[i], results[j])
})
return results[0].Parser, nil
@@ -128,7 +151,7 @@ func DetectAllFormats(files []ExtractedFile) []DetectResult {
}
sort.Slice(results, func(i, j int) bool {
return results[i].Confidence > results[j].Confidence
return lessByConfidenceThenPriority(results[i], results[j])
})
return results