saltstack/salt

View on GitHub
salt/runners/vistara.py

Summary

Maintainability
A
2 hrs
Test Coverage
# -*- coding: utf-8 -*-
'''
Vistara Runner

Runner to interact with the Vistara (http://www.vistarait.com/) REST API

:codeauthor: Brad Thurber <brad.thurber@gmail.com>

To use this runner, the Vistara client_id and Vistara oauth2 client_key
and client_secret must be set in the master config.

For example ``/etc/salt/master.d/_vistara.conf``:

.. code-block:: yaml

    vistara:
      client_id: client_012345
      client_key: N0tReallyaR3alKeyButShouldB12345
      client_secret: ThisI5AreallyLongsecretKeyIwonderwhyTheyMakethemSoBigTheseDays00


'''
from __future__ import absolute_import, print_function, unicode_literals

# Import Python libs
import logging

# Import Salt libs
import salt.output
# See https://docs.saltstack.com/en/latest/topics/tutorials/http.html
import salt.utils.http

log = logging.getLogger(__name__)


def __virtual__():
    '''
    Check to see if master config has the necessary config
    '''
    vistara_config = __opts__['vistara'] if 'vistara' in __opts__ else None

    if vistara_config:
        client_id = vistara_config.get('client_id', None)
        client_key = vistara_config.get('client_key', None)
        client_secret = vistara_config.get('client_secret', None)

        if not client_id or not client_key or not client_secret:
            return False, ("vistara client_id or client_key or client_secret "
                           "has not been specified in the Salt master config.")
        return True

    return False, ("vistara config has not been specificed in the Salt master "
                   "config. See documentation for this runner.")


def _get_vistara_configuration():
    '''
    Return the Vistara configuration read from the master config
    '''
    return {
        'client_id': __opts__['vistara']['client_id'],
        'client_key': __opts__['vistara']['client_key'],
        'client_secret': __opts__['vistara']['client_secret']
    }


def delete_device(name, safety_on=True):
    '''
    Deletes a device from Vistara based on DNS name or partial name. By default,
    delete_device will only perform the delete if a single host is returned. Set
    safety_on=False to delete all matches (up to default API search page size)

    CLI Example:

    .. code-block:: bash

        salt-run vistara.delete_device 'hostname-101.mycompany.com'
        salt-run vistara.delete_device 'hostname-101'
        salt-run vistara.delete_device 'hostname-1' safety_on=False

    '''

    config = _get_vistara_configuration()
    if not config:
        return False

    access_token = _get_oath2_access_token(config['client_key'], config['client_secret'])

    if not access_token:
        return 'Vistara access token not available'

    query_string = 'dnsName:{0}'.format(name)

    devices = _search_devices(query_string, config['client_id'], access_token)

    if not devices:
        return "No devices found"

    device_count = len(devices)

    if safety_on and device_count != 1:
        return "Expected to delete 1 device and found {0}. "\
            "Set safety_on=False to override.".format(device_count)

    delete_responses = []
    for device in devices:
        device_id = device['id']
        log.debug(device_id)
        delete_response = _delete_resource(device_id, config['client_id'], access_token)
        if not delete_response:
            return False
        delete_responses.append(delete_response)

    return delete_responses


def _search_devices(query_string, client_id, access_token):

    authstring = 'Bearer {0}'.format(access_token)

    headers = {
        'Authorization': authstring,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }

    params = {
        'queryString': query_string
    }

    method = 'GET'
    url = 'https://api.vistara.io/api/v2/tenants/{0}/devices/search'.format(client_id)

    resp = salt.utils.http.query(
        url=url,
        method=method,
        header_dict=headers,
        params=params,
        opts=__opts__
    )

    respbody = resp.get('body', None)
    if not respbody:
        return False

    respbodydict = salt.utils.json.loads(resp['body'])
    deviceresults = respbodydict['results']

    return deviceresults


def _delete_resource(device_id, client_id, access_token):

    authstring = 'Bearer {0}'.format(access_token)

    headers = {
        'Authorization': authstring,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }

    method = 'DELETE'
    url = 'https://api.vistara.io/api/v2/tenants/{0}/rtype/DEVICE/resource/{1}'.format(client_id, device_id)

    resp = salt.utils.http.query(
        url=url,
        method=method,
        header_dict=headers,
        opts=__opts__
    )

    respbody = resp.get('body', None)
    if not respbody:
        return False

    respbodydict = salt.utils.json.loads(resp['body'])

    return respbodydict


def _get_oath2_access_token(client_key, client_secret):
    '''
    Query the vistara API and get an access_token

    '''
    if not client_key and not client_secret:
        log.error(
            "client_key and client_secret have not been specified "
            "and are required parameters."
        )
        return False

    method = 'POST'
    url = 'https://api.vistara.io/auth/oauth/token'
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Accept': 'application/json'
    }

    params = {
        'grant_type': 'client_credentials',
        'client_id': client_key,
        'client_secret': client_secret
    }

    resp = salt.utils.http.query(
        url=url,
        method=method,
        header_dict=headers,
        params=params,
        opts=__opts__
    )

    respbody = resp.get('body', None)

    if not respbody:
        return False

    access_token = salt.utils.json.loads(respbody)['access_token']
    return access_token