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.
135 lines
3.8 KiB
Python
135 lines
3.8 KiB
Python
"""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
|