fix: raise when an update reader cannot read, and report host status
CI / test (3.10) (push) Successful in 26s
CI / test (3.11) (push) Successful in 25s
CI / test (3.12) (push) Successful in 26s
CI / test (3.10) (pull_request) Successful in 25s
CI / test (3.11) (pull_request) Successful in 24s
CI / test (3.12) (pull_request) Successful in 26s
CI / test (3.10) (push) Successful in 26s
CI / test (3.11) (push) Successful in 25s
CI / test (3.12) (push) Successful in 26s
CI / test (3.10) (pull_request) Successful in 25s
CI / test (3.11) (pull_request) Successful in 24s
CI / test (3.12) (pull_request) Successful in 26s
netOrk reads an empty update list as "no updates" and closes every patch clock on the host. A refused sudo, a failing pkg, syspatch or freebsd-update, or a pkg database pkg cannot read used to come back as that empty list. Each of these now raises. Only pkg's "is not installed" still means no packages. BsdDriver takes on napalm-device-types' HostStatusMixin. On FreeBSD it reports a host whose installed kernel differs from the running one as needing a reboot (device-types 4.1). OpenBSD stays unknown. Closes #4
This commit is contained in:
+29
-1
@@ -17,6 +17,7 @@ from typing import Any, Optional
|
||||
import paramiko
|
||||
from napalm.base.exceptions import ConnectionClosedException, ConnectionException
|
||||
from napalm_device_types import OSDriver
|
||||
from napalm_device_types.host_status import HostStatusMixin
|
||||
from napalm_device_types.channel import (
|
||||
ByteStream,
|
||||
CommandResult,
|
||||
@@ -47,7 +48,7 @@ _SNMPD_CONF = (
|
||||
_SNMP_TYPES = ("STRING:", "INTEGER:", "OID:", "Timeticks:", "Hex-STRING:", "IpAddress:")
|
||||
|
||||
|
||||
class BsdDriver(OSDriver):
|
||||
class BsdDriver(HostStatusMixin, OSDriver):
|
||||
"""Base for the BSD drivers; a concrete one names the commands that differ."""
|
||||
|
||||
VENDOR = ""
|
||||
@@ -163,11 +164,38 @@ class BsdDriver(OSDriver):
|
||||
line, prefix = self._privileged(command, privileged)
|
||||
return open_stream_on_transport(self._transport(), line, stdin_prefix=prefix)
|
||||
|
||||
def _read(
|
||||
self,
|
||||
command: str,
|
||||
*,
|
||||
privileged: bool = False,
|
||||
timeout: float = 60,
|
||||
ok: tuple[int, ...] = (0,),
|
||||
) -> CommandResult:
|
||||
"""Run a reader's command; raise when it did not read.
|
||||
|
||||
A reader that cannot read raises rather than return "nothing": an empty
|
||||
update list would close every patch clock netOrk keeps (napalm-bsd#4).
|
||||
"""
|
||||
result = self.run_command(command, privileged=privileged, timeout=timeout)
|
||||
if result.exit_code not in ok:
|
||||
reason = (result.stderr or result.stdout).strip() or f"exit {result.exit_code}"
|
||||
raise RuntimeError(f"{command}: {reason}")
|
||||
return result
|
||||
|
||||
def _out(self, command: str, *, privileged: bool = False, timeout: float = 60) -> str:
|
||||
"""What *command* printed. A failing command prints nothing useful,
|
||||
and the readers below treat empty output as "nothing there"."""
|
||||
return self.run_command(command, privileged=privileged, timeout=timeout).stdout.strip()
|
||||
|
||||
def _run_host_status_command(self, command: str) -> str:
|
||||
"""The transport for ``HostStatusMixin.get_host_status``: read-only, no root.
|
||||
|
||||
On FreeBSD the report compares ``freebsd-version -k`` with ``-r``
|
||||
(napalm-device-types 4.1); elsewhere "reboot required" stays unknown.
|
||||
"""
|
||||
return self.run_command(command).stdout
|
||||
|
||||
# -- facts -------------------------------------------------------------------
|
||||
|
||||
def _platform(self) -> dict[str, str]:
|
||||
|
||||
+24
-8
@@ -53,8 +53,18 @@ class FreeBSDDriver(BsdDriver):
|
||||
return parse.service_status(output)
|
||||
|
||||
def _has_pkg(self) -> bool:
|
||||
"""pkg is bootstrapped; a hand-installed classic system may have only the stub."""
|
||||
return self.run_command("pkg -N").exit_code == 0
|
||||
"""pkg is bootstrapped; a hand-installed classic system may have only the stub.
|
||||
|
||||
Only the stub's "not installed" means no pkg; any other failure (a
|
||||
database pkg cannot read) raises rather than read as "no packages".
|
||||
"""
|
||||
result = self.run_command("pkg -N")
|
||||
if result.exit_code == 0:
|
||||
return True
|
||||
message = (result.stderr or result.stdout).strip()
|
||||
if "is not installed" in message:
|
||||
return False
|
||||
raise RuntimeError(f"pkg -N: {message or f'exit {result.exit_code}'}")
|
||||
|
||||
def get_packages(self) -> list[dict]:
|
||||
return parse.pkg_query(self._out(self.PKG_QUERY)) if self._has_pkg() else []
|
||||
@@ -69,10 +79,13 @@ class FreeBSDDriver(BsdDriver):
|
||||
"""
|
||||
updates: list[dict] = []
|
||||
if self._has_pkg():
|
||||
updates = parse.pkg_upgrades(self._out("pkg upgrade -n", privileged=True, timeout=300))
|
||||
upgrade = self._read("pkg upgrade -n", privileged=True, timeout=300)
|
||||
updates = parse.pkg_upgrades(upgrade.stdout)
|
||||
audit = self.run_command("pkg audit -Fq", privileged=True, timeout=120)
|
||||
known = audit.exit_code in (0, 1) # 1: vulnerable packages found
|
||||
vulnerable = parse.pkg_audit(audit.stdout) if known else set()
|
||||
vulnerable = parse.pkg_audit(audit.stdout)
|
||||
# 1 is "vulnerable packages found" and any error alike; only a list
|
||||
# of packages makes it a verdict.
|
||||
known = audit.exit_code == 0 or (audit.exit_code == 1 and bool(vulnerable))
|
||||
for update in updates:
|
||||
update["security"] = (update["name"] in vulnerable) if known else None
|
||||
if "FreeBSD-base" in self._out("pkg repos -l").split():
|
||||
@@ -86,10 +99,13 @@ class FreeBSDDriver(BsdDriver):
|
||||
2 when there are none; it does not fetch, which ``freebsd-update cron``
|
||||
does daily where it is enabled.
|
||||
"""
|
||||
ready = self.run_command(
|
||||
"freebsd-update --not-running-from-cron updatesready", privileged=True, timeout=60
|
||||
ready = self._read(
|
||||
"freebsd-update --not-running-from-cron updatesready",
|
||||
privileged=True,
|
||||
timeout=60,
|
||||
ok=(0, 2),
|
||||
)
|
||||
if ready.exit_code != 0:
|
||||
if ready.exit_code == 2:
|
||||
return []
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -64,10 +64,10 @@ class OpenBSDDriver(BsdDriver):
|
||||
updates: list[dict] = [
|
||||
{**candidate, "origin": None, "security": None}
|
||||
for candidate in parse.pkg_add_candidates(
|
||||
self._out("pkg_add -u -n -v", privileged=True, timeout=300)
|
||||
self._read("pkg_add -u -n -v", privileged=True, timeout=300).stdout
|
||||
)
|
||||
]
|
||||
patches = parse.syspatch(self._out("syspatch -c", privileged=True, timeout=120))
|
||||
patches = parse.syspatch(self._read("syspatch -c", privileged=True, timeout=120).stdout)
|
||||
if patches:
|
||||
installed = parse.syspatch(self._out("syspatch -l", privileged=True))
|
||||
summary = (
|
||||
|
||||
Reference in New Issue
Block a user