Source code for netgear_switch.transport.cli.telnet

"""Thin telnet CLI transport (implements ``CliSession``), transport-only.

Reuses the SAME ``ShellDriver`` (and therefore the same parsers) as the SSH
transport -- it differs only in carrying bytes over ``telnetlib`` instead of a
paramiko channel. It CANNOT be live-tested from CI (no network) and shares no
parser code that isn't already covered by the SSH/mock paths.

CAVEAT: the stdlib ``telnetlib`` module was DEPRECATED in Python 3.11 and
REMOVED in Python 3.13. It is imported lazily so ``import netgear_switch`` never
depends on it; on 3.13+ constructing this transport raises a clear
``CliTransportError`` telling the caller to use SSH or the console transport
instead. Telnet on a management switch is plaintext and best avoided anyway.
"""

from __future__ import annotations

import contextlib
from typing import TYPE_CHECKING, Any

from .session import CliSession, CliTransportError, ShellDriver

if TYPE_CHECKING:
    from ...protocols.cli.commands import CliModelSpec

_DEFAULT_TIMEOUT = 20.0


[docs] class TelnetCliTransport(CliSession): """A telnet interactive-shell CLI session over ``telnetlib``.""" def __init__( self, host: str, username: str, password: str, spec: CliModelSpec, *, port: int = 23, timeout: float = _DEFAULT_TIMEOUT, ) -> None: self._host = host self._username = username self._password = password self._spec = spec self._port = port self._timeout = timeout self._conn: Any = None self._driver: ShellDriver | None = None def _login(self, conn: Any) -> None: # FASTPATH telnet prompts "User:" then "Password:" before the shell. conn.read_until(b"User:", timeout=self._timeout) conn.write(self._username.encode("latin-1") + b"\r\n") conn.read_until(b"Password:", timeout=self._timeout) conn.write(self._password.encode("latin-1") + b"\r\n")
[docs] def connect(self) -> None: try: import telnetlib except ImportError as exc: # pragma: no cover - py3.13+ / optional raise CliTransportError( "telnet CLI transport requires the stdlib 'telnetlib', removed " "in Python 3.13 -- use the SSH or console transport instead" ) from exc try: conn = telnetlib.Telnet(self._host, self._port, timeout=self._timeout) self._login(conn) except Exception as exc: self.close() raise CliTransportError(f"telnet connect/login failed: {exc}") from exc self._conn = conn self._driver = ShellDriver( lambda data: conn.write(data), lambda n: conn.read_eager() or conn.read_some(), enable_cmd=self._spec.enable_cmd, paging_off_cmd=self._spec.paging_off_cmd, enable_password=self._password, ) self._driver.setup()
[docs] def run(self, command: str) -> str: if self._driver is None: self.connect() assert self._driver is not None return self._driver.run(command)
[docs] def run_scp_copy(self, command: str, scp_password: str) -> str: if self._driver is None: self.connect() assert self._driver is not None return self._driver.run_scp_copy(command, scp_password)
[docs] def run_write_memory(self, command: str = "write memory", *, prestuff: bool) -> str: if self._driver is None: self.connect() assert self._driver is not None return self._driver.run_write_memory(command, prestuff=prestuff)
[docs] def close(self) -> None: if self._conn is not None: with contextlib.suppress(Exception): # teardown must not raise self._conn.close() self._conn = None self._driver = None