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
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:
1 parent
100a6d36c5
commit
595496549b
5 files changed
+120
-57
No files matched your search
@@ -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``.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in new issue
Block a user