Add parsers for the Redfish resources in redfish-map.md
Pure functions for ServiceRoot, ComputerSystem (facts, power state, reset action and ActionInfo), Processors, Memory, Storage/Drives/SimpleStorage, Chassis Thermal and Power as well as ThermalSubsystem/PowerSubsystem/Sensors (with environment_sources to pick whichever exists), Manager, EthernetInterfaces, ManagerNetworkProtocol, accounts, firmware inventory and log services/entries.
This commit is contained in:
18 files changed
+1926
No files matched your search
@@ -0,0 +1,134 @@
|
||||
"""Helpers shared by the parsers. All pure: dict in, plain values out."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping
|
||||
|
||||
# DMI defaults that board vendors leave in place (Supermicro boards ship most of them).
|
||||
PLACEHOLDERS = frozenset({
|
||||
"to be filled by o.e.m.",
|
||||
"default string",
|
||||
"not specified",
|
||||
"not applicable",
|
||||
"n/a",
|
||||
"none",
|
||||
"0123456789",
|
||||
"system serial number",
|
||||
"system product name",
|
||||
"system manufacturer",
|
||||
})
|
||||
|
||||
|
||||
def text(value: Any) -> str | None:
|
||||
"""A stripped string, ``None`` for missing or blank values."""
|
||||
if value is None:
|
||||
return None
|
||||
value = str(value).strip()
|
||||
return value or None
|
||||
|
||||
|
||||
def ident(value: Any) -> str | None:
|
||||
"""Like ``text``, but DMI placeholder strings count as missing too."""
|
||||
value = text(value)
|
||||
if value is None or value.lower() in PLACEHOLDERS:
|
||||
return None
|
||||
return value
|
||||
|
||||
|
||||
def number(value: Any) -> float | None:
|
||||
"""A float, ``None`` if the value is missing or not a number."""
|
||||
if isinstance(value, bool) or value is None:
|
||||
return None
|
||||
try:
|
||||
return float(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def integer(value: Any) -> int | None:
|
||||
if isinstance(value, bool) or value is None:
|
||||
return None
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def boolean(value: Any) -> bool | None:
|
||||
return value if isinstance(value, bool) else None
|
||||
|
||||
|
||||
def odata_id(ref: Any) -> str | None:
|
||||
"""The ``@odata.id`` of a reference object, or ``None``."""
|
||||
if isinstance(ref, Mapping):
|
||||
return text(ref.get("@odata.id"))
|
||||
return None
|
||||
|
||||
|
||||
def link(resource: Mapping[str, Any], *keys: str) -> str | None:
|
||||
"""Follow ``keys`` into nested objects and return the ``@odata.id`` found there."""
|
||||
value: Any = resource
|
||||
for key in keys:
|
||||
if not isinstance(value, Mapping):
|
||||
return None
|
||||
value = value.get(key)
|
||||
return odata_id(value)
|
||||
|
||||
|
||||
def links(resource: Mapping[str, Any]) -> dict[str, str]:
|
||||
"""All top-level properties of ``resource`` that are single references."""
|
||||
found = {}
|
||||
for key, value in resource.items():
|
||||
if key.startswith("@") or key == "Links":
|
||||
continue
|
||||
path = odata_id(value)
|
||||
if path is not None:
|
||||
found[key] = path
|
||||
return found
|
||||
|
||||
|
||||
def link_list(resource: Mapping[str, Any], *keys: str) -> list[str]:
|
||||
"""The ``@odata.id`` of each reference in the array at ``keys``."""
|
||||
value: Any = resource
|
||||
for key in keys:
|
||||
if not isinstance(value, Mapping):
|
||||
return []
|
||||
value = value.get(key)
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
return [path for path in (odata_id(ref) for ref in value) if path is not None]
|
||||
|
||||
|
||||
def status(resource: Mapping[str, Any]) -> tuple[str | None, str | None]:
|
||||
"""``(Status.State, Status.Health)``."""
|
||||
st = resource.get("Status")
|
||||
if not isinstance(st, Mapping):
|
||||
return None, None
|
||||
return text(st.get("State")), text(st.get("Health"))
|
||||
|
||||
|
||||
def present(resource: Mapping[str, Any]) -> bool:
|
||||
return status(resource)[0] != "Absent"
|
||||
|
||||
|
||||
def location_label(resource: Mapping[str, Any]) -> str | None:
|
||||
"""``Location.PartLocation.ServiceLabel``, the printed label of a slot or bay."""
|
||||
loc = resource.get("Location")
|
||||
if not isinstance(loc, Mapping):
|
||||
return None
|
||||
part = loc.get("PartLocation")
|
||||
if not isinstance(part, Mapping):
|
||||
return None
|
||||
return text(part.get("ServiceLabel"))
|
||||
|
||||
|
||||
def unique_name(names: Mapping[str, Any], name: str, suffix: Any) -> str:
|
||||
"""``name``, or ``name (suffix)`` if it is already taken."""
|
||||
if name not in names:
|
||||
return name
|
||||
candidate = f"{name} ({suffix})"
|
||||
n = 2
|
||||
while candidate in names:
|
||||
candidate = f"{name} ({suffix}-{n})"
|
||||
n += 1
|
||||
return candidate
|
||||
Reference in new issue
Block a user