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)
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