scrapli 2024.1.30__py3-none-any.whl → 2024.7.30.post1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scrapli/__init__.py +2 -1
- scrapli/channel/__init__.py +1 -0
- scrapli/channel/async_channel.py +16 -7
- scrapli/channel/base_channel.py +12 -7
- scrapli/channel/sync_channel.py +16 -7
- scrapli/decorators.py +2 -1
- scrapli/driver/__init__.py +1 -0
- scrapli/driver/base/__init__.py +1 -0
- scrapli/driver/base/async_driver.py +19 -13
- scrapli/driver/base/base_driver.py +35 -38
- scrapli/driver/base/sync_driver.py +19 -13
- scrapli/driver/core/__init__.py +1 -0
- scrapli/driver/core/arista_eos/__init__.py +1 -0
- scrapli/driver/core/arista_eos/async_driver.py +2 -1
- scrapli/driver/core/arista_eos/base_driver.py +4 -2
- scrapli/driver/core/arista_eos/sync_driver.py +2 -1
- scrapli/driver/core/cisco_iosxe/__init__.py +1 -0
- scrapli/driver/core/cisco_iosxe/async_driver.py +2 -1
- scrapli/driver/core/cisco_iosxe/base_driver.py +1 -0
- scrapli/driver/core/cisco_iosxe/sync_driver.py +2 -1
- scrapli/driver/core/cisco_iosxr/__init__.py +1 -0
- scrapli/driver/core/cisco_iosxr/async_driver.py +2 -1
- scrapli/driver/core/cisco_iosxr/base_driver.py +1 -0
- scrapli/driver/core/cisco_iosxr/sync_driver.py +2 -1
- scrapli/driver/core/cisco_nxos/__init__.py +1 -0
- scrapli/driver/core/cisco_nxos/async_driver.py +2 -1
- scrapli/driver/core/cisco_nxos/base_driver.py +9 -4
- scrapli/driver/core/cisco_nxos/sync_driver.py +2 -1
- scrapli/driver/core/juniper_junos/__init__.py +1 -0
- scrapli/driver/core/juniper_junos/async_driver.py +2 -1
- scrapli/driver/core/juniper_junos/base_driver.py +1 -0
- scrapli/driver/core/juniper_junos/sync_driver.py +2 -1
- scrapli/driver/generic/__init__.py +1 -0
- scrapli/driver/generic/async_driver.py +24 -5
- scrapli/driver/generic/base_driver.py +6 -1
- scrapli/driver/generic/sync_driver.py +25 -6
- scrapli/driver/network/__init__.py +1 -0
- scrapli/driver/network/async_driver.py +2 -1
- scrapli/driver/network/base_driver.py +2 -1
- scrapli/driver/network/sync_driver.py +2 -1
- scrapli/exceptions.py +1 -0
- scrapli/factory.py +7 -6
- scrapli/helper.py +21 -7
- scrapli/logging.py +2 -3
- scrapli/response.py +13 -3
- scrapli/ssh_config.py +1 -0
- scrapli/transport/base/__init__.py +1 -0
- scrapli/transport/base/async_transport.py +1 -0
- scrapli/transport/base/base_socket.py +1 -0
- scrapli/transport/base/base_transport.py +1 -0
- scrapli/transport/base/sync_transport.py +1 -0
- scrapli/transport/plugins/asyncssh/transport.py +4 -0
- scrapli/transport/plugins/asynctelnet/transport.py +10 -8
- scrapli/transport/plugins/paramiko/transport.py +1 -0
- scrapli/transport/plugins/ssh2/transport.py +6 -3
- scrapli/transport/plugins/system/ptyprocess.py +37 -0
- scrapli/transport/plugins/system/transport.py +27 -6
- scrapli/transport/plugins/telnet/transport.py +10 -9
- {scrapli-2024.1.30.dist-info → scrapli-2024.7.30.post1.dist-info}/METADATA +70 -49
- scrapli-2024.7.30.post1.dist-info/RECORD +74 -0
- {scrapli-2024.1.30.dist-info → scrapli-2024.7.30.post1.dist-info}/WHEEL +1 -1
- scrapli-2024.1.30.dist-info/RECORD +0 -74
- {scrapli-2024.1.30.dist-info → scrapli-2024.7.30.post1.dist-info}/LICENSE +0 -0
- {scrapli-2024.1.30.dist-info → scrapli-2024.7.30.post1.dist-info}/top_level.txt +0 -0
scrapli/__init__.py
CHANGED
scrapli/channel/__init__.py
CHANGED
scrapli/channel/async_channel.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.channel.async_channel"""
|
2
|
+
|
2
3
|
import asyncio
|
3
4
|
import re
|
4
5
|
import time
|
@@ -70,7 +71,7 @@ class AsyncChannel(BaseChannel):
|
|
70
71
|
buf = await self.transport.read()
|
71
72
|
buf = buf.replace(b"\r", b"")
|
72
73
|
|
73
|
-
self.logger.debug(
|
74
|
+
self.logger.debug("read: %r", buf)
|
74
75
|
|
75
76
|
if self.channel_log:
|
76
77
|
self.channel_log.write(buf)
|
@@ -490,7 +491,10 @@ class AsyncChannel(BaseChannel):
|
|
490
491
|
bytes_channel_input = channel_input.encode()
|
491
492
|
|
492
493
|
self.logger.info(
|
493
|
-
|
494
|
+
"sending channel input: %s; strip_prompt: %s; eager: %s",
|
495
|
+
channel_input,
|
496
|
+
strip_prompt,
|
497
|
+
eager,
|
494
498
|
)
|
495
499
|
|
496
500
|
async with self._channel_lock():
|
@@ -545,8 +549,12 @@ class AsyncChannel(BaseChannel):
|
|
545
549
|
]
|
546
550
|
|
547
551
|
self.logger.info(
|
548
|
-
|
549
|
-
|
552
|
+
"sending channel input and read: %s; strip_prompt: %s; "
|
553
|
+
"expected_outputs: %s; read_duration: %s",
|
554
|
+
channel_input,
|
555
|
+
strip_prompt,
|
556
|
+
expected_outputs,
|
557
|
+
read_duration,
|
550
558
|
)
|
551
559
|
|
552
560
|
async with self._channel_lock():
|
@@ -656,9 +664,10 @@ class AsyncChannel(BaseChannel):
|
|
656
664
|
|
657
665
|
_channel_input = channel_input if not hidden_input else "REDACTED"
|
658
666
|
self.logger.info(
|
659
|
-
|
660
|
-
|
661
|
-
|
667
|
+
"sending interactive input: %s; expecting: %s; hidden_input: %s",
|
668
|
+
_channel_input,
|
669
|
+
channel_response,
|
670
|
+
hidden_input,
|
662
671
|
)
|
663
672
|
|
664
673
|
self.write(channel_input=channel_input, redacted=bool(hidden_input))
|
scrapli/channel/base_channel.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.channel.base_channel"""
|
2
|
+
|
2
3
|
import re
|
3
4
|
from dataclasses import dataclass
|
4
5
|
from datetime import datetime
|
@@ -11,13 +12,15 @@ from scrapli.logging import get_instance_logger
|
|
11
12
|
from scrapli.transport.base import AsyncTransport, Transport
|
12
13
|
|
13
14
|
ANSI_ESCAPE_PATTERN = re.compile(
|
14
|
-
pattern=rb"[\x1B\x9B\x9D](\s)?" # Prefix ESC (^) or CSI (^[) or OSC (^
|
15
|
+
pattern=rb"[\x1B\x9B\x9D](\s)?" # Prefix ESC (^) or CSI (^[) or OSC (^)
|
15
16
|
rb"("
|
16
17
|
rb"([78ME])" # control cursor position
|
17
18
|
rb"|"
|
18
|
-
rb"([\x07])" # BEL (Terminal bell)
|
19
|
+
rb"((\]\d).*?[\x07])" # BEL (Terminal bell)
|
20
|
+
rb"|"
|
21
|
+
rb"(\[.*?[@-~])" # control codes starts with `[` e.x. ESC [2;37;41m
|
19
22
|
rb"|"
|
20
|
-
rb"(\[[
|
23
|
+
rb"(\[.*?[0-9;]m)" # Select Graphic Rendition (SGR) control sequence
|
21
24
|
rb")",
|
22
25
|
flags=re.VERBOSE,
|
23
26
|
)
|
@@ -299,7 +302,7 @@ class BaseChannel:
|
|
299
302
|
# ignore here
|
300
303
|
# note that this will *always* be binary mode, so there doesn't need to be any
|
301
304
|
# encoding, hence ignoring that pylint message!
|
302
|
-
self.channel_log = open( # pylint: disable=W1514,R1732
|
305
|
+
self.channel_log = open( # pylint: disable=W1514,R1732,W1501
|
303
306
|
channel_log_destination,
|
304
307
|
mode=f"{self._base_channel_args.channel_log_mode}b", # type: ignore
|
305
308
|
)
|
@@ -365,8 +368,10 @@ class BaseChannel:
|
|
365
368
|
N/A
|
366
369
|
|
367
370
|
"""
|
368
|
-
|
369
|
-
|
371
|
+
if redacted:
|
372
|
+
self.logger.debug("write: REDACTED")
|
373
|
+
else:
|
374
|
+
self.logger.debug("write: %r", channel_input)
|
370
375
|
|
371
376
|
self.transport.write(channel_input=channel_input.encode())
|
372
377
|
|
@@ -410,7 +415,7 @@ class BaseChannel:
|
|
410
415
|
|
411
416
|
return regex_channel_outputs_pattern
|
412
417
|
|
413
|
-
def _ssh_message_handler(self, output: bytes) -> None: #
|
418
|
+
def _ssh_message_handler(self, output: bytes) -> None: # pylint:disable=too-many-branches
|
414
419
|
"""
|
415
420
|
Parse EOF messages from _pty_authenticate and create log/stack exception message
|
416
421
|
|
scrapli/channel/sync_channel.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.channel.sync_channel"""
|
2
|
+
|
2
3
|
import re
|
3
4
|
import time
|
4
5
|
from contextlib import contextmanager, suppress
|
@@ -70,7 +71,7 @@ class Channel(BaseChannel):
|
|
70
71
|
buf = self.transport.read()
|
71
72
|
buf = buf.replace(b"\r", b"")
|
72
73
|
|
73
|
-
self.logger.debug(
|
74
|
+
self.logger.debug("read: %r", buf)
|
74
75
|
|
75
76
|
if self.channel_log:
|
76
77
|
self.channel_log.write(buf)
|
@@ -491,7 +492,10 @@ class Channel(BaseChannel):
|
|
491
492
|
bytes_channel_input = channel_input.encode()
|
492
493
|
|
493
494
|
self.logger.info(
|
494
|
-
|
495
|
+
"sending channel input: %s; strip_prompt: %s; eager: %s",
|
496
|
+
channel_input,
|
497
|
+
strip_prompt,
|
498
|
+
eager,
|
495
499
|
)
|
496
500
|
|
497
501
|
with self._channel_lock():
|
@@ -546,8 +550,12 @@ class Channel(BaseChannel):
|
|
546
550
|
]
|
547
551
|
|
548
552
|
self.logger.info(
|
549
|
-
|
550
|
-
|
553
|
+
"sending channel input and read: %s; strip_prompt: %s; "
|
554
|
+
"expected_outputs: %s; read_duration: %s",
|
555
|
+
channel_input,
|
556
|
+
strip_prompt,
|
557
|
+
expected_outputs,
|
558
|
+
read_duration,
|
551
559
|
)
|
552
560
|
|
553
561
|
with self._channel_lock():
|
@@ -657,9 +665,10 @@ class Channel(BaseChannel):
|
|
657
665
|
|
658
666
|
_channel_input = channel_input if not hidden_input else "REDACTED"
|
659
667
|
self.logger.info(
|
660
|
-
|
661
|
-
|
662
|
-
|
668
|
+
"sending interactive input: %s; expecting: %s; hidden_input: %s",
|
669
|
+
_channel_input,
|
670
|
+
channel_response,
|
671
|
+
hidden_input,
|
663
672
|
)
|
664
673
|
|
665
674
|
self.write(channel_input=channel_input, redacted=bool(hidden_input))
|
scrapli/decorators.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.decorators"""
|
2
|
+
|
2
3
|
import asyncio
|
3
4
|
import signal
|
4
5
|
import sys
|
@@ -76,7 +77,7 @@ def _signal_raise_exception(
|
|
76
77
|
return _handle_timeout(transport=transport, logger=logger, message=message)
|
77
78
|
|
78
79
|
|
79
|
-
def _multiprocessing_timeout(
|
80
|
+
def _multiprocessing_timeout( # pylint: disable=R0917
|
80
81
|
transport: "BaseTransport",
|
81
82
|
logger: LoggerAdapterT,
|
82
83
|
timeout: float,
|
scrapli/driver/__init__.py
CHANGED
scrapli/driver/base/__init__.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
"""scrapli.driver.base.async_driver"""
|
2
|
+
|
2
3
|
from types import TracebackType
|
3
4
|
from typing import Any, Optional, Type, TypeVar
|
4
5
|
|
5
6
|
from scrapli.channel import AsyncChannel
|
6
7
|
from scrapli.driver.base.base_driver import BaseDriver
|
7
|
-
from scrapli.exceptions import ScrapliValueError
|
8
|
-
from scrapli.transport import ASYNCIO_TRANSPORTS
|
8
|
+
from scrapli.exceptions import ScrapliConnectionError, ScrapliValueError
|
9
|
+
from scrapli.transport import ASYNCIO_TRANSPORTS, CORE_TRANSPORTS
|
9
10
|
|
10
11
|
_T = TypeVar("_T", bound="AsyncDriver")
|
11
12
|
|
@@ -14,7 +15,7 @@ class AsyncDriver(BaseDriver):
|
|
14
15
|
def __init__(self, **kwargs: Any):
|
15
16
|
super().__init__(**kwargs)
|
16
17
|
|
17
|
-
if self.transport_name not in ASYNCIO_TRANSPORTS:
|
18
|
+
if self.transport_name in CORE_TRANSPORTS and self.transport_name not in ASYNCIO_TRANSPORTS:
|
18
19
|
raise ScrapliValueError(
|
19
20
|
"provided transport is *not* an asyncio transport, must use an async transport with"
|
20
21
|
" the AsyncDriver(s)"
|
@@ -39,10 +40,22 @@ class AsyncDriver(BaseDriver):
|
|
39
40
|
_T: a concrete implementation of the opened AsyncDriver object
|
40
41
|
|
41
42
|
Raises:
|
42
|
-
|
43
|
+
ScrapliConnectionError: if an exception occurs during opening
|
43
44
|
|
44
45
|
"""
|
45
|
-
|
46
|
+
try:
|
47
|
+
await self.open()
|
48
|
+
except Exception as exc:
|
49
|
+
self.logger.critical(
|
50
|
+
"encountered exception during open in context manager,"
|
51
|
+
" attempting to close transport and channel"
|
52
|
+
)
|
53
|
+
|
54
|
+
self.transport.close()
|
55
|
+
self.channel.close()
|
56
|
+
|
57
|
+
raise ScrapliConnectionError(exc) from exc
|
58
|
+
|
46
59
|
return self
|
47
60
|
|
48
61
|
async def __aexit__(
|
@@ -87,14 +100,7 @@ class AsyncDriver(BaseDriver):
|
|
87
100
|
await self.transport.open()
|
88
101
|
self.channel.open()
|
89
102
|
|
90
|
-
if
|
91
|
-
self.transport_name
|
92
|
-
in (
|
93
|
-
"telnet",
|
94
|
-
"asynctelnet",
|
95
|
-
)
|
96
|
-
and not self.auth_bypass
|
97
|
-
):
|
103
|
+
if "telnet" in self.transport_name and not self.auth_bypass:
|
98
104
|
await self.channel.channel_authenticate_telnet(
|
99
105
|
auth_username=self.auth_username, auth_password=self.auth_password
|
100
106
|
)
|
@@ -1,4 +1,5 @@
|
|
1
|
-
"""scrapli.driver.base.base_driver""" #
|
1
|
+
"""scrapli.driver.base.base_driver""" # pylint:disable=too-many-lines
|
2
|
+
|
2
3
|
import importlib
|
3
4
|
from dataclasses import fields
|
4
5
|
from io import BytesIO
|
@@ -13,10 +14,11 @@ from scrapli.logging import get_instance_logger
|
|
13
14
|
from scrapli.ssh_config import ssh_config_factory
|
14
15
|
from scrapli.transport import CORE_TRANSPORTS
|
15
16
|
from scrapli.transport.base import BasePluginTransportArgs, BaseTransportArgs
|
17
|
+
from scrapli.transport.plugins.system.transport import SystemTransport
|
16
18
|
|
17
19
|
|
18
20
|
class BaseDriver:
|
19
|
-
def __init__(
|
21
|
+
def __init__( # pylint: disable=R0917
|
20
22
|
self,
|
21
23
|
host: str,
|
22
24
|
port: Optional[int] = None,
|
@@ -371,7 +373,7 @@ class BaseDriver:
|
|
371
373
|
cfg = ""
|
372
374
|
else:
|
373
375
|
cfg = ssh_config_file
|
374
|
-
resolved_ssh_config_file = self._resolve_ssh_config(cfg)
|
376
|
+
resolved_ssh_config_file = self._resolve_ssh_config(cfg, transport=transport)
|
375
377
|
else:
|
376
378
|
resolved_ssh_config_file = ""
|
377
379
|
|
@@ -380,7 +382,9 @@ class BaseDriver:
|
|
380
382
|
known_hosts = ""
|
381
383
|
else:
|
382
384
|
known_hosts = ssh_known_hosts_file
|
383
|
-
resolved_ssh_known_hosts_file = self._resolve_ssh_known_hosts(
|
385
|
+
resolved_ssh_known_hosts_file = self._resolve_ssh_known_hosts(
|
386
|
+
known_hosts, transport=transport
|
387
|
+
)
|
384
388
|
else:
|
385
389
|
resolved_ssh_known_hosts_file = ""
|
386
390
|
|
@@ -556,7 +560,9 @@ class BaseDriver:
|
|
556
560
|
|
557
561
|
return transport_class, plugin_transport_args
|
558
562
|
|
559
|
-
def _load_non_core_transport_plugin(
|
563
|
+
def _load_non_core_transport_plugin(
|
564
|
+
self,
|
565
|
+
) -> Tuple[Any, Type[BasePluginTransportArgs]]:
|
560
566
|
"""
|
561
567
|
Find non-core transport plugins and required plugin arguments
|
562
568
|
|
@@ -595,7 +601,7 @@ class BaseDriver:
|
|
595
601
|
|
596
602
|
return transport_class, plugin_transport_args
|
597
603
|
|
598
|
-
def _resolve_ssh_config(self, ssh_config_file: str) -> str:
|
604
|
+
def _resolve_ssh_config(self, ssh_config_file: str, transport: str) -> str:
|
599
605
|
"""
|
600
606
|
Resolve ssh configuration file from provided string
|
601
607
|
|
@@ -604,6 +610,8 @@ class BaseDriver:
|
|
604
610
|
|
605
611
|
Args:
|
606
612
|
ssh_config_file: string representation of ssh config file to try to use
|
613
|
+
transport: string name of selected transport (so we can apply a bit of special handling
|
614
|
+
if system transport in use)
|
607
615
|
|
608
616
|
Returns:
|
609
617
|
str: string path to ssh config file or an empty string
|
@@ -612,27 +620,20 @@ class BaseDriver:
|
|
612
620
|
N/A
|
613
621
|
|
614
622
|
"""
|
615
|
-
self.logger.debug("attempting to resolve 'ssh_config_file' file")
|
616
|
-
|
617
|
-
resolved_ssh_config_file = ""
|
623
|
+
self.logger.debug(f"attempting to resolve 'ssh_config_file' file {ssh_config_file}")
|
618
624
|
|
619
|
-
if
|
620
|
-
|
621
|
-
elif Path("~/.ssh/config").expanduser().is_file():
|
622
|
-
resolved_ssh_config_file = str(Path("~/.ssh/config").expanduser())
|
623
|
-
elif Path("/etc/ssh/ssh_config").is_file():
|
624
|
-
resolved_ssh_config_file = str(Path("/etc/ssh/ssh_config"))
|
625
|
+
if ssh_config_file == "" and transport == "system":
|
626
|
+
return SystemTransport.SSH_SYSTEM_CONFIG_MAGIC_STRING
|
625
627
|
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
else:
|
631
|
-
self.logger.debug("unable to resolve 'ssh_config_file' file")
|
628
|
+
for path in (ssh_config_file, "~/.ssh/config", "/etc/ssh/ssh_config"):
|
629
|
+
full_path = Path(path).expanduser()
|
630
|
+
if full_path.is_file():
|
631
|
+
return str(full_path)
|
632
632
|
|
633
|
-
|
633
|
+
self.logger.debug(f"unable to resolve 'ssh_config_file' file {ssh_config_file}")
|
634
|
+
return ""
|
634
635
|
|
635
|
-
def _resolve_ssh_known_hosts(self, ssh_known_hosts: str) -> str:
|
636
|
+
def _resolve_ssh_known_hosts(self, ssh_known_hosts: str, transport: str) -> str:
|
636
637
|
"""
|
637
638
|
Resolve ssh known hosts file from provided string
|
638
639
|
|
@@ -641,6 +642,8 @@ class BaseDriver:
|
|
641
642
|
|
642
643
|
Args:
|
643
644
|
ssh_known_hosts: string representation of ssh config file to try to use
|
645
|
+
transport: string name of selected transport (so we can apply a bit of special handling
|
646
|
+
if system transport in use)
|
644
647
|
|
645
648
|
Returns:
|
646
649
|
str: string path to ssh known hosts file or an empty string
|
@@ -651,23 +654,17 @@ class BaseDriver:
|
|
651
654
|
"""
|
652
655
|
self.logger.debug("attempting to resolve 'ssh_known_hosts file'")
|
653
656
|
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
resolved_ssh_known_hosts = str(Path(ssh_known_hosts))
|
658
|
-
elif Path("~/.ssh/known_hosts").expanduser().is_file():
|
659
|
-
resolved_ssh_known_hosts = str(Path("~/.ssh/known_hosts").expanduser())
|
660
|
-
elif Path("/etc/ssh/ssh_known_hosts").is_file():
|
661
|
-
resolved_ssh_known_hosts = str(Path("/etc/ssh/ssh_known_hosts"))
|
657
|
+
if ssh_known_hosts == "" and transport == "system":
|
658
|
+
self.logger.debug("Using system known hosts file as 'ssh_known_hosts' file")
|
659
|
+
return SystemTransport.SSH_SYSTEM_KNOWN_HOSTS_FILE_MAGIC_STRING
|
662
660
|
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
else:
|
668
|
-
self.logger.debug("unable to resolve 'ssh_known_hosts' file")
|
661
|
+
for path in (ssh_known_hosts, "~/.ssh/known_hosts", "/etc/ssh/ssh_known_hosts"):
|
662
|
+
full_path = Path(path).expanduser()
|
663
|
+
if full_path.is_file():
|
664
|
+
return str(full_path)
|
669
665
|
|
670
|
-
|
666
|
+
self.logger.info(f"unable to resolve 'ssh_known_hosts' file {ssh_known_hosts}")
|
667
|
+
return ""
|
671
668
|
|
672
669
|
@property
|
673
670
|
def comms_prompt_pattern(self) -> str:
|
@@ -1,11 +1,12 @@
|
|
1
1
|
"""scrapli.driver.base.sync_driver"""
|
2
|
+
|
2
3
|
from types import TracebackType
|
3
4
|
from typing import Any, Optional, Type, TypeVar
|
4
5
|
|
5
6
|
from scrapli.channel import Channel
|
6
7
|
from scrapli.driver.base.base_driver import BaseDriver
|
7
|
-
from scrapli.exceptions import ScrapliValueError
|
8
|
-
from scrapli.transport import ASYNCIO_TRANSPORTS
|
8
|
+
from scrapli.exceptions import ScrapliConnectionError, ScrapliValueError
|
9
|
+
from scrapli.transport import ASYNCIO_TRANSPORTS, CORE_TRANSPORTS
|
9
10
|
|
10
11
|
_T = TypeVar("_T", bound="Driver")
|
11
12
|
|
@@ -14,7 +15,7 @@ class Driver(BaseDriver):
|
|
14
15
|
def __init__(self, **kwargs: Any):
|
15
16
|
super().__init__(**kwargs)
|
16
17
|
|
17
|
-
if self.transport_name in ASYNCIO_TRANSPORTS:
|
18
|
+
if self.transport_name in CORE_TRANSPORTS and self.transport_name in ASYNCIO_TRANSPORTS:
|
18
19
|
raise ScrapliValueError(
|
19
20
|
"provided transport is *not* an sync transport, must use an sync transport with"
|
20
21
|
" the (sync)Driver(s)"
|
@@ -41,10 +42,22 @@ class Driver(BaseDriver):
|
|
41
42
|
_T: a concrete implementation of the opened Driver object
|
42
43
|
|
43
44
|
Raises:
|
44
|
-
|
45
|
+
ScrapliConnectionError: if an exception occurs during opening
|
45
46
|
|
46
47
|
"""
|
47
|
-
|
48
|
+
try:
|
49
|
+
self.open()
|
50
|
+
except Exception as exc:
|
51
|
+
self.logger.critical(
|
52
|
+
"encountered exception during open in context manager, "
|
53
|
+
"attempting to close transport and channel"
|
54
|
+
)
|
55
|
+
|
56
|
+
self.transport.close()
|
57
|
+
self.channel.close()
|
58
|
+
|
59
|
+
raise ScrapliConnectionError(exc) from exc
|
60
|
+
|
48
61
|
return self
|
49
62
|
|
50
63
|
def __exit__(
|
@@ -94,14 +107,7 @@ class Driver(BaseDriver):
|
|
94
107
|
auth_password=self.auth_password,
|
95
108
|
auth_private_key_passphrase=self.auth_private_key_passphrase,
|
96
109
|
)
|
97
|
-
if
|
98
|
-
self.transport_name
|
99
|
-
in (
|
100
|
-
"telnet",
|
101
|
-
"asynctelnet",
|
102
|
-
)
|
103
|
-
and not self.auth_bypass
|
104
|
-
):
|
110
|
+
if "telnet" in self.transport_name and not self.auth_bypass:
|
105
111
|
self.channel.channel_authenticate_telnet(
|
106
112
|
auth_username=self.auth_username, auth_password=self.auth_password
|
107
113
|
)
|
scrapli/driver/core/__init__.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.driver.core.arista_eos.async_driver"""
|
2
|
+
|
2
3
|
from copy import deepcopy
|
3
4
|
from io import BytesIO
|
4
5
|
from typing import Any, Callable, Dict, List, Optional, Union
|
@@ -47,7 +48,7 @@ async def eos_on_close(conn: AsyncNetworkDriver) -> None:
|
|
47
48
|
|
48
49
|
|
49
50
|
class AsyncEOSDriver(AsyncNetworkDriver, EOSDriverBase):
|
50
|
-
def __init__(
|
51
|
+
def __init__( # pylint: disable=R0917
|
51
52
|
self,
|
52
53
|
host: str,
|
53
54
|
privilege_levels: Optional[Dict[str, PrivilegeLevel]] = None,
|
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.driver.core.arista_eos.base_driver"""
|
2
|
+
|
2
3
|
import re
|
3
4
|
from typing import Dict
|
4
5
|
|
@@ -31,7 +32,7 @@ PRIVS = {
|
|
31
32
|
),
|
32
33
|
"configuration": (
|
33
34
|
PrivilegeLevel(
|
34
|
-
pattern=r"^[\w.\-@()/: ]{1,63}\(config[\w
|
35
|
+
pattern=r"^[\w.\-@()/: ]{1,63}\(config[\w.\-@/:+]{0,63}\)#\s?$",
|
35
36
|
name="configuration",
|
36
37
|
previous_priv="privilege_exec",
|
37
38
|
deescalate="end",
|
@@ -50,6 +51,7 @@ FAILED_WHEN_CONTAINS = [
|
|
50
51
|
"% Invalid input",
|
51
52
|
"% Cannot commit",
|
52
53
|
"% Unavailable command",
|
54
|
+
"% Duplicate sequence number",
|
53
55
|
]
|
54
56
|
|
55
57
|
|
@@ -79,7 +81,7 @@ class EOSDriverBase:
|
|
79
81
|
raise ScrapliValueError(msg)
|
80
82
|
sess_prompt = re.escape(session_name[:6])
|
81
83
|
pattern = (
|
82
|
-
rf"^[a-z0-9.\-@()/: ]{{1,63}}\(config\-s\-{sess_prompt}[a-z0-9_
|
84
|
+
rf"^[a-z0-9.\-@()/: ]{{1,63}}\(config\-s\-{sess_prompt}[a-z0-9_.\-@/:+]{{0,64}}\)#\s?$"
|
83
85
|
)
|
84
86
|
name = session_name
|
85
87
|
config_session = PrivilegeLevel(
|
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.driver.core.arista_eos.sync_driver"""
|
2
|
+
|
2
3
|
from copy import deepcopy
|
3
4
|
from io import BytesIO
|
4
5
|
from typing import Any, Callable, Dict, List, Optional, Union
|
@@ -47,7 +48,7 @@ def eos_on_close(conn: NetworkDriver) -> None:
|
|
47
48
|
|
48
49
|
|
49
50
|
class EOSDriver(NetworkDriver, EOSDriverBase):
|
50
|
-
def __init__(
|
51
|
+
def __init__( # pylint: disable=R0917
|
51
52
|
self,
|
52
53
|
host: str,
|
53
54
|
privilege_levels: Optional[Dict[str, PrivilegeLevel]] = None,
|
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.driver.core.cisco_iosxe.async_driver"""
|
2
|
+
|
2
3
|
from copy import deepcopy
|
3
4
|
from io import BytesIO
|
4
5
|
from typing import Any, Callable, Dict, List, Optional, Union
|
@@ -47,7 +48,7 @@ async def iosxe_on_close(conn: AsyncNetworkDriver) -> None:
|
|
47
48
|
|
48
49
|
|
49
50
|
class AsyncIOSXEDriver(AsyncNetworkDriver):
|
50
|
-
def __init__(
|
51
|
+
def __init__( # pylint: disable=R0917
|
51
52
|
self,
|
52
53
|
host: str,
|
53
54
|
privilege_levels: Optional[Dict[str, PrivilegeLevel]] = None,
|
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.driver.core.cisco_iosxe.sync_driver"""
|
2
|
+
|
2
3
|
from copy import deepcopy
|
3
4
|
from io import BytesIO
|
4
5
|
from typing import Any, Callable, Dict, List, Optional, Union
|
@@ -47,7 +48,7 @@ def iosxe_on_close(conn: NetworkDriver) -> None:
|
|
47
48
|
|
48
49
|
|
49
50
|
class IOSXEDriver(NetworkDriver):
|
50
|
-
def __init__(
|
51
|
+
def __init__( # pylint: disable=R0917
|
51
52
|
self,
|
52
53
|
host: str,
|
53
54
|
privilege_levels: Optional[Dict[str, PrivilegeLevel]] = None,
|
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.driver.core.cisco_iosxr.async_driver"""
|
2
|
+
|
2
3
|
from copy import deepcopy
|
3
4
|
from io import BytesIO
|
4
5
|
from typing import Any, Callable, Dict, List, Optional, Union
|
@@ -46,7 +47,7 @@ async def iosxr_on_close(conn: AsyncNetworkDriver) -> None:
|
|
46
47
|
|
47
48
|
|
48
49
|
class AsyncIOSXRDriver(AsyncNetworkDriver):
|
49
|
-
def __init__(
|
50
|
+
def __init__( # pylint: disable=R0917
|
50
51
|
self,
|
51
52
|
host: str,
|
52
53
|
privilege_levels: Optional[Dict[str, PrivilegeLevel]] = None,
|
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.driver.core.cisco_iosxr.sync_driver"""
|
2
|
+
|
2
3
|
from copy import deepcopy
|
3
4
|
from io import BytesIO
|
4
5
|
from typing import Any, Callable, Dict, List, Optional, Union
|
@@ -49,7 +50,7 @@ def iosxr_on_close(conn: NetworkDriver) -> None:
|
|
49
50
|
|
50
51
|
|
51
52
|
class IOSXRDriver(NetworkDriver):
|
52
|
-
def __init__(
|
53
|
+
def __init__( # pylint: disable=R0917
|
53
54
|
self,
|
54
55
|
host: str,
|
55
56
|
privilege_levels: Optional[Dict[str, PrivilegeLevel]] = None,
|