diff --git a/README.md b/README.md index 40f2b8358..c23793e7d 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,17 @@ Multi-vendor library to simplify Paramiko SSH connections to network devices Python 2.7, 3.4, 3.5 -
#### Requires: + Paramiko >= 1.13+ scp >= 0.10.0 pyyaml pytest (for unit tests) - -
#### Supports: ###### Regularly tested + Arista vEOS Cisco ASA Cisco IOS @@ -33,6 +32,7 @@ Juniper Junos Linux ###### Limited testing + Avaya ERS Avaya VSP Brocade VDX @@ -48,26 +48,27 @@ Pluribus Vyatta VyOS ###### Experimental + A10 Alcatel-Lucent SR-OS Ciena SAOS Cisco Telepresence +CheckPoint Gaia Enterasys Extreme F5 LTM Fortinet -
## Tutorials: ##### Standard Tutorial: + https://pynet.twb-tech.com/blog/automation/netmiko.html ##### SSH Proxy: -https://pynet.twb-tech.com/blog/automation/netmiko-proxy.html +https://pynet.twb-tech.com/blog/automation/netmiko-proxy.html -
## Examples: #### Create a dictionary representing the device. @@ -88,14 +89,14 @@ cisco_881 = { ``` -
#### Establish an SSH connection to the device by passing in the device dictionary. + ```py net_connect = ConnectHandler(**cisco_881) ``` -
#### Execute show commands. + ```py output = net_connect.send_command('show ip int brief') print(output) @@ -110,27 +111,27 @@ FastEthernet4 10.10.10.10 YES manual up up Vlan1 unassigned YES unset down down ``` -
#### For long-running commands, use `send_command_expect()` `send_command_expect` waits for the trailing prompt (or for an optional pattern) ```py net_connect.send_command_expect('write memory') ``` + ``` Building configuration... [OK] ``` -
#### Enter and exit enable mode. + ```py net_connect.enable() net_connect.exit_enable_mode() ``` -
#### Execute configuration change commands (will automatically enter into config mode) + ```py config_commands = [ 'logging buffered 20000', 'logging buffered 20010', @@ -157,7 +158,6 @@ If you have questions or would like to discuss Netmiko, a Netmiko channel exists -
--- Kirk Byers Python for Network Engineers diff --git a/netmiko/__init__.py b/netmiko/__init__.py index e97075d7a..033ea7235 100644 --- a/netmiko/__init__.py +++ b/netmiko/__init__.py @@ -15,17 +15,18 @@ from netmiko.ssh_exception import NetMikoTimeoutException from netmiko.ssh_exception import NetMikoAuthenticationException from netmiko.ssh_autodetect import SSHDetect +from netmiko.base_connection import BaseConnection # Alternate naming NetmikoTimeoutError = NetMikoTimeoutException NetmikoAuthError = NetMikoAuthenticationException -__version__ = '1.3.0' +__version__ = '1.4.0' __all__ = ('ConnectHandler', 'ssh_dispatcher', 'platforms', 'SCPConn', 'FileTransfer', 'NetMikoTimeoutException', 'NetMikoAuthenticationException', 'NetmikoTimeoutError', 'NetmikoAuthError', 'InLineTransfer', 'redispatch', - 'SSHDetect') + 'SSHDetect', 'BaseConnection') # Cisco cntl-shift-six sequence CNTL_SHIFT_6 = chr(30) diff --git a/netmiko/base_connection.py b/netmiko/base_connection.py index 7abc2c4a0..67614a052 100644 --- a/netmiko/base_connection.py +++ b/netmiko/base_connection.py @@ -22,6 +22,7 @@ from netmiko.netmiko_globals import MAX_BUFFER, BACKSPACE_CHAR from netmiko.ssh_exception import NetMikoTimeoutException, NetMikoAuthenticationException from netmiko.utilities import write_bytes +from netmiko.py23_compat import string_types from netmiko import log @@ -58,40 +59,31 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non :type port: int or None :param device_type: Class selection based on device type. :type device_type: str - :param verbose: If `True` enables more verbose logging. + :param verbose: Enable additional messages to standard output. :type verbose: bool - :param global_delay_factor: Controls global delay factor value. + :param global_delay_factor: Multiplication factor affecting Netmiko delays (default: 1). :type global_delay_factor: int - :param use_keys: If true, Paramiko will attempt to connect to - target device using SSH keys. + :param use_keys: Connect to target device using SSH keys. :type use_keys: bool - :param key_file: Name of the SSH key file to use for Paramiko - SSH connection authentication. + :param key_file: Filename path of the SSH key file to use. :type key_file: str - :param allow_agent: Set to True to enable connect to the SSH agent + :param allow_agent: Enable use of SSH key-agent. :type allow_agent: bool - :param ssh_strict: If `True` Paramiko will automatically reject - unknown hostname and keys. If 'False' Paramiko will - automatically add the hostname and new host key. + :param ssh_strict: Automatically reject unknown SSH host keys (default: False, which + means unknown SSH host keys will be accepted). :type ssh_strict: bool - :param system_host_keys: If `True` Paramiko will load host keys - from the user's local 'known hosts' file. + :param system_host_keys: Load host keys from the user's 'known_hosts' file. :type system_host_keys: bool - :param alt_host_keys: If `True` host keys will be loaded from - a local host-key file. + :param alt_host_keys: If `True` host keys will be loaded from the file specified in + 'alt_key_file'. :type alt_host_keys: bool - :param alt_key_file: If `alt_host_keys` is set to `True`, provide - the filename of the local host-key file to load. + :param alt_key_file: SSH host key file to use (if alt_host_keys=True). :type alt_key_file: str - :param ssh_config_file: File name of a OpenSSH configuration file - to load SSH connection parameters from. + :param ssh_config_file: File name of OpenSSH configuration file. :type ssh_config_file: str - :param timeout: Set a timeout on blocking read/write operations. + :param timeout: Connection timeout. :type timeout: float - :param session_timeout: Set a timeout for parallel requests. When - the channel is busy serving other tasks and the queue is - very long, in case the wait time is higher than this value, - NetMikoTimeoutException will be raised. + :param session_timeout: Set a timeout for parallel requests. :type session_timeout: float """ if ip: @@ -127,6 +119,7 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non # determine if telnet or SSH if '_telnet' in device_type: self.protocol = 'telnet' + self._modify_connection_params() self.establish_connection() self.session_preparation() else: @@ -148,6 +141,7 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non # For SSH proxy support self.ssh_config_file = ssh_config_file + self._modify_connection_params() self.establish_connection() self.session_preparation() @@ -156,15 +150,19 @@ def __init__(self, ip='', host='', username='', password='', secret='', port=Non self.clear_buffer() def __enter__(self): - """Enter runtime context""" + """Establish a session using a Context Manager.""" return self def __exit__(self, exc_type, exc_value, traceback): - """Gracefully close connection on context manager exit""" + """Gracefully close connection on Context Manager exit.""" self.disconnect() if exc_type is not None: raise exc_type(exc_value) + def _modify_connection_params(self): + """Modify connection parameters prior to SSH connection.""" + pass + def _timeout_exceeded(self, start, msg='Timeout exceeded!'): """ Raise NetMikoTimeoutException if waiting too much in the @@ -242,7 +240,7 @@ def read_channel(self): self._unlock_netmiko_session() return output - def _read_channel_expect(self, pattern='', re_flags=0): + def _read_channel_expect(self, pattern='', re_flags=0, max_loops=None): """ Function that reads channel until pattern is detected. @@ -263,10 +261,11 @@ def _read_channel_expect(self, pattern='', re_flags=0): if debug: print("Pattern is: {}".format(pattern)) - # Will loop for self.timeout time (unless modified by global_delay_factor) + # Will loop for self.timeout time (override with max_loops argument) i = 1 loop_delay = .1 - max_loops = self.timeout / loop_delay + if not max_loops: + max_loops = self.timeout / loop_delay while i < max_loops: if self.protocol == 'ssh': try: @@ -329,18 +328,10 @@ def read_until_pattern(self, *args, **kwargs): def read_until_prompt_or_pattern(self, pattern='', re_flags=0): """Read until either self.base_prompt or pattern is detected. Return ALL data available.""" - output = '' - if not pattern: - pattern = re.escape(self.base_prompt) - base_prompt_pattern = re.escape(self.base_prompt) - while True: - try: - output += self.read_channel() - if re.search(pattern, output, flags=re_flags) or re.search(base_prompt_pattern, - output, flags=re_flags): - return output - except socket.timeout: - raise NetMikoTimeoutException("Timed-out reading channel, data not available.") + combined_pattern = re.escape(self.base_prompt) + if pattern: + combined_pattern += "|{}".format(pattern) + return self._read_channel_expect(combined_pattern, re_flags=re_flags) def telnet_login(self, pri_prompt_terminator='#', alt_prompt_terminator='>', username_pattern=r"sername", pwd_pattern=r"assword", @@ -426,8 +417,7 @@ def _use_ssh_config(self, dict_arg): ssh_config_instance = paramiko.SSHConfig() with io.open(full_path, "rt", encoding='utf-8') as f: ssh_config_instance.parse(f) - host_specifier = "{0}:{1}".format(self.host, self.port) - source = ssh_config_instance.lookup(host_specifier) + source = ssh_config_instance.lookup(self.host) else: source = {} @@ -948,6 +938,9 @@ def send_config_set(self, config_commands=None, exit_config_mode=True, delay_fac delay_factor = self.select_delay_factor(delay_factor) if config_commands is None: return '' + elif isinstance(config_commands, string_types): + config_commands = (config_commands,) + if not hasattr(config_commands, '__iter__'): raise ValueError("Invalid argument passed into send_config_set") diff --git a/netmiko/checkpoint/__init__.py b/netmiko/checkpoint/__init__.py new file mode 100644 index 000000000..56967ca39 --- /dev/null +++ b/netmiko/checkpoint/__init__.py @@ -0,0 +1,4 @@ +from __future__ import unicode_literals +from netmiko.checkpoint.checkpoint_gaia_ssh import CheckPointGaiaSSH + +__all__ = ['CheckPointGaiaSSH'] diff --git a/netmiko/checkpoint/checkpoint_gaia_ssh.py b/netmiko/checkpoint/checkpoint_gaia_ssh.py new file mode 100644 index 000000000..591f3db09 --- /dev/null +++ b/netmiko/checkpoint/checkpoint_gaia_ssh.py @@ -0,0 +1,26 @@ +from __future__ import unicode_literals +from netmiko.base_connection import BaseConnection + + +class CheckPointGaiaSSH(BaseConnection): + """ + Implements methods for communicating with Check Point Gaia + firewalls. + """ + def session_preparation(self): + """ + Prepare the session after the connection has been established. + + Set the base prompt for interaction ('>'). + """ + self._test_channel_read() + self.set_base_prompt() + self.disable_paging(command="set clienv rows 0\n") + + def config_mode(self, config_command=''): + """No config mode for Check Point devices.""" + return '' + + def exit_config_mode(self, exit_config=''): + """No config mode for Check Point devices.""" + return '' diff --git a/netmiko/cisco/cisco_asa_ssh.py b/netmiko/cisco/cisco_asa_ssh.py index a34977006..71e22e085 100644 --- a/netmiko/cisco/cisco_asa_ssh.py +++ b/netmiko/cisco/cisco_asa_ssh.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import re +import time from netmiko.cisco_base_connection import CiscoSSHConnection @@ -11,7 +12,10 @@ def session_preparation(self): """Prepare the session after the connection has been established.""" self._test_channel_read() self.set_base_prompt() - self.enable() + if self.secret: + self.enable() + else: + self.asa_login() self.disable_paging(command="terminal pager 0\n") self.set_terminal_width(command="terminal width 511\n") @@ -69,3 +73,29 @@ def set_base_prompt(self, *args, **kwargs): # strip off (conf.* from base_prompt self.base_prompt = match.group(1) return self.base_prompt + + def asa_login(self): + """ + Handle ASA reaching privilege level 15 using login + + twb-dc-fw1> login + Username: admin + Password: ************ + """ + delay_factor = self.select_delay_factor(0) + + i = 1 + max_attempts = 50 + self.write_channel("login\n") + while i <= max_attempts: + time.sleep(.5 * delay_factor) + output = self.read_channel() + if 'sername' in output: + self.write_channel(self.username + '\n') + elif 'ssword' in output: + self.write_channel(self.password + '\n') + elif '#' in output: + break + else: + self.write_channel("login\n") + i += 1 diff --git a/netmiko/cisco/cisco_wlc_ssh.py b/netmiko/cisco/cisco_wlc_ssh.py index e9990d52b..2a357b270 100644 --- a/netmiko/cisco/cisco_wlc_ssh.py +++ b/netmiko/cisco/cisco_wlc_ssh.py @@ -4,6 +4,7 @@ import time from netmiko.base_connection import BaseConnection +from netmiko.py23_compat import string_types class CiscoWlcSSH(BaseConnection): @@ -137,6 +138,9 @@ def send_config_set(self, config_commands=None, exit_config_mode=True, delay_fac delay_factor = self.select_delay_factor(delay_factor) if config_commands is None: return '' + elif isinstance(config_commands, string_types): + config_commands = (config_commands,) + if not hasattr(config_commands, '__iter__'): raise ValueError("Invalid argument passed into send_config_set") diff --git a/netmiko/cisco_base_connection.py b/netmiko/cisco_base_connection.py index ff229d5bc..4614287a2 100644 --- a/netmiko/cisco_base_connection.py +++ b/netmiko/cisco_base_connection.py @@ -27,7 +27,7 @@ def check_config_mode(self, check_string=')#', pattern=''): Cisco IOS devices abbreviate the prompt at 20 chars in config mode """ if not pattern: - pattern = self.base_prompt[:16] + pattern = re.escape(self.base_prompt[:16]) return super(CiscoBaseConnection, self).check_config_mode(check_string=check_string, pattern=pattern) @@ -38,14 +38,14 @@ def config_mode(self, config_command='config term', pattern=''): Cisco IOS devices abbreviate the prompt at 20 chars in config mode """ if not pattern: - pattern = self.base_prompt[:16] + pattern = re.escape(self.base_prompt[:16]) return super(CiscoBaseConnection, self).config_mode(config_command=config_command, pattern=pattern) def exit_config_mode(self, exit_config='end', pattern=''): """Exit from configuration mode.""" if not pattern: - pattern = self.base_prompt[:16] + pattern = re.escape(self.base_prompt[:16]) return super(CiscoBaseConnection, self).exit_config_mode(exit_config=exit_config, pattern=pattern) diff --git a/netmiko/dell/__init__.py b/netmiko/dell/__init__.py index d9dd7c4cd..15868b82b 100644 --- a/netmiko/dell/__init__.py +++ b/netmiko/dell/__init__.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals from netmiko.dell.dell_force10_ssh import DellForce10SSH from netmiko.dell.dell_powerconnect_ssh import DellPowerConnectSSH +from netmiko.dell.dell_powerconnect_telnet import DellPowerConnectTelnet -__all__ = ['DellForce10SSH', 'DellPowerConnectSSH'] +__all__ = ['DellForce10SSH', 'DellPowerConnectSSH', 'DellPowerConnectTelnet'] diff --git a/netmiko/dell/dell_powerconnect_telnet.py b/netmiko/dell/dell_powerconnect_telnet.py new file mode 100644 index 000000000..c4167707a --- /dev/null +++ b/netmiko/dell/dell_powerconnect_telnet.py @@ -0,0 +1,40 @@ +"""Dell Telnet Driver.""" +from __future__ import unicode_literals + +import time +from netmiko.cisco_base_connection import CiscoBaseConnection + + +class DellPowerConnectTelnet(CiscoBaseConnection): + def disable_paging(self, command="terminal length 0", delay_factor=1): + """Must be in enable mode to disable paging.""" + debug = False + + self.enable() + delay_factor = self.select_delay_factor(delay_factor) + time.sleep(delay_factor * .1) + self.clear_buffer() + command = self.normalize_cmd(command) + if debug: + print("In disable_paging") + print("Command: {}".format(command)) + self.write_channel(command) + output = self.read_until_prompt() + if self.ansi_escape_codes: + output = self.strip_ansi_escape_codes(output) + if debug: + print(output) + print("Exiting disable_paging") + return output + + def telnet_login(self, pri_prompt_terminator='#', alt_prompt_terminator='>', + username_pattern=r"User:", pwd_pattern=r"assword", + delay_factor=1, max_loops=60): + """Telnet login. Can be username/password or just password.""" + super(DellPowerConnectTelnet, self).telnet_login( + pri_prompt_terminator=pri_prompt_terminator, + alt_prompt_terminator=alt_prompt_terminator, + username_pattern=username_pattern, + pwd_pattern=pwd_pattern, + delay_factor=delay_factor, + max_loops=max_loops) diff --git a/netmiko/extreme/extreme_ssh.py b/netmiko/extreme/extreme_ssh.py index 2ef0465d0..2b4c64ebe 100644 --- a/netmiko/extreme/extreme_ssh.py +++ b/netmiko/extreme/extreme_ssh.py @@ -1,13 +1,64 @@ """Extreme support.""" from __future__ import unicode_literals + +import re from netmiko.cisco_base_connection import CiscoSSHConnection class ExtremeSSH(CiscoSSHConnection): - """Extreme support.""" + """Extreme support. + + Designed for EXOS >= 15.0 + """ def session_preparation(self): """Extreme requires enable mode to disable paging.""" self._test_channel_read() self.enable() self.set_base_prompt() self.disable_paging(command="disable clipaging\n") + + def set_base_prompt(self, *args, **kwargs): + """ + Extreme attaches an id to the prompt. The id increases with every command. + It needs to br stripped off to match the prompt. Eg. + + testhost.1 # + testhost.2 # + testhost.3 # + + If new config is loaded and not saved yet, a '* ' prefix appears before the + prompt, eg. + + * testhost.4 # + * testhost.5 # + """ + cur_base_prompt = super(ExtremeSSH, self).set_base_prompt(*args, **kwargs) + # Strip off any leading * or whitespace chars; strip off trailing period and digits + match = re.search(r'[\*\s]*(.*)\.\d+', cur_base_prompt) + if match: + self.base_prompt = match.group(1) + return self.base_prompt + else: + return self.base_prompt + + def send_command(self, *args, **kwargs): + """Extreme needs special handler here due to the prompt changes.""" + + # Change send_command behavior to use self.base_prompt + kwargs.setdefault('auto_find_prompt', False) + + # refresh self.base_prompt + self.set_base_prompt() + return super(ExtremeSSH, self).send_command(*args, **kwargs) + + def config_mode(self, config_command=''): + """No configuration mode on Extreme.""" + return '' + + def check_config_mode(self, check_string='#'): + """Checks whether in configuration mode. Returns a boolean.""" + return super(ExtremeSSH, self).check_config_mode(check_string=check_string) + + def exit_config_mode(self, exit_config=''): + """No configuration mode on Extreme.""" + return '' diff --git a/netmiko/fortinet/fortinet_ssh.py b/netmiko/fortinet/fortinet_ssh.py index 1f5840a2e..4cfa0084d 100644 --- a/netmiko/fortinet/fortinet_ssh.py +++ b/netmiko/fortinet/fortinet_ssh.py @@ -1,9 +1,17 @@ from __future__ import unicode_literals +import paramiko from netmiko.cisco_base_connection import CiscoSSHConnection class FortinetSSH(CiscoSSHConnection): + def _modify_connection_params(self): + """Modify connection parameters prior to SSH connection.""" + paramiko.Transport._preferred_kex = ('diffie-hellman-group14-sha1', + 'diffie-hellman-group-exchange-sha1', + 'diffie-hellman-group-exchange-sha256', + 'diffie-hellman-group1-sha1',) + def session_preparation(self): """Prepare the session after the connection has been established.""" self._test_channel_read() diff --git a/netmiko/py23_compat.py b/netmiko/py23_compat.py new file mode 100644 index 000000000..fb753e176 --- /dev/null +++ b/netmiko/py23_compat.py @@ -0,0 +1,15 @@ +"""Simplify Python3 compatibility. Modeled after six behavior for small set of things""" +from __future__ import print_function +from __future__ import unicode_literals + +import sys + +PY2 = sys.version_info.major == 2 +PY3 = sys.version_info.major == 3 + +if sys.version_info.major == 3: + string_types = (str,) + text_type = str +else: + string_types = (basestring,) # noqa + text_type = unicode # noqa diff --git a/netmiko/scp_handler.py b/netmiko/scp_handler.py index 21743cc30..6a2649812 100644 --- a/netmiko/scp_handler.py +++ b/netmiko/scp_handler.py @@ -312,8 +312,8 @@ def __exit__(self, exc_type, exc_value, traceback): def _enter_tcl_mode(self): TCL_ENTER = 'tclsh' cmd_failed = ['Translating "tclsh"', '% Unknown command', '% Bad IP address'] - output = self.ssh_ctl_chan.send_command_timing(TCL_ENTER, strip_prompt=False, - strip_command=False) + output = self.ssh_ctl_chan.send_command(TCL_ENTER, expect_string=r"\(tcl\)#", + strip_prompt=False, strip_command=False) for pattern in cmd_failed: if pattern in output: raise ValueError("Failed to enter tclsh mode on router: {}".format(output)) @@ -364,24 +364,37 @@ def put_file(self): file_contents = self.source_config file_contents = self._tcl_newline_rationalize(file_contents) + # Try to remove any existing data + self.ssh_ctl_chan.clear_buffer() + self.ssh_ctl_chan.write_channel(TCL_FILECMD_ENTER) + time.sleep(.25) self.ssh_ctl_chan.write_channel(file_contents) self.ssh_ctl_chan.write_channel(TCL_FILECMD_EXIT + "\r") - output = '' - while True: - time.sleep(1.5) - new_output = self.ssh_ctl_chan.read_channel() - if not new_output: - break - output += new_output + # This operation can be slow (depends on the size of the file) + max_loops = 400 + sleep_time = 4 + if self.file_size >= 2500: + max_loops = 1500 + sleep_time = 12 + elif self.file_size >= 7500: + max_loops = 3000 + sleep_time = 25 + + # Initial delay + time.sleep(sleep_time) + + # File paste and TCL_FILECMD_exit should be indicated by "router(tcl)#" + output = self.ssh_ctl_chan._read_channel_expect(pattern=r"\(tcl\)", max_loops=max_loops) # The file doesn't write until tclquit TCL_EXIT = 'tclquit' self.ssh_ctl_chan.write_channel(TCL_EXIT + "\r") time.sleep(1) - output += self.ssh_ctl_chan.read_channel() + # Read all data remaining from the TCLSH session + output += self.ssh_ctl_chan._read_channel_expect(max_loops=max_loops) return output def get_file(self): diff --git a/netmiko/snmp_autodetect.py b/netmiko/snmp_autodetect.py index 70924f235..75afc6d68 100644 --- a/netmiko/snmp_autodetect.py +++ b/netmiko/snmp_autodetect.py @@ -30,10 +30,11 @@ from netmiko.ssh_dispatcher import CLASS_MAPPER + # Higher priority indicates a better match. SNMP_MAPPER_BASE = { 'arista_eos': {"oid": ".1.3.6.1.2.1.1.1.0", - "expr": re.compile(r".Arista Networks EOS.", re.IGNORECASE), + "expr": re.compile(r".*Arista Networks EOS.*", re.IGNORECASE), "priority": 99}, 'hp_comware': {"oid": ".1.3.6.1.2.1.1.1.0", "expr": re.compile(r".*HP Comware.*", re.IGNORECASE), @@ -59,6 +60,9 @@ 'fortinet': {"oid": ".1.3.6.1.2.1.1.1.0", "expr": re.compile(r"Forti.*", re.IGNORECASE), "priority": 80}, + 'checkpoint': {"oid": ".1.3.6.1.4.1.2620.1.6.16.9.0", + "expr": re.compile(r"CheckPoint"), + "priority": 79}, } # Ensure all SNMP device types are supported by Netmiko diff --git a/netmiko/ssh_autodetect.py b/netmiko/ssh_autodetect.py index c773c3794..17b2de94d 100644 --- a/netmiko/ssh_autodetect.py +++ b/netmiko/ssh_autodetect.py @@ -90,7 +90,7 @@ }, 'juniper_junos': { "cmd": "show version | match JUNOS", - "search_patterns": ["JUNOS Software Release"], + "search_patterns": ["JUNOS Software Release", "JUNOS .+ Software"], "priority": 99, "dispatch": "_autodetect_std", }, diff --git a/netmiko/ssh_dispatcher.py b/netmiko/ssh_dispatcher.py index 6b269ffca..68bf39101 100644 --- a/netmiko/ssh_dispatcher.py +++ b/netmiko/ssh_dispatcher.py @@ -16,6 +16,7 @@ from netmiko.brocade import BrocadeNetironSSH from netmiko.brocade import BrocadeFastironSSH from netmiko.fortinet import FortinetSSH +from netmiko.checkpoint import CheckPointGaiaSSH from netmiko.a10 import A10SSH from netmiko.avaya import AvayaVspSSH from netmiko.avaya import AvayaErsSSH @@ -26,6 +27,7 @@ from netmiko.alcatel import AlcatelSrosSSH from netmiko.dell import DellForce10SSH from netmiko.dell import DellPowerConnectSSH +from netmiko.dell import DellPowerConnectTelnet from netmiko.paloalto import PaloAltoPanosSSH from netmiko.quanta import QuantaMeshSSH from netmiko.aruba import ArubaSSH @@ -72,6 +74,7 @@ 'extreme': ExtremeSSH, 'alcatel_sros': AlcatelSrosSSH, 'fortinet': FortinetSSH, + 'checkpoint_gaia': CheckPointGaiaSSH, 'dell_force10': DellForce10SSH, 'dell_powerconnect': DellPowerConnectSSH, 'paloalto_panos': PaloAltoPanosSSH, @@ -95,12 +98,11 @@ # Add telnet drivers CLASS_MAPPER['cisco_ios_telnet'] = CiscoIosBase +CLASS_MAPPER['dell_powerconnect_telnet'] = DellPowerConnectTelnet CLASS_MAPPER['generic_termserver_telnet'] = TerminalServerTelnet -# Add general terminal_server driver +# Add general terminal_server driver and autodetect CLASS_MAPPER['terminal_server'] = TerminalServerSSH - -# Add autodetect driver (mapped to TerminalServerSSH) CLASS_MAPPER['autodetect'] = TerminalServerSSH platforms = list(CLASS_MAPPER.keys()) diff --git a/netmiko/utilities.py b/netmiko/utilities.py index a66adf6e9..6d25d3ddf 100644 --- a/netmiko/utilities.py +++ b/netmiko/utilities.py @@ -14,6 +14,7 @@ 'hp_comware': 'display current-configuration', 'huawei': 'display current-configuration', 'fortinet': 'show full-configuration', + 'checkpoint': 'show configuration', 'cisco_wlc': 'show run-config', 'enterasys': 'show running-config', 'dell_force10': 'show running-config', diff --git a/netmiko/vyos/vyos_ssh.py b/netmiko/vyos/vyos_ssh.py index 98faf4283..6a6bd50cb 100644 --- a/netmiko/vyos/vyos_ssh.py +++ b/netmiko/vyos/vyos_ssh.py @@ -28,11 +28,11 @@ def check_config_mode(self, check_string='#'): """Checks if the device is in configuration mode""" return super(VyOSSSH, self).check_config_mode(check_string=check_string) - def config_mode(self, config_command='configure', pattern='[edit]'): + def config_mode(self, config_command='configure', pattern=r'[edit]'): """Enter configuration mode.""" return super(VyOSSSH, self).config_mode(config_command=config_command, pattern=pattern) - def exit_config_mode(self, exit_config='exit', pattern='exit'): + def exit_config_mode(self, exit_config='exit', pattern=r'exit'): """Exit configuration mode""" output = "" if self.check_config_mode(): diff --git a/setup.py b/setup.py index 8b6517895..1129a522d 100644 --- a/setup.py +++ b/setup.py @@ -53,6 +53,7 @@ def find_version(*file_paths): 'netmiko/extreme', 'netmiko/f5', 'netmiko/fortinet', + 'netmiko/checkpoint', 'netmiko/hp', 'netmiko/huawei', 'netmiko/juniper', diff --git a/tests/test_asa.sh b/tests/test_asa.sh index 80b371ce0..0b7b90a0c 100755 --- a/tests/test_asa.sh +++ b/tests/test_asa.sh @@ -4,6 +4,8 @@ RETURN_CODE=0 # Exit on the first test failure and set RETURN_CODE = 1 echo "Starting tests...good luck:" \ +&& py.test -v test_netmiko_show.py --test_device cisco_asa_login \ +&& py.test -v test_netmiko_config.py --test_device cisco_asa_login \ && py.test -v test_netmiko_show.py --test_device cisco_asa \ && py.test -v test_netmiko_config.py --test_device cisco_asa \ || RETURN_CODE=1 diff --git a/tests/test_cisco.sh b/tests/test_cisco.sh new file mode 100755 index 000000000..cb4e5f341 --- /dev/null +++ b/tests/test_cisco.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +RETURN_CODE=0 + +# Exit on the first test failure and set RETURN_CODE = 1 +echo "Cisco IOS SSH" \ +&& py.test -v test_netmiko_show.py --test_device cisco881_ssh_config \ +&& py.test -v test_netmiko_config.py --test_device cisco881_ssh_config \ +|| RETURN_CODE=1 + +exit $RETURN_CODE + diff --git a/tests/test_netmiko_config.py b/tests/test_netmiko_config.py index f8bf6f368..275e3e96d 100755 --- a/tests/test_netmiko_config.py +++ b/tests/test_netmiko_config.py @@ -56,22 +56,18 @@ def test_exit_config_mode(net_connect, commands, expected_responses): assert net_connect.check_config_mode() == False def test_command_set(net_connect, commands, expected_responses): - ''' - Test sending configuration commands - ''' - print(1) + """Test sending configuration commands.""" config_commands = commands['config'] support_commit = commands.get('support_commit') config_verify = commands['config_verification'] - print(2) - net_connect.send_config_set(config_commands[0:1]) + # Set to initial value and testing sending command as a string + net_connect.send_config_set(config_commands[0]) if support_commit: net_connect.commit() - print(3) cmd_response = expected_responses.get('cmd_response_init') - config_commands_output = net_connect.send_command_expect(config_verify) + config_commands_output = net_connect.send_command(config_verify) print(config_verify) print(config_commands_output) if cmd_response: @@ -79,15 +75,12 @@ def test_command_set(net_connect, commands, expected_responses): else: assert config_commands[0] in config_commands_output - print(4) net_connect.send_config_set(config_commands) if support_commit: net_connect.commit() - print(5) cmd_response = expected_responses.get('cmd_response_final') config_commands_output = net_connect.send_command_expect(config_verify) - print(6) if cmd_response: assert cmd_response in config_commands_output else: diff --git a/tests/test_suite_alt.sh b/tests/test_suite_alt.sh index 2c09d705e..5ab0bb7b1 100755 --- a/tests/test_suite_alt.sh +++ b/tests/test_suite_alt.sh @@ -7,12 +7,22 @@ echo "Starting tests...good luck:" \ && echo "Linux SSH (using keys)" \ && py.test -s -v test_netmiko_show.py --test_device linux_srv1 \ \ +&& echo "Cisco IOS SSH (including SCP) using key auth" \ +&& py.test -v test_netmiko_scp.py --test_device cisco881_key \ +&& py.test -v test_netmiko_tcl.py --test_device cisco881_key \ +&& py.test -v test_netmiko_show.py --test_device cisco881_key \ +&& py.test -v test_netmiko_config.py --test_device cisco881_key \ +\ && echo "Cisco IOS SSH (including SCP)" \ && py.test -v test_netmiko_scp.py --test_device cisco881 \ && py.test -v test_netmiko_tcl.py --test_device cisco881 \ && py.test -v test_netmiko_show.py --test_device cisco881 \ && py.test -v test_netmiko_config.py --test_device cisco881 \ \ +&& echo "Cisco IOS using SSH config with SSH Proxy" \ +&& py.test -v test_netmiko_show.py --test_device cisco881_ssh_config \ +&& py.test -v test_netmiko_config.py --test_device cisco881_ssh_config \ +\ && echo "Cisco IOS telnet" \ && py.test -v test_netmiko_show.py --test_device cisco881_telnet \ && py.test -v test_netmiko_config.py --test_device cisco881_telnet \ @@ -41,6 +51,8 @@ echo "Starting tests...good luck:" \ && echo "Cisco ASA" \ && py.test -v test_netmiko_show.py --test_device cisco_asa \ && py.test -v test_netmiko_config.py --test_device cisco_asa \ +&& py.test -v test_netmiko_show.py --test_device cisco_asa_login \ +&& py.test -v test_netmiko_config.py --test_device cisco_asa_login \ \ && echo "Cisco IOS-XR" \ && py.test -v test_netmiko_show.py --test_device cisco_xrv \