Skip to content

Commit

Permalink
Merge pull request #454 from ktbyers/develop
Browse files Browse the repository at this point in the history
Release 1.4.0
  • Loading branch information
ktbyers authored May 5, 2017
2 parents 23ff444 + 6aa565b commit 86fa8ee
Show file tree
Hide file tree
Showing 24 changed files with 303 additions and 90 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@ Multi-vendor library to simplify Paramiko SSH connections to network devices

Python 2.7, 3.4, 3.5

<br>
#### Requires:

Paramiko >= 1.13+
scp >= 0.10.0
pyyaml
pytest (for unit tests)


<br>
#### Supports:

###### Regularly tested

Arista vEOS
Cisco ASA
Cisco IOS
Expand All @@ -33,6 +32,7 @@ Juniper Junos
Linux

###### Limited testing

Avaya ERS
Avaya VSP
Brocade VDX
Expand All @@ -48,26 +48,27 @@ Pluribus
Vyatta VyOS

###### Experimental

A10
Alcatel-Lucent SR-OS
Ciena SAOS
Cisco Telepresence
CheckPoint Gaia
Enterasys
Extreme
F5 LTM
Fortinet

<br>
## 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

<br>
## Examples:

#### Create a dictionary representing the device.
Expand All @@ -88,14 +89,14 @@ cisco_881 = {

```

<br>
#### Establish an SSH connection to the device by passing in the device dictionary.

```py
net_connect = ConnectHandler(**cisco_881)
```

<br>
#### Execute show commands.

```py
output = net_connect.send_command('show ip int brief')
print(output)
Expand All @@ -110,27 +111,27 @@ FastEthernet4 10.10.10.10 YES manual up up
Vlan1 unassigned YES unset down down
```

<br>
#### 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]
```

<br>
#### Enter and exit enable mode.

```py
net_connect.enable()
net_connect.exit_enable_mode()
```

<br>
#### Execute configuration change commands (will automatically enter into config mode)

```py
config_commands = [ 'logging buffered 20000',
'logging buffered 20010',
Expand All @@ -157,7 +158,6 @@ If you have questions or would like to discuss Netmiko, a Netmiko channel exists



<br>
---
Kirk Byers
Python for Network Engineers
Expand Down
5 changes: 3 additions & 2 deletions netmiko/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
77 changes: 35 additions & 42 deletions netmiko/base_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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()

Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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 = {}

Expand Down Expand Up @@ -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")

Expand Down
4 changes: 4 additions & 0 deletions netmiko/checkpoint/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from __future__ import unicode_literals
from netmiko.checkpoint.checkpoint_gaia_ssh import CheckPointGaiaSSH

__all__ = ['CheckPointGaiaSSH']
26 changes: 26 additions & 0 deletions netmiko/checkpoint/checkpoint_gaia_ssh.py
Original file line number Diff line number Diff line change
@@ -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 ''
32 changes: 31 additions & 1 deletion netmiko/cisco/cisco_asa_ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import unicode_literals
import re
import time
from netmiko.cisco_base_connection import CiscoSSHConnection


Expand All @@ -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")

Expand Down Expand Up @@ -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
Loading

0 comments on commit 86fa8ee

Please sign in to comment.