"""NAPALM ``get_facts`` from a host's properties or a vCenter's ``about``.""" from __future__ import annotations import ipaddress from typing import Any #: otherIdentifyingInfo keys that carry a chassis serial, most specific first. #: Dell reports its service tag as ``ServiceTag``; HPE and Supermicro use #: ``SerialNumberTag``; blades add ``EnclosureSerialNumberTag`` for the chassis. _SERIAL_KEYS = ("SerialNumberTag", "EnclosureSerialNumberTag", "ServiceTag") def host_serial(host: dict[str, Any]) -> str: """The host's hardware serial, or ``""`` when it does not report one.""" info = host.get("hardware.systemInfo") or {} if str(info.get("serialNumber", "")).strip(): return str(info["serialNumber"]).strip() for source in (info, host.get("summary.hardware") or {}): by_key: dict[str, str] = {} for entry in source.get("otherIdentifyingInfo", []): value = str(entry.get("identifierValue", "")).strip() key = (entry.get("identifierType") or {}).get("key", "") if value and key not in by_key: by_key[key] = value for key in _SERIAL_KEYS: if key in by_key: return by_key[key] return "" def _host_names(host: dict[str, Any]) -> tuple[str, str]: dns = host.get("config.network.dnsConfig") or {} short = dns.get("hostName") or "" domain = dns.get("domainName") or "" if short: return short, f"{short}.{domain}" if domain else short name = host.get("name", "") return name.split(".", 1)[0], name def host_interface_names(host: dict[str, Any]) -> list[str]: nics = (host.get("config.network.pnic") or []) + (host.get("config.network.vnic") or []) return sorted(n["device"] for n in nics if n.get("device")) def esxi_facts(about: dict[str, Any], host: dict[str, Any]) -> dict[str, Any]: """Facts of a standalone ESXi host (or one reached directly).""" hostname, fqdn = _host_names(host) # hardware.systemInfo is read straight from SMBIOS; summary.hardware is a # cached copy that has been seen to disagree with it. system = host.get("hardware.systemInfo") or {} summary = host.get("summary.hardware") or {} product = host.get("config.product") or about uptime = (host.get("summary.quickStats") or {}).get("uptime") return { "hostname": hostname, "fqdn": fqdn, "vendor": system.get("vendor") or summary.get("vendor", ""), "model": system.get("model") or summary.get("model", ""), "serial_number": host_serial(host), "os_version": product.get("fullName", ""), "uptime": float(uptime) if uptime is not None else -1.0, "interface_list": host_interface_names(host), } def _is_ip(value: str) -> bool: try: ipaddress.ip_address(value) except ValueError: return False return True def vcenter_facts(about: dict[str, Any], address: str) -> dict[str, Any]: """Facts of a vCenter Server. The vSphere API does not expose the appliance's own name, uptime or NICs; those belong to the appliance management API. The address netOrk used to connect is the best available name, and the instance UUID is the one identifier that is stable for the lifetime of the installation. """ hostname = address if _is_ip(address) else address.split(".", 1)[0] return { "hostname": hostname, "fqdn": address, "vendor": about.get("vendor", ""), "model": about.get("name", ""), "serial_number": about.get("instanceUuid", ""), "os_version": about.get("fullName", ""), "uptime": -1.0, "interface_list": [], }