180 lines
5.8 KiB
Python
180 lines
5.8 KiB
Python
|
|
from typing import Any, Optional, Union, Sequence
|
||
|
|
from typing import TYPE_CHECKING
|
||
|
|
import re
|
||
|
|
from netmiko.cisco_base_connection import CiscoSSHConnection
|
||
|
|
from netmiko.cisco_base_connection import CiscoFileTransfer
|
||
|
|
from netmiko.exceptions import NetmikoTimeoutException
|
||
|
|
|
||
|
|
if TYPE_CHECKING:
|
||
|
|
from netmiko.base_connection import BaseConnection
|
||
|
|
|
||
|
|
|
||
|
|
class AristaBase(CiscoSSHConnection):
|
||
|
|
prompt_pattern = r"[$>#]"
|
||
|
|
|
||
|
|
def session_preparation(self) -> None:
|
||
|
|
"""Prepare the session after the connection has been established."""
|
||
|
|
self.ansi_escape_codes = True
|
||
|
|
self._test_channel_read(pattern=self.prompt_pattern)
|
||
|
|
try:
|
||
|
|
cmd = "terminal width 511"
|
||
|
|
self.set_terminal_width(command=cmd, pattern=r"Width set to")
|
||
|
|
except NetmikoTimeoutException:
|
||
|
|
# Continue on if setting 'terminal width' fails
|
||
|
|
pass
|
||
|
|
self.disable_paging(cmd_verify=False, pattern=r"Pagination disabled")
|
||
|
|
self.set_base_prompt()
|
||
|
|
|
||
|
|
def find_prompt(
|
||
|
|
self, delay_factor: float = 1.0, pattern: Optional[str] = None
|
||
|
|
) -> str:
|
||
|
|
"""
|
||
|
|
Arista's sometimes duplicate the command echo if they fall behind.
|
||
|
|
|
||
|
|
arista9-napalm#
|
||
|
|
show version | json
|
||
|
|
arista9-napalm#show version | json
|
||
|
|
|
||
|
|
Using the terminating pattern tries to ensure that it is less likely they
|
||
|
|
fall behind.
|
||
|
|
"""
|
||
|
|
if not pattern:
|
||
|
|
pattern = self.prompt_pattern
|
||
|
|
return super().find_prompt(delay_factor=delay_factor, pattern=pattern)
|
||
|
|
|
||
|
|
def enable(
|
||
|
|
self,
|
||
|
|
cmd: str = "enable",
|
||
|
|
pattern: str = "ssword",
|
||
|
|
enable_pattern: Optional[str] = r"\#",
|
||
|
|
check_state: bool = True,
|
||
|
|
re_flags: int = re.IGNORECASE,
|
||
|
|
) -> str:
|
||
|
|
return super().enable(
|
||
|
|
cmd=cmd,
|
||
|
|
pattern=pattern,
|
||
|
|
enable_pattern=enable_pattern,
|
||
|
|
check_state=check_state,
|
||
|
|
re_flags=re_flags,
|
||
|
|
)
|
||
|
|
|
||
|
|
def check_config_mode(
|
||
|
|
self,
|
||
|
|
check_string: str = ")#",
|
||
|
|
pattern: str = r"[>\#]",
|
||
|
|
force_regex: bool = False,
|
||
|
|
) -> bool:
|
||
|
|
"""
|
||
|
|
Checks if the device is in configuration mode or not.
|
||
|
|
|
||
|
|
Arista, unfortunately, does this:
|
||
|
|
loc1-core01(s1)#
|
||
|
|
|
||
|
|
Can also be (s2)
|
||
|
|
"""
|
||
|
|
self.write_channel(self.RETURN)
|
||
|
|
output = self.read_until_pattern(pattern=pattern)
|
||
|
|
output = output.replace("(s1)", "")
|
||
|
|
output = output.replace("(s2)", "")
|
||
|
|
return check_string in output
|
||
|
|
|
||
|
|
def config_mode(
|
||
|
|
self,
|
||
|
|
config_command: str = "configure terminal",
|
||
|
|
pattern: str = "",
|
||
|
|
re_flags: int = 0,
|
||
|
|
) -> str:
|
||
|
|
"""Force arista to read pattern all the way to prompt on the next line."""
|
||
|
|
|
||
|
|
if not re_flags:
|
||
|
|
re_flags = re.DOTALL
|
||
|
|
check_string = re.escape(")#")
|
||
|
|
|
||
|
|
if not pattern:
|
||
|
|
pattern = re.escape(self.base_prompt[:16])
|
||
|
|
pattern = f"{pattern}.*{check_string}"
|
||
|
|
return super().config_mode(
|
||
|
|
config_command=config_command, pattern=pattern, re_flags=re_flags
|
||
|
|
)
|
||
|
|
|
||
|
|
def _enter_shell(self) -> str:
|
||
|
|
"""Enter the Bourne Shell."""
|
||
|
|
output = self._send_command_str("bash", expect_string=r"[\$#]")
|
||
|
|
return output
|
||
|
|
|
||
|
|
def _return_cli(self) -> str:
|
||
|
|
"""Return to the CLI."""
|
||
|
|
output = self._send_command_str("exit", expect_string=r"[#>]")
|
||
|
|
return output
|
||
|
|
|
||
|
|
|
||
|
|
class AristaSSH(AristaBase):
|
||
|
|
pass
|
||
|
|
|
||
|
|
|
||
|
|
class AristaTelnet(AristaBase):
|
||
|
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||
|
|
default_enter = kwargs.get("default_enter")
|
||
|
|
kwargs["default_enter"] = "\r\n" if default_enter is None else default_enter
|
||
|
|
super().__init__(*args, **kwargs)
|
||
|
|
|
||
|
|
|
||
|
|
class AristaFileTransfer(CiscoFileTransfer):
|
||
|
|
"""Arista SCP File Transfer driver."""
|
||
|
|
|
||
|
|
prompt_pattern = r"[$>#]"
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
ssh_conn: "BaseConnection",
|
||
|
|
source_file: str,
|
||
|
|
dest_file: str,
|
||
|
|
file_system: Optional[str] = "/mnt/flash",
|
||
|
|
direction: str = "put",
|
||
|
|
**kwargs: Any,
|
||
|
|
) -> None:
|
||
|
|
return super().__init__(
|
||
|
|
ssh_conn=ssh_conn,
|
||
|
|
source_file=source_file,
|
||
|
|
dest_file=dest_file,
|
||
|
|
file_system=file_system,
|
||
|
|
direction=direction,
|
||
|
|
**kwargs,
|
||
|
|
)
|
||
|
|
|
||
|
|
def remote_space_available(self, search_pattern: str = "") -> int:
|
||
|
|
"""Return space available on remote device."""
|
||
|
|
search_pattern = self.prompt_pattern
|
||
|
|
return self._remote_space_available_unix(search_pattern=search_pattern)
|
||
|
|
|
||
|
|
def check_file_exists(self, remote_cmd: str = "") -> bool:
|
||
|
|
"""Check if the dest_file already exists on the file system (return boolean)."""
|
||
|
|
return self._check_file_exists_unix(remote_cmd=remote_cmd)
|
||
|
|
|
||
|
|
def remote_file_size(
|
||
|
|
self, remote_cmd: str = "", remote_file: Optional[str] = None
|
||
|
|
) -> int:
|
||
|
|
"""Get the file size of the remote file."""
|
||
|
|
return self._remote_file_size_unix(
|
||
|
|
remote_cmd=remote_cmd, remote_file=remote_file
|
||
|
|
)
|
||
|
|
|
||
|
|
def remote_md5(
|
||
|
|
self, base_cmd: str = "verify /md5", remote_file: Optional[str] = None
|
||
|
|
) -> str:
|
||
|
|
if remote_file is None:
|
||
|
|
if self.direction == "put":
|
||
|
|
remote_file = self.dest_file
|
||
|
|
elif self.direction == "get":
|
||
|
|
remote_file = self.source_file
|
||
|
|
remote_md5_cmd = f"{base_cmd} file:{self.file_system}/{remote_file}"
|
||
|
|
dest_md5 = self.ssh_ctl_chan._send_command_str(remote_md5_cmd, read_timeout=600)
|
||
|
|
dest_md5 = self.process_md5(dest_md5)
|
||
|
|
return dest_md5
|
||
|
|
|
||
|
|
def enable_scp(self, cmd: Union[str, Sequence[str], None] = None) -> None:
|
||
|
|
raise NotImplementedError
|
||
|
|
|
||
|
|
def disable_scp(self, cmd: Union[str, Sequence[str], None] = None) -> None:
|
||
|
|
raise NotImplementedError
|