From a6e5568e0b2448718f93b60942d4d0b1bfac0346 Mon Sep 17 00:00:00 2001 From: Christian Manivong Date: Mon, 5 Oct 2026 06:17:30 +0200 Subject: [PATCH] feat: report the running kernel's modules and build configuration LinuxDriver mixes in KernelFactsMixin from napalm-device-types and supplies only the transport: the shared read-only command over the existing SSH session, no sudo, one round trip. OpenMediaVault and QNAP inherit it. Requires napalm-device-types 2.1.0. --- napalm_linux/linux.py | 12 ++++++++++-- pyproject.toml | 2 +- tests/test_linux.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/napalm_linux/linux.py b/napalm_linux/linux.py index a996405..e2d09bd 100644 --- a/napalm_linux/linux.py +++ b/napalm_linux/linux.py @@ -31,7 +31,7 @@ from netmiko.exceptions import ( ) from napalm.base.exceptions import ConnectionException, ConnectionClosedException from napalm.base.netmiko_helpers import netmiko_args -from napalm_device_types import FingerprintRule, OSDriver +from napalm_device_types import FingerprintRule, KernelFactsMixin, OSDriver from napalm_device_types.models import ( ApplyUpdatesResultDict, CronJobDict, @@ -138,7 +138,7 @@ def _short_image_id(raw: str) -> str: return raw.strip().removeprefix("sha256:")[:12] -class LinuxDriver(OSDriver): +class LinuxDriver(KernelFactsMixin, OSDriver): """NAPALM driver for generic Linux systems. Connects via SSH (netmiko ``linux`` device type) and auto-detects the @@ -840,6 +840,14 @@ class LinuxDriver(OSDriver): } } + # ------------------------------------------------------------------ + # KernelFactsMixin – the transport for get_kernel_facts + # ------------------------------------------------------------------ + + def _run_kernel_facts_command(self, command: str) -> str: + """The transport for ``KernelFactsMixin.get_kernel_facts``: read-only, no sudo.""" + return self._send(command, read_timeout=60) + # ------------------------------------------------------------------ # OSDriver – package management # ------------------------------------------------------------------ diff --git a/pyproject.toml b/pyproject.toml index 176f40a..7ac3220 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ classifiers = [ ] dependencies = [ "napalm>=4.0", - "napalm-device-types>=0.3.0", + "napalm-device-types>=2.1.0", "netmiko>=4.0.0", "paramiko>=5.0.0", # CVE-2026-44405 ] diff --git a/tests/test_linux.py b/tests/test_linux.py index 8559323..a5b6a94 100644 --- a/tests/test_linux.py +++ b/tests/test_linux.py @@ -1145,3 +1145,38 @@ class TestUninstallExitStatus: result = driver.uninstall_package("wazuh-agent") assert result["success"] is False + + +# --------------------------------------------------------------------------- +# get_kernel_facts -- the command and its parse live in napalm-device-types +# --------------------------------------------------------------------------- + + +def _kernel_wire(report: str) -> str: + import base64 + import gzip + + return "KFACTS_BEGIN\n" + base64.encodebytes(gzip.compress(report.encode())).decode() + "KFACTS_END" + + +def test_get_kernel_facts_carries_the_shared_command_across(driver): + from napalm_device_types import KernelFactsMixin + from napalm_device_types.kernel import KERNEL_FACTS_COMMAND + + assert isinstance(driver, KernelFactsMixin) + + report = "[release]\n6.1.0-25-amd64\n[loaded]\ntipc\n[available]\nkernel/net/tipc/tipc.ko.xz\n" + with patch.object(driver, "_send", return_value=_kernel_wire(report)) as send: + facts = driver.get_kernel_facts() + + assert send.call_args.args[0] == KERNEL_FACTS_COMMAND + assert facts["release"] == "6.1.0-25-amd64" + assert facts["loaded"] == ["tipc"] + assert facts["available"] == ["tipc"] + assert facts["builtin"] is None + + +def test_get_kernel_facts_raises_on_output_without_a_report(driver): + with patch.object(driver, "_send", return_value="sh: base64: not found"): + with pytest.raises(ValueError): + driver.get_kernel_facts()