feat: run commands and streams on an exec channel, reach the container engine
CI / test (3.10) (push) Failing after 16s
CI / test (3.11) (push) Failing after 16s
CI / test (3.12) (push) Successful in 39s
CI / test (3.10) (pull_request) Successful in 38s
CI / test (3.11) (pull_request) Successful in 36s
CI / test (3.12) (pull_request) Successful in 38s
CI / test (3.10) (push) Failing after 16s
CI / test (3.11) (push) Failing after 16s
CI / test (3.12) (push) Successful in 39s
CI / test (3.10) (pull_request) Successful in 38s
CI / test (3.11) (pull_request) Successful in 36s
CI / test (3.12) (pull_request) Successful in 38s
netOrk drove this driver's shell through the private `_send` (an interactive PTY, stdout and stderr merged, no exit code) and opened its own paramiko connections for Docker. napalm-device-types 2.6.0 makes the channel public; this implements it (NetOrk/netork#765): - `run_command()` and `open_stream()` open an exec channel on the SSH transport netmiko already holds: no second login, no PTY, a real exit status. - `privileged=True` follows the driver's existing rules: as root the command runs directly; with a sudo password it goes through `sudo -S -p ''` and the password is written to stdin, never onto the command line; without one `sudo -n` fails at once instead of hanging. - `ContainerEngineMixin` is mixed in, so `open_container_engine("docker")` streams the Engine API over `docker system dial-stdio`. The existing Docker methods are unchanged. Version 0.2.0, requires napalm-device-types >= 2.6.0. Refs NAPALM/napalm-device-types#17
This commit is contained in:
+55
-1
@@ -34,6 +34,9 @@ from napalm.base.netmiko_helpers import netmiko_args
|
||||
from napalm_device_types import (
|
||||
APT_UPGRADABLE_COMMAND,
|
||||
DNF_SECURITY_COMMAND,
|
||||
ByteStream,
|
||||
CommandResult,
|
||||
ContainerEngineMixin,
|
||||
FingerprintRule,
|
||||
HostStatusMixin,
|
||||
KernelFactsMixin,
|
||||
@@ -42,7 +45,9 @@ from napalm_device_types import (
|
||||
SystemdServicesMixin,
|
||||
SystemdUnavailable,
|
||||
parse_apt_upgradable,
|
||||
open_stream_on_transport,
|
||||
parse_dnf_security,
|
||||
run_on_transport,
|
||||
strip_terminal_codes,
|
||||
)
|
||||
from napalm_device_types.models import (
|
||||
@@ -216,7 +221,12 @@ def _short_image_id(raw: str) -> str:
|
||||
|
||||
|
||||
class LinuxDriver(
|
||||
KernelFactsMixin, ListeningSocketsMixin, SystemdServicesMixin, HostStatusMixin, OSDriver
|
||||
KernelFactsMixin,
|
||||
ListeningSocketsMixin,
|
||||
SystemdServicesMixin,
|
||||
HostStatusMixin,
|
||||
ContainerEngineMixin,
|
||||
OSDriver,
|
||||
):
|
||||
"""NAPALM driver for generic Linux systems.
|
||||
|
||||
@@ -397,6 +407,50 @@ class LinuxDriver(
|
||||
return self._sudo(command, read_timeout=timeout)
|
||||
return self._send(f"sudo -n {command}", read_timeout=timeout)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Command channel (napalm-device-types CommandChannelMixin)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _transport(self) -> Any:
|
||||
"""The SSH transport netmiko already holds, for exec channels next to its PTY."""
|
||||
if not self._device:
|
||||
raise ConnectionClosedException("Not connected")
|
||||
return self._device.remote_conn_pre.get_transport()
|
||||
|
||||
def _privileged(self, command: str, privileged: bool) -> tuple[str, bytes | None]:
|
||||
"""The command line that runs *command* with the privileges asked for,
|
||||
and what has to reach its stdin first.
|
||||
|
||||
Root runs it directly. With a sudo password, ``sudo -S`` reads it from
|
||||
stdin, so it never appears in a process list. Without one, ``sudo -n``
|
||||
fails at once where a prompt would hang. The command goes to ``sh -c``
|
||||
as one argument, so the privilege covers every part of it.
|
||||
"""
|
||||
if not privileged or self._is_root():
|
||||
return command, None
|
||||
if self._sudo_password:
|
||||
line = f"sudo -S -p '' sh -c {_shlex_quote(command)}"
|
||||
return line, f"{self._sudo_password}\n".encode()
|
||||
return f"sudo -n sh -c {_shlex_quote(command)}", None
|
||||
|
||||
def run_command(
|
||||
self,
|
||||
command: str,
|
||||
*,
|
||||
privileged: bool = False,
|
||||
timeout: float = 60,
|
||||
stdin: bytes | None = None,
|
||||
) -> CommandResult:
|
||||
"""Run *command* on an exec channel: no PTY, stderr apart, a real exit code."""
|
||||
line, prefix = self._privileged(command, privileged)
|
||||
data = (prefix or b"") + (stdin or b"") if (prefix or stdin) else None
|
||||
return run_on_transport(self._transport(), line, stdin=data, timeout=timeout)
|
||||
|
||||
def open_stream(self, command: str, *, privileged: bool = False) -> ByteStream:
|
||||
"""Start *command* on an exec channel and return a stream to it."""
|
||||
line, prefix = self._privileged(command, privileged)
|
||||
return open_stream_on_transport(self._transport(), line, stdin_prefix=prefix)
|
||||
|
||||
def reboot_host(self) -> None:
|
||||
"""Restart the host (``HostRebootMixin``); returns once the restart is under way.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user