Files
napalm-linux/tests/test_command_channel.py
T
christianmanivong 350e3a6d6e
CI / test (3.10) (push) Successful in 38s
CI / test (3.11) (push) Successful in 37s
CI / test (3.12) (push) Successful in 39s
fix: adapt to napalm-device-types 4.7
napalm-device-types 4.7 changed three things this driver depends on:

- parse_dnf_security needs the "__DNF_RC=<status>" line that
  DNF_SECURITY_COMMAND prints and raises ValueError without it or when dnf
  failed. The yum security query had no such line, so listing updates on
  any yum host raised. _YUM_SECURITY_COMMAND now prints the marker the same
  way ("{ ...; echo __DNF_RC=$?; } | cat"). _rpm_security_names checks
  __DNF_RC itself and turns a failed query, or output cut short before the
  marker, into None (security unknown) rather than "no advisories" or an
  exception.
- OSDriver.get_users was renamed to get_os_users, since NAPALM's own
  get_users has another shape. LinuxDriver.get_users is now get_os_users
  with the same return value.
- run_on_transport reads an exec channel to EOF after its exit status
  arrives; the tests' fake channels gain recv/recv_stderr for that.

Require napalm-device-types>=4.7.1 and bump the version to 0.6.0.
2026-10-13 06:25:17 +00:00

180 lines
5.3 KiB
Python

"""The public command channel and container engine access (napalm-device-types 2.6.0).
netOrk used to reach a Linux host's shell through the private ``_send``: an
interactive PTY, stdout and stderr merged, no exit code. ``run_command`` and
``open_stream`` go through an exec channel on the same SSH transport instead,
and ``open_container_engine`` builds on them (NetOrk/netork#765). Privileges
work as they do everywhere else in this driver: root runs directly, a sudo
password goes to ``sudo -S`` on stdin and never onto a command line, and
without one ``sudo -n`` fails at once instead of hanging.
"""
from __future__ import annotations
from types import SimpleNamespace
import pytest
from napalm.base.exceptions import ConnectionClosedException
from napalm_linux import LinuxDriver
class FakeChannel:
def __init__(self):
self.command = None
self.sent = b""
def exec_command(self, command):
self.command = command
def settimeout(self, timeout):
pass
def sendall(self, data):
self.sent += data
def shutdown_write(self):
pass
def close(self):
pass
def recv_ready(self):
return False
def recv(self, size):
return b""
def recv_stderr_ready(self):
return False
def recv_stderr(self, size):
return b""
def exit_status_ready(self):
return True
def recv_exit_status(self):
return 0
class FakeTransport:
def __init__(self):
self.channels = []
def open_session(self):
self.channels.append(FakeChannel())
return self.channels[-1]
def _driver(*, root=False, sudo_password=None):
driver = LinuxDriver("h", "u", "p", optional_args={"sudo_password": sudo_password})
transport = FakeTransport()
driver._device = SimpleNamespace(remote_conn_pre=SimpleNamespace(get_transport=lambda: transport))
driver._root = root
return driver, transport
def test_an_unprivileged_command_runs_as_given():
driver, transport = _driver()
result = driver.run_command("docker version", timeout=5)
assert transport.channels[-1].command == "docker version"
assert transport.channels[-1].sent == b""
assert result.exit_code == 0
def test_a_privileged_command_with_a_sudo_password_reads_it_from_stdin():
driver, transport = _driver(sudo_password="s3cr3t")
driver.run_command("usermod -aG docker u", privileged=True, timeout=5)
channel = transport.channels[-1]
assert channel.command == "sudo -S -p '' sh -c 'usermod -aG docker u'"
assert channel.sent == b"s3cr3t\n"
assert "s3cr3t" not in channel.command
def test_a_privileged_command_without_a_password_fails_fast_instead_of_prompting():
driver, transport = _driver()
driver.run_command("id", privileged=True, timeout=5)
assert transport.channels[-1].command == "sudo -n sh -c id"
def test_a_root_login_needs_no_sudo():
driver, transport = _driver(root=True, sudo_password="s3cr3t")
driver.run_command("id", privileged=True, timeout=5)
assert transport.channels[-1].command == "id"
assert transport.channels[-1].sent == b""
def test_a_privileged_command_with_a_sudo_password_takes_no_stdin():
"""sudo -S reads the password from stdin. A sudo that does not prompt --
NOPASSWD, a cached timestamp -- leaves it there, and the command reads it as
its first line: it wrote the password into an sshd drop-in (NetOrk/netork#1029)."""
driver, transport = _driver(sudo_password="pw")
with pytest.raises(ValueError, match="stdin"):
driver.run_command("tee /etc/x", privileged=True, stdin=b"data", timeout=5)
assert transport.channels == [] # nothing reached the host
def test_privileged_stdin_without_a_sudo_password_goes_to_the_command():
"""sudo -n reads nothing from stdin: the data is the command's alone."""
driver, transport = _driver()
driver.run_command("tee /etc/x", privileged=True, stdin=b"data", timeout=5)
channel = transport.channels[-1]
assert channel.command == "sudo -n sh -c 'tee /etc/x'"
assert channel.sent == b"data"
def test_privileged_stdin_for_a_root_login_goes_to_the_command():
driver, transport = _driver(root=True, sudo_password="pw")
driver.run_command("tee /etc/x", privileged=True, stdin=b"data", timeout=5)
assert transport.channels[-1].command == "tee /etc/x"
assert transport.channels[-1].sent == b"data"
def test_unprivileged_stdin_with_a_sudo_password_goes_to_the_command():
driver, transport = _driver(sudo_password="pw")
driver.run_command("cat > /tmp/x", stdin=b"data", timeout=5)
assert transport.channels[-1].command == "cat > /tmp/x"
assert transport.channels[-1].sent == b"data"
def test_a_privileged_stream_gets_the_password_first():
driver, transport = _driver(sudo_password="pw")
driver.open_stream("journalctl -f", privileged=True)
channel = transport.channels[-1]
assert channel.command == "sudo -S -p '' sh -c 'journalctl -f'"
assert channel.sent == b"pw\n"
def test_the_container_engine_api_is_a_stream_over_dial_stdio():
driver, transport = _driver()
driver.open_container_engine("docker").open_api()
assert transport.channels[-1].command == "docker system dial-stdio"
def test_the_channel_needs_an_open_connection():
driver = LinuxDriver("h", "u", "p")
with pytest.raises(ConnectionClosedException):
driver.run_command("true")