def run_module():
    # define available arguments/parameters a user can pass to the module
    argument_spec = vmanage_argument_spec()
    argument_spec.update(state=dict(type='str',
                                    choices=['absent', 'present'],
                                    default='present'),
                         name=dict(type='str', alias='templateName'),
                         description=dict(type='str',
                                          alias='templateDescription'),
                         definition=dict(type='dict',
                                         alias='templateDefinition'),
                         template_type=dict(type='str', alias='templateType'),
                         device_type=dict(type='list', alias='deviceType'),
                         template_min_version=dict(type='str',
                                                   alias='templateMinVersion'),
                         factory_default=dict(type='bool',
                                              alias='factoryDefault'),
                         url=dict(type='bool', alias='templateUrl'),
                         update=dict(type='bool', default=True),
                         aggregate=dict(type='list'),
                         push=dict(type='bool', default=False))

    # seed the result dict in the object
    # we primarily care about changed and state
    # change is if this module effectively modified the target
    # state will include any data that you want your module to pass back
    # for consumption, for example, in a subsequent task
    result = dict(changed=False, )

    # the AnsibleModule object will be our abstraction working with Ansible
    # this includes instantiation, a couple of common attr would be the
    # args/params passed to the execution, as well as if the module
    # supports check mode
    module = AnsibleModule(
        argument_spec=argument_spec,
        supports_check_mode=True,
    )
    vmanage = Vmanage(module)
    vmanage_feature_templates = FeatureTemplates(vmanage.auth, vmanage.host)
    vmanage_template_data = TemplateData(vmanage.auth, vmanage.host)

    # Always as an aggregate... make a list if just given a single entry
    if vmanage.params['aggregate']:
        feature_template_list = vmanage.params['aggregate']
    else:
        if vmanage.params['state'] == 'present':
            try:

                feature_template_list = [{
                    'templateName':
                    vmanage.params['name'],
                    'templateDescription':
                    vmanage.params['description'],
                    'deviceType':
                    vmanage.params['device_type'],
                    'templateDefinition':
                    vmanage.params['definition'],
                    'templateType':
                    vmanage.params['template_type'],
                    'templateMinVersion':
                    vmanage.params['template_min_version'],
                    'factoryDefault':
                    vmanage.params['factory_default']
                }]
            except:
                module.fail_json(
                    msg=
                    "Required values: name, description, device_type, definition, template_type, template_min_version, factory_default"
                )
        else:
            try:
                feature_template_list = [{
                    'templateName': vmanage.params['name']
                }]
            except:
                module.fail_json(msg='Required values: name')

    feature_template_updates = []
    if vmanage.params['state'] == 'present':
        feature_template_updates = vmanage_template_data.import_feature_template_list(
            feature_template_list,
            check_mode=module.check_mode,
            update=vmanage.params['update'],
            push=vmanage.params['push'])
        if feature_template_updates:
            vmanage.result['changed'] = True

    else:
        feature_template_dict = vmanage_feature_templates.get_feature_template_dict(
            factory_default=True, remove_key=False)
        for feature_template in feature_template_list:
            if feature_template['templateName'] in feature_template_dict:
                if not module.check_mode:
                    vmanage_feature_templates.delete_feature_template(
                        feature_template_dict[
                            feature_template['templateName']]['templateId'])
                vmanage.result['changed'] = True

    vmanage.result['updates'] = feature_template_updates
    vmanage.exit_json(**vmanage.result)
