Files
logpile/internal/parser/vendors/dell/redfish_walk.go
T
Mikhail ChusavitinandClaude Sonnet 5 eaaf5c09d3 feat(hardware): collect and export licenses via Dell iDRAC10 Redfish walk
Implements the hardware.licenses[] contract section (v2.12, refreshed from
reanimator/core's hardware-ingest-contract.md — was v2.11 locally).

- models.License / HardwareConfig.Licenses mirror the contract field set.
- collector.collectLicenses() reads the standard DMTF
  /redfish/v1/LicenseService/Licenses collection during Redfish-walk replay;
  it's a generic DMTF resource, not Dell-specific, so any future vendor's
  Redfish walk gets license collection for free through
  ReplayRedfishFromRawPayloads.
- vendors/dell merges replayed Licenses like every other category.
- exporter.convertLicenses/dedupeLicenses wire hw.Licenses into the
  reanimator export directly (no canonical-devices merge — licenses have no
  physical identity to merge on), setting Present on every record from the
  start (per the ADL-049 round-trip lesson).
- chart viewer renders a licenses section in /chart/current.

Verified end-to-end on the PowerEdge R7715 (1TVFYL4) TSR: 3 system-level
licenses extracted and correctly exported/rendered.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 11:48:21 +03:00

88 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...)
hw.Licenses = append(hw.Licenses, rhw.Licenses...)
}
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
}
}