saltstack/salt

View on GitHub
salt/proxy/onyx.py

Summary

Maintainability
F
4 days
Test Coverage
# -*- coding: utf-8 -*-
'''
Proxy Minion for Onyx OS Switches

.. versionadded: Neon

The Onyx OS Proxy Minion uses the built in SSHConnection module
in :mod:`salt.utils.vt_helper <salt.utils.vt_helper>`

To configure the proxy minion:

.. code-block:: yaml

    proxy:
      proxytype: onyx
      host: 192.168.187.100
      username: admin
      password: admin
      prompt_name: switch
      ssh_args: '-o PubkeyAuthentication=no'
      key_accept: True

proxytype
    (REQUIRED) Use this proxy minion `onyx`

host
    (REQUIRED) ip address or hostname to connect to

username
    (REQUIRED) username to login with

password
    (REQUIRED) password to use to login with

prompt_name
    (REQUIRED) The name in the prompt on the switch.  By default, use your
    devices hostname.

ssh_args
    Any extra args to use to connect to the switch.

key_accept
    Whether or not to accept a the host key of the switch on initial login.
    Defaults to False.


The functions from the proxy minion can be run from the salt commandline using
the :mod:`salt.modules.onyx<salt.modules.onyx>` execution module.

.. note:
    If `multiprocessing: True` is set for the proxy minion config, each forked
    worker will open up a new connection to the Cisco NX OS Switch.  If you
    only want one consistent connection used for everything, use
    `multiprocessing: False`

'''

# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals
import logging
import multiprocessing
import re

# Import Salt libs
from salt.utils.vt_helper import SSHConnection
from salt.utils.vt import TerminalException
log = logging.getLogger(__file__)

__proxyenabled__ = ['onyx']
__virtualname__ = 'onyx'
DETAILS = {'grains_cache': {}}


def __virtual__():
    '''
    Only return if all the modules are available
    '''
    log.info('onyx proxy __virtual__() called...')

    return __virtualname__


def _worker_name():
    return multiprocessing.current_process().name


def init(opts=None):
    '''
    Required.
    Can be used to initialize the server connection.
    '''
    if opts is None:
        opts = __opts__
    try:
        DETAILS[_worker_name()] = SSHConnection(
            host=opts['proxy']['host'],
            username=opts['proxy']['username'],
            password=opts['proxy']['password'],
            key_accept=opts['proxy'].get('key_accept', False),
            ssh_args=opts['proxy'].get('ssh_args', ''),
            prompt='{0}.*(#|>) '.format(opts['proxy']['prompt_name']))
        DETAILS[_worker_name()].sendline('no cli session paging enable')

    except TerminalException as e:
        log.error(e)
        return False
    DETAILS['initialized'] = True


def initialized():
    '''
        module initialization
    '''
    return DETAILS.get('initialized', False)


def ping():
    '''
    Ping the device on the other end of the connection

    .. code-block: bash

        salt '*' onyx.cmd ping
    '''
    if _worker_name() not in DETAILS:
        init()
    try:
        return DETAILS[_worker_name()].conn.isalive()
    except TerminalException as e:
        log.error(e)
        return False


def shutdown():
    '''
    Disconnect
    '''
    DETAILS[_worker_name()].close_connection()


def sendline(command):
    '''
    Run command through switch's cli

    .. code-block: bash

        salt '*' onyx.cmd sendline 'show run | include
        "username admin password 7"'
    '''
    if ping() is False:
        init()
    out, _ = DETAILS[_worker_name()].sendline(command)
    return out


def enable():
    '''
    Shortcut to run `enable` on switch

    .. code-block:: bash

        salt '*' onyx.cmd enable
    '''
    try:
        ret = sendline('enable')
    except TerminalException as e:
        log.error(e)
        return 'Failed to enable switch'
    return ret


def configure_terminal():
    '''
    Shortcut to run `configure terminal` on switch

    .. code-block:: bash

        salt '*' onyx.cmd configure_terminal
    '''
    try:
        ret = sendline('configure terminal ')
    except TerminalException as e:
        log.error(e)
        return 'Failed to enable ' \
               'configuration mode on switch'
    return ret


