"""Cisco vManage Device Templates API Methods.
"""
import json
import re
from vmanage.api.feature_templates import FeatureTemplates
from vmanage.api.http_methods import HttpMethods
from vmanage.data.parse_methods import ParseMethods
from vmanage.utils import list_to_dict
from vmanage.api.utilities import Utilities
[docs]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)
[docs] 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
[docs] 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
#
[docs] def get_device_template_object(self, template_id):
"""Obtain a device template object.
Args:
template_id (str): Object ID for device template
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 {}
[docs] 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 = []
#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
obj['templateId'] = device['templateId']
# obj['attached_devices'] = self.get_template_attachments(device['templateId'])
# obj['input'] = self.get_template_input(device['templateId'])
return_list.append(obj)
return return_list
[docs] 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
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.get_device_template_list(factory_default=factory_default, name_list=name_list)
return list_to_dict(device_template_list, key_name, remove_key)
[docs] 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
[docs] 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'],
'featureTemplateUidRange': []
}
if 'policyId' in device_template:
payload['policyId'] = device_template['policyId']
else:
payload['policyId'] = ''
if 'securityPolicyId' in device_template:
payload['securityPolicyId'] = device_template['securityPolicyId']
else:
payload['securityPolicyId'] = ''
#
# File templates are much easier in that they are just a bunch of CLI
#
if device_template['configType'] == 'file':
payload['templateConfiguration'] = device_template['templateConfiguration']
url = f"{self.base_url}template/device/cli"
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:
payload['generalTemplates'] = device_template['generalTemplates']
url = f"{self.base_url}template/device/feature"
response = HttpMethods(self.session, url).request('POST', payload=json.dumps(payload))
return response
[docs] 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
#
# I'm not sure where this api call was found, but I can't find it in any doc and it doesn't currently work
# 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))
# ParseMethods.parse_data(response)
#
# Feature based templates are just a list of templates Id that make up a device template. We are
# given the name of the feature templates, but we need to translate that to the template ID
#
#else:
url = f"{self.base_url}template/device/{device_template['templateId']}"
response = HttpMethods(self.session, url).request('PUT', payload=json.dumps(device_template))
ParseMethods.parse_data(response)
return response
[docs] def reattach_device_template(self, template_id, config_type, is_edited=True, is_master_edited=True):
"""Re-Attach a template to the devices it it attached to.
Args:
template_id (str): The template ID to attach to
config_type (str): Type of template i.e. device or CLI template
is_edited (bool): True if the template has been edited
is_master_edited (bool): For CLI device template needs to match is_edited.
For device templates using feature templates needs to be set to False.
Returns:
action_id (str): Returns the action id of the attachment
"""
device_list = self.get_template_attachments(template_id, key='uuid')
template_input = self.get_template_input(template_id, device_list)
# Then we feed that to the attach
if 'data' in template_input and template_input['data']:
payload = {
"deviceTemplateList": [{
"templateId": template_id,
"device": template_input['data'],
"isEdited": is_edited,
"isMasterEdited": is_master_edited
}]
}
if config_type == 'file':
url = f"{self.base_url}template/device/config/attachcli"
elif config_type == 'template':
url = f"{self.base_url}template/device/config/attachfeature"
else:
raise RuntimeError('Got invalid Config Type')
utils = Utilities(self.session, self.host, self.port)
response = HttpMethods(self.session, url).request('POST', payload=json.dumps(payload))
action_id = ParseMethods.parse_id(response)
utils.waitfor_action_completion(action_id)
else:
raise RuntimeError(f"Could not retrieve input for template {template_id}")
return action_id
[docs] def attach_to_template(self, template_id, config_type, uuid):
"""Attach and device to a template
Args:
template_id (str): The template ID to attach to
config_type (str): Type of template i.e. device or CLI template
uuid (dict): The UUIDs of the device to attach and mapping for corresponding variables, system-ip, host-name
Returns:
action_id (str): Returns the action id of the attachment
"""
# Construct the variable payload
device_template_var_list = list()
template_variables = self.get_template_input(template_id)
for device_uuid in uuid:
device_template_variables = {
"csv-status": "complete",
"csv-deviceId": device_uuid,
"csv-deviceIP": uuid[device_uuid]['system_ip'],
"csv-host-name": uuid[device_uuid]['host_name'],
'//system/host-name': uuid[device_uuid]['host_name'],
'//system/system-ip': uuid[device_uuid]['system_ip'],
'//system/site-id': uuid[device_uuid]['site_id'],
}
# Make sure they passed in the required variables and map
# variable name -> property mapping
for entry in template_variables['columns']:
if entry['variable']:
if entry['variable'] in uuid[device_uuid]['variables']:
device_template_variables[entry['property']] = uuid[device_uuid]['variables'][entry['variable']]
else:
raise RuntimeError(
f"{entry['variable']} is missing for template {uuid[device_uuid]['host_name']}")
device_template_var_list.append(device_template_variables)
payload = {
"deviceTemplateList": [{
"templateId": template_id,
"device": device_template_var_list,
"isEdited": False,
"isMasterEdited": False
}]
}
if config_type == 'file':
url = f"{self.base_url}template/device/config/attachcli"
elif config_type == 'template':
url = f"{self.base_url}template/device/config/attachfeature"
else:
raise RuntimeError('Got invalid Config Type')
utils = Utilities(self.session, self.host, self.port)
response = HttpMethods(self.session, url).request('POST', payload=json.dumps(payload))
action_id = ParseMethods.parse_id(response)
utils.waitfor_action_completion(action_id)
return action_id
[docs] 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
device_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_status(response)
action_id = ParseMethods.parse_id(response)
return action_id
[docs] 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
[docs] def get_device_running_config(self, uuid):
"""
Get the running configuration of a specific device.
Args:
uuid (str): UUID of device
Returns:
result (str): The running configuration of the specified device.
"""
url = f"{self.base_url}template/config/running/{uuid}"
response = HttpMethods(self.session, url).request('GET')
return ParseMethods.parse_config(response)
[docs] def reattach_multi_device_templates(self, template_ids):
"""Re-Attach a template to the devices it it attached to.
Args:
template_id (str): The template ID to attach to
config_type (str): Type of template i.e. device or CLI template
is_edited (bool): True if the template has been edited
is_master_edited (bool): For CLI device template needs to match is_edited.
For device templates using feature templates needs to be set to False.
Returns:
action_id (str): Returns the action id of the attachment
"""
payload = self.get_multi_attach_payload(template_ids)
if payload['deviceTemplateList'][0]['device']:
url = f"{self.base_url}template/device/config/attachfeature"
utils = Utilities(self.session, self.host, self.port)
response = HttpMethods(self.session, url).request('POST', payload=json.dumps(payload))
action_id = ParseMethods.parse_id(response)
utils.waitfor_action_completion(action_id)
else:
raise RuntimeError(f"Could not retrieve input for template {template_ids}")
return action_id
[docs] def get_multi_attach_payload(self, template_ids):
return_dict = {"deviceTemplateList": []}
i = 0
for template_id in template_ids:
return_dict['deviceTemplateList'].append({
"templateId": template_id,
"device": [],
"isEdited": True,
"isMasterEdited": False
})
url = f"{self.base_url}template/device/config/attached/{template_id}"
attach_resp = HttpMethods(self.session, url).request('GET')
device_list = attach_resp['json']['data']
for device in device_list:
payload = {
"templateId": template_id,
"deviceIds": [device['uuid']],
"isEdited": False,
"isMasterEdited": False
}
url = f"{self.base_url}template/device/config/input"
input_resp = HttpMethods(self.session, url).request('POST', payload=json.dumps(payload))
return_dict['deviceTemplateList'][i]['device'].append(input_resp['json']['data'][0])
i += 1
return return_dict