|
|
|
@@ -30,9 +30,26 @@ from .ssh import SshError, open_transport
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
#: Steuerzeichen eines Terminals -- ANSI-Sequenzen, Glocke, Einzelbefehle.
|
|
|
|
|
#:
|
|
|
|
|
#: Ein ArubaOS-Switch an einem Pseudoterminal zeichnet seine Zeile neu und
|
|
|
|
|
#: haengt dabei Sequenzen wie ``ESC [ K`` ("bis Zeilenende loeschen") an. Im
|
|
|
|
|
#: Terminal sieht man davon **nichts** -- und genau das war der Fehler, der
|
|
|
|
|
#: Konsole und PoE-Neustart lahmgelegt hat: der Prompt stand da, aber hinter
|
|
|
|
|
#: ihm standen drei unsichtbare Bytes, und das verankerte Muster traf nicht
|
|
|
|
|
#: mehr. Im Protokoll las man "kein Prompt", waehrend der Prompt im Text
|
|
|
|
|
#: stand. Deshalb kommen die Steuerzeichen weg, bevor irgendetwas gesucht
|
|
|
|
|
#: wird -- das macht zugleich die Ausgabe in der Konsole lesbar.
|
|
|
|
|
ANSI_RE = re.compile(rb"\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b[@-Z\\-_]|[\x07\x00]")
|
|
|
|
|
|
|
|
|
|
#: Erkennt Operator- (``>``) und privilegierten Modus (``#``) sowie den
|
|
|
|
|
#: Konfigurationsmodus (``switch(config)#``).
|
|
|
|
|
PROMPT_RE = re.compile(rb"[\r\n][^\r\n]{0,80}?[>#]\s?$")
|
|
|
|
|
#:
|
|
|
|
|
#: Zwei Lockerungen gegenueber der ersten Fassung, beide aus dem Betrieb:
|
|
|
|
|
#: der Prompt darf am Anfang des Puffers stehen (nicht nur nach einem
|
|
|
|
|
#: Zeilenumbruch -- so kommt er nach einem geleerten Puffer), und hinter ihm
|
|
|
|
|
#: darf beliebiger Leerraum stehen (nicht genau eines).
|
|
|
|
|
PROMPT_RE = re.compile(rb"(?:^|[\r\n])[^\r\n]{0,120}?[>#]\s*$")
|
|
|
|
|
PAGER_RE = re.compile(rb"(--\s?MORE\s?--|Press any key to continue)", re.IGNORECASE)
|
|
|
|
|
ERROR_RE = re.compile(
|
|
|
|
|
r"(invalid input|unknown command|incomplete|ambiguous|not supported|% *error)", re.IGNORECASE
|
|
|
|
@@ -175,27 +192,30 @@ class SwitchCli:
|
|
|
|
|
"""
|
|
|
|
|
assert self._channel is not None
|
|
|
|
|
deadline = time.monotonic() + timeout
|
|
|
|
|
collected = b""
|
|
|
|
|
# Zwei Puffer: das Rohe kommt herein, gesucht wird im Sichtbaren.
|
|
|
|
|
# Ohne diese Trennung entscheiden unsichtbare Steuerzeichen darueber,
|
|
|
|
|
# ob ein Prompt erkannt wird.
|
|
|
|
|
sichtbar = b""
|
|
|
|
|
prompt_gesehen = False
|
|
|
|
|
while time.monotonic() < deadline:
|
|
|
|
|
if self._channel.recv_ready():
|
|
|
|
|
chunk = self._channel.recv(READ_CHUNK)
|
|
|
|
|
if not chunk:
|
|
|
|
|
break
|
|
|
|
|
collected += chunk
|
|
|
|
|
if PAGER_RE.search(collected[-200:]):
|
|
|
|
|
sichtbar = ANSI_RE.sub(b"", sichtbar + chunk)
|
|
|
|
|
if PAGER_RE.search(sichtbar[-300:]):
|
|
|
|
|
self._channel.send(" ")
|
|
|
|
|
collected = PAGER_RE.sub(b"", collected)
|
|
|
|
|
sichtbar = PAGER_RE.sub(b"", sichtbar)
|
|
|
|
|
continue
|
|
|
|
|
if PROMPT_RE.search(collected[-200:]):
|
|
|
|
|
if PROMPT_RE.search(sichtbar[-300:]):
|
|
|
|
|
prompt_gesehen = True
|
|
|
|
|
break
|
|
|
|
|
# Der Login-Banner mancher Geraete verlangt eine Bestaetigung.
|
|
|
|
|
if initial and collected.rstrip().endswith(b"continue"):
|
|
|
|
|
if initial and sichtbar.rstrip().endswith(b"continue"):
|
|
|
|
|
self._channel.send("\r\n")
|
|
|
|
|
else:
|
|
|
|
|
time.sleep(0.05)
|
|
|
|
|
text = collected.decode("utf-8", errors="replace")
|
|
|
|
|
text = sichtbar.decode("utf-8", errors="replace")
|
|
|
|
|
self.transcript.append(text)
|
|
|
|
|
if not prompt_gesehen:
|
|
|
|
|
letzte = " ".join(text.split())[-200:]
|
|
|
|
|