def configure_terminal_exit():
    '''
        Shortcut to run `exit` from configuration mode on switch

        .. code-block:: bash

            salt '*' onyx.cmd configure_terminal_exit
        '''
    try:
        ret = sendline('exit')
    except TerminalException as e:
        log.error(e)
        return 'Failed to exit form configuration mode on switch'
    return ret


def disable():
    '''
        Shortcut to run `disable` on switch

        .. code-block:: bash

            salt '*' onyx.cmd disable
        '''
    try:
        ret = sendline('disable')
    except TerminalException as e:
        log.error(e)
        return 'Failed to "configure terminal"'
    return ret


def grains():
    '''
    Get grains for proxy minion

    .. code-block: bash

        salt '*' onyx.cmd grains
    '''
    if not DETAILS['grains_cache']:
        ret = system_info()
        log.debug(ret)
        DETAILS['grains_cache'].update(ret)
    return {'onyx': DETAILS['grains_cache']}


def grains_refresh():
    '''
    Refresh the grains from the proxy device.

    .. code-block: bash

        salt '*' onyx.cmd grains_refresh
    '''
    DETAILS['grains_cache'] = {}
    return grains()


def get_user(username):
    '''
    Get username line from switch

    .. code-block: bash

        salt '*' onyx.cmd get_user username=admin
    '''
    try:
        enable()
        configure_terminal()
        cmd_out = sendline('show running-config | include "username {0} password 7"'.format(username))
        cmd_out.split('\n')
        user = cmd_out[1:-1]
        configure_terminal_exit()
        disable()
        return user
    except TerminalException as e:
        log.error(e)
        return 'Failed to get user'


def get_roles(username):
    '''
    Get roles that the username is assigned from switch

    .. code-block: bash

        salt '*' onyx.cmd get_roles username=admin
    '''
    info = sendline('show user-account {0}'.format(username))
    roles = re.search(r'^\s*roles:(.*)$', info, re.MULTILINE)
    if roles:
        roles = roles.group(1).strip().split(' ')
    else:
        roles = []
    return roles


def check_role(username, role):
    '''
    Check if user is assigned a specific role on switch

    .. code-block:: bash

        salt '*' onyx.cmd check_role username=admin role=network-admin
    '''
    return role in get_roles(username)


def remove_user(username):
    '''
    Remove user from switch

    .. code-block:: bash

        salt '*' onyx.cmd remove_user username=daniel
    '''
    try:
        sendline('config terminal')
        user_line = 'no username {0}'.format(username)
        ret = sendline(user_line)
        sendline('end')
        sendline('copy running-config startup-config')
        return '\n'.join([user_line, ret])
    except TerminalException as e:
        log.error(e)
        return 'Failed to set password'


def set_role(username, role):
    '''
    Assign role to username

    .. code-block:: bash

        salt '*' onyx.cmd set_role username=daniel role=vdc-admin
    '''
    try:
        sendline('config terminal')
        role_line = 'username {0} role {1}'.format(username, role)
        ret = sendline(role_line)
        sendline('end')
        sendline('copy running-config startup-config')
        return '\n'.join([role_line, ret])
    except TerminalException as e:
        log.error(e)
        return 'Failed to set password'


def unset_role(username, role):
    '''
    Remove role from username

    .. code-block:: bash

        salt '*' onyx.cmd unset_role username=daniel role=vdc-admin
    '''
    try:
        sendline('config terminal')
        role_line = 'no username {0} role {1}'.format(username, role)
        ret = sendline(role_line)
        sendline('end')
        sendline('copy running-config startup-config')
        return '\n'.join([role_line, ret])
    except TerminalException as e:
        log.error(e)
        return 'Failed to set password'


def show_run():
    '''
    Shortcut to run `show run` on switch

    .. code-block:: bash

        salt '*' onyx.cmd show_run
    '''
    try:
        enable()
        configure_terminal()
        ret = sendline('show running-config')
        configure_terminal_exit()
        disable()
    except TerminalException as e:
        log.error(e)
        return 'Failed to show running-config on switch'
    return ret


