feat: report the running kernel's modules and build configuration #8

Merged
christianmanivong merged 1 commits from feat/kernel-facts into master 2026-10-05 04:36:47 +00:00
3 changed files with 46 additions and 3 deletions
+10 -2
View File
@@ -31,7 +31,7 @@ from netmiko.exceptions import (
) )
from napalm.base.exceptions import ConnectionException, ConnectionClosedException from napalm.base.exceptions import ConnectionException, ConnectionClosedException
from napalm.base.netmiko_helpers import netmiko_args 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 ( from napalm_device_types.models import (
ApplyUpdatesResultDict, ApplyUpdatesResultDict,
CronJobDict, CronJobDict,
@@ -138,7 +138,7 @@ def _short_image_id(raw: str) -> str:
return raw.strip().removeprefix("sha256:")[:12] return raw.strip().removeprefix("sha256:")[:12]
class LinuxDriver(OSDriver): class LinuxDriver(KernelFactsMixin, OSDriver):
"""NAPALM driver for generic Linux systems. """NAPALM driver for generic Linux systems.
Connects via SSH (netmiko ``linux`` device type) and auto-detects the 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 # OSDriver – package management
# ------------------------------------------------------------------ # ------------------------------------------------------------------
+1 -1
View File
@@ -37,7 +37,7 @@ classifiers = [
] ]
dependencies = [ dependencies = [
"napalm>=4.0", "napalm>=4.0",
"napalm-device-types>=0.3.0", "napalm-device-types>=2.1.0",
"netmiko>=4.0.0", "netmiko>=4.0.0",
"paramiko>=5.0.0", # CVE-2026-44405 "paramiko>=5.0.0", # CVE-2026-44405
] ]
+35
View File
@@ -1145,3 +1145,38 @@ class TestUninstallExitStatus:
result = driver.uninstall_package("wazuh-agent") result = driver.uninstall_package("wazuh-agent")
assert result["success"] is False 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()