diff --git a/doc/ref/modules/all/index.rst b/doc/ref/modules/all/index.rst index 4bfc1e3bfa..526f4fdc00 100644 --- a/doc/ref/modules/all/index.rst +++ b/doc/ref/modules/all/index.rst @@ -283,6 +283,7 @@ execution modules netbox netbsd_sysctl netbsdservice + netmiko_mod netscaler network neutron diff --git a/doc/ref/modules/all/salt.modules.netmiko_mod.rst b/doc/ref/modules/all/salt.modules.netmiko_mod.rst new file mode 100644 index 0000000000..dd308b1b26 --- /dev/null +++ b/doc/ref/modules/all/salt.modules.netmiko_mod.rst @@ -0,0 +1,7 @@ +======================== +salt.modules.netmiko_mod +======================== + +.. automodule:: salt.modules.netmiko_mod + :members: + diff --git a/doc/ref/proxy/all/index.rst b/doc/ref/proxy/all/index.rst index d986cd5b67..651ac05154 100644 --- a/doc/ref/proxy/all/index.rst +++ b/doc/ref/proxy/all/index.rst @@ -20,6 +20,7 @@ proxy modules junos marathon napalm + netmiko_px nxos panos philips_hue diff --git a/doc/ref/proxy/all/salt.proxy.netmiko_px.rst b/doc/ref/proxy/all/salt.proxy.netmiko_px.rst new file mode 100644 index 0000000000..c4d5ddf0dd --- /dev/null +++ b/doc/ref/proxy/all/salt.proxy.netmiko_px.rst @@ -0,0 +1,6 @@ +================== +salt.proxy.netmiko +================== + +.. automodule:: salt.proxy.netmiko_px + :members: diff --git a/salt/modules/netmiko_mod.py b/salt/modules/netmiko_mod.py new file mode 100644 index 0000000000..333b23b92c --- /dev/null +++ b/salt/modules/netmiko_mod.py @@ -0,0 +1,645 @@ +# -*- coding: utf-8 -*- +''' +Netmiko Execution Module +======================== + +.. versionadded:: Fluorine + +Execution module to interface the connection with a remote network device. It is +flexible enough to execute the commands both when running under a Netmiko Proxy +Minion, as well as running under a Regular Minion by specifying the connection +arguments, i.e., ``device_type``, ``ip``, ``username``, ``password`` etc. + +:codeauthor: Mircea Ulinic & Kirk Byers +:maturity: new +:depends: netmiko +:platform: unix + +Dependencies +------------ + +The ``netmiko`` proxy modules requires Netmiko to be installed: ``pip install netmiko``. + +Usage +----- + +This module can equally be used via the :mod:`netmiko ` +Proxy module (check documentation), or directly from an arbitrary (Proxy) Minion +that is running on a server (computer) having access to the network device, and +has the ``netmiko`` library installed. + +When running outside of the :mod:`netmiko Proxy ` (i.e., +from another Proxy Minion type, or regular Minion), the netmiko connection +arguments can be either specified from the CLI when executing the command, or +in a configuration block under the ``netmiko`` key in the configuration opts +(i.e., (Proxy) Minion configuration file), or Pillar. The module supports these +simultaneously. These fields are the exact same supported by the ``netmiko`` +Proxy Module: + +device_type + Class selection based on device type. Supported options: + + - ``a10``: A10 Networks + - ``accedian``: Accedian Networks + - ``alcatel_aos``: Alcatel AOS + - ``alcatel_sros``: Alcatel SROS + - ``apresia_aeos``: Apresia AEOS + - ``arista_eos``: Arista EOS + - ``aruba_os``: Aruba + - ``avaya_ers``: Avaya ERS + - ``avaya_vsp``: Avaya VSP + - ``brocade_fastiron``: Brocade Fastiron + - ``brocade_netiron``: Brocade Netiron + - ``brocade_nos``: Brocade NOS + - ``brocade_vdx``: Brocade NOS + - ``brocade_vyos``: VyOS + - ``checkpoint_gaia``: Check Point GAiA + - ``calix_b6``: Calix B6 + - ``ciena_saos``: Ciena SAOS + - ``cisco_asa``: Cisco SA + - ``cisco_ios``: Cisco IOS + - ``cisco_nxos``: Cisco NX-oS + - ``cisco_s300``: Cisco S300 + - ``cisco_tp``: Cisco TpTcCe + - ``cisco_wlc``: Cisco WLC + - ``cisco_xe``: Cisco IOS + - ``cisco_xr``: Cisco XR + - ``coriant``: Coriant + - ``dell_force10``: Dell Force10 + - ``dell_os10``: Dell OS10 + - ``dell_powerconnect``: Dell PowerConnect + - ``eltex``: Eltex + - ``enterasys``: Enterasys + - ``extreme``: Extreme + - ``extreme_wing``: Extreme Wing + - ``f5_ltm``: F5 LTM + - ``fortinet``: Fortinet + - ``generic_termserver``: TerminalServer + - ``hp_comware``: HP Comware + - ``hp_procurve``: HP Procurve + - ``huawei``: Huawei + - ``huawei_vrpv8``: Huawei VRPV8 + - ``juniper``: Juniper Junos + - ``juniper_junos``: Juniper Junos + - ``linux``: Linux + - ``mellanox``: Mellanox + - ``mrv_optiswitch``: MrvOptiswitch + - ``netapp_cdot``: NetAppcDot + - ``netscaler``: Netscaler + - ``ovs_linux``: OvsLinux + - ``paloalto_panos``: PaloAlto Panos + - ``pluribus``: Pluribus + - ``quanta_mesh``: Quanta Mesh + - ``ruckus_fastiron``: Ruckus Fastiron + - ``ubiquiti_edge``: Ubiquiti Edge + - ``ubiquiti_edgeswitch``: Ubiquiti Edge + - ``vyatta_vyos``: VyOS + - ``vyos``: VyOS + - ``brocade_fastiron_telnet``: Brocade Fastiron over Telnet + - ``brocade_netiron_telnet``: Brocade Netiron over Telnet + - ``cisco_ios_telnet``: Cisco IOS over Telnet + - ``apresia_aeos_telnet``: Apresia AEOS over Telnet + - ``arista_eos_telnet``: Arista EOS over Telnet + - ``hp_procurve_telnet``: HP Procurve over Telnet + - ``hp_comware_telnet``: HP Comware over Telnet + - ``juniper_junos_telnet``: Juniper Junos over Telnet + - ``calix_b6_telnet``: Calix B6 over Telnet + - ``dell_powerconnect_telnet``: Dell PowerConnect over Telnet + - ``generic_termserver_telnet``: TerminalServer over Telnet + - ``extreme_telnet``: Extreme Networks over Telnet + - ``ruckus_fastiron_telnet``: Ruckus Fastiron over Telnet + - ``cisco_ios_serial``: Cisco IOS over serial port + +ip + IP address of target device. Not required if ``host`` is provided. + +host + Hostname of target device. Not required if ``ip`` is provided. + +username + Username to authenticate against target device if required. + +password + Password to authenticate against target device if required. + +secret + The enable password if target device requires one. + +port + The destination port used to connect to the target device. + +global_delay_factor: ``1`` + Multiplication factor affecting Netmiko delays (default: ``1``). + +use_keys: ``False`` + Connect to target device using SSH keys. + +key_file + Filename path of the SSH key file to use. + +allow_agent + Enable use of SSH key-agent. + +ssh_strict: ``False`` + Automatically reject unknown SSH host keys (default: ``False``, which means + unknown SSH host keys will be accepted). + +system_host_keys: ``False`` + Load host keys from the user's 'known_hosts' file. + +alt_host_keys: ``False`` + If ``True`` host keys will be loaded from the file specified in + ``alt_key_file``. + +alt_key_file + SSH host key file to use (if ``alt_host_keys=True``). + +ssh_config_file + File name of OpenSSH configuration file. + +timeout: ``90`` + Connection timeout (in seconds). + +session_timeout: ``60`` + Set a timeout for parallel requests (in seconds). + +keepalive: ``0`` + Send SSH keepalive packets at a specific interval, in seconds. Currently + defaults to ``0``, for backwards compatibility (it will not attempt to keep + the connection alive using the KEEPALIVE packets). + +default_enter: ``\n`` + Character(s) to send to correspond to enter key (default: ``\n``). + +response_return: ``\n`` + Character(s) to use in normalized return data to represent enter key + (default: ``\n``) + +Example (when not running in a ``netmiko`` Proxy Minion): + +.. code-block:: yaml + + netmiko: + username: test + password: test + +In case the ``username`` and ``password`` are the same on any device you are +targeting, the block above (besides other parameters specific to your +environment you might need) should suffice to be able to execute commands from +outside a ``netmiko`` Proxy, e.g.: + +.. code-block:: bash + + salt '*' netmiko.send_command 'show version' host=router1.example.com device_type=juniper + salt '*' netmiko.send_config https://bit.ly/2sgljCB host=sw2.example.com device_type=cisco_ios + +.. note:: + + Remember that the above applies only when not running in a ``netmiko`` Proxy + Minion. If you want to use the :mod:``, please follow + the documentation notes for a proper setup. +''' +from __future__ import absolute_import + +# Import python stdlib +import logging +import inspect + +# Import Salt libs +from salt.ext import six +from salt.exceptions import CommandExecutionError +try: + from salt.utils.args import clean_kwargs + from salt.utils.files import mkstemp +except ImportError: + from salt.utils import clean_kwargs + from salt.utils import mkstemp + +# Import third party libs +try: + from netmiko import ConnectHandler + from netmiko import BaseConnection + HAS_NETMIKO = True +except ImportError: + HAS_NETMIKO = False + +# ----------------------------------------------------------------------------- +# execution module properties +# ----------------------------------------------------------------------------- + +__proxyenabled__ = ['*'] +# Any Proxy Minion should be able to execute these (not only netmiko) + +__virtualname__ = 'netmiko' +# The Execution Module will be identified as ``netmiko`` + +# ----------------------------------------------------------------------------- +# globals +# ----------------------------------------------------------------------------- + +log = logging.getLogger(__name__) + +# ----------------------------------------------------------------------------- +# propery functions +# ----------------------------------------------------------------------------- + + +def __virtual__(): + ''' + Execution module available only if Netmiko is installed. + ''' + if not HAS_NETMIKO: + return False, 'The netmiko execution module requires netmiko library to be installed.' + return __virtualname__ + +# ----------------------------------------------------------------------------- +# helper functions +# ----------------------------------------------------------------------------- + + +def _prepare_connection(**kwargs): + ''' + Prepare the connection with the remote network device, and clean up the key + value pairs, removing the args used for the connection init. + ''' + init_args = {} + fun_kwargs = {} + netmiko_kwargs = __salt__['config.get']('netmiko', {}) + netmiko_kwargs.update(kwargs) # merge the CLI args with the opts/pillar + netmiko_init_args, _, _, netmiko_defaults = inspect.getargspec(BaseConnection.__init__) + check_self = netmiko_init_args.pop(0) + for karg, warg in six.iteritems(netmiko_kwargs): + if karg not in netmiko_init_args: + if warg is not None: + fun_kwargs[karg] = warg + continue + if warg is not None: + init_args[karg] = warg + conn = ConnectHandler(**init_args) + return conn, fun_kwargs + +# ----------------------------------------------------------------------------- +# callable functions +# ----------------------------------------------------------------------------- + + +def get_connection(**kwargs): + ''' + Return the Netmiko connection object. + + .. warning:: + + This function returns an unserializable object, hence it is not meant + to be used on the CLI. This should mainly be used when invoked from + other modules for the low level connection with the network device. + + kwargs + Key-value dictionary with the authentication details. + + USAGE Example: + + .. code-block:: python + + conn = __salt__['netmiko.get_connection'](host='router1.example.com', + username='example', + password='example') + show_if = conn.send_command('show interfaces') + conn.disconnect() + ''' + kwargs = clean_kwargs(**kwargs) + if 'netmiko.conn' in __proxy__: + return __proxy__['netmiko.conn']() + conn, kwargs = _prepare_connection(**kwargs) + return conn + + +def call(method, *args, **kwargs): + ''' + Invoke an arbitrary Netmiko method. + + method + The name of the Netmiko method to invoke. + + args + A list of arguments to send to the method invoked. + + kwargs + Key-value dictionary to send to the method invoked. + ''' + kwargs = clean_kwargs(**kwargs) + if 'netmiko.call' in __proxy__: + return __proxy__['netmiko.call'](method, *args, **kwargs) + conn, kwargs = _prepare_connection(**kwargs) + ret = getattr(conn, method)(*args, **kwargs) + conn.disconnect() + return ret + + +def multi_call(*methods, **kwargs): + ''' + Invoke multiple Netmiko methods at once, and return their output, as list. + + methods + A list of dictionaries with the following keys: + + - ``name``: the name of the Netmiko method to be executed. + - ``args``: list of arguments to be sent to the Netmiko method. + - ``kwargs``: dictionary of arguments to be sent to the Netmiko method. + + kwargs + Key-value dictionary with the connection details (when not running + under a Proxy Minion). + ''' + kwargs = clean_kwargs(**kwargs) + if 'netmiko.conn' in __proxy__: + conn = __proxy__['netmiko.conn']() + else: + conn, kwargs = _prepare_connection(**kwargs) + ret = [] + for method in methods: + # Explicit unpacking + method_name = method['name'] + method_args = method.get('args', []) + method_kwargs = method.get('kwargs', []) + ret.append(getattr(conn, method_name)(*method_args, **method_kwargs)) + if 'netmiko.conn' not in __proxy__: + conn.disconnect() + return ret + + +def send_command(command_string, **kwargs): + ''' + Execute command_string on the SSH channel using a pattern-based mechanism. + Generally used for show commands. By default this method will keep waiting + to receive data until the network device prompt is detected. The current + network device prompt will be determined automatically. + + command_string + The command to be executed on the remote device. + + expect_string + Regular expression pattern to use for determining end of output. + If left blank will default to being based on router prompt. + + delay_factor: ``1`` + Multiplying factor used to adjust delays (default: ``1``). + + max_loops: ``500`` + Controls wait time in conjunction with delay_factor. Will default to be + based upon self.timeout. + + auto_find_prompt: ``True`` + Whether it should try to auto-detect the prompt (default: ``True``). + + strip_prompt: ``True`` + Remove the trailing router prompt from the output (default: ``True``). + + strip_command: ``True`` + Remove the echo of the command from the output (default: ``True``). + + normalize: ``True`` + Ensure the proper enter is sent at end of command (default: ``True``). + + use_textfsm: ``False`` + Process command output through TextFSM template (default: ``False``). + + CLI Example: + + .. code-block:: bash + + salt '*' netmiko.send_command 'show version' + salt '*' netmiko.send_command 'show_version' host='router1.example.com' username='example' device_type='cisco_ios' + ''' + return call('send_command', command_string, **kwargs) + + +def send_command_timing(command_string, **kwargs): + ''' + Execute command_string on the SSH channel using a delay-based mechanism. + Generally used for show commands. + + command_string + The command to be executed on the remote device. + + delay_factor: ``1`` + Multiplying factor used to adjust delays (default: ``1``). + + max_loops: ``500`` + Controls wait time in conjunction with delay_factor. Will default to be + based upon self.timeout. + + strip_prompt: ``True`` + Remove the trailing router prompt from the output (default: ``True``). + + strip_command: ``True`` + Remove the echo of the command from the output (default: ``True``). + + normalize: ``True`` + Ensure the proper enter is sent at end of command (default: ``True``). + + use_textfsm: ``False`` + Process command output through TextFSM template (default: ``False``). + + CLI Example: + + .. code-block:: bash + + salt '*' netmiko.send_command_timing 'show version' + salt '*' netmiko.send_command_timing 'show version' host='router1.example.com' username='example' device_type='arista_eos' + ''' + return call('send_command_timing', command_string, **kwargs) + + +def enter_config_mode(**kwargs): + ''' + Enter into config mode. + + config_command + Configuration command to send to the device. + + pattern + Pattern to terminate reading of channel. + + CLI Example: + + .. code-block:: bash + + salt '*' netmiko.enter_config_mode + salt '*' netmiko.enter_config_mode device_type='juniper_junos' ip='192.168.0.1' username='example' + ''' + return call('config_mode', **kwargs) + + +def exit_config_mode(**kwargs): + ''' + Exit from configuration mode. + + exit_config + Command to exit configuration mode. + + pattern + Pattern to terminate reading of channel. + + CLI Example: + + .. code-block:: bash + + salt '*' netmiko.exit_config_mode + salt '*' netmiko.exit_config_mode device_type='juniper' ip='192.168.0.1' username='example' + ''' + return call('exit_config_mode', **kwargs) + + +def send_config(config_file=None, + config_commands=None, + template_engine='jinja', + source_hash=None, + source_hash_name=None, + user=None, + group=None, + mode=None, + attrs=None, + context=None, + defaults=None, + skip_verify=False, + saltenv='base', + **kwargs): + ''' + Send configuration commands down the SSH channel. + Return the configuration lines sent to the device. + + The function is flexible to send the configuration from a local or remote + file, or simply the commands as list. + + config_file + The source file with the configuration commands to be sent to the + device. + + The file can also be a template that can be rendered using the template + engine of choice. + + This can be specified using the absolute path to the file, or using one + of the following URL schemes: + + - ``salt://``, to fetch the file from the Salt fileserver. + - ``http://`` or ``https://`` + - ``ftp://`` + - ``s3://`` + - ``swift://`` + + config_commands + Multiple configuration commands to be sent to the device. + + .. note:: + + This argument is ignored when ``config_file`` is specified. + + template_engine: ``jinja`` + The template engine to use when rendering the source file. Default: + ``jinja``. To simply fetch the file without attempting to render, set + this argument to ``None``. + + source_hash + The hash of the ``config_file`` + + source_hash_name + When ``source_hash`` refers to a remote file, this specifies the + filename to look for in that file. + + user + Owner of the file. + + group + Group owner of the file. + + mode + Permissions of the file. + + attrs + Attributes of the file. + + context + Variables to add to the template context. + + defaults + Default values of the context_dict. + + skip_verify: ``False`` + If ``True``, hash verification of remote file sources (``http://``, + ``https://``, ``ftp://``, etc.) will be skipped, and the ``source_hash`` + argument will be ignored. + + exit_config_mode: ``True`` + Determines whether or not to exit config mode after complete. + + delay_factor: ``1`` + Factor to adjust delays. + + max_loops: ``150`` + Controls wait time in conjunction with delay_factor (default: ``150``). + + strip_prompt: ``False`` + Determines whether or not to strip the prompt (default: ``False``). + + strip_command: ``False`` + Determines whether or not to strip the command (default: ``False``). + + config_mode_command + The command to enter into config mode. + + CLI Example: + + .. code-block:: bash + + salt '*' netmiko.send_config config_file=salt://config.txt + salt '*' netmiko.send_config config_file=https://bit.ly/2sgljCB device_type='cisco_ios' ip='1.2.3.4' username='example' + ''' + if config_file: + if template_engine: + tmp_file = mkstemp() + file_mgd = __salt__['file.get_managed'](tmp_file, + template_engine, + config_file, + source_hash, + source_hash_name, + user, + group, + mode, + attrs, + saltenv, + context, + defaults, + skip_verify) + if not file_mgd[0]: + raise CommandExecutionError(file_mgd[2]) + file_str = __salt__['file.read'](file_mgd[0]) + __salt__['file.remove'](file_mgd[0]) + else: + # If no template engine wanted, simply fetch the source file + file_str = __salt__['cp.get_file_str'](config_file, + saltenv=saltenv) + if file_str is False: + raise CommandExecutionError('Source file {} not found'.format(config_file)) + config_commands = file_str.splitlines() + if isinstance(config_commands, (six.string_types, six.text_type)): + config_commands = [config_commands] + call('send_config_set', config_commands=config_commands, **kwargs) + return config_commands + + +def commit(**kwargs): + ''' + Commit the configuration changes. + + .. warning:: + + This function is supported only on the platforms that support the + ``commit`` operation. + + CLI Example: + + .. code-block:: bash + + salt '*' netmiko.commit + ''' + return call('commit', **kwargs) diff --git a/salt/proxy/netmiko_px.py b/salt/proxy/netmiko_px.py new file mode 100644 index 0000000000..3df7d704fe --- /dev/null +++ b/salt/proxy/netmiko_px.py @@ -0,0 +1,340 @@ +# -*- coding: utf-8 -*- +''' +Netmiko +======= + +.. versionadded:: Fluorine + +Proxy module for managing network devices via +`Netmiko `_. + +:codeauthor: Mircea Ulinic & Kirk Byers +:maturity: new +:depends: netmiko +:platform: unix + +Dependencies +------------ + +The ``netmiko`` proxy modules requires Netmiko to be installed: ``pip install netmiko``. + +Pillar +------ + +The ``netmiko`` proxy configuration requires the following parameters in order +to connect to the network device: + +device_type + Class selection based on device type. Supported options: + + - ``a10``: A10 Networks + - ``accedian``: Accedian Networks + - ``alcatel_aos``: Alcatel AOS + - ``alcatel_sros``: Alcatel SROS + - ``apresia_aeos``: Apresia AEOS + - ``arista_eos``: Arista EOS + - ``aruba_os``: Aruba + - ``avaya_ers``: Avaya ERS + - ``avaya_vsp``: Avaya VSP + - ``brocade_fastiron``: Brocade Fastiron + - ``brocade_netiron``: Brocade Netiron + - ``brocade_nos``: Brocade NOS + - ``brocade_vdx``: Brocade NOS + - ``brocade_vyos``: VyOS + - ``checkpoint_gaia``: Check Point GAiA + - ``calix_b6``: Calix B6 + - ``ciena_saos``: Ciena SAOS + - ``cisco_asa``: Cisco SA + - ``cisco_ios``: Cisco IOS + - ``cisco_nxos``: Cisco NX-oS + - ``cisco_s300``: Cisco S300 + - ``cisco_tp``: Cisco TpTcCe + - ``cisco_wlc``: Cisco WLC + - ``cisco_xe``: Cisco IOS + - ``cisco_xr``: Cisco XR + - ``coriant``: Coriant + - ``dell_force10``: Dell Force10 + - ``dell_os10``: Dell OS10 + - ``dell_powerconnect``: Dell PowerConnect + - ``eltex``: Eltex + - ``enterasys``: Enterasys + - ``extreme``: Extreme + - ``extreme_wing``: Extreme Wing + - ``f5_ltm``: F5 LTM + - ``fortinet``: Fortinet + - ``generic_termserver``: TerminalServer + - ``hp_comware``: HP Comware + - ``hp_procurve``: HP Procurve + - ``huawei``: Huawei + - ``huawei_vrpv8``: Huawei VRPV8 + - ``juniper``: Juniper Junos + - ``juniper_junos``: Juniper Junos + - ``linux``: Linux + - ``mellanox``: Mellanox + - ``mrv_optiswitch``: MrvOptiswitch + - ``netapp_cdot``: NetAppcDot + - ``netscaler``: Netscaler + - ``ovs_linux``: OvsLinux + - ``paloalto_panos``: PaloAlto Panos + - ``pluribus``: Pluribus + - ``quanta_mesh``: Quanta Mesh + - ``ruckus_fastiron``: Ruckus Fastiron + - ``ubiquiti_edge``: Ubiquiti Edge + - ``ubiquiti_edgeswitch``: Ubiquiti Edge + - ``vyatta_vyos``: VyOS + - ``vyos``: VyOS + - ``brocade_fastiron_telnet``: Brocade Fastiron over Telnet + - ``brocade_netiron_telnet``: Brocade Netiron over Telnet + - ``cisco_ios_telnet``: Cisco IOS over Telnet + - ``apresia_aeos_telnet``: Apresia AEOS over Telnet + - ``arista_eos_telnet``: Arista EOS over Telnet + - ``hp_procurve_telnet``: HP Procurve over Telnet + - ``hp_comware_telnet``: HP Comware over Telnet + - ``juniper_junos_telnet``: Juniper Junos over Telnet + - ``calix_b6_telnet``: Calix B6 over Telnet + - ``dell_powerconnect_telnet``: Dell PowerConnect over Telnet + - ``generic_termserver_telnet``: TerminalServer over Telnet + - ``extreme_telnet``: Extreme Networks over Telnet + - ``ruckus_fastiron_telnet``: Ruckus Fastiron over Telnet + - ``cisco_ios_serial``: Cisco IOS over serial port + +ip + IP address of target device. Not required if ``host`` is provided. + +host + Hostname of target device. Not required if ``ip`` is provided. + +username + Username to authenticate against target device if required. + +password + Password to authenticate against target device if required. + +secret + The enable password if target device requires one. + +port + The destination port used to connect to the target device. + +global_delay_factor: ``1`` + Multiplication factor affecting Netmiko delays (default: ``1``). + +use_keys: ``False`` + Connect to target device using SSH keys. + +key_file + Filename path of the SSH key file to use. + +allow_agent + Enable use of SSH key-agent. + +ssh_strict: ``False`` + Automatically reject unknown SSH host keys (default: ``False``, which means + unknown SSH host keys will be accepted). + +system_host_keys: ``False`` + Load host keys from the user's 'known_hosts' file. + +alt_host_keys: ``False`` + If ``True`` host keys will be loaded from the file specified in + ``alt_key_file``. + +alt_key_file + SSH host key file to use (if ``alt_host_keys=True``). + +ssh_config_file + File name of OpenSSH configuration file. + +timeout: ``90`` + Connection timeout (in seconds). + +session_timeout: ``60`` + Set a timeout for parallel requests (in seconds). + +keepalive: ``0`` + Send SSH keepalive packets at a specific interval, in seconds. Currently + defaults to ``0``, for backwards compatibility (it will not attempt to keep + the connection alive using the KEEPALIVE packets). + +default_enter: ``\n`` + Character(s) to send to correspond to enter key (default: ``\n``). + +response_return: ``\n`` + Character(s) to use in normalized return data to represent enter key + (default: ``\n``) + +always_alive: ``True`` + In certain less dynamic environments, maintaining the remote connection + permanently open with the network device is not always beneficial. In that + case, the user can select to initialize the connection only when needed, by + specifying this field to ``false``. + Default: ``true`` (maintains the connection with the remote network device). + +multiprocessing: ``False`` + Overrides the :conf_minion:`multiprocessing` option, per proxy minion, as + the Netmiko communication channel is mainly SSH. + +Proxy Pillar Example +-------------------- + +.. code-block:: yaml + + proxy: + proxytype: netmiko + device_type: juniper_junos + host: router1.example.com + username: example + password: example + +.. code-block:: yaml + + proxy: + proxytype: netmiko + device_type: cisco_ios + ip: 1.2.3.4 + username: test + use_keys: true + secret: w3@k +''' +from __future__ import absolute_import + +# Import python stdlib +import logging + +# Import third party libs +try: + from netmiko import ConnectHandler + from netmiko.ssh_exception import NetMikoTimeoutException + from netmiko.ssh_exception import NetMikoAuthenticationException + HAS_NETMIKO = True +except ImportError: + HAS_NETMIKO = False + +# Import salt modules +try: + from salt.utils.args import clean_kwargs +except ImportError: + from salt.utils import clean_kwargs + +# ----------------------------------------------------------------------------- +# proxy properties +# ----------------------------------------------------------------------------- + +__proxyenabled__ = ['netmiko'] +# proxy name + +# ----------------------------------------------------------------------------- +# globals +# ----------------------------------------------------------------------------- + +__virtualname__ = 'netmiko' +log = logging.getLogger(__name__) +netmiko_device = {} + +# ----------------------------------------------------------------------------- +# propery functions +# ----------------------------------------------------------------------------- + + +def __virtual__(): + ''' + Proxy module available only if Netmiko is installed. + ''' + if not HAS_NETMIKO: + return False, 'The netmiko proxy module requires netmiko library to be installed.' + return __virtualname__ + +# ----------------------------------------------------------------------------- +# proxy functions +# ----------------------------------------------------------------------------- + + +def init(opts): + ''' + Open the connection to the network device + managed through netmiko. + ''' + proxy_dict = opts.get('proxy', {}) + opts['multiprocessing'] = proxy_dict.get('multiprocessing', False) + netmiko_connection_args = proxy_dict.copy() + netmiko_connection_args.pop('proxytype', None) + netmiko_device['always_alive'] = netmiko_connection_args.pop('always_alive', + opts.get('proxy_always_alive', True)) + try: + connection = ConnectHandler(**netmiko_connection_args) + netmiko_device['connection'] = connection + netmiko_device['initialized'] = True + netmiko_device['args'] = netmiko_connection_args + netmiko_device['up'] = True + if not netmiko_device['always_alive']: + netmiko_device['connection'].disconnect() + except NetMikoTimeoutException as t_err: + log.error('Unable to setup the netmiko connection', exc_info=True) + except NetMikoAuthenticationException as au_err: + log.error('Unable to setup the netmiko connection', exc_info=True) + return True + + +def alive(opts): + ''' + Return the connection status with the network device. + ''' + log.debug('Checking if %s is still alive', opts.get('id', '')) + if not netmiko_device['always_alive']: + return True + if ping() and initialized(): + return netmiko_device['connection'].remote_conn.transport.is_alive() + return False + + +def ping(): + ''' + Connection open successfully? + ''' + return netmiko_device.get('up', False) + + +def initialized(): + ''' + Connection finished initializing? + ''' + return netmiko_device.get('initialized', False) + + +def shutdown(opts): + ''' + Closes connection with the device. + ''' + return call('disconnect') + + +# ----------------------------------------------------------------------------- +# callable functions +# ----------------------------------------------------------------------------- + + +def conn(): + ''' + Return the connection object. + ''' + return netmiko_device.get('connection') + + +def args(): + ''' + Return the Netmiko device args. + ''' + return netmiko_device['args'] + + +def call(method, *args, **kwargs): + ''' + Calls an arbitrary netmiko method. + ''' + kwargs = clean_kwargs(**kwargs) + if not netmiko_device['always_alive']: + connection = ConnectHandler(**netmiko_device['args']) + ret = getattr(connection, method)(*args, **kwargs) + connection.disconnect() + return ret + return getattr(netmiko_device['connection'], method)(*args, **kwargs)