def show_ver():
    '''
    Shortcut to run `show ver` on switch

    .. code-block:: bash

        salt '*' onyx.cmd show_ver
    '''
    try:
        ret = sendline('show ver')
    except TerminalException as e:
        log.error(e)
        return 'Failed to "show ver"'
    return ret


def add_config(lines):
    '''
    Add one or more config lines to the switch running config

    .. code-block:: bash

        salt '*' onyx.cmd add_config 'snmp-server community TESTSTRINGHERE rw'

    .. note::
        For more than one config added per command, lines should be a list.
    '''
    if not isinstance(lines, list):
        lines = [lines]
    try:
        enable()
        configure_terminal()
        for line in lines:
            sendline(line)

        configure_terminal_exit()
        disable()
    except TerminalException as e:
        log.error(e)
        return False
    return True


def delete_config(lines):
    '''
    Delete one or more config lines to the switch running config

    .. code-block:: bash

        salt '*' onyx.cmd delete_config 'snmp-server community TESTSTRINGHERE group network-operator'

    .. note::
        For more than one config deleted per command, lines should be a list.
    '''
    if not isinstance(lines, list):
        lines = [lines]
    try:
        sendline('config terminal')
        for line in lines:
            sendline(' '.join(['no', line]))

        sendline('end')
        sendline('copy running-config startup-config')
    except TerminalException as e:
        log.error(e)
        return False
    return True


def find(pattern):
    '''
    Find all instances where the pattern is in the running command

    .. code-block:: bash

        salt '*' onyx.cmd find '^snmp-server.*$'

    .. note::
        This uses the `re.MULTILINE` regex format for python, and runs the
        regex against the whole show_run output.
    '''
    matcher = re.compile(pattern, re.MULTILINE)
    return matcher.findall(show_run())


def replace(old_value, new_value, full_match=False):
    '''
    Replace string or full line matches in switch's running config

    If full_match is set to True, then the whole line will need to be matched
    as part of the old value.

    .. code-block:: bash

        salt '*' onyx.cmd replace 'TESTSTRINGHERE' 'NEWTESTSTRINGHERE'
    '''
    if full_match is False:
        matcher = re.compile('^.*{0}.*$'.format(re.escape(old_value)), re.MULTILINE)
        repl = re.compile(re.escape(old_value))
    else:
        matcher = re.compile(old_value, re.MULTILINE)
        repl = re.compile(old_value)

    lines = {'old': [], 'new': []}
    for line in matcher.finditer(show_run()):
        lines['old'].append(line.group(0))
        lines['new'].append(repl.sub(new_value, line.group(0)))

    delete_config(lines['old'])
    add_config(lines['new'])

    return lines


def _parser(block):
    return re.compile('^{block}\n(?:^[ \n].*$\n?)+'.format(block=block), re.MULTILINE)


def _parse_software(data):
    ret = {'software': {}}
    software = _parser('Software').search(data).group(0)
    matcher = re.compile('^  ([^:]+): *([^\n]+)', re.MULTILINE)
    for line in matcher.finditer(software):
        key, val = line.groups()
        ret['software'][key] = val
    return ret['software']


def _parse_hardware(data):
    ret = {'hardware': {}}
    hardware = _parser('Hardware').search(data).group(0)
    matcher = re.compile('^  ([^:\n]+): *([^\n]+)', re.MULTILINE)
    for line in matcher.finditer(hardware):
        key, val = line.groups()
        ret['hardware'][key] = val
    return ret['hardware']


def _parse_plugins(data):
    ret = {'plugins': []}
    plugins = _parser('plugin').search(data).group(0)
    matcher = re.compile('^  (?:([^,]+), )+([^\n]+)', re.MULTILINE)
    for line in matcher.finditer(plugins):
        ret['plugins'].extend(line.groups())
    return ret['plugins']


def system_info():
    '''
    Return system information for grains of the NX OS proxy minion

    .. code-block:: bash

        salt '*' onyx.system_info
    '''
    # data = show_ver()
    info = {
        'software': 'Test',
        'hardware': '',
        'plugins': ''
    }
    return info