"""The interactive shell netmiko opens, made fit to be read before the first command.""" from __future__ import annotations import logging from typing import Any logger = logging.getLogger(__name__) #: The end of a command's output when nothing better is known: a line that looks #: like a shell prompt. PROMPT_RE = r"[#$\>]\s*$" #: Sent before anything else in the interactive session (NetOrk/netork#1039): #: the shell writes no history file and records nothing more. The leading space #: keeps this line itself out of a history under ``HISTCONTROL=ignorespace``; #: a shell without ``set -o history`` (dash) only complains, to /dev/null. HISTORY_OFF = " unset HISTFILE; set +o history 2>/dev/null; true" #: Sent right after ``HISTORY_OFF`` (#41): systemd 258's #: ``/etc/profile.d/80-systemd-osc-context.sh`` wraps every command in OSC 3008 #: sequences from ``PS0`` and ``PROMPT_COMMAND``, each with a fresh UUID. It appends #: to ``PROMPT_COMMAND`` as an array, so only ``unset`` removes its hook. PROMPT_HOOKS_OFF = " unset PS0 PROMPT_COMMAND; true" #: Keeps the PTY from wrapping long output lines (e.g. docker JSON). NO_LINE_WRAP = "stty cols 10000 2>/dev/null || true" def prepare_shell(device: Any, hostname: str) -> None: """Make netmiko's fresh shell *device* fit to read, one step at a time. A step the shell refuses is logged and skipped: none of them is worth failing the connection for. """ # No history before anything else is typed (NetOrk/netork#1039). try: device.send_command(HISTORY_OFF, expect_string=PROMPT_RE) except Exception: logger.debug("Could not switch the shell history off on %s", hostname, exc_info=True) # No systemd OSC 3008 context around every command (#41). netmiko learned # the prompt with those sequences in front, and a prompt carrying a fresh # UUID never matches again, so it reads the prompt anew once they are gone. try: device.send_command(PROMPT_HOOKS_OFF, expect_string=PROMPT_RE) device.set_base_prompt() except Exception: logger.debug("Could not switch the prompt hooks off on %s", hostname, exc_info=True) try: device.send_command(NO_LINE_WRAP, expect_string=PROMPT_RE) except Exception: logger.debug("%s: ignored", hostname, exc_info=True)