Exemple #2
0
class TemplateData(object):
    """Methods that deal with importing, exporting, and converting data from templates.


    """
    def __init__(self, session, host, port=443):
        """Initialize Templates Method object with session parameters.

        Args:
            session (obj): Requests Session object
            host (str): hostname or IP address of vManage
            port (int): default HTTPS 443

        """

        self.session = session
        self.host = host
        self.port = port
        self.base_url = f'https://{self.host}:{self.port}/dataservice/'
        self.device_templates = DeviceTemplates(self.session, self.host,
                                                self.port)
        self.feature_templates = FeatureTemplates(self.session, self.host,
                                                  self.port)

    def convert_device_template_to_name(self, device_template):
        """Convert a device template objects from IDs to Names.

        Args:
            device_template (dict): Device Template

        Returns:
            result (dict): Converted Device Template.
        """

        feature_template_dict = self.feature_templates.get_feature_template_dict(
            factory_default=True, key_name='templateId')

        if 'policyId' in device_template and device_template['policyId']:
            policy_id = device_template['policyId']
            vmanage_local_policy = LocalPolicy(self.session, self.host,
                                               self.port)
            local_policy_dict = vmanage_local_policy.get_local_policy_dict(
                key_name='policyId')
            if policy_id in list(local_policy_dict.keys()):
                device_template['policyName'] = local_policy_dict[policy_id][
                    'policyName']
            else:
                raise Exception(f"Could not find local policy {policy_id}")

        if 'securityPolicyId' in device_template and device_template[
                'securityPolicyId']:
            security_policy_id = device_template['securityPolicyId']
            vmanage_security_policy = SecurityPolicy(self.session, self.host,
                                                     self.port)
            security_policy_dict = vmanage_security_policy.get_security_policy_dict(
                key_name='policyId')
            if security_policy_id in list(security_policy_dict.keys()):
                device_template['securityPolicyName'] = security_policy_dict[
                    security_policy_id]['policyName']
            else:
                raise Exception(
                    f"Could not find security policy {security_policy_id}")

        if 'generalTemplates' in device_template:
            generalTemplates = []
            for old_template in device_template.pop('generalTemplates'):
                new_template = {
                    'templateName':
                    feature_template_dict[old_template['templateId']]
                    ['templateName'],
                    'templateType':
                    old_template['templateType']
                }
                if 'subTemplates' in old_template:
                    subTemplates = self.subTemplates_to_name(
                        old_template, feature_template_dict)
                    new_template['subTemplates'] = subTemplates
                generalTemplates.append(new_template)
            device_template['generalTemplates'] = generalTemplates

        return device_template

    def convert_device_template_to_id(self, device_template):
        """Convert a device template objects from Names to IDs.

        Args:
            device_template (dict): Device Template

        Returns:
            result (dict): Converted Device Template.
        """

        if 'policyName' in device_template:
            vmanage_local_policy = LocalPolicy(self.session, self.host,
                                               self.port)
            local_policy_dict = vmanage_local_policy.get_local_policy_dict(
                key_name='policyName')
            if device_template['policyName'] in local_policy_dict:
                device_template['policyId'] = local_policy_dict[
                    device_template['policyName']]['policyId']
                device_template.pop('policyName')
            else:
                raise Exception(
                    f"Could not find local policy {device_template['policyName']}"
                )
        else:
            device_template['policyId'] = ''

        if 'securityPolicyName' in device_template:
            vmanage_security_policy = SecurityPolicy(self.session, self.host,
                                                     self.port)
            security_policy_dict = vmanage_security_policy.get_security_policy_dict(
                key_name='policyName')
            if device_template['securityPolicyName'] in security_policy_dict:
                device_template['securityPolicyId'] = security_policy_dict[
                    device_template['securityPolicyName']]['policyId']
                device_template.pop('securityPolicyName')
            else:
                raise Exception(
                    f"Could not find security policy {device_template['securityPolicyName']}"
                )
        else:
            device_template['securityPolicyId'] = ''

        if 'generalTemplates' in device_template:
            device_template['generalTemplates'] = self.generalTemplates_to_id(
                device_template['generalTemplates'])

        return device_template

    def generalTemplates_to_id(self, generalTemplates):
        """Convert a generalTemplates object from Names to IDs.

        Args:
            generalTemplates (dict): generalTemplates object

        Returns:
            result (dict): Converted generalTemplates object.
        """

        converted_generalTemplates = []
        feature_template_dict = self.feature_templates.get_feature_template_dict(
            factory_default=True)
        for template in generalTemplates:
            if 'templateName' not in template:
                self.result['generalTemplates'] = generalTemplates
                self.fail_json(msg="Bad template")
            if template['templateName'] in feature_template_dict:
                template_item = {
                    'templateId':
                    feature_template_dict[template['templateName']]
                    ['templateId'],
                    'templateType':
                    template['templateType']
                }
                if 'subTemplates' in template:
                    subTemplates = self.subTemplates_to_id(
                        template, feature_template_dict)
                    template_item['subTemplates'] = subTemplates
                converted_generalTemplates.append(template_item)
            else:
                self.fail_json(
                    msg="There is no existing feature template named {0}".
                    format(template['templateName']))

        return converted_generalTemplates

    def import_feature_template_list(self,
                                     feature_template_list,
                                     check_mode=False,
                                     update=False):
        """Import a list of feature templates from list to vManage.  Object Names are converted to IDs.


        Args:
            feature_template_list (list): List of feature templates
            check_mode (bool): Only check to see if changes would be made
            update (bool): Update the template if it exists

        Returns:
            result (list): Returns the diffs of the updates.

        """
        # Process the feature templates
        feature_template_updates = []
        feature_template_dict = self.feature_templates.get_feature_template_dict(
            factory_default=True, remove_key=False)
        for feature_template in feature_template_list:
            if 'templateId' in feature_template:
                feature_template.pop('templateId')
            if feature_template['templateName'] in feature_template_dict:
                existing_template = feature_template_dict[
                    feature_template['templateName']]
                feature_template['templateId'] = existing_template[
                    'templateId']
                diff = list(
                    dictdiffer.diff(existing_template['templateDefinition'],
                                    feature_template['templateDefinition']))
                if len(diff):
                    feature_template_updates.append({
                        'name':
                        feature_template['templateName'],
                        'diff':
                        diff
                    })
                    if not check_mode and update:
                        self.feature_templates.update_feature_template(
                            feature_template)
            else:
                diff = list(
                    dictdiffer.diff({},
                                    feature_template['templateDefinition']))
                feature_template_updates.append({
                    'name':
                    feature_template['templateName'],
                    'diff':
                    diff
                })
                if not check_mode:
                    self.feature_templates.add_feature_template(
                        feature_template)

        return feature_template_updates

    def export_device_template_list(self,
                                    factory_default=False,
                                    name_list=None):
        """Export device templates from vManage into a list.  Object IDs are converted to Names.

        Args:
            factory_default (bool): Include factory default
            name_list (list of strings): A list of template names to retreive.

        Returns:
            result (dict): All data associated with a response.
        """
        if name_list is None:
            name_list = []
        device_template_list = self.device_templates.get_device_templates()
        return_list = []

        #pylint: disable=too-many-nested-blocks
        for device_template in device_template_list:
            # If there is a list of template name, only return the ones asked for.
            # Otherwise, return them all
            if name_list and device_template['templateName'] not in name_list:
                continue
            obj = self.device_templates.get_device_template_object(
                device_template['templateId'])
            if obj:
                if not factory_default and obj['factoryDefault']:
                    continue
                obj['templateId'] = device_template['templateId']

                # obj['attached_devices'] = self.get_template_attachments(device['templateId'])
                # obj['input'] = self.get_template_input(device['templateId'])
                converted_device_template = self.convert_device_template_to_name(
                    obj)
                return_list.append(converted_device_template)
        return return_list

    def import_device_template_list(self,
                                    device_template_list,
                                    check_mode=False,
                                    update=False):
        """Import a list of device templates from list to vManage.  Object Names are converted to IDs.


        Args:
            device_template_list (list): List of device templates
            check_mode (bool): Only check to see if changes would be made
            update (bool): Update the template if it exists

        Returns:
            result (list): Returns the diffs of the updates.

        """
        device_template_updates = []
        device_template_dict = self.device_templates.get_device_template_dict()
        diff = []
        for device_template in device_template_list:
            if 'policyId' in device_template:
                device_template.pop('policyId')
            if 'securityPolicyId' in device_template:
                device_template.pop('securityPolicyId')
            if device_template['templateName'] in device_template_dict:
                existing_template = self.convert_device_template_to_name(
                    device_template_dict[device_template['templateName']])
                device_template['templateId'] = existing_template['templateId']
                # Just check the things that we care about changing.
                diff_ignore = set([
                    'templateId', 'policyId', 'connectionPreferenceRequired',
                    'connectionPreference', 'templateName', 'attached_devices',
                    'input', 'securityPolicyId'
                ])
                diff = list(
                    dictdiffer.diff(existing_template,
                                    device_template,
                                    ignore=diff_ignore))
                if len(diff):
                    device_template_updates.append({
                        'name':
                        device_template['templateName'],
                        'diff':
                        diff
                    })
                    if not check_mode and update:
                        if not check_mode:
                            converted_device_template = self.convert_device_template_to_id(
                                device_template)
                            self.device_templates.update_device_template(
                                converted_device_template)
            else:
                if 'generalTemplates' in device_template:
                    diff = list(
                        dictdiffer.diff({},
                                        device_template['generalTemplates']))
                elif 'templateConfiguration' in device_template:
                    diff = list(
                        dictdiffer.diff(
                            {}, device_template['templateConfiguration']))
                else:
                    raise Exception("Template {0} is of unknown type".format(
                        device_template['templateName']))
                device_template_updates.append({
                    'name':
                    device_template['templateName'],
                    'diff':
                    diff
                })
                if not check_mode:
                    converted_device_template = self.convert_device_template_to_id(
                        device_template)
                    self.device_templates.add_device_template(
                        converted_device_template)

        return device_template_updates

    def import_attachment_list(self,
                               attachment_list,
                               check_mode=False,
                               update=False):
        """Import a list of device attachments to vManage.


        Args:
            attachment_list (list): List of attachments
            check_mode (bool): Only check to see if changes would be made
            update (bool): Update the template if it exists

        Returns:
            result (list): Returns the diffs of the updates.

        """
        attachment_updates = {}
        attachment_failures = {}
        action_id_list = []
        device_template_dict = self.device_templates.get_device_template_dict()
        vmanage_device = Device(self.session, self.host, self.port)
        for attachment in attachment_list:
            if attachment['template'] in device_template_dict:
                if attachment['device_type'] == 'vedge':
                    # The UUID is fixes from the serial file/upload
                    device_uuid = attachment['uuid']
                else:
                    # If this is not a vedge, we need to get the UUID from the vmanage since
                    # it is generated by that vmanage
                    device_status = vmanage_device.get_device_status(
                        attachment['host_name'], key='host-name')
                    if device_status:
                        device_uuid = device_status['uuid']
                    else:
                        raise Exception(
                            f"Cannot find UUID for {attachment['host_name']}")

                template_id = device_template_dict[
                    attachment['template']]['templateId']
                attached_uuid_list = self.device_templates.get_attachments(
                    template_id, key='uuid')
                if device_uuid in attached_uuid_list:
                    # The device is already attached to the template.  We need to see if any of
                    # the input changed, so we make an API call to get the input on last attach
                    existing_template_input = self.device_templates.get_template_input(
                        device_template_dict[attachment['template']]
                        ['templateId'], [device_uuid])
                    current_variables = existing_template_input['data'][0]
                    changed = False
                    for property_name in attachment['variables']:
                        # Check to see if any of the passed in varibles have changed from what is
                        # already on the attachment.  We are are not checking to see if the
                        # correct variables are here.  That will be done on attachment.
                        if ((property_name in current_variables) and
                            (str(attachment['variables'][property_name]) !=
                             str(current_variables[property_name]))):
                            changed = True
                    if changed:
                        if not check_mode and update:
                            action_id = self.device_templates.attach_to_template(
                                template_id, device_uuid,
                                attachment['system_ip'],
                                attachment['host_name'], attachment['site_id'],
                                attachment['variables'])
                            action_id_list.append(action_id)
                else:
                    if not check_mode:
                        action_id = self.device_templates.attach_to_template(
                            template_id, device_uuid, attachment['system_ip'],
                            attachment['host_name'], attachment['site_id'],
                            attachment['variables'])
                        action_id_list.append(action_id)
            else:
                raise Exception(f"No template named {attachment['template']}")

        utilities = Utilities(self.session, self.host)
        # Batch the waits so that the peocessing of the attachments is in parallel
        for action_id in action_id_list:
            result = utilities.waitfor_action_completion(action_id)
            data = result['action_response']['data'][0]
            if result['action_status'] == 'failure':
                attachment_failures.update(
                    {data['uuid']: data['currentActivity']})
            else:
                attachment_updates.update(
                    {data['uuid']: data['currentActivity']})

        result = {
            'updates': attachment_updates,
            'failures': attachment_failures
        }
        return result

    def subTemplates_to_name(self, old_template, feature_template_dict):
        """Convert a Sub Template objects from IDs to Names.

        Args:
            old_template (dict): a device template
            feature_template_dict (dict): dict of all the feature templates

        Returns:
            result (dict): Converted Device Template.
        """

        subTemplates = []
        for sub_template in old_template['subTemplates']:
            if 'subTemplates' in sub_template:
                subsubTemplates = []
                for sub_sub_template in sub_template['subTemplates']:
                    subsubTemplates.append({
                        'templateName':
                        feature_template_dict[sub_sub_template['templateId']]
                        ['templateName'],
                        'templateType':
                        sub_sub_template['templateType']
                    })
                subTemplates.append({
                    'templateName':
                    feature_template_dict[sub_template['templateId']]
                    ['templateName'],
                    'templateType':
                    sub_template['templateType'],
                    'subTemplates':
                    subsubTemplates
                })
            else:
                subTemplates.append({
                    'templateName':
                    feature_template_dict[sub_template['templateId']]
                    ['templateName'],
                    'templateType':
                    sub_template['templateType']
                })
        return (subTemplates)

    def subTemplates_to_id(self, template, feature_template_dict):
        """Convert a Sub Template objects from IDs to Names.

        Args:
            template (dict): a device template
            feature_template_dict (dict): dict of all the feature templates

        Returns:
            result (dict): Converted Device Template.
        """
        subTemplates = []
        for sub_template in template['subTemplates']:
            if sub_template[
                    'templateName'] in feature_template_dict and 'subTemplates' in sub_template:
                subsubTemplates = []
                for sub_sub_template in sub_template['subTemplates']:
                    if sub_sub_template[
                            'templateName'] in feature_template_dict:
                        subsubTemplates.append({
                            'templateId':
                            feature_template_dict[sub_sub_template[
                                'templateName']]['templateId'],
                            'templateType':
                            sub_sub_template['templateType']
                        })
                    else:
                        self.fail_json(
                            msg="There is no existing feature template named {0}"
                            .format(sub_sub_template['templateName']))
                subTemplates.append({
                    'templateId':
                    feature_template_dict[sub_template['templateName']]
                    ['templateId'],
                    'templateType':
                    sub_template['templateType'],
                    'subTemplates':
                    subsubTemplates
                })
            elif sub_template['templateName'] in feature_template_dict:
                subTemplates.append({
                    'templateId':
                    feature_template_dict[sub_template['templateName']]
                    ['templateId'],
                    'templateType':
                    sub_template['templateType']
                })
            else:
                self.fail_json(
                    msg="There is no existing feature template named {0}".
                    format(sub_template['templateName']))
        return (subTemplates)
