1"""
2Netmiko Execution Module
3========================
4
5.. versionadded:: 2019.2.0
6
7Execution module to interface the connection with a remote network device. It is
8flexible enough to execute the commands both when running under a Netmiko Proxy
9Minion, as well as running under a Regular Minion by specifying the connection
10arguments, i.e., ``device_type``, ``ip``, ``username``, ``password`` etc.
11
12:codeauthor: Mircea Ulinic <ping@mirceaulinic.net> & Kirk Byers <ktbyers@twb-tech.com>
13:maturity:   new
14:depends:    netmiko
15:platform:   unix
16
17Dependencies
18------------
19
20The ``netmiko`` proxy modules requires Netmiko to be installed: ``pip install netmiko``.
21
22Usage
23-----
24
25This module can equally be used via the :mod:`netmiko <salt.proxy.netmiko_px>`
26Proxy module (check documentation), or directly from an arbitrary (Proxy) Minion
27that is running on a server (computer) having access to the network device, and
28has the ``netmiko`` library installed.
29
30When running outside of the :mod:`netmiko Proxy <salt.proxy.netmiko_px>` (i.e.,
31from another Proxy Minion type, or regular Minion), the netmiko connection
32arguments can be either specified from the CLI when executing the command, or
33in a configuration block under the ``netmiko`` key in the configuration opts
34(i.e., (Proxy) Minion configuration file), or Pillar. The module supports these
35simultaneously. These fields are the exact same supported by the ``netmiko``
36Proxy Module:
37
38- ``device_type`` - Class selection based on device type. Supported options:
39
40  - ``a10``: A10 Networks
41  - ``accedian``: Accedian Networks
42  - ``alcatel_aos``: Alcatel AOS
43  - ``alcatel_sros``: Alcatel SROS
44  - ``apresia_aeos``: Apresia AEOS
45  - ``arista_eos``: Arista EOS
46  - ``aruba_os``: Aruba
47  - ``avaya_ers``: Avaya ERS
48  - ``avaya_vsp``: Avaya VSP
49  - ``brocade_fastiron``: Brocade Fastiron
50  - ``brocade_netiron``: Brocade Netiron
51  - ``brocade_nos``: Brocade NOS
52  - ``brocade_vdx``: Brocade NOS
53  - ``brocade_vyos``: VyOS
54  - ``checkpoint_gaia``: Check Point GAiA
55  - ``calix_b6``: Calix B6
56  - ``ciena_saos``: Ciena SAOS
57  - ``cisco_asa``: Cisco SA
58  - ``cisco_ios``: Cisco IOS
59  - ``cisco_nxos``: Cisco NX-oS
60  - ``cisco_s300``: Cisco S300
61  - ``cisco_tp``: Cisco TpTcCe
62  - ``cisco_wlc``: Cisco WLC
63  - ``cisco_xe``: Cisco IOS
64  - ``cisco_xr``: Cisco XR
65  - ``coriant``: Coriant
66  - ``dell_force10``: Dell Force10
67  - ``dell_os10``: Dell OS10
68  - ``dell_powerconnect``: Dell PowerConnect
69  - ``eltex``: Eltex
70  - ``enterasys``: Enterasys
71  - ``extreme``: Extreme
72  - ``extreme_wing``: Extreme Wing
73  - ``f5_ltm``: F5 LTM
74  - ``fortinet``: Fortinet
75  - ``generic_termserver``: TerminalServer
76  - ``hp_comware``: HP Comware
77  - ``hp_procurve``: HP Procurve
78  - ``huawei``: Huawei
79  - ``huawei_vrpv8``: Huawei VRPV8
80  - ``juniper``: Juniper Junos
81  - ``juniper_junos``: Juniper Junos
82  - ``linux``: Linux
83  - ``mellanox``: Mellanox
84  - ``mrv_optiswitch``: MrvOptiswitch
85  - ``netapp_cdot``: NetAppcDot
86  - ``netscaler``: Netscaler
87  - ``ovs_linux``: OvsLinux
88  - ``paloalto_panos``: PaloAlto Panos
89  - ``pluribus``: Pluribus
90  - ``quanta_mesh``: Quanta Mesh
91  - ``ruckus_fastiron``: Ruckus Fastiron
92  - ``ubiquiti_edge``: Ubiquiti Edge
93  - ``ubiquiti_edgeswitch``: Ubiquiti Edge
94  - ``vyatta_vyos``: VyOS
95  - ``vyos``: VyOS
96  - ``brocade_fastiron_telnet``: Brocade Fastiron over Telnet
97  - ``brocade_netiron_telnet``: Brocade Netiron over Telnet
98  - ``cisco_ios_telnet``: Cisco IOS over Telnet
99  - ``apresia_aeos_telnet``: Apresia AEOS over Telnet
100  - ``arista_eos_telnet``: Arista EOS over Telnet
101  - ``hp_procurve_telnet``: HP Procurve over Telnet
102  - ``hp_comware_telnet``: HP Comware over Telnet
103  - ``juniper_junos_telnet``: Juniper Junos over Telnet
104  - ``calix_b6_telnet``: Calix B6 over Telnet
105  - ``dell_powerconnect_telnet``: Dell PowerConnect over Telnet
106  - ``generic_termserver_telnet``: TerminalServer over Telnet
107  - ``extreme_telnet``: Extreme Networks over Telnet
108  - ``ruckus_fastiron_telnet``: Ruckus Fastiron over Telnet
109  - ``cisco_ios_serial``: Cisco IOS over serial port
110
111- ``ip`` - IP address of target device (not required if ``host`` is provided)
112
113- ``host`` - Hostname of target device (not required if ``ip`` is provided)
114
115- ``username`` - Username to authenticate against target device, if required
116
117- ``password`` - Password to authenticate against target device, if required
118
119- ``secret`` - The enable password if target device requires one
120
121- ``port`` - The destination port used to connect to the target device
122
123- ``global_delay_factor`` - Multiplication factor affecting Netmiko delays
124  (default: ``1``)
125
126- ``use_keys`` - Connect to target device using SSH keys (default: ``False``)
127
128- ``key_file`` - Filename path of the SSH key file to use
129
130- ``allow_agent`` - Enable use of SSH key-agent
131
132- ``ssh_strict`` - Automatically reject unknown SSH host keys (default:
133  ``False``, which means unknown SSH host keys will be accepted)
134
135- ``system_host_keys`` - Load host keys from the user's "known_hosts" file
136  (default: ``False``)
137
138- ``alt_host_keys`` - If ``True``,  host keys will be loaded from the file
139  specified in ``alt_key_file`` (default: ``False``)
140
141- ``alt_key_file`` - SSH host key file to use (if ``alt_host_keys=True``)
142
143- ``ssh_config_file`` - File name of OpenSSH configuration file
144
145- ``timeout`` - Connection timeout, in seconds (default: ``90``)
146
147- ``session_timeout`` - Set a timeout for parallel requests, in seconds
148  (default: ``60``)
149
150- ``keepalive`` - Send SSH keepalive packets at a specific interval, in
151  seconds. Currently defaults to ``0``, for backwards compatibility (it will
152  not attempt to keep the connection alive using the KEEPALIVE packets).
153
154- ``default_enter`` - Character(s) to send to correspond to enter key (default:
155  ``\\n``)
156
157- ``response_return`` - Character(s) to use in normalized return data to
158  represent enter key (default: ``\\n``)
159
160Example (when not running in a ``netmiko`` Proxy Minion):
161
162.. code-block:: yaml
163
164  netmiko:
165    username: test
166    password: test
167
168In case the ``username`` and ``password`` are the same on any device you are
169targeting, the block above (besides other parameters specific to your
170environment you might need) should suffice to be able to execute commands from
171outside a ``netmiko`` Proxy, e.g.:
172
173.. code-block:: bash
174
175    salt '*' netmiko.send_command 'show version' host=router1.example.com device_type=juniper
176    salt '*' netmiko.send_config https://bit.ly/2sgljCB host=sw2.example.com device_type=cisco_ios
177
178.. note::
179
180    Remember that the above applies only when not running in a ``netmiko`` Proxy
181    Minion. If you want to use the :mod:`<salt.proxy.netmiko_px>`, please follow
182    the documentation notes for a proper setup.
183"""
184
185import logging
186
187import salt.utils.platform
188from salt.exceptions import CommandExecutionError
189from salt.utils.args import clean_kwargs
190
191try:
192    from netmiko import ConnectHandler
193    from netmiko import BaseConnection
194
195    HAS_NETMIKO = True
196except ImportError:
197    HAS_NETMIKO = False
198
199# -----------------------------------------------------------------------------
200# execution module properties
201# -----------------------------------------------------------------------------
202
203__proxyenabled__ = ["*"]
204# Any Proxy Minion should be able to execute these (not only netmiko)
205
206__virtualname__ = "netmiko"
207# The Execution Module will be identified as ``netmiko``
208
209# -----------------------------------------------------------------------------
210# globals
211# -----------------------------------------------------------------------------
212
213log = logging.getLogger(__name__)
214
215# -----------------------------------------------------------------------------
216# propery functions
217# -----------------------------------------------------------------------------
218
219
220def __virtual__():
221    """
222    Execution module available only if Netmiko is installed.
223    """
224    if not HAS_NETMIKO:
225        return (
226            False,
227            "The netmiko execution module requires netmiko library to be installed.",
228        )
229    if (
230        salt.utils.platform.is_proxy()
231        and __opts__["proxy"]["proxytype"] == "deltaproxy"
232    ):
233        return (
234            False,
235            "Unsupported proxy minion type.",
236        )
237
238    return __virtualname__
239
240
241# -----------------------------------------------------------------------------
242# helper functions
243# -----------------------------------------------------------------------------
244
245
246def _prepare_connection(**kwargs):
247    """
248    Prepare the connection with the remote network device, and clean up the key
249    value pairs, removing the args used for the connection init.
250    """
251    init_args = {}
252    fun_kwargs = {}
253    netmiko_kwargs = __salt__["config.get"]("netmiko", {})
254    netmiko_kwargs.update(kwargs)  # merge the CLI args with the opts/pillar
255    netmiko_init_args, _, _, netmiko_defaults = __utils__["args.get_function_argspec"](
256        BaseConnection.__init__
257    )
258    check_self = netmiko_init_args.pop(0)
259    for karg, warg in netmiko_kwargs.items():
260        if karg not in netmiko_init_args:
261            if warg is not None:
262                fun_kwargs[karg] = warg
263            continue
264        if warg is not None:
265            init_args[karg] = warg
266    conn = ConnectHandler(**init_args)
267    return conn, fun_kwargs
268
269
270# -----------------------------------------------------------------------------
271# callable functions
272# -----------------------------------------------------------------------------
273
274
275def get_connection(**kwargs):
276    """
277    Return the Netmiko connection object.
278
279    .. warning::
280
281        This function returns an unserializable object, hence it is not meant
282        to be used on the CLI. This should mainly be used when invoked from
283        other modules for the low level connection with the network device.
284
285    kwargs
286        Key-value dictionary with the authentication details.
287
288    USAGE Example:
289
290    .. code-block:: python
291
292        conn = __salt__['netmiko.get_connection'](host='router1.example.com',
293                                                  username='example',
294                                                  password='example')
295        show_if = conn.send_command('show interfaces')
296        conn.disconnect()
297    """
298    kwargs = clean_kwargs(**kwargs)
299    if "netmiko.conn" in __proxy__:
300        return __proxy__["netmiko.conn"]()
301    conn, kwargs = _prepare_connection(**kwargs)
302    return conn
303
304
305def call(method, *args, **kwargs):
306    """
307    Invoke an arbitrary Netmiko method.
308
309    method
310        The name of the Netmiko method to invoke.
311
312    args
313        A list of arguments to send to the method invoked.
314
315    kwargs
316        Key-value dictionary to send to the method invoked.
317    """
318    kwargs = clean_kwargs(**kwargs)
319    if "netmiko.call" in __proxy__:
320        return __proxy__["netmiko.call"](method, *args, **kwargs)
321    conn, kwargs = _prepare_connection(**kwargs)
322    ret = getattr(conn, method)(*args, **kwargs)
323    conn.disconnect()
324    return ret
325
326
327def multi_call(*methods, **kwargs):
328    """
329    Invoke multiple Netmiko methods at once, and return their output, as list.
330
331    methods
332        A list of dictionaries with the following keys:
333
334        - ``name``: the name of the Netmiko method to be executed.
335        - ``args``: list of arguments to be sent to the Netmiko method.
336        - ``kwargs``: dictionary of arguments to be sent to the Netmiko method.
337
338    kwargs
339        Key-value dictionary with the connection details (when not running
340        under a Proxy Minion).
341    """
342    kwargs = clean_kwargs(**kwargs)
343    if "netmiko.conn" in __proxy__:
344        conn = __proxy__["netmiko.conn"]()
345    else:
346        conn, kwargs = _prepare_connection(**kwargs)
347    ret = []
348    for method in methods:
349        # Explicit unpacking
350        method_name = method["name"]
351        method_args = method.get("args", [])
352        method_kwargs = method.get("kwargs", [])
353        ret.append(getattr(conn, method_name)(*method_args, **method_kwargs))
354    if "netmiko.conn" not in __proxy__:
355        conn.disconnect()
356    return ret
357
358
359def send_command(command_string, **kwargs):
360    """
361    Execute command_string on the SSH channel using a pattern-based mechanism.
362    Generally used for show commands. By default this method will keep waiting
363    to receive data until the network device prompt is detected. The current
364    network device prompt will be determined automatically.
365
366    command_string
367        The command to be executed on the remote device.
368
369    expect_string
370        Regular expression pattern to use for determining end of output.
371        If left blank will default to being based on router prompt.
372
373    delay_factor: ``1``
374        Multiplying factor used to adjust delays (default: ``1``).
375
376    max_loops: ``500``
377        Controls wait time in conjunction with delay_factor. Will default to be
378        based upon self.timeout.
379
380    auto_find_prompt: ``True``
381        Whether it should try to auto-detect the prompt (default: ``True``).
382
383    strip_prompt: ``True``
384        Remove the trailing router prompt from the output (default: ``True``).
385
386    strip_command: ``True``
387        Remove the echo of the command from the output (default: ``True``).
388
389    normalize: ``True``
390        Ensure the proper enter is sent at end of command (default: ``True``).
391
392    use_textfsm: ``False``
393        Process command output through TextFSM template (default: ``False``).
394
395    CLI Example:
396
397    .. code-block:: bash
398
399        salt '*' netmiko.send_command 'show version'
400        salt '*' netmiko.send_command 'show_version' host='router1.example.com' username='example' device_type='cisco_ios'
401    """
402    return call("send_command", command_string, **kwargs)
403
404
405def send_command_timing(command_string, **kwargs):
406    """
407    Execute command_string on the SSH channel using a delay-based mechanism.
408    Generally used for show commands.
409
410    command_string
411        The command to be executed on the remote device.
412
413    delay_factor: ``1``
414        Multiplying factor used to adjust delays (default: ``1``).
415
416    max_loops: ``500``
417        Controls wait time in conjunction with delay_factor. Will default to be
418        based upon self.timeout.
419
420    strip_prompt: ``True``
421        Remove the trailing router prompt from the output (default: ``True``).
422
423    strip_command: ``True``
424        Remove the echo of the command from the output (default: ``True``).
425
426    normalize: ``True``
427        Ensure the proper enter is sent at end of command (default: ``True``).
428
429    use_textfsm: ``False``
430        Process command output through TextFSM template (default: ``False``).
431
432    CLI Example:
433
434    .. code-block:: bash
435
436        salt '*' netmiko.send_command_timing 'show version'
437        salt '*' netmiko.send_command_timing 'show version' host='router1.example.com' username='example' device_type='arista_eos'
438    """
439    return call("send_command_timing", command_string, **kwargs)
440
441
442def enter_config_mode(**kwargs):
443    """
444    Enter into config mode.
445
446    config_command
447        Configuration command to send to the device.
448
449    pattern
450        Pattern to terminate reading of channel.
451
452    CLI Example:
453
454    .. code-block:: bash
455
456        salt '*' netmiko.enter_config_mode
457        salt '*' netmiko.enter_config_mode device_type='juniper_junos' ip='192.168.0.1' username='example'
458    """
459    return call("config_mode", **kwargs)
460
461
462def exit_config_mode(**kwargs):
463    """
464    Exit from configuration mode.
465
466    exit_config
467        Command to exit configuration mode.
468
469    pattern
470        Pattern to terminate reading of channel.
471
472    CLI Example:
473
474    .. code-block:: bash
475
476        salt '*' netmiko.exit_config_mode
477        salt '*' netmiko.exit_config_mode device_type='juniper' ip='192.168.0.1' username='example'
478    """
479    return call("exit_config_mode", **kwargs)
480
481
482def send_config(
483    config_file=None,
484    config_commands=None,
485    template_engine="jinja",
486    commit=False,
487    context=None,
488    defaults=None,
489    saltenv="base",
490    **kwargs
491):
492    """
493    Send configuration commands down the SSH channel.
494    Return the configuration lines sent to the device.
495
496    The function is flexible to send the configuration from a local or remote
497    file, or simply the commands as list.
498
499    config_file
500        The source file with the configuration commands to be sent to the
501        device.
502
503        The file can also be a template that can be rendered using the template
504        engine of choice.
505
506        This can be specified using the absolute path to the file, or using one
507        of the following URL schemes:
508
509        - ``salt://``, to fetch the file from the Salt fileserver.
510        - ``http://`` or ``https://``
511        - ``ftp://``
512        - ``s3://``
513        - ``swift://``
514
515    config_commands
516        Multiple configuration commands to be sent to the device.
517
518        .. note::
519
520            This argument is ignored when ``config_file`` is specified.
521
522    template_engine: ``jinja``
523        The template engine to use when rendering the source file. Default:
524        ``jinja``. To simply fetch the file without attempting to render, set
525        this argument to ``None``.
526
527    commit: ``False``
528        Commit the configuration changes before exiting the config mode. This
529        option is by default disabled, as many platforms don't have this
530        capability natively.
531
532    context
533        Variables to add to the template context.
534
535    defaults
536        Default values of the context_dict.
537
538    exit_config_mode: ``True``
539        Determines whether or not to exit config mode after complete.
540
541    delay_factor: ``1``
542        Factor to adjust delays.
543
544    max_loops: ``150``
545        Controls wait time in conjunction with delay_factor (default: ``150``).
546
547    strip_prompt: ``False``
548        Determines whether or not to strip the prompt (default: ``False``).
549
550    strip_command: ``False``
551        Determines whether or not to strip the command (default: ``False``).
552
553    config_mode_command
554        The command to enter into config mode.
555
556    CLI Example:
557
558    .. code-block:: bash
559
560        salt '*' netmiko.send_config config_commands="['interface GigabitEthernet3', 'no ip address']"
561        salt '*' netmiko.send_config config_commands="['snmp-server location {{ grains.location }}']"
562        salt '*' netmiko.send_config config_file=salt://config.txt
563        salt '*' netmiko.send_config config_file=https://bit.ly/2sgljCB device_type='cisco_ios' ip='1.2.3.4' username='example'
564    """
565    if config_file:
566        file_str = __salt__["cp.get_file_str"](config_file, saltenv=saltenv)
567        if file_str is False:
568            raise CommandExecutionError("Source file {} not found".format(config_file))
569    elif config_commands:
570        if isinstance(config_commands, ((str,), str)):
571            config_commands = [config_commands]
572        file_str = "\n".join(config_commands)
573        # unify all the commands in a single file, to render them in a go
574    if template_engine:
575        file_str = __salt__["file.apply_template_on_contents"](
576            file_str, template_engine, context, defaults, saltenv
577        )
578    # whatever the source of the commands would be, split them line by line
579    config_commands = [line for line in file_str.splitlines() if line.strip()]
580    kwargs = clean_kwargs(**kwargs)
581    if "netmiko.conn" in __proxy__:
582        conn = __proxy__["netmiko.conn"]()
583        if not conn or not conn.is_alive():
584            conn, _ = _prepare_connection(**__proxy__["netmiko.args"]())
585    else:
586        conn, kwargs = _prepare_connection(**kwargs)
587    if commit:
588        kwargs["exit_config_mode"] = False  # don't exit config mode after
589        # loading the commands, wait for explicit commit
590    ret = conn.send_config_set(config_commands=config_commands, **kwargs)
591    if commit:
592        ret += conn.commit()
593    return ret
594
595
596def commit(**kwargs):
597    """
598    Commit the configuration changes.
599
600    .. warning::
601
602        This function is supported only on the platforms that support the
603        ``commit`` operation.
604
605    CLI Example:
606
607    .. code-block:: bash
608
609        salt '*' netmiko.commit
610    """
611    return call("commit", **kwargs)
612