iDRAC10-generation TSR bundles no longer ship sysinfo_dcim_view.xml / sysinfo_dcim_softwareidentity.xml, so the dell parser produced events but no hardware inventory for them. These bundles instead carry redfishidracwalk.tar.gz, a captured dump of the iDRAC's own Redfish tree. Add vendors/redfishtree, a shared helper that reconstructs a path->document map from a tar.gz/zip-packaged Redfish walk (vendor-independent detection: path hint + /redfish/v1 service-root/Systems/Chassis structural check) and replays it through the existing collector.ReplayRedfishFromRawPayloads. vendors/dell uses it to enrich DCIM-XML-derived data (append-only, existing dedupe passes resolve overlaps). Also register vendors/redfishwalk, a low-confidence fallback VendorParser using the same helpers, so any other vendor that starts shipping this kind of raw Redfish walk is picked up automatically without a dedicated parser. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
87 lines
3.3 KiB
Go
87 lines
3.3 KiB
Go
package dell
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"git.mchus.pro/mchus/logpile/internal/collector"
|
|
"git.mchus.pro/mchus/logpile/internal/models"
|
|
"git.mchus.pro/mchus/logpile/internal/parser"
|
|
"git.mchus.pro/mchus/logpile/internal/parser/vendors/redfishtree"
|
|
)
|
|
|
|
// redfishidracwalk.tar.gz is the inventory source on newer iDRAC10-generation
|
|
// TSR bundles, which no longer ship the sysinfo_dcim_view.xml / softwareidentity
|
|
// CIM-XML files. It is a captured directory dump of the iDRAC's own Redfish
|
|
// tree (see vendors/redfishtree for the detection/parsing rule), stored under
|
|
// tsr/hardware/sysinfo/inventory/.
|
|
func findRedfishWalkArchive(expanded []parser.ExtractedFile) *parser.ExtractedFile {
|
|
for i := range expanded {
|
|
path := strings.ToLower(strings.TrimSpace(expanded[i].Path))
|
|
if strings.HasSuffix(path, "redfishidracwalk.tar.gz") {
|
|
return &expanded[i]
|
|
}
|
|
}
|
|
// Fall back to the vendor-independent structural detection, in case a
|
|
// future iDRAC generation renames the export.
|
|
for _, f := range redfishtree.FindCandidateArchives(expanded) {
|
|
if redfishtree.Build(f.Content) != nil {
|
|
f := f
|
|
return &f
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// parseRedfishWalk enriches result with hardware inventory replayed from a
|
|
// captured Redfish tree dump. It only adds data; existing dedupe passes in
|
|
// Parse() resolve overlaps with the DCIM-XML-derived source in favor of
|
|
// whichever entry was appended first (DCIM-derived data, when present).
|
|
func parseRedfishWalk(f *parser.ExtractedFile, result *models.AnalysisResult) {
|
|
if f == nil {
|
|
return
|
|
}
|
|
tree := redfishtree.Build(f.Content)
|
|
if tree == nil {
|
|
return
|
|
}
|
|
replayed, err := collector.ReplayRedfishFromRawPayloads(map[string]any{"redfish_tree": tree}, nil)
|
|
if err != nil || replayed == nil {
|
|
return
|
|
}
|
|
mergeRedfishReplay(result, replayed)
|
|
}
|
|
|
|
func mergeRedfishReplay(result *models.AnalysisResult, replayed *models.AnalysisResult) {
|
|
if result.Hardware == nil {
|
|
result.Hardware = &models.HardwareConfig{}
|
|
}
|
|
hw := result.Hardware
|
|
if rhw := replayed.Hardware; rhw != nil {
|
|
setIfEmpty(&hw.BoardInfo.Manufacturer, rhw.BoardInfo.Manufacturer)
|
|
setIfEmpty(&hw.BoardInfo.ProductName, rhw.BoardInfo.ProductName)
|
|
setIfEmpty(&hw.BoardInfo.Description, rhw.BoardInfo.Description)
|
|
setIfEmpty(&hw.BoardInfo.SerialNumber, rhw.BoardInfo.SerialNumber)
|
|
setIfEmpty(&hw.BoardInfo.PartNumber, rhw.BoardInfo.PartNumber)
|
|
setIfEmpty(&hw.BoardInfo.Version, rhw.BoardInfo.Version)
|
|
setIfEmpty(&hw.BoardInfo.UUID, rhw.BoardInfo.UUID)
|
|
setIfEmpty(&hw.BoardInfo.BMCMACAddress, rhw.BoardInfo.BMCMACAddress)
|
|
|
|
hw.Firmware = append(hw.Firmware, rhw.Firmware...)
|
|
hw.CPUs = append(hw.CPUs, rhw.CPUs...)
|
|
hw.Memory = append(hw.Memory, rhw.Memory...)
|
|
hw.Storage = append(hw.Storage, rhw.Storage...)
|
|
hw.Volumes = append(hw.Volumes, rhw.Volumes...)
|
|
hw.PCIeDevices = append(hw.PCIeDevices, rhw.PCIeDevices...)
|
|
hw.GPUs = append(hw.GPUs, rhw.GPUs...)
|
|
hw.NetworkAdapters = append(hw.NetworkAdapters, rhw.NetworkAdapters...)
|
|
hw.PowerSupply = append(hw.PowerSupply, rhw.PowerSupply...)
|
|
}
|
|
|
|
result.Sensors = append(result.Sensors, replayed.Sensors...)
|
|
result.FRU = append(result.FRU, replayed.FRU...)
|
|
result.Events = append(result.Events, replayed.Events...)
|
|
if result.InventoryLastModifiedAt.IsZero() {
|
|
result.InventoryLastModifiedAt = replayed.InventoryLastModifiedAt
|
|
}
|
|
}
|