class DeviceTemplates(object):
    """vManage Device Templates API

    Responsible for DELETE, GET, POST, PUT methods against vManage
    Device Templates.

    """
    def __init__(self, session, host, port=443):
        """Initialize Device Templates object with session parameters.

        Args:
            session (obj): Requests Session object
            host (str): hostname or IP address of vManage
            port (int): default HTTPS 443

        """

        self.session = session
        self.host = host
        self.port = port
        self.base_url = f'https://{self.host}:{self.port}/dataservice/'
        self.feature_templates = FeatureTemplates(self.session, self.host,
                                                  self.port)

    def delete_device_template(self, templateId):
        """Obtain a list of all configured device templates.

        Args:
            templateId (str): Object ID for device template

        Returns:
            result (dict): All data associated with a response.

        """

        api = f"template/device/{templateId}"
        url = self.base_url + api
        response = HttpMethods(self.session, url).request('DELETE')
        result = ParseMethods.parse_status(response)
        return result

    def get_device_templates(self):
        """Obtain a list of all configured device templates.

        Returns:
            result (dict): All data associated with a response.

        """

        api = "template/device"
        url = self.base_url + api
        response = HttpMethods(self.session, url).request('GET')
        result = ParseMethods.parse_data(response)
        return result

    #
    # Templates
    #
    def get_device_template_object(self, template_id):
        """Obtain a device template object.

        Returns:
            result (dict): All data associated with a response.

        """

        api = f"template/device/object/{template_id}"
        url = self.base_url + api
        response = HttpMethods(self.session, url).request('GET')
        if 'json' in response:
            return response['json']

        return {}

    def get_device_template_list(self, factory_default=False, name_list=None):
        """Get the list of device templates.

        Args:
            factory_default (bool): Include factory default
            name_list (list of strings): A list of template names to retreive.

        Returns:
            result (dict): All data associated with a response.
        """
        if name_list is None:
            name_list = []
        device_templates = self.get_device_templates()

        return_list = []
        feature_template_dict = self.feature_templates.get_feature_template_dict(
            factory_default=True, key_name='templateId')

        #pylint: disable=too-many-nested-blocks
        for device in device_templates:
            # If there is a list of template name, only return the ones asked for.
            # Otherwise, return them all
            if name_list and device['templateName'] not in name_list:
                continue
            obj = self.get_device_template_object(device['templateId'])
            if obj:
                if not factory_default and obj['factoryDefault']:
                    continue
                if 'generalTemplates' in obj:
                    generalTemplates = []
                    for old_template in obj.pop('generalTemplates'):
                        new_template = {
                            'templateName':
                            feature_template_dict[old_template['templateId']]
                            ['templateName'],
                            'templateType':
                            old_template['templateType']
                        }
                        if 'subTemplates' in old_template:
                            subTemplates = []
                            for sub_template in old_template['subTemplates']:
                                subTemplates.append({
                                    'templateName':
                                    feature_template_dict[sub_template[
                                        'templateId']]['templateName'],
                                    'templateType':
                                    sub_template['templateType']
                                })
                            new_template['subTemplates'] = subTemplates
                        generalTemplates.append(new_template)
                    obj['generalTemplates'] = generalTemplates

                    obj['templateId'] = device['templateId']
                    obj['attached_devices'] = self.get_template_attachments(
                        device['templateId'])
                    obj['input'] = self.get_template_input(
                        device['templateId'])
                    # obj.pop('templateId')
                    return_list.append(obj)

        return return_list

    def get_device_template_dict(self,
                                 factory_default=False,
                                 key_name='templateName',
                                 remove_key=True,
                                 name_list=None):
        """Obtain a dictionary of all configured device templates.


        Args:
            factory_default (bool): Wheter to return factory default templates
            key_name (string): The name of the attribute to use as the dictionary key
            remove_key (boolean): remove the search key from the element

        Returns:
            result (dict): All data associated with a response.

        """
        if name_list is None:
            name_list = []
        device_template_list = self.get_device_template_list(
            factory_default=factory_default, name_list=name_list)

        return list_to_dict(device_template_list, key_name, remove_key)

    def get_template_attachments(self, template_id, key='host-name'):
        """Get the devices that a template is attached to.


        Args:
            template_id (string): Template ID
            key (string): The key of the device to put in the list (default: host-name)

        Returns:
            result (list): List of keys.

        """
        url = f"{self.base_url}template/device/config/attached/{template_id}"
        response = HttpMethods(self.session, url).request('GET')
        result = ParseMethods.parse_data(response)

        attached_devices = []
        for device in result:
            attached_devices.append(device[key])

        return attached_devices

    def get_template_input(self, template_id, device_id_list=None):
        """Get the input associated with a device attachment.

        Args:
            template_id (string): Template ID

        Returns:
            result (dict): All data associated with a response.

        """

        if device_id_list:
            deviceIds = device_id_list
        else:
            deviceIds = []
        payload = {
            "deviceIds": deviceIds,
            "isEdited": False,
            "isMasterEdited": False,
            "templateId": template_id
        }
        return_dict = {"columns": [], "data": []}

        url = f"{self.base_url}template/device/config/input"
        response = HttpMethods(self.session,
                               url).request('POST',
                                            payload=json.dumps(payload))

        if 'json' in response:
            if 'header' in response['json'] and 'columns' in response['json'][
                    'header']:
                column_list = response['json']['header']['columns']

                regex = re.compile(r'\((?P<variable>[^(]+)\)')

                for column in column_list:
                    if column['editable']:
                        match = regex.search(column['title'])
                        if match:
                            variable = match.groups('variable')[0]
                        else:
                            # If the variable is not found, but is a default entry
                            variable = None

                        entry = {
                            'title': column['title'],
                            'property': column['property'],
                            'variable': variable
                        }
                        return_dict['columns'].append(entry)
            if 'data' in response['json'] and response['json']['data']:
                return_dict['data'] = response['json']['data']

        return return_dict

    def add_device_template(self, device_template):
        """Add a single device template to Vmanage.


        Args:
            device_template (dict): Device Template

        Returns:
            result (list): Response from Vmanage

        """
        payload = {
            'templateName': device_template['templateName'],
            'templateDescription': device_template['templateDescription'],
            'deviceType': device_template['deviceType'],
            'factoryDefault': device_template['factoryDefault'],
            'configType': device_template['configType'],
            'policyId': '',
            'featureTemplateUidRange': []
        }
        #
        # File templates are much easier in that they are just a bunch of CLI
        #
        if device_template['configType'] == 'file':
            payload['templateConfiguration'] = device_template[
                'templateConfiguration']
            api = "template/device/cli"
            url = self.base_url + api
            response = HttpMethods(self.session,
                                   url).request('POST',
                                                payload=json.dumps(payload))
        #
        # Feature based templates are just a list of templates Id that make up a devie template.  We are
        # given the name of the feature templates, but we need to translate that to the template ID
        #
        else:
            if 'generalTemplates' in device_template:
                payload['generalTemplates'] = self.generalTemplates_to_id(
                    device_template['generalTemplates'])
            else:
                raise Exception("No generalTemplates found in device template",
                                data=device_template)
            url = f"{self.base_url}template/device/feature"
            response = HttpMethods(self.session,
                                   url).request('POST',
                                                payload=json.dumps(payload))
        return response

    def update_device_template(self, device_template):
        """Update a single device template to Vmanage.


        Args:
            device_template (dict): Device Template

        Returns:
            result (list): Response from Vmanage

        """
        #
        # File templates are much easier in that they are just a bunch of CLI
        #
        if device_template['configType'] == 'file':
            url = f"{self.base_url}template/device/cli/{device_template['templateId']}"
            response = HttpMethods(self.session, url).request(
                'PUT', payload=json.dumps(device_template))
        #
        # Feature based templates are just a list of templates Id that make up a devie template.  We are
        # given the name of the feature templates, but we need to translate that to the template ID
        #
        else:
            if 'generalTemplates' in device_template:
                device_template[
                    'generalTemplates'] = self.generalTemplates_to_id(
                        device_template['generalTemplates'])
            else:
                raise Exception("No generalTemplates found in device template",
                                data=device_template)
            url = f"{self.base_url}template/device/feature/{device_template['templateId']}"
            response = HttpMethods(self.session, url).request(
                'PUT', payload=json.dumps(device_template))
        return response

    def import_device_template_list(self,
                                    device_template_list,
                                    check_mode=False,
                                    update=False):
        """Import a list of feature templates to vManage.


        Args:
            check_mode (bool): Only check to see if changes would be made
            update (bool): Update the template if it exists

        Returns:
            result (list): Returns the diffs of the updates.

        """
        device_template_updates = []
        device_template_dict = self.get_device_template_dict()
        for device_template in device_template_list:
            if device_template['templateName'] in device_template_dict:
                existing_template = device_template_dict[
                    device_template['templateName']]
                if 'generalTemplates' in device_template:
                    diff = list(
                        dictdiffer.diff(existing_template['generalTemplates'],
                                        device_template['generalTemplates']))
                elif 'templateConfiguration' in device_template:
                    diff = list(
                        dictdiffer.diff(
                            existing_template['templateConfiguration'],
                            device_template['templateConfiguration']))
                else:
                    raise Exception("Template {0} is of unknown type".format(
                        device_template['templateName']))
                if len(diff):
                    device_template_updates.append({
                        'name':
                        device_template['templateName'],
                        'diff':
                        diff
                    })
                    if not check_mode and update:
                        self.update_device_template(device_template)
            else:
                if 'generalTemplates' in device_template:
                    diff = list(
                        dictdiffer.diff({},
                                        device_template['generalTemplates']))
                elif 'templateConfiguration' in device_template:
                    diff = list(
                        dictdiffer.diff(
                            {}, device_template['templateConfiguration']))
                else:
                    raise Exception("Template {0} is of unknown type".format(
                        device_template['templateName']))
                device_template_updates.append({
                    'name':
                    device_template['templateName'],
                    'diff':
                    diff
                })
                if not check_mode:
                    self.add_device_template(device_template)

        return device_template_updates

    def import_attachment_list(self,
                               attachment_list,
                               check_mode=False,
                               update=False):
        """Import a list of device attachments to vManage.


        Args:
            check_mode (bool): Only check to see if changes would be made
            update (bool): Update the template if it exists

        Returns:
            result (list): Returns the diffs of the updates.

        """
        attachment_updates = {}
        attachment_failures = {}
        action_id_list = []
        device_template_dict = self.get_device_template_dict()
        vmanage_device = Device(self.session, self.host, self.port)
        for attachment in attachment_list:
            if attachment['template'] in device_template_dict:
                if attachment['device_type'] == 'vedge':
                    # The UUID is fixes from the serial file/upload
                    device_uuid = attachment['uuid']
                else:
                    # If this is not a vedge, we need to get the UUID from the vmanage since
                    # it is generated by that vmanage
                    device_status = vmanage_device.get_device_status(
                        attachment['host_name'], key='host-name')
                    if device_status:
                        device_uuid = device_status['uuid']
                    else:
                        raise Exception(
                            f"Cannot find UUID for {attachment['host_name']}")

                template_id = device_template_dict[
                    attachment['template']]['templateId']
                attached_uuid_list = self.get_attachments(template_id,
                                                          key='uuid')
                if device_uuid in attached_uuid_list:
                    # The device is already attached to the template.  We need to see if any of
                    # the input changed, so we make an API call to get the input on last attach
                    existing_template_input = self.get_template_input(
                        device_template_dict[attachment['template']]
                        ['templateId'], [device_uuid])
                    current_variables = existing_template_input['data'][0]
                    changed = False
                    for property_name in attachment['variables']:
                        # Check to see if any of the passed in varibles have changed from what is
                        # already on the attachment.  We are are not checking to see if the
                        # correct variables are here.  That will be done on attachment.
                        if ((property_name in current_variables) and
                            (str(attachment['variables'][property_name]) !=
                             str(current_variables[property_name]))):
                            changed = True
                    if changed:
                        if not check_mode and update:
                            action_id = self.attach_to_template(
                                template_id, device_uuid,
                                attachment['system_ip'],
                                attachment['host_name'], attachment['site_id'],
                                attachment['variables'])
                            action_id_list.append(action_id)
                else:
                    if not check_mode:
                        action_id = self.attach_to_template(
                            template_id, device_uuid, attachment['system_ip'],
                            attachment['host_name'], attachment['site_id'],
                            attachment['variables'])
                        action_id_list.append(action_id)
            else:
                raise Exception(
                    f"No template named Template {attachment['templateName']}")

        # pp = pprint.PrettyPrinter(indent=2)
        utilities = Utilities(self.session, self.host)
        # Batch the waits so that the peocessing of the attachments is in parallel
        for action_id in action_id_list:
            result = utilities.waitfor_action_completion(action_id)
            data = result['action_response']['data'][0]
            # pp.pprint(data)
            if result['action_status'] == 'failure':
                attachment_failures.update(
                    {data['uuid']: data['currentActivity']})
            else:
                attachment_updates.update(
                    {data['uuid']: data['currentActivity']})

        result = {
            'updates': attachment_updates,
            'failures': attachment_failures
        }
        return result

    def attach_to_template(self, template_id, uuid, system_ip, host_name,
                           site_id, variables):
        """Attach and device to a template

        Args:
            template_id (str): The template ID to attach to
            uuid (str): The UUID of the device to attach
            system_ip (str): The System IP of the system to attach
            host_name (str): The host-name of the device to attach
            variables (dict): The variables needed by the template

        Returns:
            action_id (str): Returns the action id of the attachment

        """
        # Construct the variable payload
        device_template_variables = {
            "csv-status": "complete",
            "csv-deviceId": uuid,
            "csv-deviceIP": system_ip,
            "csv-host-name": host_name,
            '//system/host-name': host_name,
            '//system/system-ip': system_ip,
            '//system/site-id': site_id,
        }
        # Make sure they passed in the required variables and map
        # variable name -> property mapping
        template_variables = self.get_template_input(template_id)
        for entry in template_variables['columns']:
            if entry['variable']:
                if entry['variable'] in variables:
                    device_template_variables[entry['property']] = variables[
                        entry['variable']]
                else:
                    raise Exception(
                        f"{entry['variable']} is missing for {host_name}")

        payload = {
            "deviceTemplateList": [{
                "templateId": template_id,
                "device": [device_template_variables],
                "isEdited": False,
                "isMasterEdited": False
            }]
        }
        url = f"{self.base_url}template/device/config/attachfeature"
        response = HttpMethods(self.session,
                               url).request('POST',
                                            payload=json.dumps(payload))
        if 'json' in response and 'id' in response['json']:
            action_id = response['json']['id']
        else:
            raise Exception(
                'Did not get action ID after attaching device to template.')

        return action_id

    def detach_from_template(self, uuid, device_ip, device_type):
        """Detach a device from a template (i.e. Put in CLI mode)

        Args:
            uuid (str): The UUID of the device to detach
            system_ip (str): The System IP of the system to detach
            device_type (str): The device type of the device to detach

        Returns:
            action_id (str): Returns the action id of the attachment

        """
        payload = {
            "deviceType": device_type,
            "devices": [{
                "deviceId": uuid,
                "deviceIP": device_ip,
            }]
        }
        url = f"{self.base_url}template/config/device/mode/cli"
        response = HttpMethods(self.session,
                               url).request('POST',
                                            payload=json.dumps(payload))
        ParseMethods.parse_data(response)

        if 'json' in response and 'id' in response['json']:
            action_id = response.json['id']
        else:
            raise Exception(
                'Did not get action ID after attaching device to template.')
        return action_id

    def get_attachments(self, template_id, key='host-name'):
        """Get a list of attachments to a particular template.

        Args:
            template_id (str): Template ID of the template
            key (str): The key of the elements to return

        Returns:
            result (list): Returns the specified key of the attached devices.

        """
        url = f"{self.base_url}template/device/config/attached/{template_id}"
        response = HttpMethods(self.session, url).request('GET')
        result = ParseMethods.parse_data(response)

        attached_devices = []
        for device in result:
            attached_devices.append(device[key])

        return attached_devices

    def generalTemplates_to_id(self, generalTemplates):
        converted_generalTemplates = []
        feature_templates = self.feature_templates.get_feature_template_dict(
            factory_default=True)
        for template in generalTemplates:
            if 'templateName' not in template:
                self.result['generalTemplates'] = generalTemplates
                self.fail_json(msg="Bad template")
            if template['templateName'] in feature_templates:
                template_item = {
                    'templateId':
                    feature_templates[template['templateName']]['templateId'],
                    'templateType':
                    template['templateType']
                }
                if 'subTemplates' in template:
                    subTemplates = []
                    for sub_template in template['subTemplates']:
                        if sub_template['templateName'] in feature_templates:
                            subTemplates.append({
                                'templateId':
                                feature_templates[sub_template['templateName']]
                                ['templateId'],
                                'templateType':
                                sub_template['templateType']
                            })
                        else:
                            self.fail_json(
                                msg=
                                "There is no existing feature template named {0}"
                                .format(sub_template['templateName']))
                    template_item['subTemplates'] = subTemplates

                converted_generalTemplates.append(template_item)
            else:
                self.fail_json(
                    msg="There is no existing feature template named {0}".
                    format(template['templateName']))

        return converted_generalTemplates