From 5a392c9db62dc02cc56d2c5760d4353d4f09cfa9 Mon Sep 17 00:00:00 2001 From: mgdransfield Date: Mon, 16 Aug 2021 20:06:35 +1000 Subject: [PATCH 1/4] Update device.py add get_device_ping and generate_bootstrap --- vmanage/api/device.py | 56 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/vmanage/api/device.py b/vmanage/api/device.py index f70c71f..1832427 100644 --- a/vmanage/api/device.py +++ b/vmanage/api/device.py @@ -28,6 +28,36 @@ def __init__(self, session, host, port=443): self.port = port self.base_url = f'https://{self.host}:{self.port}/dataservice/' + def generate_bootstrap(self, uuid): + """Generate a bootstrap file for a device + + Args: + uuid (str): uuid of device + + Returns: + result (dict): All bootstrap data + """ + + url = f"{self.base_url}system/device/bootstrap/device/{uuid}?configtype=cloudinit" + #print("DEBUG: ",url) + response = HttpMethods(self.session, url).request('GET') + #print("DEBUG2:",response) + #result = ParseMethods.parse_data(response) + #bootstrap_config = result.json['bootstrapConfig'] + bootstrap_config = response['json']['bootstrapConfig'] + regex = re.compile(r'otp : (?P[a-z0-9]+)[^a-z0-9]') + match = regex.search(bootstrap_config) + if match: + otp = match.groups('otp')[0] + else: + otp = None + return_dict = { + 'bootstrapConfig': bootstrap_config, + 'otp': otp, + 'uuid': uuid + } + return return_dict + def get_device_list(self, category): """Obtain a list of specified device type @@ -59,7 +89,33 @@ def post_device_cli_mode(self, deviceId, deviceIP, deviceType): response = HttpMethods(self.session, url).request('POST', payload=payload) result = ParseMethods.parse_status(response) return result + + def get_device_ping(self,systemip,vpn,sourceip,destip): + """Obtain a list of specified device type + Args: None + + + Returns: + result (list): Device status + """ + + #api = f"device/tools/nping/{systemip}" + #url = self.base_url + api + url = f"{self.base_url}device/tools/nping/{systemip}" + payload = f"{{'host':'{destip}', 'vpn':'{vpn}', 'source':'{sourceip}', 'probetype':'icmp'}}" + #print("DEBUG:",url,payload) + response = HttpMethods(self.session, url).request('POST', payload=payload) + #print("DEBUG:",response) + if response['json']: + #if response['status_code']==200: + return(response['json']) + else: + error = result=response['details'] + return result + #result = ParseMethods.parse_data(response) + #return result + def get_device_status_list(self): """Obtain a list of specified device type From 25fabba8bdf1c59596ab39a47e622f9bdd2cab30 Mon Sep 17 00:00:00 2001 From: Mike Dransfield Date: Sat, 2 Oct 2021 09:26:47 +1000 Subject: [PATCH 2/4] added notificaton.py updated to resolve variables with parentheses in title --- vmanage/api/device_templates.py | 19 +++++++++++--- vmanage/api/notifications.py | 44 +++++++++++++++++++++++++++++++++ vmanage/data/policy_data.py | 4 +++ vmanage/data/template_data.py | 13 ++++++++-- 4 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 vmanage/api/notifications.py diff --git a/vmanage/api/device_templates.py b/vmanage/api/device_templates.py index 6464e16..9786d8d 100644 --- a/vmanage/api/device_templates.py +++ b/vmanage/api/device_templates.py @@ -185,15 +185,28 @@ def get_template_input(self, template_id, device_id_list=None): column_list = response['json']['header']['columns'] regex = re.compile(r'\((?P[^(]+)\)') + #regex=re.compile(r"(?P.*)\((?P.*)\)") + prod1=re.compile(r"(?P.*)\((?P.*)\)") for column in column_list: if column['editable']: - match = regex.search(column['title']) - if match: - variable = match.groups('variable')[0] + #print("DEBUG1: ",column['title']) + # check with prod1 first + m=prod1.match(column['title']) + if m: + intdict=m.groupdict() + titledesc=intdict['titledesc'] + variable=titledesc + #print("DEBUG: prod1 variable: ",titledesc) + #match = regex.search(column['title']) + #if match: + # variable = match.groups('variable')[0] + # #if variable!=titledesc: + # print("DEBUG: variable: ",variable,titledesc) else: # If the variable is not found, use toolTip as variable name variable = column.get("toolTip") + #print("DEBUG: toolTip variable: ",variable) entry = {'title': column['title'], 'property': column['property'], 'variable': variable} return_dict['columns'].append(entry) diff --git a/vmanage/api/notifications.py b/vmanage/api/notifications.py new file mode 100644 index 0000000..7d9cf94 --- /dev/null +++ b/vmanage/api/notifications.py @@ -0,0 +1,44 @@ +"""Cisco vManage Notifications API Methods. +""" + +import time +from vmanage.api.http_methods import HttpMethods +from vmanage.data.parse_methods import ParseMethods + + +class Notifications(object): + """Access to Various vManage Utilitiesinstance. + + vManage has several utilities that are needed for correct execution + of applications against the API. For example, this includes waiting + for an action to complete before moving onto the next task. + + """ + def __init__(self, session, host, port=443): + """Initialize Notifications 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/' + + def get_notification_rules(self): + """Provides notification rules + + Returns: + result (dict): All data associated with a response. + """ + + api = "notifications/rules" + url = self.base_url + api + response = HttpMethods(self.session, url).request('GET') + result = ParseMethods.parse_data(response) + return result + diff --git a/vmanage/data/policy_data.py b/vmanage/data/policy_data.py index 6d67f9d..d5fab6d 100644 --- a/vmanage/data/policy_data.py +++ b/vmanage/data/policy_data.py @@ -171,6 +171,7 @@ def convert_list_id_to_name(self, id_list): id_list (list): Object """ + #print("DEBUG:",id_list) if isinstance(id_list, dict): for key, value in list(id_list.items()): if key.endswith( @@ -211,6 +212,7 @@ def convert_list_id_to_name(self, id_list): id_list['listType'] = policy_list['type'] id_list.pop('ref') else: + #print("DEBUG:",id_list) raise RuntimeError(f"Could not find name for list {id_list['ref']}") elif key == 'class': policy_list = self.policy_lists.get_policy_list_by_id(id_list['class']) @@ -340,6 +342,8 @@ def export_policy_definition_list(self, definition_type='all'): policy_definition_list = self.policy_definitions.get_policy_definition_list(definition_type) export_definition_list = [] for policy_definition in policy_definition_list: + #print("DEBUG: ",policy_definition) + #print("") definition_detail = self.policy_definitions.get_policy_definition(policy_definition['type'], policy_definition['definitionId']) converted_policy_definition = self.convert_policy_definition_to_name(definition_detail) diff --git a/vmanage/data/template_data.py b/vmanage/data/template_data.py index 910831f..e8e5f2c 100644 --- a/vmanage/data/template_data.py +++ b/vmanage/data/template_data.py @@ -337,16 +337,25 @@ def import_attachment_list(self, attachment_list, check_mode=False, update=False # 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] + #current_variables = existing_template_input['data'][0] + current_variables = {} + data = existing_template_input['data'][0] + for column in existing_template_input['columns']: + current_variables[column['variable']] = data[column['property']] + #print("DEBUG: current_variables: ",current_variables) + # haven't changed variables from properties to variables + # changed = False for property_name in attachment['variables']: - # Check to see if any of the passed in varibles have changed from what is + #print("DEBUG: checking: ",property_name) + # Check to see if any of the passed in variables 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: + print("Changed!!!") if not check_mode and update: template_device_map.setdefault(template_id, []).append({ "config_type": config_type, From 3dc2a33b2aa843f767881e1f244df38cf09be70c Mon Sep 17 00:00:00 2001 From: mgdransfield Date: Sat, 2 Oct 2021 11:07:38 +0000 Subject: [PATCH 3/4] Resolved issue with parentheses in Title for variables Added notifications Tried to cleanup pylint errors --- vmanage/api/device.py | 37 +++++++++++------------- vmanage/api/device_templates.py | 21 ++++---------- vmanage/api/notifications.py | 9 ++---- vmanage/cli/deactivate/central_policy.py | 6 ++-- vmanage/cli/decommission/device.py | 6 ++-- vmanage/cli/reset/__init__.py | 1 - 6 files changed, 31 insertions(+), 49 deletions(-) diff --git a/vmanage/api/device.py b/vmanage/api/device.py index d8a5926..9628bdd 100644 --- a/vmanage/api/device.py +++ b/vmanage/api/device.py @@ -1,6 +1,7 @@ """Cisco vManage Device Inventory API Methods. """ +import re from vmanage.api.http_methods import HttpMethods from vmanage.data.parse_methods import ParseMethods from vmanage.utils import list_to_dict @@ -28,7 +29,7 @@ def __init__(self, session, host, port=443): self.port = port self.base_url = f'https://{self.host}:{self.port}/dataservice/' - def generate_bootstrap(self, uuid): + def generate_bootstrap(self, uuid): """Generate a bootstrap file for a device Args: @@ -39,9 +40,7 @@ def generate_bootstrap(self, uuid): """ url = f"{self.base_url}system/device/bootstrap/device/{uuid}?configtype=cloudinit" - #print("DEBUG: ",url) response = HttpMethods(self.session, url).request('GET') - #print("DEBUG2:",response) #result = ParseMethods.parse_data(response) #bootstrap_config = result.json['bootstrapConfig'] bootstrap_config = response['json']['bootstrapConfig'] @@ -51,11 +50,7 @@ def generate_bootstrap(self, uuid): otp = match.groups('otp')[0] else: otp = None - return_dict = { - 'bootstrapConfig': bootstrap_config, - 'otp': otp, - 'uuid': uuid - } + return_dict = {'bootstrapConfig': bootstrap_config, 'otp': otp, 'uuid': uuid} return return_dict def get_device_list(self, category): @@ -88,33 +83,35 @@ def post_device_cli_mode(self, deviceId, deviceType): response = HttpMethods(self.session, url).request('POST', payload=payload) result = ParseMethods.parse_status(response) return result - - def get_device_ping(self,systemip,vpn,sourceip,destip): - """Obtain a list of specified device type - Args: None + def get_device_ping(self, systemip, vpn, sourceip, destip): + """Ping a specified destination ip address + + Args: + systemip (str): systemip of source device + vpn (str): vpn id of source + sourceip (str): source ip address + destip (str): destination ip address Returns: - result (list): Device status + result (str): ping response time """ #api = f"device/tools/nping/{systemip}" #url = self.base_url + api url = f"{self.base_url}device/tools/nping/{systemip}" payload = f"{{'host':'{destip}', 'vpn':'{vpn}', 'source':'{sourceip}', 'probetype':'icmp'}}" - #print("DEBUG:",url,payload) response = HttpMethods(self.session, url).request('POST', payload=payload) - #print("DEBUG:",response) if response['json']: - #if response['status_code']==200: - return(response['json']) + #if response['status_code']==200: + result = response['json'] else: - error = result=response['details'] + result = response['details'] return result #result = ParseMethods.parse_data(response) #return result - + def get_device_status_list(self): """Obtain a list of specified device type @@ -272,7 +269,7 @@ def post_device(self, device_ip, personality, username, password): Returns: result (list): Device status """ - + def post_reset_interface(self, device_ip, vpn_id, ifname): """Reset an Interface Args: diff --git a/vmanage/api/device_templates.py b/vmanage/api/device_templates.py index 9786d8d..6ce8f93 100644 --- a/vmanage/api/device_templates.py +++ b/vmanage/api/device_templates.py @@ -184,29 +184,18 @@ def get_template_input(self, template_id, device_id_list=None): if 'header' in response['json'] and 'columns' in response['json']['header']: column_list = response['json']['header']['columns'] - regex = re.compile(r'\((?P[^(]+)\)') - #regex=re.compile(r"(?P.*)\((?P.*)\)") - prod1=re.compile(r"(?P.*)\((?P.*)\)") + prod1 = re.compile(r"(?P.*)\((?P.*)\)") for column in column_list: if column['editable']: - #print("DEBUG1: ",column['title']) - # check with prod1 first - m=prod1.match(column['title']) + m = prod1.match(column['title']) if m: - intdict=m.groupdict() - titledesc=intdict['titledesc'] - variable=titledesc - #print("DEBUG: prod1 variable: ",titledesc) - #match = regex.search(column['title']) - #if match: - # variable = match.groups('variable')[0] - # #if variable!=titledesc: - # print("DEBUG: variable: ",variable,titledesc) + intdict = m.groupdict() + titledesc = intdict['titledesc'] + variable = titledesc else: # If the variable is not found, use toolTip as variable name variable = column.get("toolTip") - #print("DEBUG: toolTip variable: ",variable) entry = {'title': column['title'], 'property': column['property'], 'variable': variable} return_dict['columns'].append(entry) diff --git a/vmanage/api/notifications.py b/vmanage/api/notifications.py index 7d9cf94..c4d6886 100644 --- a/vmanage/api/notifications.py +++ b/vmanage/api/notifications.py @@ -1,17 +1,15 @@ """Cisco vManage Notifications API Methods. """ -import time from vmanage.api.http_methods import HttpMethods from vmanage.data.parse_methods import ParseMethods class Notifications(object): - """Access to Various vManage Utilitiesinstance. + """Access to Notificaton rules. - vManage has several utilities that are needed for correct execution - of applications against the API. For example, this includes waiting - for an action to complete before moving onto the next task. + vManage can generate notificatons. This class allows you to + read these notifications. """ def __init__(self, session, host, port=443): @@ -41,4 +39,3 @@ def get_notification_rules(self): response = HttpMethods(self.session, url).request('GET') result = ParseMethods.parse_data(response) return result - diff --git a/vmanage/cli/deactivate/central_policy.py b/vmanage/cli/deactivate/central_policy.py index 1ec0eb6..3b68b11 100644 --- a/vmanage/cli/deactivate/central_policy.py +++ b/vmanage/cli/deactivate/central_policy.py @@ -7,7 +7,7 @@ @click.option('--name', '-n', help="Name of policy to deactivate.") @click.option('--id', '-i', help="Id of policy to deactivate.") @click.pass_obj -def central_policy(ctx, name, id): +def central_policy(ctx, name, cpid): """ deactivate Central Policy """ @@ -15,8 +15,8 @@ def central_policy(ctx, name, id): vmanage_central_policy = CentralPolicy(ctx.auth, ctx.host, ctx.port) vmanage_utilities = Utilities(ctx.auth, ctx.host, ctx.port) central_policy_dict = vmanage_central_policy.get_central_policy_dict(remove_key=True) - if id: - vmanage_central_policy.deactivate_central_policy(id) + if cpid: + vmanage_central_policy.deactivate_central_policy(cpid) elif name: if name in central_policy_dict: click.echo(f'Deactivating Central Policy {name}') diff --git a/vmanage/cli/decommission/device.py b/vmanage/cli/decommission/device.py index e820fae..8de37d9 100644 --- a/vmanage/cli/decommission/device.py +++ b/vmanage/cli/decommission/device.py @@ -5,14 +5,14 @@ @click.command('device') @click.argument('device', required=True) @click.pass_obj -def device(ctx, device): +def device(ctx, decommdevice): """ Decommission device """ vmanage_device = Device(ctx.auth, ctx.host, ctx.port) - status = vmanage_device.get_device_status(device, key='host-name') + status = vmanage_device.get_device_status(decommdevice, key='host-name') if 'uuid' in status: vmanage_device.put_device_decommission(status['uuid']) else: - click.secho(f'Cannot find UUID for device {device}', fg="red") + click.secho(f'Cannot find UUID for device {decommdevice}', fg="red") diff --git a/vmanage/cli/reset/__init__.py b/vmanage/cli/reset/__init__.py index 199fa95..9aa2446 100644 --- a/vmanage/cli/reset/__init__.py +++ b/vmanage/cli/reset/__init__.py @@ -2,7 +2,6 @@ from vmanage.cli.reset.interface import interface - @click.group('reset') def reset(): """ From 12bc3909800d4134bbb08ef498aef5fddc80f259 Mon Sep 17 00:00:00 2001 From: mgdransfield Date: Thu, 20 Jan 2022 21:12:49 +1100 Subject: [PATCH 4/4] Update device.py --- vmanage/api/device.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/vmanage/api/device.py b/vmanage/api/device.py index 9628bdd..bcc3676 100644 --- a/vmanage/api/device.py +++ b/vmanage/api/device.py @@ -264,11 +264,19 @@ def post_device(self, device_ip, personality, username, password): """Add control plane device Args: - device_id (str): uuid for device object + device_ip (str): device interface IP + personality (str): controller type (vmanage, vsmart, vbond) + username (str): device username + password (str): device password Returns: result (list): Device status """ + url = f"{self.base_url}system/device" + payload = f"{{'deviceIP':'{device_ip}','username':'{username}','password':'{password}','personality':'{personality}','generateCSR':'false'}}" + response = HttpMethods(self.session, url).request('POST', payload=payload,timeout=35) + result = ParseMethods.parse_status(response) + return result def post_reset_interface(self, device_ip, vpn_id, ifname): """Reset an Interface