Example #1
0
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
Example #3
0
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()
Example #6
0
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!"
Example #8
0
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"
Example #9
0
    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)
Example #10
0
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
Example #11
0
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")
Example #13
0
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
Example #14
0
def base_channel_args():
    base_channel_args = BaseChannelArgs()
    return base_channel_args