async def test_channel_lock_context_manager_no_channel_lock( async_transport_no_abc): base_channel_args = BaseChannelArgs(channel_lock=False) async_channel = AsyncChannel(transport=async_transport_no_abc, base_channel_args=base_channel_args) async with async_channel._channel_lock(): assert True
def test_channel_log_user_bytesio(base_transport_no_abc): bytes_log = BytesIO() base_channel_args = BaseChannelArgs(channel_log=bytes_log) chan = BaseChannel(transport=base_transport_no_abc, base_channel_args=base_channel_args) chan.open() assert chan.channel_log is bytes_log
def test_channel_lock_context_manager(base_transport_no_abc): base_channel_args = BaseChannelArgs(channel_lock=True) sync_channel = Channel(transport=base_transport_no_abc, base_channel_args=base_channel_args) assert sync_channel.channel_lock.locked() is False with sync_channel._channel_lock(): assert sync_channel.channel_lock.locked() is True assert sync_channel.channel_lock.locked() is False
def test_channel_log_user_defined(fs, base_transport_no_abc): # fs needed to mock filesystem for asserting log location _ = fs base_channel_args = BaseChannelArgs(channel_log="/log.log") chan = BaseChannel(transport=base_transport_no_abc, base_channel_args=base_channel_args) chan.open() assert Path("/log.log").is_file()
def test_channel_log(fs, base_transport_no_abc): # fs needed to mock filesystem for asserting log location _ = fs base_channel_args = BaseChannelArgs(channel_log=True) chan = BaseChannel(transport=base_transport_no_abc, base_channel_args=base_channel_args) chan.open() assert Path("/scrapli_channel.log").is_file()
async def test_channel_lock_context_manager(async_transport_no_abc): base_channel_args = BaseChannelArgs(channel_lock=True) async_channel = AsyncChannel(transport=async_transport_no_abc, base_channel_args=base_channel_args) assert async_channel.channel_lock.locked() is False async with async_channel._channel_lock(): assert async_channel.channel_lock.locked() is True assert async_channel.channel_lock.locked() is False
def test_channel_log_append(fs, base_transport_no_abc): fs.create_file( "scrapli_channel.log", contents="APPEND TO ME PLEASE!", ) base_channel_args = BaseChannelArgs(channel_log=True, channel_log_mode="append") chan = BaseChannel(transport=base_transport_no_abc, base_channel_args=base_channel_args) chan.open() chan.channel_log.write(b"\nDOIN IT!") chan.close() channel_log_contents = open("scrapli_channel.log", "rb") assert channel_log_contents.read() == b"APPEND TO ME PLEASE!\nDOIN IT!"
def test_channel_read(fs, caplog, monkeypatch, sync_transport_no_abc): # fs needed to mock filesystem for asserting log location _ = fs caplog.set_level(logging.DEBUG, logger="scrapli.channel") channel_read_called = False expected_read_output = b"read_data" base_channel_args = BaseChannelArgs(channel_log=True) sync_channel = Channel(transport=sync_transport_no_abc, base_channel_args=base_channel_args) sync_channel.open() def _read(cls): nonlocal channel_read_called channel_read_called = True return b"read_data\r" monkeypatch.setattr("scrapli.transport.base.sync_transport.Transport.read", _read) actual_read_output = sync_channel.read() sync_channel.channel_log.close() assert channel_read_called is True assert actual_read_output == expected_read_output # assert the log output/level as expected; skip the first log message that will be about # channel_log being on log_record = caplog.records[1] assert "read: b'read_data'" == log_record.msg assert logging.DEBUG == log_record.levelno # assert channel log output as expected assert Path("/scrapli_channel.log").is_file() with open("/scrapli_channel.log", "r") as actual_channel_log: assert actual_channel_log.readline() == "read_data"
def __init__( self, host: str, port: int = 22, auth_username: str = "", auth_password: str = "", auth_private_key: str = "", auth_private_key_passphrase: str = "", auth_strict_key: bool = True, auth_bypass: bool = False, timeout_socket: float = 15.0, timeout_transport: float = 30.0, timeout_ops: float = 30.0, comms_prompt_pattern: str = r"^[a-z0-9.\-@()/:]{1,48}[#>$]\s*$", comms_return_char: str = "\n", comms_ansi: bool = False, ssh_config_file: Union[str, bool] = False, ssh_known_hosts_file: Union[str, bool] = False, on_init: Optional[Callable[..., Any]] = None, on_open: Optional[Callable[..., Any]] = None, on_close: Optional[Callable[..., Any]] = None, transport: str = "system", transport_options: Optional[Dict[str, Any]] = None, channel_log: Union[str, bool, BytesIO] = False, channel_lock: bool = False, logging_uid: str = "", ) -> None: r""" BaseDriver Object BaseDriver is the root for all Scrapli driver classes. The synchronous and asyncio driver base driver classes can be used to provide a semi-pexpect like experience over top of whatever transport a user prefers. Generally, however, the base driver classes should not be used directly. It is best to use the GenericDriver (or AsyncGenericDriver) or NetworkDriver (or AsyncNetworkDriver) sub-classes of the base drivers. Args: host: host ip/name to connect to port: port to connect to auth_username: username for authentication auth_private_key: path to private key for authentication auth_private_key_passphrase: passphrase for decrypting ssh key if necessary auth_password: password for authentication auth_strict_key: strict host checking or not auth_bypass: bypass "in channel" authentication -- only supported with telnet, asynctelnet, and system transport plugins timeout_socket: timeout for establishing socket/initial connection in seconds timeout_transport: timeout for ssh|telnet transport in seconds timeout_ops: timeout for ssh channel operations comms_prompt_pattern: raw string regex pattern -- preferably use `^` and `$` anchors! this is the single most important attribute here! if this does not match a prompt, scrapli will not work! IMPORTANT: regex search uses multi-line + case insensitive flags. multi-line allows for highly reliably matching for prompts however we do NOT strip trailing whitespace for each line, so be sure to add '\\s?' or similar if your device needs that. This should be mostly sorted for you if using network drivers (i.e. `IOSXEDriver`). Lastly, the case insensitive is just a convenience factor so i can be lazy. comms_return_char: character to use to send returns to host comms_ansi: True/False strip comms_ansi characters from output, generally the default value of False should be fine ssh_config_file: string to path for ssh config file, True to use default ssh config file or False to ignore default ssh config file ssh_known_hosts_file: string to path for ssh known hosts file, True to use default known file locations. Only applicable/needed if `auth_strict_key` is set to True on_init: callable that accepts the class instance as its only argument. this callable, if provided, is executed as the last step of object instantiation -- its purpose is primarily to provide a mechanism for scrapli community platforms to have an easy way to modify initialization arguments/object attributes without needing to create a class that extends the driver, instead allowing the community platforms to simply build from the GenericDriver or NetworkDriver classes, and pass this callable to do things such as appending to a username (looking at you RouterOS!!). Note that this is *always* a synchronous function (even for asyncio drivers)! on_open: callable that accepts the class instance as its only argument. this callable, if provided, is executed immediately after authentication is completed. Common use cases for this callable would be to disable paging or accept any kind of banner message that prompts a user upon connection on_close: callable that accepts the class instance as its only argument. this callable, if provided, is executed immediately prior to closing the underlying transport. Common use cases for this callable would be to save configurations prior to exiting, or to logout properly to free up vtys or similar transport: name of the transport plugin to use for the actual telnet/ssh/netconf connection. Available "core" transports are: - system - telnet - asynctelnet - ssh2 - paramiko - asyncssh Please see relevant transport plugin section for details. Additionally third party transport plugins may be available. transport_options: dictionary of options to pass to selected transport class; see docs for given transport class for details of what to pass here channel_lock: True/False to lock the channel (threading.Lock/asyncio.Lock) during any channel operations, defaults to False channel_log: True/False or a string path to a file of where to write out channel logs -- these are not "logs" in the normal logging module sense, but only the output that is read from the channel. In other words, the output of the channel log should look similar to what you would see as a human connecting to a device logging_uid: unique identifier (string) to associate to log messages; useful if you have multiple connections to the same device (i.e. one console, one ssh, or one to each supervisor module, etc.) Returns: None Raises: N/A """ self.logger = get_instance_logger( instance_name="scrapli.driver", host=host, port=port, uid=logging_uid ) self._base_channel_args = BaseChannelArgs( comms_prompt_pattern=comms_prompt_pattern, comms_return_char=comms_return_char, comms_ansi=comms_ansi, timeout_ops=timeout_ops, channel_log=channel_log, channel_lock=channel_lock, ) # transport options is unused in most transport plugins, but when used will be a dict of # user provided arguments, defaults to None to not be mutable argument, so if its still # None at this point turn it into an empty dict to pass into the transports transport_options = transport_options or {} self._base_transport_args = BaseTransportArgs( transport_options=transport_options, host=host, port=port, timeout_socket=timeout_socket, timeout_transport=timeout_transport, logging_uid=logging_uid, ) self.host, self.port = self._setup_host(host=host, port=port) self.auth_username = auth_username self.auth_password = auth_password self.auth_private_key_passphrase = auth_private_key_passphrase self.auth_private_key, self.auth_strict_key, self.auth_bypass = self._setup_auth( auth_private_key=auth_private_key, auth_strict_key=auth_strict_key, auth_bypass=auth_bypass, ) self.ssh_config_file, self.ssh_known_hosts_file = self._setup_ssh_file_args( transport=transport, ssh_config_file=ssh_config_file, ssh_known_hosts_file=ssh_known_hosts_file, ) self._setup_callables(on_init=on_init, on_open=on_open, on_close=on_close) self.transport_name = transport if self.transport_name in ("asyncssh", "ssh2", "paramiko"): # for mostly(?) historical reasons these transports use the `ssh_config` module to get # port/username/key file. asyncssh may not need this at all anymore as asyncssh core # has added ssh config file support since scrapli's inception self._update_ssh_args_from_ssh_config() transport_class, self._plugin_transport_args = self._transport_factory() self.transport = transport_class( base_transport_args=self._base_transport_args, plugin_transport_args=self._plugin_transport_args, ) if self.on_init: self.on_init(self)
def test_channel_lock_context_manager_no_channel_lock(base_transport_no_abc): base_channel_args = BaseChannelArgs(channel_lock=False) sync_channel = Channel(transport=base_transport_no_abc, base_channel_args=base_channel_args) with sync_channel._channel_lock(): assert True
def test_channel_lock(base_transport_no_abc): base_channel_args = BaseChannelArgs(channel_lock=True) sync_channel = Channel(transport=base_transport_no_abc, base_channel_args=base_channel_args) assert sync_channel.channel_lock
def test_channel_log_invalid_mode(base_transport_no_abc): with pytest.raises(ScrapliValueError): BaseChannelArgs(channel_log=True, channel_log_mode="not valid")
def test_channel_lock(async_transport_no_abc): base_channel_args = BaseChannelArgs(channel_lock=True) async_channel = AsyncChannel(transport=async_transport_no_abc, base_channel_args=base_channel_args) assert async_channel.channel_lock
def base_channel_args(): base_channel_args = BaseChannelArgs() return base_channel_args