feat: a public command channel; no warnings of its own (0.4.0)
CI / test (3.10) (push) Successful in 25s
CI / test (3.11) (push) Successful in 24s
CI / test (3.12) (push) Successful in 25s
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 25s

run_command and open_stream (napalm-device-types CommandChannelMixin,
which AccessPointDriver carries) run on an SSH exec channel next to
netmiko's shell: stdin, stderr apart, a real exit code. netOrk sent UCI
scripts and the wifi channel through the private _send_command, typing
a file in base64 chunks or printf lines; with stdin it writes it with
cat, and base64 is needed for nothing (netOrk #1113). OpenWrt has no
sudo, so a privileged command from a login other than root is refused
before it runs.

get_device_warnings goes with it: no_base64 was its last code.
This commit is contained in:
Christian Manivong committed 2026-10-18 14:08:09 +02:00
1 parent 100a6d36c5
commit 595496549b
5 files changed
+120 -57

No files matched your search

+40
View File
@@ -29,10 +29,13 @@ from netmiko.exceptions import NetmikoTimeoutException, NetmikoAuthenticationExc
from napalm_device_types import (
AccessPointDriver,
ByteStream,
CommandResult,
EnsurePackageMixin,
FingerprintRule,
SnmpAgentMixin,
)
from napalm_device_types.channel import open_stream_on_transport, run_on_transport
from napalm_device_types.listening import ListeningSocketsMixin
from napalm.base.exceptions import (
ConnectionException,
@@ -183,6 +186,43 @@ class OpenWrtDriver(
except (socket.error, EOFError) as exc:
raise ConnectionClosedException(str(exc)) from exc
# ------------------------------------------------------------------
# Command channel (napalm-device-types CommandChannelMixin, via AccessPointDriver)
# ------------------------------------------------------------------
def _transport(self) -> Any:
"""The SSH transport netmiko already holds, for exec channels next to its shell."""
if self.device is None:
raise ConnectionClosedException("Not connected")
return self.device.remote_conn_pre.get_transport()
def _check_privilege(self, privileged: bool) -> None:
if privileged and self.username != "root":
raise PermissionError(
f"{self.hostname}: OpenWrt has no sudo; {self.username!r} cannot run as root"
)
def run_command(
self,
command: str,
*,
privileged: bool = False,
timeout: float = 60,
stdin: bytes | None = None,
) -> CommandResult:
"""Run *command* on an SSH exec channel: no PTY, stderr apart, a real exit code.
The login is root on OpenWrt as a rule, and there is no sudo: a
privileged command from any other login is refused before it runs.
"""
self._check_privilege(privileged)
return run_on_transport(self._transport(), command, stdin=stdin, timeout=timeout)
def open_stream(self, command: str, *, privileged: bool = False) -> ByteStream:
"""Start *command* on an SSH exec channel and return a stream to it."""
self._check_privilege(privileged)
return open_stream_on_transport(self._transport(), command)
def _run_listening_sockets_command(self, command: str, *, privileged: bool) -> str:
"""Run the listening-socket command for ``ListeningSocketsMixin``.
-13
View File
@@ -390,19 +390,6 @@ class OpenWrtSystemMixin:
output = self._send_command(f"/etc/init.d/{name} {action} 2>&1")
return {"success": True, "output": output}
def get_device_warnings(self) -> list[dict[str, Any]]:
"""Only a missing ``base64`` binary, which BusyBox may lack.
It is not a package the caller can look up -- BusyBox may provide it as
an applet -- so it stays here until the shell commands callers send get
a public method. lldpd, auc and the pending updates are facts
(``get_packages``, ``get_available_updates``); whether they are a
problem is the caller's to decide.
"""
if self._send_command("command -v base64 2>/dev/null").strip():
return []
return [{"code": "no_base64"}]
def get_available_updates(self) -> list[dict[str, Any]]:
"""Return list of upgradable packages from the local package manager cache.