From e55f05cd0af53a1937339545053959050280116e Mon Sep 17 00:00:00 2001 From: "th.l" <thl-cmk@outlook.com> Date: Fri, 9 Jun 2023 18:53:04 +0200 Subject: [PATCH] update project --- agent_based/inv_cisco_eox.py | 2 +- agent_based/inv_cisco_psirt.py | 52 +- agent_based/utils/inv_cisco_support.py | 4 +- bin/ciscoapi/cisco-bug.py | 188 +++----- bin/ciscoapi/cisco-eox.py | 208 +++----- bin/ciscoapi/cisco-psirt.py | 150 ++---- bin/ciscoapi/cisco-sn2info.py | 157 +++--- bin/ciscoapi/cisco_live_cycle_utils.py | 270 +++++++++++ bin/ciscoapi/ciscoapi.py | 638 +++++++++++-------------- gui/views/inv_cisco_livecycle.py | 307 ++++++++++++ gui/wato/inv_cisco_bug.py | 70 +++ gui/wato/inv_cisco_contract.py | 73 +++ gui/wato/inv_cisco_eox.py | 94 ++++ gui/wato/inv_cisco_psirt.py | 122 +++++ inv_cisco_support-0.2.0-20230609.mkp | Bin 0 -> 26029 bytes inv_cisco_support.mkp | Bin 26335 -> 26029 bytes packages/inv_cisco_support | 30 +- 17 files changed, 1516 insertions(+), 849 deletions(-) create mode 100755 bin/ciscoapi/cisco_live_cycle_utils.py create mode 100644 gui/views/inv_cisco_livecycle.py create mode 100644 gui/wato/inv_cisco_bug.py create mode 100644 gui/wato/inv_cisco_contract.py create mode 100644 gui/wato/inv_cisco_eox.py create mode 100644 gui/wato/inv_cisco_psirt.py create mode 100644 inv_cisco_support-0.2.0-20230609.mkp diff --git a/agent_based/inv_cisco_eox.py b/agent_based/inv_cisco_eox.py index 58bcccf..23ce74f 100644 --- a/agent_based/inv_cisco_eox.py +++ b/agent_based/inv_cisco_eox.py @@ -58,7 +58,7 @@ def _create_eox_record(eoxfile, optional_columns): with open(eoxfile) as f: try: eoxrecord = json.load(f) - except ValueError as e: + except ValueError: return {} modifytime = os.path.getmtime(eoxfile) diff --git a/agent_based/inv_cisco_psirt.py b/agent_based/inv_cisco_psirt.py index 6348315..f72a108 100644 --- a/agent_based/inv_cisco_psirt.py +++ b/agent_based/inv_cisco_psirt.py @@ -63,8 +63,8 @@ def _create_psirt_record(filepath, filename, not_updated, dont_show_older_then, with open(psirtfile) as f: try: psirtrecord = json.load(f) - except ValueError as e: - exit() + except ValueError: + exit() advisories = psirtrecord.get('advisories') remove_advisories = [] @@ -132,31 +132,31 @@ def _get_profuct_family(sysdescription: str) -> str: def _get_os_version(cw_version, sysdescription, pids): - version = None + version = None - if str(cw_version).find('CW_VERSION$') == 0: - version = cw_version.split('$')[1] # .upper() - else: - sysdescription = sysdescription.split(',') - for entry in sysdescription: - # if entry.strip().lower().startswith('version'): - if 'version' in entry.lower(): - version = entry[entry.lower().find('version') + 7:].strip() - if version.startswith(':'): # AsyncOS - version = version[1:].strip() - version = version.split(' ')[0].strip() - version = version # .upper() - if not version: - for pid in pids: - physoftwarerev, phymodelname, physicalclass = pid - if physicalclass == '3': # chassies - version = physoftwarerev - # remove leading '0' in IOSXE version numbers - if version and version[0] == '0': - version = version.replace('.0', '.') - version = version.lstrip('0') - - return version + if str(cw_version).find('CW_VERSION$') == 0: + version = cw_version.split('$')[1] # .upper() + else: + sysdescription = sysdescription.split(',') + for entry in sysdescription: + # if entry.strip().lower().startswith('version'): + if 'version' in entry.lower(): + version = entry[entry.lower().find('version') + 7:].strip() + if version.startswith(':'): # AsyncOS + version = version[1:].strip() + version = version.split(' ')[0].strip() + version = version # .upper() + if not version: + for pid in pids: + physoftwarerev, phymodelname, physicalclass = pid + if physicalclass == '3': # chassies + version = physoftwarerev + # remove leading '0' in IOSXE version numbers + if version and version[0] == '0': + version = version.replace('.0', '.') + version = version.lstrip('0') + + return version def parse_inv_cisco_psirt(string_table: List[StringTable]) -> SnmpPsirt: diff --git a/agent_based/utils/inv_cisco_support.py b/agent_based/utils/inv_cisco_support.py index 958bf73..90dc6a7 100644 --- a/agent_based/utils/inv_cisco_support.py +++ b/agent_based/utils/inv_cisco_support.py @@ -31,9 +31,7 @@ class SnmpContractEntry: phydescr: str pid: str -# -# global variables -# + # list of PIDs to drop g_PID_black_list = ['BUILT-IN', 'MICRON', 'C400-MTFDD'] # list of PIDs to try by serial number diff --git a/bin/ciscoapi/cisco-bug.py b/bin/ciscoapi/cisco-bug.py index 9ddbb7f..e766bef 100755 --- a/bin/ciscoapi/cisco-bug.py +++ b/bin/ciscoapi/cisco-bug.py @@ -10,88 +10,55 @@ # # 2021-07-24: rewrite for python3.8 # -import logging -import ciscosupport -import ciscoapi import os import json -import time -import sys + +from cisco_live_cycle_utils import ( + configure_logger, + log_message, + expand_path, + get_ids_from_dir, + get_subdirs_from_dir, + remove_empty_sub_dirs, + sleep_random, + move_dir, +) +from ciscoapi import ( + AccessToken, + Settings, + get_bug_by_pid_and_release, +) def main(): + settings = Settings() + access_token = AccessToken(settings.client_id, settings.client_secret) + configure_logger(log_level=settings.log_level) - def get_new_token(): - response = ciscoapi.get_access_token() - access_token = response.get('access_token', '') - lifetime = int(time.time()) - int(response.get('lifetime', 3600)) - 30 - return access_token, lifetime - - ciscosupport.set_logging('debug') - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - base_path = '~/var/ciscoapi' - conf_file = '~/etc/ciscoapi/ciscoapi.json' - conf_file = os.path.expanduser(conf_file) - - access_token = '' - lifetime = 0 - config = {} - - refresh_found = 2 - refresh_notfound = 1 - - wait_after_start = True - max_wait_time = 15 - - loglevel = 'warning' - - if os.path.isfile(conf_file): - with open(conf_file) as f: - try: - config = json.load(f) - except ValueError as e: - logging.warning(f'snmp_cisco_eox:status:JSON load error: {e}') - exit() - # global options - base_path = config['global'].get('base_path', base_path) - wait_after_start = config['global'].get('wait_after_start', wait_after_start) - max_wait_time = config['global'].get('max_wait_time', max_wait_time) - loglevel = config['global'].get('loglevel', loglevel) - # bug api specific options - refresh_found = config['bug'].get('refresh_found', refresh_found) - refresh_notfound = config['bug'].get('refresh_notfound', refresh_notfound) - else: - logger.critical(f'Config file not found ({conf_file}).') - exit() - - # loglevel = 'debug' - ciscosupport.set_logging(loglevel) - - bug_path = base_path + '/bug' - path_found = ciscosupport.expand_path(bug_path + '/found') - path_not_found = ciscosupport.expand_path(bug_path + '/not_found') - path_request = ciscosupport.expand_path(bug_path + '/request') - path_missing = ciscosupport.expand_path(bug_path + '/missing') + bug_path = settings.base_path + '/bug' + path_found = expand_path(bug_path + '/found') + path_not_found = expand_path(bug_path + '/not_found') + path_request = expand_path(bug_path + '/request') + path_missing = expand_path(bug_path + '/missing') pids_requested = {} pids_refresh = {} - pids = [] # get list of bug reports to refresh - pids = ciscosupport.get_subdirs_from_dir(path_found) + pids = get_subdirs_from_dir(path_found) for pid in pids: - pids_refresh[pid.replace('_', '/')] = ','.join(ciscosupport.get_ids_from_dir(path_found+pid, refresh_time=refresh_found)) + pids_refresh[pid.replace('_', '/')] = ','.join(get_ids_from_dir(path_found+pid, + refresh_time=settings.bug_refresh_found)) # move not_found bug reports older then 'refresh_notfound' days to request (for refresh) - ciscosupport.move_dir(path_not_found, path_request, refresh_time=refresh_notfound) + move_dir(path_not_found, path_request, refresh_time=settings.bug_refresh_not_found) # get list of PIDs requests - pids = ciscosupport.get_subdirs_from_dir(path_request) - logger.debug('bug requests (PIDs): %s' % pids) + pids = get_subdirs_from_dir(path_request) + log_message(f'bug requests (PIDs): {pids}') for pid in pids: - pids_requested[pid.replace('_', '/')] = ','.join(ciscosupport.get_ids_from_dir(path_request+pid)) - logger.debug('bug requests (PIDs and releases): %s' % pids_requested) + pids_requested[pid.replace('_', '/')] = ','.join(get_ids_from_dir(path_request+pid)) + log_message(f'bug requests (PIDs and releases): {pids_requested}') if len(pids_refresh.keys()) > 0 or len(pids_requested.keys()) > 0: @@ -137,29 +104,26 @@ def main(): reqoptions = '' # wait random time after startup (load spread) - if wait_after_start: - ciscosupport.sleep_random(max_wait_time) - - # request access_token if no token available or if is expired - if access_token == '': - access_token, lifetime = get_new_token() - - if access_token == '': - logger.critical('failed to get access_token') - return + if settings.wait_after_start: + sleep_random(settings.max_wait_time) # first refresh bug reports for pid in pids_refresh.keys(): - if lifetime > int(time.time()): - access_token, lifetime = get_new_token() + # get bug records for time frame - bug_records = ciscoapi.get_bug_by_pid_and_release(pid, pids_refresh.get(pid), access_token, reqoptions) + bug_records = get_bug_by_pid_and_release( + pid, + pids_refresh.get(pid), + access_token, + reqoptions, + settings=settings + ) for entry in bug_records: pid = entry.get('pid') software_releases = entry.get('software_releases') status_code = int(entry.get('status_code', 200)) - logger.debug('bug return:PID: %s, Status: %s, Version: %s' % (pid, status_code, software_releases)) - path = ciscosupport.expand_path(path_found + pid.replace('/', '_')) + log_message(f'bug return:PID: {pid}, Status: {status_code}, Version: {software_releases}') + path = expand_path(path_found + pid.replace('/', '_')) # check if there where was an error, if so go to next pid if status_code == 200: bugs = entry.get('bugs', None) @@ -168,8 +132,7 @@ def main(): bug.pop('description') # remove description for release in software_releases.keys(): # create one bug list per software release bug_release = software_releases.get(release) - logger.debug('bug found:PID: %s, Releases: %s' % (pid, release)) - missing = entry.get('missing', {}) + log_message(f'bug found:PID: {pid}, Releases: {release}') new_bugs = [] for bug in bugs: if bug_release in bug.get('known_affected_releases'): @@ -180,7 +143,8 @@ def main(): try: bug_record = json.load(f) except ValueError as e: - logging.warning(f'{pid}:{release}:snmp_cisco_bug:bug_found:JSON load error: {e}') + log_message(f'{pid}:{release}:snmp_cisco_bug:bug_found:JSON load error: {e}', + level='WARNING') found_bugs = bug_record.get('bugs') for found_bug in found_bugs: @@ -193,16 +157,9 @@ def main(): bug_record['bugs'] = found_bugs # replace bug list with new list bug_record['total_records'] = len(found_bugs) # change number of records - # bug_record['missing'] = missing # replace missing state with new missing state - with open(path + release, 'w') as f: json.dump(bug_record, f) # write updated bug report - # if len(missing.keys()) != 0: - # path = ciscosupport.expand_path(path_missing + pid.replace('/', '_')) - # with open(path + release, 'w') as f: - # json.dump(missing, f) # write update missing state - else: for release in software_releases.keys(): if os.path.exists(path + release): @@ -242,7 +199,7 @@ def main(): # modified_date_earliest (earliest first) # reqoptions.append('sort_by=severity') - #reqoptions.append('page_index=2') + # reqoptions.append('page_index=2') if len(reqoptions) > 0: reqoptions = '?' + '&'.join(reqoptions) @@ -250,14 +207,18 @@ def main(): reqoptions = '' for pid in pids_requested.keys(): - if lifetime > int(time.time()): - access_token, lifetime = get_new_token() - bug_records = ciscoapi.get_bug_by_pid_and_release(pid, pids_requested.get(pid), access_token, reqoptions) + bug_records = get_bug_by_pid_and_release( + pid, + pids_requested.get(pid), + access_token, + reqoptions, + settings=settings + ) for entry in bug_records: pid = entry.get('pid') software_releases = entry.get('software_releases') status_code = int(entry.get('status_code', 200)) - logger.debug('bug return:PID: %s, Status: %s, Version: %s' % (pid, status_code, software_releases)) + log_message(f'bug return:PID: {pid}, Status: {status_code}, Version: {software_releases}') # check if there where was an error, if so go to next pid if status_code == 200: bugs = entry.get('bugs', None) @@ -267,49 +228,48 @@ def main(): for release in software_releases.keys(): # create one bug list per software release bug_release = software_releases.get(release) if bugs: - logger.debug('bug found:PID: %s, Releases: %s' % (pid, release)) + log_message(f'bug found:PID: %s{pid}, Releases: {release}') missing = entry.get('missing', {}) # split bugs by release - release_bugs = {'pid': pid, - 'software_release': release, - 'bugs': [], - 'missing': missing} + release_bugs = { + 'pid': pid, + 'software_release': release, + 'bugs': [], + 'missing': missing + } for bug in bugs: if bug_release in bug.get('known_affected_releases'): release_bugs['bugs'].append(bug) - # changed from entry.get('total_records') --> total records for all requested software releases release_bugs['total_records'] = len(release_bugs['bugs']) - path = ciscosupport.expand_path(path_found + pid.replace('/', '_')) + path = expand_path(path_found + pid.replace('/', '_')) with open(path + release, 'w') as f: json.dump(release_bugs, f) if len(missing.keys()) != 0: - path = ciscosupport.expand_path(path_missing + pid.replace('/', '_')) + path = expand_path(path_missing + pid.replace('/', '_')) with open(path + release, 'w') as f: json.dump(missing, f) else: - logger.debug('bug not found:PID: %s, Version: %s' % (pid, release)) - path = ciscosupport.expand_path(path_not_found + pid.replace('/', '_')) - logger.debug('not found: %s' % entry) + log_message(f'bug not found:PID: {pid}, Version: {release}') + path = expand_path(path_not_found + pid.replace('/', '_')) + log_message(f'not found: {entry}') with open(path + release, 'w') as f: json.dump(entry, f) pass # remove request file try: - logger.debug('bug delete request:PID: %s, Version: %s' % (pid, release)) + log_message(f'bug delete request:PID: {pid}, Version: {release}') os.remove(path_request + pid.replace('/', '_') + '/' + release) except OSError: pass # clean up (remove empty directories) - ciscosupport.remove_empty_sub_dirs(path_request) - ciscosupport.remove_empty_sub_dirs(path_found) - ciscosupport.remove_empty_sub_dirs(path_not_found) - ciscosupport.remove_empty_sub_dirs(path_missing) - -main() - + remove_empty_sub_dirs(path_request) + remove_empty_sub_dirs(path_found) + remove_empty_sub_dirs(path_not_found) + remove_empty_sub_dirs(path_missing) +main() diff --git a/bin/ciscoapi/cisco-eox.py b/bin/ciscoapi/cisco-eox.py index 85ed776..630ca3c 100755 --- a/bin/ciscoapi/cisco-eox.py +++ b/bin/ciscoapi/cisco-eox.py @@ -11,20 +11,27 @@ # # 2021-07-23: rewrite for python 3.8 # -import logging -import os import json -import time -import ciscosupport -import ciscoapi -import sys +from cisco_live_cycle_utils import ( + configure_logger, + log_message, + expand_path, + get_ids_from_dir, + remove_ids_from_list, + refresh_ids_from_dir, + remove_ids_from_dir, + sleep_random, +) +from ciscoapi import ( + AccessToken, + Settings, + get_eox_by_pid, + get_eox_by_serials, +) # split pids in known and unknown eox state def split_pids(eoxr): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - eox_known = [] eox_unkown = [] @@ -39,30 +46,27 @@ def split_pids(eoxr): for PID in EOXRecord: if PID.get('EOLProductID') != '': eox_known.append(PID) - logger.debug('EOLProductID : %s' % PID.get('EOLProductID')) + log_message(message=f'EOLProductID : {PID.get("EOLProductID")}') else: eox_unkown.append(PID) - logger.debug('EOXInputValue : %s' % PID.get('EOXInputValue')) + log_message(f'EOXInputValue : {PID.get("EOXInputValue")}') return {'eox_known': eox_known, 'eox_unknown': eox_unkown} # split serials, expects a list of EoX Records from get EoX by serial def split_serials(eoxr): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - serials = [] for PID in eoxr: EOLProductID = PID.get('EOLProductID') - logger.debug('serial split Eox: found PID: %s' % EOLProductID) + log_message(f'serial split Eox: found PID: {EOLProductID}') EOXInputValue = PID.get('EOXInputValue').split(',') for serial in EOXInputValue: - logger.debug('found Serial: %s' % serial) + log_message(f'found Serial: {serial}') eox_serial = PID.copy() eox_serial.update({'EOXInputValue': serial}) serials.append(eox_serial) - logger.debug('Serial EoX: %s' % eox_serial) + log_message(f'Serial EoX: {eox_serial}') return serials @@ -71,9 +75,6 @@ def split_serials(eoxr): # expects a list of EoX Records from Cisco EoX API 5.0, # returns a list of saved PIDs def save_eox(eox, path): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - saved = [] for PID in eox: @@ -82,147 +83,96 @@ def save_eox(eox, path): if EOLProductID == '': EOLProductID = PID.get('EOXInputValue') - with open(path + (EOLProductID.replace('/','_')), 'w') as f: + with open(path + (EOLProductID.replace('/', '_')), 'w') as f: json.dump(PID, f) saved.append(EOLProductID) return saved -# save EoX records to file by serial number, -# expects a list of EoX Records from Cisco EoX API 5.0, -# returns a list of saved Serials def save_serials(eox, path): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) + """ + Saves EoX records to file by serial number. + Args: + eox: List of EoX Records from Cisco EoX API 5.0 + path: file path where to save the EoX records + + Returns: List of serial numbers of saved EoX records + + """ saved = [] - for SERIAL in eox: - EOXInputValue = SERIAL.get('EOXInputValue') + for serial in eox: + EOXInputValue = serial.get('EOXInputValue') if EOXInputValue != '': with open(path + EOXInputValue, 'w') as f: - json.dump(SERIAL, f) + json.dump(serial, f) saved.append(EOXInputValue) return saved def main(): + settings = Settings() + access_token = AccessToken(settings.client_id, settings.client_secret) + configure_logger(log_level=settings.log_level) - ciscosupport.set_logging('debug') - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - base_path = '~/var/ciscoapi' - conf_file = '~/etc/ciscoapi/ciscoapi.json' - conf_file = os.path.expanduser(conf_file) - - access_token = '' - lifetime = 0 - pids = [] - serials = [] - config = {} - - # refresh time in days - eox_refresh_known = 31 - eox_refresh_unknown = 7 - - wait_after_start = True - max_wait_time = 15 - - loglevel = 'warning' - - if os.path.isfile(conf_file): - with open(conf_file) as f: - try: - config = json.load(f) - except ValueError as e: - logging.warning(f'snmp_cisco_eox:status:JSON load error: {e}') - exit() - - base_path = config['global'].get('base_path', base_path) - wait_after_start = config['global'].get('wait_after_start', wait_after_start) - max_wait_time = config['global'].get('max_wait_time', max_wait_time) - loglevel = config['global'].get('loglevel', loglevel) - - eox_refresh_known = config['eox'].get('eox_refresh_known', eox_refresh_known) - eox_refresh_unknown = config['eox'].get('eox_refresh_unknown', eox_refresh_unknown) - else: - logger.critical('Config file not found (%s).' % conf_file) - return False - - ciscosupport.set_logging(loglevel) - - eox_path = base_path + '/EoX' + eox_path = settings.base_path + '/EoX' path_found = eox_path + '/found' path_not_found = eox_path + '/not_found' path_request = eox_path + '/request' - path_request_pid = ciscosupport.expand_path(path_request + '/pid') - path_found_pid = ciscosupport.expand_path(path_found + '/pid') - path_not_found_pid = ciscosupport.expand_path(path_not_found + '/pid') + path_request_pid = expand_path(path_request + '/pid') + path_found_pid = expand_path(path_found + '/pid') + path_not_found_pid = expand_path(path_not_found + '/pid') - path_request_ser = ciscosupport.expand_path(path_request + '/ser') - path_found_ser = ciscosupport.expand_path(path_found + '/ser') - path_not_found_ser = ciscosupport.expand_path(path_not_found + '/ser') + path_request_ser = expand_path(path_request + '/ser') + path_found_ser = expand_path(path_found + '/ser') + path_not_found_ser = expand_path(path_not_found + '/ser') # create list of PIDs to request EoX status for - pids = ciscosupport.get_ids_from_dir(path_request_pid) - logger.debug('pid requests : %s' % pids) + pids = get_ids_from_dir(path_request_pid) + log_message(f'pid requests : {pids}') # remove already known PIDs from list - pids = ciscosupport.remove_ids_from_list(pids, path_found_pid) - logger.debug('pid requests : %s' % pids) + pids = remove_ids_from_list(pids, path_found_pid) + log_message(f'pid requests : {pids}') # remove PIDs already requested with unknown EoX status from list - pids = ciscosupport.remove_ids_from_list(pids, path_not_found_pid) - logger.debug('pid requests : %s' % pids) + pids = remove_ids_from_list(pids, path_not_found_pid) + log_message(f'pid requests : {pids}') # refresh PIDs after 30 days by default - pids = ciscosupport.refresh_ids_from_dir(path_not_found_pid, eox_refresh_unknown, pids, True) - logger.debug('pid requests : %s' % pids) - pids = ciscosupport.refresh_ids_from_dir(path_found_pid, eox_refresh_known, pids, False) - logger.debug('pid requests : %s' % pids) + pids = refresh_ids_from_dir(path_not_found_pid, settings.eox_refresh_unknown, pids, True) + log_message(f'pid requests : {pids}') + pids = refresh_ids_from_dir(path_found_pid, settings.eox_refresh_known, pids, False) + log_message(f'pid requests : {pids}') # create list of serial numbers to request EoX status for - serials = ciscosupport.get_ids_from_dir(path_request_ser) - logger.debug('ser requests : %s' % serials) + serials = get_ids_from_dir(path_request_ser) + log_message(f'ser requests : {serials}') # remove already known serials from list - serials = ciscosupport.remove_ids_from_list(serials, path_found_ser) - logger.debug('ser requests : %s' % serials) + serials = remove_ids_from_list(serials, path_found_ser) + log_message(f'ser requests : {serials}') # remove serials already requested with unknown EoX status from list - serials = ciscosupport.remove_ids_from_list(serials, path_not_found_ser) - logger.debug('ser requests : %s' % serials) + serials = remove_ids_from_list(serials, path_not_found_ser) + log_message(f'ser requests : {serials}') # refresh serials after 30 days by default - serials = ciscosupport.refresh_ids_from_dir(path_not_found_ser, eox_refresh_unknown, serials, True) - logger.debug('ser requests : %s' % serials) - serials = ciscosupport.refresh_ids_from_dir(path_found_ser, eox_refresh_known, serials, False) - logger.debug('ser requests : %s' % serials) + serials = refresh_ids_from_dir(path_not_found_ser, settings.eox_refresh_unknown, serials, True) + log_message(f'ser requests : {serials}') + serials = refresh_ids_from_dir(path_found_ser, settings.eox_refresh_known, serials, False) + log_message(f'ser requests : {serials}') if pids == [] and serials == []: - logger.debug('all list are empty. Do nothing.') + log_message('all list are empty. Do nothing.') return # wait random time after startup (load spread) - if wait_after_start: - ciscosupport.sleep_random(max_wait_time) - - # get time since epoch in seconds - starttime = int(time.time()) - - # request access_token if no token available or if is expired - if (access_token == '') or lifetime < starttime: - response = ciscoapi.get_access_token() - access_token = response.get('access_token', '') - lifetime = int(response.get('lifetime', 3600)) - - lifetime = starttime + lifetime - 30 - - if access_token == '': - logger.critical('failed to get access_token') - return + if settings.wait_after_start: + sleep_random(settings.max_wait_time) - if pids != []: - eox = ciscoapi.get_eox_by_pid(pids, access_token) + if pids is not []: + eox = get_eox_by_pid(pids=pids, access_token=access_token, settings=settings) # split eox records in a list of known and unknown pid records eox = split_pids(eox) @@ -230,18 +180,18 @@ def main(): # save known pid reports pids = save_eox(eox.get('eox_known'), path_found_pid) # delete requests for known pids - ciscosupport.remove_ids_from_dir(pids, path_request_pid) + remove_ids_from_dir(pids, path_request_pid) # save unknown pid reports pids = save_eox(eox.get('eox_unknown'), path_not_found_pid) # delete requests for unknown pids - ciscosupport.remove_ids_from_dir(pids, path_request_pid) + remove_ids_from_dir(pids, path_request_pid) # delete pids from known were the status changed to unknown - ciscosupport.remove_ids_from_dir(pids, path_found_pid) + remove_ids_from_dir(pids, path_found_pid) - if serials != []: - eox = ciscoapi.get_eox_by_serials(serials, access_token) - logger.debug('eox by ser: %s' % eox) + if serials is not []: + eox = get_eox_by_serials(serials=serials, access_token=access_token, settings=settings) + log_message(f'eox by ser: {eox}') # split eox records in a list of known and unknown pid records eox = split_pids(eox) @@ -250,18 +200,18 @@ def main(): serials = split_serials(eox.get('eox_known')) # save EoX records for serials with known EoX state serials = save_serials(serials, path_found_ser) - logger.debug('EoX Serials: known: %s' % serials) + log_message(f'EoX Serials: known: {serials}') # delete requests for known serials - ciscosupport.remove_ids_from_dir(serials, path_request_ser) + remove_ids_from_dir(serials, path_request_ser) # split EoX records for unknown PIDs in one entry per serial serials = split_serials(eox.get('eox_unknown')) # save EoX records for serials with known EoX state serials = save_serials(serials, path_not_found_ser) # delete requests for unknown serials - ciscosupport.remove_ids_from_dir(serials, path_request_ser) + remove_ids_from_dir(serials, path_request_ser) # delete serials from known were the status changed to unknown - ciscosupport.remove_ids_from_dir(serials, path_found_ser) + remove_ids_from_dir(serials, path_found_ser) main() diff --git a/bin/ciscoapi/cisco-psirt.py b/bin/ciscoapi/cisco-psirt.py index cc1caa6..3b0db9b 100755 --- a/bin/ciscoapi/cisco-psirt.py +++ b/bin/ciscoapi/cisco-psirt.py @@ -7,21 +7,33 @@ # # https://developer.cisco.com/docs/support-apis/ # -# 2018-06-06: fixed handling if state changes form found to notfound (delete old psirt found file) +# 2018-06-06: fixed handling if state changes form found to not_found (delete old psirt found file) # 2021-07-24: rewritten for python 3.8 # # -import logging import ntpath import os import json -import time -import ciscosupport -import ciscoapi -import sys from typing import List from dataclasses import dataclass +from cisco_live_cycle_utils import ( + configure_logger, + log_message, + get_ids_from_dir, + remove_ids_from_list, + refresh_ids_from_dir, + sleep_random, + expand_path, +) +from ciscoapi import ( + AccessToken, + Settings, + get_psirt_by_product_family, + get_psirt_by_iosxe_version, + get_psirt_by_ios_version, +) + @dataclass class Paths: @@ -40,21 +52,15 @@ g_logger = None def _psirt_remove_id_file(psirt_path: str, psirt_id: str): - # set logg module name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - # delete psirt file try: - logger.debug(f'delete psirt id file : {psirt_path + psirt_id}') + log_message(f'delete psirt id file : {psirt_path + psirt_id}') os.remove(psirt_path + psirt_id) except OSError: pass def _psirt_dump_record(psirt_record, psirt_id: str, psirt_path: str, psirt_path_request: str): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - with open(psirt_path + psirt_id, 'w') as f: json.dump(psirt_record, f) # delete request file @@ -72,8 +78,6 @@ def _check_psirt_record(psirt_record, psirt_id, psirt_path_found, psirt_path_req :param psirt_path_request: :returns: """ - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) for advisory in psirt_record.get('advisories'): # remove unwanted information from advisories @@ -102,22 +106,20 @@ def _get_psirt_id_list(product_family: str, paths: Paths, refresh: Refresh) -> L @return: list of PIDs """ # create list of ID's to request PSIRT status for - psirt_id_list = ciscosupport.get_ids_from_dir(paths.request + product_family) - g_logger.debug(f'psirt requests : {psirt_id_list}') + psirt_id_list = get_ids_from_dir(paths.request + product_family) + log_message(f'psirt requests : {psirt_id_list}') # remove already found ID's from list - psirt_id_list = ciscosupport.remove_ids_from_list(psirt_id_list, paths.found + product_family) - g_logger.debug(f'psirt requests : {psirt_id_list}') + psirt_id_list = remove_ids_from_list(psirt_id_list, paths.found + product_family) + log_message(f'psirt requests : {psirt_id_list}') # remove not found ID's from list - psirt_id_list = ciscosupport.remove_ids_from_list(psirt_id_list, paths.not_found + product_family) - g_logger.debug(f'psirt requests : {psirt_id_list}') + psirt_id_list = remove_ids_from_list(psirt_id_list, paths.not_found + product_family) + log_message(f'psirt requests : {psirt_id_list}') # refresh psirt after 1 day by default - psirt_id_list = ciscosupport.refresh_ids_from_dir(paths.not_found + product_family, refresh.not_found, - psirt_id_list, True) - g_logger.debug(f'psirt requests : {psirt_id_list}') - psirt_id_list = ciscosupport.refresh_ids_from_dir(paths.found + product_family, refresh.found, - psirt_id_list, False) - g_logger.debug(f'psirt requests : {psirt_id_list}') + psirt_id_list = refresh_ids_from_dir(paths.not_found + product_family, refresh.not_found, psirt_id_list, True) + log_message(f'psirt requests : {psirt_id_list}') + psirt_id_list = refresh_ids_from_dir(paths.found + product_family, refresh.found, psirt_id_list, False) + log_message(f'psirt requests : {psirt_id_list}') return psirt_id_list @@ -141,58 +143,19 @@ def _update_psirt_id(psirt_records: list, family_name: str, paths: Paths): def main(): - global g_logger - - ciscosupport.set_logging('debug') - # set logg modul name <file>:<module>.<function> - g_logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - - conf_file = '~/etc/ciscoapi/ciscoapi.json' - conf_file = os.path.expanduser(conf_file) - - # default paths - base_path = '~/var/ciscoapi' - - access_token = '' - lifetime = 0 - config = {} - - # refresh time in days - psirt_refresh_found = 1 - psirt_refresh_notfound = 1 - - wait_after_start = True - max_wait_time = 15 - loglevel = 'warning' - # loglevel = 'debug' - - if os.path.isfile(conf_file): - with open(conf_file) as f: - try: - config = json.load(f) - except ValueError as e: - logging.warning(f'snmp_cisco_eox:status:JSON load error: {e}') - exit() - - base_path = config['global'].get('base_path', base_path) - wait_after_start = config['global'].get('wait_after_start', wait_after_start) - max_wait_time = config['global'].get('max_wait_time', max_wait_time) - loglevel = config['global'].get('loglevel', loglevel) - refresh = Refresh( - found=config['psirt'].get('psirt_refresh_found', psirt_refresh_found), - not_found=config['psirt'].get('psirt_refresh_notfound', psirt_refresh_notfound) - ) - else: - g_logger.critical(f'Config file not found ({conf_file}).') - return False - - ciscosupport.set_logging(loglevel) - - base_path = ciscosupport.expand_path(base_path) - psirt_dir = base_path + 'psirt' + settings = Settings() + access_token = AccessToken(settings.client_id, settings.client_secret) + configure_logger(log_level=settings.log_level) + + refresh = Refresh( + found=settings.psirt_refresh_found, + not_found=settings.psirt_refresh_not_found + ) + + psirt_dir = expand_path(settings.base_path + '/psirt') paths = Paths( found=psirt_dir + '/found/', - not_found= psirt_dir + '/not_found/', + not_found=psirt_dir + '/not_found/', request=psirt_dir + '/request/' ) @@ -201,38 +164,23 @@ def main(): psirt_family = _get_psirt_id_list('family', paths, refresh) if (psirt_ios == []) and psirt_ios_xe == [] and psirt_family == []: - g_logger.debug('all list are empty. Do nothing.') + log_message('all list are empty. Do nothing.') return # wait random time after startup - if wait_after_start: - ciscosupport.sleep_random(max_wait_time) - - # get time since epoch in seconds - starttime = int(time.time()) - - # request access_token if no token available or if is expired - if (access_token == '') or lifetime < starttime: - response = ciscoapi.get_access_token() - access_token = response.get('access_token', '') - lifetime = int(response.get('lifetime', 3600)) - - lifetime = starttime + lifetime - 30 - - if access_token == '': - g_logger.critical('failed to get access_token') - return + if settings.wait_after_start: + sleep_random(settings.max_wait_time) - if psirt_family != []: - psirt_records = ciscoapi.get_psirt_by_product_family(psirt_family, access_token) + if psirt_family is not []: + psirt_records = get_psirt_by_product_family(psirt_family, access_token, settings=settings) _update_psirt_id(psirt_records, 'family', paths) - if psirt_ios_xe != []: - psirt_records = ciscoapi.get_psirt_by_iosxe_version(psirt_ios_xe, access_token) + if psirt_ios_xe is not []: + psirt_records = get_psirt_by_iosxe_version(psirt_ios_xe, access_token, settings=settings) _update_psirt_id(psirt_records, 'IOS-XE', paths) - if psirt_ios != []: - psirt_records = ciscoapi.get_psirt_by_ios_version(psirt_ios, access_token) + if psirt_ios is not []: + psirt_records = get_psirt_by_ios_version(psirt_ios, access_token, settings=settings) _update_psirt_id(psirt_records, 'IOS', paths) diff --git a/bin/ciscoapi/cisco-sn2info.py b/bin/ciscoapi/cisco-sn2info.py index 3a9b2e6..1a45b2b 100755 --- a/bin/ciscoapi/cisco-sn2info.py +++ b/bin/ciscoapi/cisco-sn2info.py @@ -13,23 +13,30 @@ # # 2021-07-23: rewrite for python 3.8 # -import logging -import os import json -import time -import ciscosupport -import ciscoapi -import sys - - -def sn2info_split_covered(sn2inforecords): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) +from cisco_live_cycle_utils import ( + configure_logger, + log_message, + expand_path, + get_ids_from_dir, + remove_ids_from_list, + refresh_ids_from_dir, + remove_ids_from_dir, + sleep_random, +) +from ciscoapi import ( + AccessToken, + Settings, + get_coverage_summary_by_serials, +) + + +def sn2info_split_covered(sn2info_records): sn2info_covered = [] sn2info_notcovered = [] - for response in sn2inforecords: + for response in sn2info_records: sn2inforecord = response.get('serial_numbers') # PaginationResponseRecord = response.get('PaginationResponseRecord') # PageIndex = PaginationResponseRecord.get('PageIndex') @@ -40,17 +47,14 @@ def sn2info_split_covered(sn2inforecords): for serial in sn2inforecord: if serial.get('is_covered') == 'YES': sn2info_covered.append(serial) - logger.debug('SN2INFO covered : %s' % serial.get('sr_no')) + log_message(f'SN2INFO covered : {serial.get("sr_no")}') else: sn2info_notcovered.append(serial) - logger.debug('SN2INFO not covered : %s' % serial.get('sr_no')) + log_message(f'SN2INFO not covered : {serial.get("sr_no")}') return {'sn2info_covered': sn2info_covered, 'sn2info_notcovered': sn2info_notcovered} def sn2info_save_serials(sn2infos, path): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - saved = [] for sn2info in sn2infos: @@ -64,106 +68,53 @@ def sn2info_save_serials(sn2infos, path): def main(): + settings = Settings() + access_token = AccessToken(settings.client_id, settings.client_secret) + configure_logger(log_level=settings.log_level) - ciscosupport.set_logging('debug') - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - - conf_file = '~/etc/ciscoapi/ciscoapi.json' - conf_file = os.path.expanduser(conf_file) - - # default paths - base_path = '~/var/ciscoapi' - - access_token = '' - lifetime = 0 - sn2info = [] - config = {} - - # refresh time in days - sn2info_refresh_covered = 31 - sn2info_refresh_notcovered = 7 - - wait_after_start = True - max_wait_time = 15 - loglevel = 'warning' - - if os.path.isfile(conf_file): - with open(conf_file) as f: - try: - config = json.load(f) - except ValueError as e: - logging.warning(f'snmp_cisco_eox:status:JSON load error: {e}') - exit() - - wait_after_start = config['global'].get('base_path', base_path) - sn2info_refresh_covered = config['sn2info'].get('sn2info_refresh_covered', sn2info_refresh_covered) - sn2info_refresh_notcovered = config['sn2info'].get('sn2info_refresh_notcovered', sn2info_refresh_notcovered) - - wait_after_start = config['global'].get('wait_after_start', wait_after_start) - max_wait_time = config['global'].get('max_wait_time', max_wait_time) - loglevel = config['global'].get('loglevel', loglevel) - else: - logger.critical(f'Config file not found ({conf_file}).') - exit() - - ciscosupport.set_logging(loglevel) - - sn2info_dir = base_path + '/sn2info' - path_found = ciscosupport.expand_path(sn2info_dir + '/found/') - path_not_found = ciscosupport.expand_path(sn2info_dir + '/not_found/') - path_request = ciscosupport.expand_path(sn2info_dir + '/request/') + sn2info_dir = settings.base_path + '/sn2info' + path_found = expand_path(sn2info_dir + '/found/') + path_not_found = expand_path(sn2info_dir + '/not_found/') + path_request = expand_path(sn2info_dir + '/request/') # create list of serial numbers to request SN2INFO status for - sn2info = ciscosupport.get_ids_from_dir(path_request) - logger.debug('sn2info requests : %s' % sn2info) + sn2info = get_ids_from_dir(path_request) + log_message(f'sn2info requests : {sn2info}') # remove covered serials from list - sn2info = ciscosupport.remove_ids_from_list(sn2info, path_found) - logger.debug('sn2info requests : %s' % sn2info) + sn2info = remove_ids_from_list(sn2info, path_found) + log_message(f'sn2info requests : {sn2info}') # remove not covered serials from list - sn2info = ciscosupport.remove_ids_from_list(sn2info, path_not_found) - logger.debug('sn2info requests : %s' % sn2info) + sn2info = remove_ids_from_list(sn2info, path_not_found) + log_message(f'sn2info requests : {sn2info}') # refresh sn2info serials after 31 days by default - sn2info = ciscosupport.refresh_ids_from_dir(path_not_found, sn2info_refresh_notcovered, sn2info, True) - logger.debug('sn2info requests : %s' % sn2info) - sn2info = ciscosupport.refresh_ids_from_dir(path_found, sn2info_refresh_covered, sn2info, False) - logger.debug('sn2info requests : %s' % sn2info) + sn2info = refresh_ids_from_dir(path_not_found, settings.sn2info_refresh_not_covered, sn2info, True) + log_message(f'sn2info requests : {sn2info}') + sn2info = refresh_ids_from_dir(path_found, settings.sn2info_refresh_covered, sn2info, False) + log_message(f'sn2info requests : {sn2info}') - if sn2info == [] : - logger.debug('all list are empty. Do nothing.') + if sn2info is []: + log_message('all list are empty. Do nothing.') return # wait random time after startup - if wait_after_start: - ciscosupport.sleep_random(max_wait_time) - - # get time since epoch in seconds - starttime = int(time.time()) - - # request access_token if no token available or if is expired - if (access_token == '') or lifetime < starttime: - response = ciscoapi.get_access_token() - access_token = response.get('access_token', '') - lifetime = int(response.get('lifetime', 3600)) - - lifetime = starttime + lifetime - 30 - - if access_token == '': - logger.critical('failed to get access_token') - return - - if sn2info != []: - sn2inforecords = ciscoapi.get_coverage_summary_by_serials(sn2info, access_token) - logger.debug('sn2info response: %s' % sn2inforecords) - sn2info = sn2info_split_covered(sn2inforecords) + if settings.wait_after_start: + sleep_random(settings.max_wait_time) + + if sn2info is not []: + sn2info_records = get_coverage_summary_by_serials( + serials=sn2info, + access_token=access_token, + settings=settings + ) + log_message(f'sn2info response: {sn2info_records}') + sn2info = sn2info_split_covered(sn2info_records) serials = sn2info_save_serials(sn2info.get('sn2info_covered'), path_found) - ciscosupport.remove_ids_from_dir(serials, path_request) + remove_ids_from_dir(serials, path_request) serials = sn2info_save_serials(sn2info.get('sn2info_notcovered'), path_not_found) - ciscosupport.remove_ids_from_dir(serials, path_request) + remove_ids_from_dir(serials, path_request) # delete serials from covered were the status changed to uncovered - ciscosupport.remove_ids_from_dir(serials, path_found) + remove_ids_from_dir(serials, path_found) main() - diff --git a/bin/ciscoapi/cisco_live_cycle_utils.py b/bin/ciscoapi/cisco_live_cycle_utils.py new file mode 100755 index 0000000..a32ddf9 --- /dev/null +++ b/bin/ciscoapi/cisco_live_cycle_utils.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python +# -*- encoding: utf-8; py-indent-offset: 4 -*- + +# +# 15.04.2017 : Th.L. : Support for Cisco API +# +# https://developer.cisco.com/docs/support-apis/ +# + +import logging +import os +import time +import random +import sys +# import json +# import copy +# from ciscoapi import ( +# Settings, +# get_product_mdf_information_by_pid, +# ) + + +def configure_logger(_path: str = '', _log_to_console: bool = True, log_level: str = 'INFO'): + log_formatter = logging.Formatter('%(asctime)s :: %(levelname)s :: %(name)s :: %(module)s ::%(message)s') + log = logging.getLogger('root') + + numeric_level = getattr(logging, log_level.upper(), None) + if isinstance(numeric_level, int): + logging.getLogger().setLevel(numeric_level) + else: + logging.getLogger().setLevel(logging.WARNING) + + # log_file = path + # # create a new file > 5 mb size + # log_handler_file = logging.handlers.RotatingFileHandler( + # log_file, + # mode='a', + # maxBytes=5 * 1024 * 1024, + # backupCount=10, + # # encoding=None, + # # delay=0 + # ) + + # log_handler_file.setFormatter(log_formatter) + # log_handler_file.setLevel(logging.INFO) + # log.addHandler(log_handler_file) + + log_handler_console = logging.StreamHandler(sys.stdout) + log_handler_console.setFormatter(log_formatter) + log_handler_console.setLevel(logging.INFO) + log.addHandler(log_handler_console) + + +def log_message(message: str, level: str = 'DEBUG'): + log = logging.getLogger() + if level.upper() == 'CRITICAL': + log.critical(message) + elif level.upper() == 'ERROR': + log.error(message) + elif level.upper() == 'WARNING': + log.warning(message) + elif level.upper() == 'INFO': + log.info(message) + elif level.upper() == 'DEBUG': + log.debug(message) + else: + log.warning(f'unknown log_level: {level}') + + +def sleep_random(max_minutes): + sleep_time = random.randint(1, 60 * max_minutes) + log_message(message=f'{sleep_time} seconds', level='INFO') + time.sleep(sleep_time) + return + + +# read list of files from dir (eq. (P)IDs or SERIALs) (don't change to uppercase) +def get_ids_from_dir(directory, refresh_time: int = 0): + refresh_time = refresh_time * 86400 + start_time = int(time.time()) + ids = [] + for (dir_path, dir_names, file_names) in os.walk(directory): + for entry in file_names: + modify_time = int(os.path.getmtime(dir_path + '/' + entry)) + if (start_time - modify_time) > refresh_time: + ids.append(str(entry).replace('_', '/')) + # do not read subdirs + break + # insert cleanup here (filter unwanted names, chars, etc...) + return ids + + +# read list of sub directories from directory (PIDs) (don't anything) +def get_subdirs_from_dir(base_dir): + sub_dirs = [] + for (dir_path, sub_dirs, filenames) in os.walk(base_dir): + break + # insert cleanup here (filter unwanted names, chars, etc...) + return sub_dirs + + +# read list of IOS/IOSXE Versions from directory (don't change to uppercase) +def get_version_from_dir(directory): + versions = [] + for (dir_path, dir_names, file_names) in os.walk(directory): + for entry in file_names: + versions.append(str(entry)) + # do not read subdirs + break + # insert cleanup here (filter unwanted names, chars, etc...) + return versions + + +# delete (P)IDs or SERIALs files from directory (requests) +def remove_ids_from_dir(ids, directory): + for entry in ids: + try: + os.remove(directory + entry.replace('/', '_')) + except OSError: + pass + + +# remove (P)IDs or SERIALs from list of (P)ID or serials +def remove_ids_from_list(ids, directory): + known_ids = [] + for (dir_path, dir_names, file_names) in os.walk(directory): + known_ids.extend(file_names) + # do not read subdirs + break + + for known_id in known_ids: + known_id = known_id.replace('_', '/') + for entry in ids: + if known_id == entry: + ids.remove(entry) + return ids + + +# returns al list of ids to refresh, +# expects a directory with ids to check, the time interval, a list of IDs to add +# if remove True it will delete the ID files from refresh_dir +def refresh_ids_from_dir(refresh_dir, refresh_time, ids, remove): + refresh_dir = expand_path(refresh_dir) + # get seconds from # of days (days * 24 * 60 * 60 --> days * 86400) + refresh_time = int(refresh_time) * 86400 + start_time = int(time.time()) + refresh_ids = get_ids_from_dir(refresh_dir) + if refresh_ids is not []: + for entry in refresh_ids: + modify_time = int(os.path.getmtime(refresh_dir + entry.replace('/', '_'))) + if (start_time - modify_time) > refresh_time: + ids.append(entry) + if remove: + try: + os.remove(refresh_dir + entry.replace('/', '_')) + except OSError: + pass + return ids + + +# check if dir exists, if not try to create it. +# return True if dir exists or creation was ok. +# return False if dir not exists and creation was not ok +def check_dir_and_create(directory): + directory = os.path.dirname(directory) + if not os.path.exists(directory): + try: + os.makedirs(directory) + except: + return False + return True + + +# expand homedir and add '/' if necessary and create directory if it not exists +def expand_path(path): + homedir = os.path.expanduser('~') + + if path.startswith('~'): + path = homedir + path[1:] + + if not path.endswith('/'): + path += '/' + + if not check_dir_and_create(path): + return '' + + return path + +# needs to be moved out of cisco_live_cycle_utils +# get cisco product series by pid +# def get_cisco_product_series_by_pid(pids, access_token, settings: Settings): +# +# conf_file = '~/etc/ciscoapi/ciscoapi.json' +# conf_file = os.path.expanduser(conf_file) +# +# productseriesfile = '~/var/ciscoapi/productinfo' +# product_series = {} +# +# if os.path.isfile(conf_file): +# with open(conf_file) as f: +# try: +# config = json.load(f) +# productseriesfile = config['productinfo'].get('productseriesfile', productseriesfile) +# except ValueError as e: +# log_message(f'snmp_cisco_eox:status:JSON load error: {e}', level='WARNING') +# +# productseriesfile = expand_path(productseriesfile) + 'productseries.json' +# +# if os.path.isfile(productseriesfile): +# with open(productseriesfile) as f: +# product_series = json.load(f) +# +# requestpids = copy.deepcopy(pids) +# if product_series != {}: +# keys = product_series.keys() +# for pid in requestpids: +# if pid in keys: +# requestpids.remove(pid) +# +# if requestpids is not []: +# product_infos = get_product_mdf_information_by_pid(pids, access_token, settings=settings) +# +# for entry in product_infos: +# product_series.update({entry.get('product_id'): entry.get('product_series')}) +# +# with open(productseriesfile, 'w') as f: +# json.dump(product_series, f) +# +# return_product_series = {} +# for pid in pids: +# return_product_series.update({pid: product_series.get(pid, 'not found')}) +# +# return return_product_series + + +# remove empty directories +def remove_empty_sub_dirs(base_dir): + subdirs = get_subdirs_from_dir(base_dir) + for subdir in subdirs: + try: + os.rmdir(base_dir + subdir) + except OSError as e: + log_message(f'can not delete: {base_dir}, Error:{e}') + pass + + +# move contents of source_dir to destination_dir +# only one level deep, lave source_dir +def move_dir(source_dir, destination_dir, **kwargs): + refresh_time = int(kwargs.get('refresh_time', 0)) * 86400 + starttime = int(time.time()) + + sub_dirs = get_subdirs_from_dir(source_dir) + for sub_dir in sub_dirs: + files = get_ids_from_dir(source_dir + sub_dir) + if len(files) > 0: + source_path = expand_path(source_dir + sub_dir) + destination_path = expand_path(destination_dir + sub_dir) + for file in files: + source_file = source_path + file + destination_file = destination_path + file + modify_time = int(os.path.getmtime(source_file)) + if (starttime - modify_time) > refresh_time: + try: + os.rename(source_file, destination_file) # rename (move) contents of not_found to request + except OSError as e: + log_message(message=f'error:{e}, source: {source_file}, destionation: {destination_file}', + level='ERROR') + + remove_empty_sub_dirs(source_dir) diff --git a/bin/ciscoapi/ciscoapi.py b/bin/ciscoapi/ciscoapi.py index 4232b96..133c77e 100755 --- a/bin/ciscoapi/ciscoapi.py +++ b/bin/ciscoapi/ciscoapi.py @@ -11,157 +11,208 @@ # 2018-09-25: performance improvement "psirt_response.encoding = 'UTF-8'", drops json.loads # from about 4 min to some seconds # 2021-07-23: rewritten for python 3.8 +# 2023-06-09: changed for new rest api endpoint (apix.cisco.com) +# refactoring get_token and settings +# some cleanup +# +# supportapis[dash]help[at]cisco[dot]com # import requests import json import time -import logging -from os.path import isfile -from os.path import expanduser -import urllib -import sys - -# global variables -g_client_id = '' -g_client_secret = '' -g_proxies = {} -g_use_system_proxies = True -g_useauthproxy = False -g_client_fqdn = '' -g_root_cert = True -g_authproxyurl = 'https://cmk.bech-noc.de/api/cauthproxy.py' - - -def read_config(): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - - # define global variables - global g_client_id - global g_client_secret - global g_proxies - global g_use_system_proxies - global g_useauthproxy - global g_client_fqdn - global g_root_cert - global g_authproxyurl - - # read parameters from config file - conf_file = '~/etc/ciscoapi/ciscoapi.json' - conf_file = expanduser(conf_file) - - if isfile(conf_file): +from os.path import ( + expanduser, +) +from typing import Dict, List +from cisco_live_cycle_utils import ( + log_message, +) + + +class Settings: + def __init__(self): + conf_file = '~/etc/ciscoapi/ciscoapi.json' + conf_file = expanduser(conf_file) + with open(conf_file) as f: try: - config = json.load(f) + self.__settings = json.load(f) except ValueError as e: - logging.warning(f'snmp_cisco_eox:status:JSON load error: {e}') + log_message(f'snmp_cisco_eox:status:JSON load error: {e}', level='WARNING') + exit() + except FileNotFoundError as e: + log_message(f'Config file not found {e}.', level='CRITICAL') exit() - g_client_id = config['cisco_api']['client_id'] - g_client_secret = config['cisco_api']['client_secret'] - if config['global'].get('http_proxy'): - g_proxies.update({'http': config['global'].get('http_proxy')}) - if config['global'].get('https_proxy'): - g_proxies.update({'https': config['global'].get('https_proxy')}) - g_use_system_proxies = (config['global'].get('use_system_proxies', False)) - # parameters for authproxy - g_useauthproxy = config['cisco_api']['useauthproxy'] - if g_useauthproxy: - g_client_fqdn = config['cisco_api'].get('client_fqdn') - g_root_cert = config['cisco_api'].get('root_cert') - g_authproxyurl = config['cisco_api'].get('authproxyurl') - else: - logger.critical('Config file not found (%s).' % conf_file) - return False - - #if g_proxies == {}: - # if g_use_system_proxies: - # g_proxies = urllib.getproxies() - - return True - - -# Request Cisco API access token, token is valid for 1 hour (by default) -def get_access_token(): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - - # define global variables - global g_client_id - global g_client_secret - global g_proxies - global g_useauthproxy - global g_client_fqdn - global g_root_cert - global g_authproxyurl - - # define local variables - access_token = '' - lifetime = 0 - grant_type = 'client_credentials' - authheaders = {'content-Type': 'application/x-www-form-urlencoded', 'accept': 'application/json'} - authurl = 'https://cloudsso.cisco.com/as/token.oauth2' - param_missing = False - verify = True - - if read_config(): - if g_client_id == '': - logging.critical('Option client_id missing.') - param_missing = True - if g_client_secret == '': - logging.critical('Option client_secret missing.') - param_missing = True - # parameters for authproxy - if g_useauthproxy: - # if not g_root_cert: - # g_root_cert = expanduser(g_root_cert) - # if not isfile(g_root_cert): - # logging.critical('Root Cert not found: %s' % g_root_cert) - # param_missing = True - if g_authproxyurl == '': - logging.critical('Option authproxyurl missing') - param_missing = True - - else: - return {'access_token': access_token, 'lifetime': lifetime} - - if param_missing: - return {'access_token': access_token, 'lifetime': lifetime} - - authreqdata = {'client_id': g_client_id, 'client_secret': g_client_secret, 'grant_type': grant_type} - - # Disable invalid certificate warnings. - # requests.packages.urllib3.disable_warnings() - - # modify request if we use an authentication proxy - if g_useauthproxy: - if g_client_fqdn != '': - authreqdata.update({'client_fqdn': g_client_fqdn}) - verify = g_root_cert - authurl = g_authproxyurl - - logger.debug('auth request: authurl=%s headers=%s data=%s proxies=%s verify=%s' % (authurl, authheaders, authreqdata, g_proxies, verify)) - response = requests.post(authurl, headers=authheaders, data=authreqdata, proxies=g_proxies, verify=verify) - - logger.debug('response.text: %s' % response.text) - if response.ok: - authresponse = json.loads(response.text) - access_token = authresponse.get("access_token") - # access_token = access_token.decode('ascii') - lifetime = int(authresponse.get("expires_in")) + self.__base_path = '~/var/ciscoapi' + self.__auth_proxy_url = 'https://cmk.bech-noc.de/api/cauthproxy.py' + self.__proxies = {} + self.__wait_after_start = True + self.__max_wait_time = 15 + self.__log_level = 'warning' + self.__eox_refresh_known = 31 + self.__eox_refresh_unknown = 7 + self.__sn2info_refresh_covered = 31 + self.__sn2info_refresh_not_covered = 7 + self.__bug_refresh_found = 2 + self.__bug_refresh_not_found = 1 + self.__psirt_refresh_found = 1 + self.__psirt_refresh_not_found = 1 + self.__suggestion_refresh_found = 31 + self.__suggestion_refresh_not_found = 7 + + if self.__settings['global'].get('http_proxy'): + self.__proxies .update({'http': self.__settings['global'].get('http_proxy')}) + if self.__settings['global'].get('https_proxy'): + self.__proxies .update({'https': self.__settings['global'].get('https_proxy')}) + + @property + def client_id(self) -> str: + return self.__settings['cisco_api']['client_id'] + + @property + def client_secret(self) -> str: + return self.__settings['cisco_api']['client_secret'] + + @property + def proxies(self) -> Dict[str, str]: + return self.__proxies + + @property + def use_system_proxies(self) -> bool: + return self.__settings['global'].get('use_system_proxies', False) + + @property + def use_auth_proxy(self) -> bool: + return self.__settings['cisco_api'].get('use_auth_proxy', False) + + @property + def client_fqdn(self) -> str: + return self.__settings['cisco_api'].get('client_fqdn') + + @property + def root_cert(self) -> bool: + return self.__settings['cisco_api'].get('root_cert', False) + + @property + def auth_proxy_url(self) -> str: + return self.__settings['cisco_api'].get('auth_proxy_url') + + @property + def base_path(self) -> str: + return self.__settings['global'].get('base_path', self.__base_path) + + @property + def wait_after_start(self) -> bool: + return self.__settings['global'].get('wait_after_start', self.__wait_after_start) + + @property + def max_wait_time(self) -> int: + return self.__settings['global'].get('max_wait_time', self.__max_wait_time) + + @property + def log_level(self) -> str: + return self.__settings['global'].get('log_level', self.__log_level) + + @property + def eox_refresh_known(self) -> int: + return self.__settings['eox'].get('refresh_known', self.__eox_refresh_known) + + @property + def eox_refresh_unknown(self) -> int: + return self.__settings['eox'].get('refresh_known', self.__eox_refresh_unknown) + + @property + def sn2info_refresh_covered(self) -> int: + return self.__settings['sn2info'].get('refresh_covered', self.__sn2info_refresh_covered) + + @property + def sn2info_refresh_not_covered(self) -> int: + return self.__settings['sn2info'].get('refresh_not_covered', self.__sn2info_refresh_not_covered) + + @property + def bug_refresh_found(self) -> int: + return self.__settings['bug'].get('refresh_found', self.__bug_refresh_found) + + @property + def bug_refresh_not_found(self) -> int: + return self.__settings['bug'].get('refresh_found', self.__bug_refresh_not_found) + + @property + def psirt_refresh_found(self) -> int: + return self.__settings['psirt'].get('refresh_found', self.__psirt_refresh_found) + + @property + def psirt_refresh_not_found(self) -> int: + return self.__settings['psirt'].get('refresh_not_found', self.__psirt_refresh_not_found) + + @property + def suggestion_refresh_found(self) -> int: + return self.__settings['suggestion'].get('refresh_found', self.__suggestion_refresh_found) + + @property + def suggestion_refresh_not_found(self) -> int: + return self.__settings['suggestion'].get('refresh_not_found', self.__suggestion_refresh_not_found) + + +class AccessToken: + def __init__( + self, + client_id: str, + client_secret: str, + ): + self.__client_id = client_id + self.__client_secret = client_secret + self.__proxies = {} + self.__use_auth_proxy = None + self.__client_fqdn = '' + self.__root_cert = True + self.__auth_proxy_url = 'https://cmk.bech-noc.de/api/cauthproxy.py' + self.__access_token = '' + self.__lifetime = 0 + self.__time = 0 + self.__auth_headers = { + 'content-Type': 'application/x-www-form-urlencoded', + 'accept': 'application/json' + } + self.__grant_type = 'client_credentials' + self.__auth_url = 'https://id.cisco.com/oauth2/default/v1/token' + self.__verify = True + self.__auth_req_data = { + 'client_id': self.__client_id, + 'client_secret': self.__client_secret, + 'grant_type': self.__grant_type + } + + @property + def token(self) -> str: + + if self.__access_token and time.time() < self.__lifetime: + return self.__access_token + else: + self.__time = time.time() + response = requests.post( + self.__auth_url, + headers=self.__auth_headers, + data=self.__auth_req_data, + proxies=self.__proxies, + verify=self.__verify) - return {'access_token': access_token, 'lifetime': lifetime} + if response.ok: + auth_response = json.loads(response.text) + self.__lifetime = self.__time + int(auth_response.get("expires_in")) - 60 + self.__access_token = auth_response.get("access_token") + return self.__access_token # generic cisco api request for all get info by serialnumber -def get_info_by_serials(serials, access_token, requrl, max_serials): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - - # define global variables - global g_proxies - +def get_info_by_serials( + serials: List[str], + access_token: AccessToken, + req_url: str, + max_serials: int, + settings: Settings +): # locale variablen max_serial_length = 40 max_req_per_second = 5 @@ -171,9 +222,6 @@ def get_info_by_serials(serials, access_token, requrl, max_serials): count = 1 info = [] - # read config, we need only the proxies - read_config() - # split list of Serials in chunks of max 75 serials, each max 40 bytes length for serial in serials: if len(serial) <= max_serial_length: @@ -185,32 +233,26 @@ def get_info_by_serials(serials, access_token, requrl, max_serials): count = 1 optimisedserials.append(serialsstr[:-1]) - headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token} + headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token.token} count = 0 for serials in optimisedserials: # Disable invalid certificate warnings. # requests.packages.urllib3.disable_warnings() - apiresponse = requests.get(requrl + serials, headers=headers, proxies=g_proxies) + response = requests.get(req_url + serials, headers=headers, proxies=settings.proxies) count += 1 # only 5 request per second are allowed if count == max_req_per_second: time.sleep(wait_time) count = 0 - if apiresponse.ok: - apiresponse.encoding = 'UTF-8' - info.append(json.loads(apiresponse.text)) + if response.ok: + response.encoding = 'UTF-8' + info.append(json.loads(response.text)) return info -def get_eox_by_pid(pids, access_token): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - - # define global variables - global g_proxies - +def get_eox_by_pid(pids: List[str], access_token: AccessToken, settings: Settings): # local variables max_pid_length = 240 max_pids = 20 @@ -221,9 +263,6 @@ def get_eox_by_pid(pids, access_token): count = 1 eoxr = [] - # read config, we need only the proxies - read_config() - # split list of PIDs in chunks of max 240 bytes length for pid in pids: if (len(pidstr) + len(pid)) < max_pid_length: @@ -234,15 +273,14 @@ def get_eox_by_pid(pids, access_token): pidstr = '' count = 1 optimisedpids.append(pidstr[:-1]) - - headers = {'accept': 'application/json', 'Authorization': f'Bearer {access_token}'} - requrl = 'https://api.cisco.com/supporttools/eox/rest/5/EOXByProductID/1/' + headers = {'accept': 'application/json', 'Authorization': f'Bearer {access_token.token}'} + req_url = 'https://apix.cisco.com/supporttools/eox/rest/5/EOXByProductID/1/' count = 0 for productids in optimisedpids: # Disable invalid certificate warnings. # requests.packages.urllib3.disable_warnings() - eoxresponse = requests.get(requrl + productids, headers=headers, proxies=g_proxies) + eoxresponse = requests.get(req_url + productids, headers=headers, proxies=settings.proxies) count += 1 # only 5 request per second are allowed if count == max_req_per_second: @@ -253,181 +291,108 @@ def get_eox_by_pid(pids, access_token): return eoxr -def get_eox_by_serials(serials, access_token): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) +def get_eox_by_serials(serials: List[str], access_token: AccessToken, settings: Settings): max_serials = 20 - requrl = 'https://api.cisco.com/supporttools/eox/rest/5/EOXBySerialNumber/1/' - info = get_info_by_serials(serials, access_token, requrl, max_serials) - return info - - -def get_coverage_status_by_serials(serials, access_token): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - max_serials = 75 - requrl = 'https://api.cisco.com/sn2info/v2/coverage/status/serial_numbers/' - info = get_info_by_serials(serials, access_token, requrl, max_serials) + req_url = 'https://apix.cisco.com/supporttools/eox/rest/5/EOXBySerialNumber/1/' + info = get_info_by_serials(serials, access_token, req_url, max_serials, settings) return info -def get_coverage_summary_by_serials(serials, access_token): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) +def get_coverage_summary_by_serials(serials: List[str], access_token: AccessToken, settings: Settings): max_serials = 75 - requrl = 'https://api.cisco.com/sn2info/v2/coverage/summary/serial_numbers/' - info = get_info_by_serials(serials, access_token, requrl, max_serials) + req_url = 'https://apix.cisco.com/sn2info/v2/coverage/summary/serial_numbers/' + info = get_info_by_serials(serials, access_token, req_url, max_serials, settings) return info -def get_orderable_pid_by_serials(serials, access_token): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - max_serials = 75 - requrl = 'https://api.cisco.com/sn2info/v2/identifiers/orderable/serial_numbers/' - info = get_info_by_serials(serials, access_token, requrl, max_serials) - return info - - -def get_owner_coverage_status_by_serials(serials, access_token): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - max_serials = 75 - requrl = 'https://api.cisco.com/sn2info/v2/coverage/owner_status/serial_numbers/' - info = get_info_by_serials(serials, access_token, requrl, max_serials) - return info - - -def get_psirt_by_ios_version(psirtios, access_token): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - +def get_psirt_by_ios_version(psirtios: List[str], access_token: AccessToken, settings: Settings): info = [] - # read config, we need only the proxies - read_config() - - headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token} - requrl = 'https://api.cisco.com/security/advisories/ios?version=' + headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token.token} + req_url = 'https://apix.cisco.com/security/advisories/ios?version=' # requests.packages.urllib3.disable_warnings() - if list(psirtios) != []: + if list(psirtios) is not []: for ios_version in psirtios: - logger.debug('request ios_version: %s, time: %s' % (ios_version, time.asctime(time.localtime(time.time())))) - psirt_response = requests.get(requrl + ios_version, headers=headers, proxies=g_proxies) + log_message('request ios_version: %s, time: %s' % (ios_version, time.asctime(time.localtime(time.time())))) + psirt_response = requests.get(req_url + ios_version, headers=headers, proxies=settings.proxies) if psirt_response.ok: - logger.debug('ok ios_version: %s, time: %s, len: %s' % (ios_version, time.asctime(time.localtime(time.time())), len(str(psirt_response)))) - psirt_response.encoding = 'UTF-8' # makes json.loads() mutch more faster (from 4 min. down to 1 sec for about 2MB) + log_message( + f'ok. ios_version: {ios_version}, ' + f'time: {time.asctime(time.localtime(time.time()))}, ' + f'len: {len(str(psirt_response))}' + ) + # makes json.loads() mutch more faster (from 4 min. down to 1 sec for about 2MB) + psirt_response.encoding = 'UTF-8' response = (json.loads(psirt_response.text)) - logger.debug('response loaded: ios_version: %s, time: %s, len: %s' % (ios_version, time.asctime(time.localtime(time.time())), len(str(response)))) + log_message( + f'response loaded: ios_version: {ios_version}, ' + f'time: {time.asctime(time.localtime(time.time()))}, ' + f'len: {len(str(response))}' + ) info.append({'version': ios_version, 'advisories': response.get('advisories', 'notfound')}) - logger.debug('ciscoapi:psirt-ios-found: %s' % info) + log_message('ciscoapi:psirt-ios-found: %s' % info) else: - logger.debug('notfound ios_version: %s, time: %s, len: %s' % (ios_version, time.asctime(time.localtime(time.time())), len(str(psirt_response)))) + log_message( + f'notfound. ios_version: {ios_version}, ' + f'time: {time.asctime(time.localtime(time.time()))}, ' + f'len: {len(str(psirt_response))}' + ) info.append({'version': ios_version, 'advisories': 'notfound'}) - logger.debug('ciscoapi:psirt-ios-notfound: %s' % info) + log_message('ciscoapi:psirt-ios-notfound: %s' % info) return info -def get_psirt_by_iosxe_version(psirtios, access_token): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - +def get_psirt_by_iosxe_version(psirtios: List[str], access_token: AccessToken, settings: Settings): info = [] - # read config, we need only the proxies - read_config() - - headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token} - requrl = 'https://api.cisco.com/security/advisories/iosxe?version=' + headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token.token} + req_url = 'https://apix.cisco.com/security/advisories/iosxe?version=' # requests.packages.urllib3.disable_warnings() - if list(psirtios) != []: + if list(psirtios) is not []: for ios_version in psirtios: - psirt_response = requests.get(requrl + ios_version, headers=headers, proxies=g_proxies) + psirt_response = requests.get(req_url + ios_version, headers=headers, proxies=settings.proxies) if psirt_response.ok: psirt_response.encoding = 'UTF-8' response = (json.loads(psirt_response.text)) info.append({'version': ios_version, 'advisories': response.get('advisories', 'notfound')}) - logger.debug('ciscoapi:psirt-iosxe-found: %s' % info) + log_message(f'ciscoapi:psirt-iosxe-found: {info}') else: info.append({'version': ios_version, 'advisories': 'notfound'}) - logger.debug('ciscoapi:psirt-iosxe-notfound: %s' % info) + log_message(f'ciscoapi:psirt-iosxe-notfound: {info}') return info -def get_psirt_by_product_family(families, access_token): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - +def get_psirt_by_product_family(families: List[str], access_token: AccessToken, settings: Settings): info = [] - # read config, we need only the proxies - read_config() - headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token} - requrl = 'https://api.cisco.com/security/advisories/cvrf/product?product=' + headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token.token} + req_url = 'https://apix.cisco.com/security/advisories/cvrf/product?product=' # requests.packages.urllib3.disable_warnings() - if list(families) != []: - + if list(families) is not []: for family in families: - psirt_response = requests.get(requrl + family, headers=headers, proxies=g_proxies) + psirt_response = requests.get(req_url + family, headers=headers, proxies=settings.proxies) if psirt_response.ok: psirt_response.encoding = 'UTF-8' response = (json.loads(psirt_response.text)) info.append({'family': family, 'advisories': response.get('advisories', 'notfound')}) - logger.debug('ciscoapi:psirt-family-found: %s' % info) + log_message('ciscoapi:psirt-family-found: %s' % info) else: info.append({'family': family, 'advisories': 'notfound'}) - logger.debug('ciscoapi:psirt-family-notfound: %s' % info) + log_message('ciscoapi:psirt-family-notfound: %s' % info) return info -def get_suggested_image_and_release_by_pid(pids, access_token): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - - info = [] - - # read config, we need only the proxies - read_config() - - headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token} - # requrl = 'https://api.cisco.com/software/suggestion/v1.0/suggestions/productid/' - requrl = 'https://api.cisco.com/software/suggestion/v2/suggestions/software/productIds/' - - # requests.packages.urllib3.disable_warnings() - if list(pids) != []: - for pid in pids: - suggestion_response = requests.get(requrl + pid, headers=headers, proxies=g_proxies) - if suggestion_response.ok: - suggestion_response.encoding = 'UTF-8' - response = (json.loads(suggestion_response.text)) - logger.debug('cisco-api:get_suggested_image_and_release_by_pid:response: %s' % response) - info.append({'pid': pid, 'suggetion': response.get('advisories', 'notfound')}) - logger.debug('ciscoapi:info-found: %s' % info) - else: - info.append({'pid': pid, 'advisories': 'notfound'}) - logger.debug('ciscoapi:info-notfound: %s' % info) - return info - - -def get_suggested_release_by_pid(pids, access_token): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - +def get_suggested_release_by_pid(pids: List[str], access_token: AccessToken, settings: Settings): max_pid_length = 240 max_pids = 10 - max_req_per_second = 5 - wait_time = 5 optimisedpids = [] pidstr = '' count = 1 info = [] - # read config, we need only the proxies - read_config() - # split list of PIDs in chunks of max 240 bytes length for pid in pids: if (len(pidstr) + len(pid)) < max_pid_length: @@ -439,32 +404,24 @@ def get_suggested_release_by_pid(pids, access_token): count = 1 optimisedpids.append(pidstr[:-1]) - headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token} - # requrl = 'https://api.cisco.com/software/suggestion/v1.0/suggestions/releases/' - requrl = 'https://api.cisco.com/software/suggestion/v2/suggestions/releases/productIds/' + headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token.token} + req_url = 'https://apix.cisco.com/software/suggestion/v2/suggestions/releases/productIds/' # requests.packages.urllib3.disable_warnings() - if optimisedpids != []: + if optimisedpids is not []: for productids in optimisedpids: - suggestion_response = requests.get(requrl + productids, headers=headers, proxies=g_proxies) - logger.debug('cisco-api:get_sugessted_release_by_pid:response:response.text %s' % suggestion_response.text) + suggestion_response = requests.get(req_url + productids, headers=headers, proxies=settings.proxies) + log_message('cisco-api:get_sugessted_release_by_pid:response:response.text %s' % suggestion_response.text) if suggestion_response.ok: suggestion_response.encoding = 'UTF-8' response = (json.loads(suggestion_response.text)) - logger.debug('cisco-api:get_suggested_release_by_pid:response: %s' % response) + log_message('cisco-api:get_suggested_release_by_pid:response: %s' % response) info.append(response.get('productList', 'notfound')) - logger.debug('ciscoapi:info-found: %s' % info) + log_message('ciscoapi:info-found: %s' % info) return info -def get_sugessted_compatible_release_by_pid(pids, access_token): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - - return - - # get_clean_sn_for_bug_api('ASA5510', '9.1(7)15,8.4(7)30,9.1(6)1') # return {'9.1(7)15': '9.1(7.15)', '9.1(6)1': '9.1(6.1)', '8.4(7)30': '8.4(7.30)'} def get_clean_sn_for_bug_api(pid, software_releases): @@ -525,22 +482,21 @@ def get_clean_sn_for_bug_api(pid, software_releases): return clean_sns -def get_bug_by_pid_and_release(pid, release, access_token, reqoptions): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - +def get_bug_by_pid_and_release(pid, release, access_token: AccessToken, reqoptions, settings: Settings): info = [] - # read config, we need only the proxies - read_config() - headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token} - requrl = 'https://api.cisco.com/bug/v2.0/bugs/products/product_id/' + headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token.token} + req_url = 'https://apix.cisco.com/bug/v2.0/bugs/products/product_id/' if release != '': software_releases = get_clean_sn_for_bug_api(pid, release) clean_sn = ','.join(software_releases.values()) missing = {} - bug_response = requests.get(requrl + '%s/software_releases/%s%s' % (pid, clean_sn, reqoptions), headers=headers, proxies=g_proxies) + bug_response = requests.get( + url=req_url + f'{pid}/software_releases/{clean_sn}{reqoptions}', + headers=headers, + proxies=settings.proxies + ) if bug_response.ok: bug_response.encoding = 'UTF-8' response = (json.loads(bug_response.text)) @@ -548,7 +504,8 @@ def get_bug_by_pid_and_release(pid, release, access_token, reqoptions): pagination_record = response.get('pagination_response_record') last_page = int(pagination_record.get('last_index')) total_records = int(pagination_record.get('total_records')) - logger.info('PID: %s, Version: %s, Total records: %s, Pages: %s' % (pid, clean_sn, total_records, last_page)) + log_message(message=f'PID: {pid}, Version: {clean_sn}, Total records: {total_records}, Pages: {last_page}', + level='INFO') if last_page > 1: if reqoptions != '': reqoptions = reqoptions + '&page_index=' @@ -557,7 +514,8 @@ def get_bug_by_pid_and_release(pid, release, access_token, reqoptions): for page in range(2, last_page + 1): # time.sleep(2) page_options = reqoptions + '%s' % page - bug_response = requests.get(requrl + '%s/software_releases/%s%s' % (pid, clean_sn, page_options), headers=headers, proxies=g_proxies) + bug_response = requests.get(req_url + f'{pid}/software_releases/{clean_sn}{page_options}', + headers=headers, proxies=settings.proxies) if bug_response.ok: response = (json.loads(bug_response.text)) bug_list += response.get('bugs') @@ -567,7 +525,9 @@ def get_bug_by_pid_and_release(pid, release, access_token, reqoptions): reason = bug_response.reason url = bug_response.url text = bug_response.text - logging.warning('ciscoapi error: %s, %s, Page: %s, LastPage: %s, URL: \'%s\'. Text: \'%s\'' % (status_code, reason, page, last_page, url, text)) + log_message(message=f'ciscoapi error: {status_code}, {reason}, Page: {page}, LastPage: ' + f'{last_page}, URL: \'{url}\'. Text: \'{text}\'', + level='WARNING') missing.update({page: {'status_code': status_code, 'reason': reason, 'url': url, @@ -577,7 +537,7 @@ def get_bug_by_pid_and_release(pid, release, access_token, reqoptions): 'bugs': bug_list, 'total_records': total_records, 'missing': missing}) - logger.debug('ciscoapi:bug-found: %s' % info) + log_message('ciscoapi:bug-found: %s' % info) else: bug_response.encoding = 'UTF-8' status_code = bug_response.status_code @@ -585,7 +545,7 @@ def get_bug_by_pid_and_release(pid, release, access_token, reqoptions): url = bug_response.url text = bug_response.text page = 'ALL' - logging.warning('ciscoapi error: %s, %s, URL: \'%s\'. Text: \'%s\'' % (status_code, reason, url, text)) + log_message(f'ciscoapi error: {status_code}, {reason}, URL: \'{url}\'. Text: \'{text}\'', level='WARNING') missing.update({page: {'status_code': status_code, 'reason': reason, 'url': url, @@ -598,54 +558,14 @@ def get_bug_by_pid_and_release(pid, release, access_token, reqoptions): return info -def get_bug_by_productseries_and_affected_release(ps_release, access_token, reqoptions): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - - info = [] - - # read config, we need only the proxies - read_config() - headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token} - requrl = 'https://api.cisco.com/bug/v2.0/bugs/product_series/' - - if len(ps_release.keys()) > 0: - for product_series in ps_release.keys(): - affected_releases = ps_release.get(product_series).split(',') - for affected_release in affected_releases: - bug_response = requests.get(requrl + '%s/affected_releases/%s%s' % (product_series, affected_release, reqoptions), headers=headers, proxies=g_proxies) - if bug_response.ok: - bug_response.encoding = 'UTF-8' - response = (json.loads(bug_response.text)) - last_page = int(response.get('pagination_response_record').get('last_index')) - total_records = int(response.get('pagination_response_record').get('total_records')) - logger.debug('Product series: %s, Version: %s, Total records: %s, Pages: %s' % (product_series, affected_release, total_records, last_page)) - - info.append({'produckt_series': product_series, 'affected_releases': affected_releases, 'bugs': response}) - logger.debug('ciscoapi:bug-found: %s' % info) - else: - info.append({'produckt_series': product_series, 'affected_releases': affected_releases, 'bugs': 'notfound'}) - logger.debug('ciscoapi:bug-not-found: %s' % info) - - return info - - -def get_product_mdf_information_by_pid(pids, access_token): - # set logg modul name <file>:<module>.<function> - logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name) - +def get_product_mdf_information_by_pid(pids, access_token: AccessToken, settings: Settings): max_pid_length = 40 max_pids = 5 - max_req_per_second = 2 - wait_time = 5 optimisedpids = [] pidstr = '' count = 1 info = [] - # read config, we need only the proxies - read_config() - # split list of PIDs in chunks of max 240 bytes length for pid in pids: if (len(pidstr) + len(pid)) < max_pid_length: @@ -657,21 +577,23 @@ def get_product_mdf_information_by_pid(pids, access_token): count = 1 optimisedpids.append(pidstr[:-1]) - headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token} - requrl = 'https://api.cisco.com/product/v1/information/product_ids_mdf/' -# requrl = 'https://api.cisco.com/product/v1/information/product_ids/' + headers = {'accept': 'application/json', 'Authorization': 'Bearer ' + access_token.token} + req_url = 'https://apix.cisco.com/product/v1/information/product_ids_mdf/' +# req_url = 'https://apix.cisco.com/product/v1/information/product_ids/' # requests.packages.urllib3.disable_warnings() - if optimisedpids != []: + if optimisedpids is not []: for productids in optimisedpids: - productinfo_response = requests.get(requrl + productids, headers=headers, proxies=g_proxies) - logger.debug('cisco-api:get_product_mdf_information_by_pid:response:response.text %s' % productinfo_response.text) + productinfo_response = requests.get(req_url + productids, headers=headers, proxies=settings.proxies) + log_message( + f'cisco-api:get_product_mdf_information_by_pid:response:response.text {productinfo_response.text}' + ) if productinfo_response.ok: productinfo_response.encoding = 'UTF-8' response = (json.loads(productinfo_response.text)) - logger.debug('cisco-api:get_product_mdf_information_by_pid:response: %s' % response) + log_message('cisco-api:get_product_mdf_information_by_pid:response: %s' % response) # info.append(response) info.extend(response.get('product_list', 'notfound')) - logger.debug('ciscoapi:info-found: %s' % info) + log_message('ciscoapi:info-found: %s' % info) - return info \ No newline at end of file + return info diff --git a/gui/views/inv_cisco_livecycle.py b/gui/views/inv_cisco_livecycle.py new file mode 100644 index 0000000..acca1e3 --- /dev/null +++ b/gui/views/inv_cisco_livecycle.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 +# +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : 2017-08-14 +# +# CheckMK views for Cisco support APIs (EoX, SN2Info, PSIRT, SUGGESTION) +# +# 2021-07-23: rewrite for CMK 2.0 +# suggestion removed --> table to complicated :-( +# +# 2021-07-25: removed inv_cisco_suggestion +# rework painter section + +import time +from cmk.gui.plugins.views.inventory import ( + declare_invtable_view, + decorate_inv_paint, + PaintResult, +) +from cmk.gui.plugins.visuals.inventory import ( + FilterInvtableText, +) +from cmk.gui.i18n import _ +from cmk.gui.plugins.views.utils import ( + inventory_displayhints, +) +from cmk.gui.htmllib import HTML + +# +# to get the status colors and the clickable IDs for EOL, PSIRT and Bug you need to copy the file +# ~/lib/check_mk/gui/plugins/views/inventory.py to ~/local/lib/check_mk/gui/plugins/views. +# Then change the value of ENABLE_PAINTERS to True and restart the apache service (omd restart apache) + +ENABLE_PAINTERS = True + +# ################################################################################# +# +# Painter functions START +# + + +@decorate_inv_paint() +def inv_paint_date_status(date_string) -> PaintResult: + + warn_days = -90 + crit_days = -30 + + # check if date_sting not None, if so return no CSS Class and None + if date_string is None: + return '', '' + + try: + days = int((time.time() - time.mktime(time.strptime(date_string, '%Y-%m-%d'))) / 86400) + except ValueError: + return '', date_string + + if days > crit_days: + css_class = 'date_crit' + elif days > warn_days: + css_class = 'date_warn' + else: + css_class = 'date_default' + + return css_class, '%s' % date_string + + +@decorate_inv_paint() +def inv_paint_last_checked_status(date_string) -> PaintResult: + warn_days = 32 + crit_days = 40 + if date_string is None: + return '', '' + try: + days = int((time.time() - time.mktime(time.strptime(date_string, '%Y-%m-%d'))) / 86400) + except ValueError: + return '', date_string + if days <= warn_days: + css_class = '' + elif days >= crit_days: + css_class = 'date_crit' + else: + css_class = 'date_warn' + return css_class, ' %s' % date_string + + +@decorate_inv_paint() +def inv_paint_psirt_advisoryid(advisoryId) -> PaintResult: + psirt_url = HTML( + f'<a class="href_blue" target="_blank" ' + f'href="https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/{advisoryId}">{advisoryId}</a>') + return '', psirt_url + + +@decorate_inv_paint() +def inv_paint_eox_eolid(eolid) -> PaintResult: + if eolid is not None: + search_eolid_url = HTML( + f'<a class="href_blue" target="_blank" ' + f'href="https://search.cisco.com/search?query={eolid}">{eolid}</a>') + else: + search_eolid_url = '' + return '', search_eolid_url + + +@decorate_inv_paint() +def inv_paint_bug_bugid(bugid) -> PaintResult: + if bugid is not None: + search_bugid_url = HTML( + f'<a class="href_blue" target="_blank" ' + f'href="https://bst.cloudapps.cisco.com/bugsearch/bug/{bugid}">{bugid}</a>') + else: + search_bugid_url = '' + return '', search_bugid_url + + +@decorate_inv_paint() +def inv_paint_psirt_bugid(bugids) -> PaintResult: + search_bugid_url = '' + bugids = bugids.split(',') + if bugids: + search_bugid_url = [] + for bugid in bugids: + bugid = bugid.strip(' ') + search_bugid_url.append(f'<a class="href_blue" target="_blank" ' + f'href="https://bst.cloudapps.cisco.com/bugsearch/bug/{bugid}">{bugid}</a>') + search_bugid_url = HTML(', '.join(search_bugid_url)) + return '', search_bugid_url + +# +# Painter functions END +# +# ################################################################################# + + +# EoX display hints +inventory_displayhints.update({ + '.hardware.support': {'title': _('Cisco Live Cycle')}, + '.hardware.support.cisco_eox:': { + 'title': _('EoX'), + 'keyorder': + [ + 'pid', 'serial_number', 'ProductIDDescription', 'Last_checked', 'ProductBulletinNumber', + 'EOXExternalAnnouncementDate', 'EndOfSaleDate', 'LastDateOfSupport', 'EndOfSvcAttachDate', + 'UpdatedTimeStamp', + ], + 'view': 'invciscoeox_of_host', + }, + '.hardware.support.cisco_eox:*.pid': {'title': _('PID (EoX)'), }, + '.hardware.support.cisco_eox:*.serial_number': {'title': _('Serial number'), }, + '.hardware.support.cisco_eox:*.ProductIDDescription': {'title': _('Description'), }, + '.hardware.support.cisco_eox:*.LinkToProductBulletinURL': {'title': _('EOL bulletin URL'), }, + '.hardware.support.cisco_eox:*.UpdatedTimeStamp': {'title': _('EOL bulletin last update'), }, + '.hardware.support.cisco_eox:*.MigrationProductId': {'title': _('Migration PID'), }, + '.hardware.support.cisco_eox:*.MigrationInformation': {'title': _('Migration information'), }, + '.hardware.support.cisco_eox:*.MigrationProductInfoURL': {'title': _('Migration PID URL'), }, + '.hardware.support.cisco_eox:*.MigrationProductName': {'title': _('Migration product name'), }, +}) + +# SN2Info (contract) display hints +inventory_displayhints.update({ + '.hardware.support.cisco_contract:': { + 'title': _('Contract'), + 'keyorder': [ + 'pid', 'serial_number', 'ProductIDDescription', 'Last_checked', 'is_covered', 'service_contract_number', + 'covered_product_line_end_date', + ], + 'view': 'invciscocontract_of_host', + }, + '.hardware.support.cisco_contract:*.pid': {'title': _('PID (contract)'), }, + '.hardware.support.cisco_contract:*.serial_number': {'title': _('Serial number'), }, + '.hardware.support.cisco_contract:*.ProductIDDescription': {'title': _('Description'), }, + '.hardware.support.cisco_contract:*.is_covered': {'title': _('is covered'), }, + '.hardware.support.cisco_contract:*.contract_site_customer_name': {'title': _('Customer name'), }, + '.hardware.support.cisco_contract:*.contract_site_address1': {'title': _('Address'), }, + '.hardware.support.cisco_contract:*.contract_site_city': {'title': _('City'), }, + '.hardware.support.cisco_contract:*.contract_site_state_province': {'title': _('State/Province'), }, + '.hardware.support.cisco_contract:*.contract_site_country': {'title': _('Country'), }, + '.hardware.support.cisco_contract:*.service_line_descr': {'title': _('Service description'), }, + '.hardware.support.cisco_contract:*.service_contract_number': {'title': _('Contract number'), }, + '.hardware.support.cisco_contract:*.parent_sr_no': {'title': _('Parent S/N'), }, + '.hardware.support.cisco_contract:*.warranty_type': {'title': _('Warranty type'), }, + '.hardware.support.cisco_contract:*.warranty_type_description': {'title': _('Warranty Description'), }, + '.hardware.support.cisco_contract:*.warranty_end_date': {'title': _('Warranty end date'), }, +}) + +# BUG display hints +inventory_displayhints.update({ + '.software.support.cisco_bug.Total_records': {'title': _('Records total'), }, + '.software.support.cisco_bug.duplicate_records': {'title': _('Records duplicate'), }, + '.software.support.cisco_bug.missing_records': {'title': _('Records missing'), }, + '.software.support.cisco_bug.PID': {'title': _('PID'), }, + '.software.support.cisco_bug.os_version': {'title': _('OS version'), }, + '.software.support.cisco_bug.bugs:': { + 'title': _('Cisco BUG IDs'), + 'keyorder': [ + 'bug_id', 'severity', 'status', 'last_modified_date', 'headline', 'support_case_count', 'behavior_changed', + ], + 'view': 'invciscobugs_of_host', + }, + '.software.support.cisco_bug.bugs:*.status': {'title': _('Status'), }, + '.software.support.cisco_bug.bugs:*.product': {'title': _('Product'), }, + '.software.support.cisco_bug.bugs:*.description': {'title': _('Description'), }, + '.software.support.cisco_bug.bugs:*.headline': {'title': _('Headline'), }, + '.software.support.cisco_bug.bugs:*.support_case_count': {'title': _('Support case count'), }, + '.software.support.cisco_bug.bugs:*.last_modified_date': {'title': _('Last modified date'), }, + '.software.support.cisco_bug.bugs:*.behavior_changed': {'title': _('Behavior changed'), }, + '.software.support.cisco_bug.bugs:*.base_pid': {'title': _('Base PID'), }, + '.software.support.cisco_bug.bugs:*.known_fixed_releases': {'title': _('Known fixed releases'), }, + '.software.support.cisco_bug.bugs:*.id': {'title': _('ID'), }, + '.software.support.cisco_bug.bugs:*.known_affected_releases': {'title': _('known affected releases'), }, + '.software.support.cisco_bug.bugs:*.severity': {'title': _('Severity'), }, +}) + +# PSIRT display hints +inventory_displayhints.update({ + '.software.support.cisco_psirt.dont_show_older_than': {'title': _('Don\'t show advisories not updated since'), }, + '.software.support.cisco_psirt.dont_show_not_updated_since': { + 'title': _('Don\'t show advisories not updated for X days'), }, + '.software.support.cisco_psirt.removed_advisories': {'title': _('Advisories removed'), }, + '.software.support.cisco_psirt.advisories:': { + 'title': _('Cisco PSIRT advisories'), + 'keyorder': [ + 'advisoryId', 'sir', 'cvssBaseScore', 'advisoryTitle', + ], + 'view': 'invciscopsirt_of_host', + }, + '.software.support.cisco_psirt.advisories:*.advisoryTitle': {'title': _('Advisory Title'), }, + '.software.support.cisco_psirt.advisories:*.cvssBaseScore': {'title': _('CVSS base Score'), }, + '.software.support.cisco_psirt.advisories:*.firstFixed': {'title': _('First fixed in'), }, + '.software.support.cisco_psirt.advisories:*.firstPublished': {'title': _('First Published'), }, + '.software.support.cisco_psirt.advisories:*.installed_version': {'title': _('OS version/family'), }, + '.software.support.cisco_psirt.advisories:*.lastUpdated': {'title': _('Last Updated'), }, + '.software.support.cisco_psirt.advisories:*.publicationUrl': {'title': _('Public URL'), }, + '.software.support.cisco_psirt.advisories:*.sir': {'title': _('Severity'), }, + '.software.support.cisco_psirt.advisories:*.summary': {'title': _('Summary'), }, + '.software.support.cisco_psirt.advisories:*.cwe': {'title': _('CWE'), }, + '.software.support.cisco_psirt.advisories:*.cves': {'title': _('CVEs'), }, + '.software.support.cisco_psirt.advisories:*.productNames': {'title': _('Product names'), }, + '.software.support.cisco_psirt.advisories:*.ipsSignatures': {'title': _('IPS signatures')}, + '.software.support.cisco_psirt.advisories:*.cvrfUrl': {'title': _('CVRF URL')}, + '.software.support.cisco_psirt.advisories:*.ovalUrl': {'title': _('OVAL URL')}, + '.software.support.cisco_psirt.os_version': {'title': _('OS version')}, + '.software.support.cisco_psirt.not_updated_for_x_days': {'title': _('don\'t show advisories not updated for X days')}, + '.software.support.cisco_psirt.dont_show_older_then': {'title': _('don\'t show advisories not updated after')}, +}) + + +if ENABLE_PAINTERS: + inventory_displayhints.update({ + # EoX + '.hardware.support.cisco_eox:*.EOXExternalAnnouncementDate': {'title': _('EOL Announcement'), 'paint': 'date_status'}, + '.hardware.support.cisco_eox:*.EndOfSvcAttachDate': {'title': _('End of service attachment'), 'paint': 'date_status'}, + '.hardware.support.cisco_eox:*.EndOfSecurityVulSupportDate': {'title': _('End of service vulnerability support'), 'paint': 'date_status'}, + '.hardware.support.cisco_eox:*.EndOfSWMaintenanceReleases': {'title': _('End of software maintenace releases'), 'paint': 'date_status'}, + '.hardware.support.cisco_eox:*.EndOfRoutineFailureAnalysisDate': {'title': _('End of routine failure analysis'), 'paint': 'date_status'}, + '.hardware.support.cisco_eox:*.EndOfSaleDate': {'title': _('End of sale'), 'paint': 'date_status'}, + '.hardware.support.cisco_eox:*.LastDateOfSupport': {'title': _('End of support'), 'paint': 'date_status'}, + '.hardware.support.cisco_eox:*.ProductBulletinNumber': {'title': _('EOL bulletin ID'), 'filter': FilterInvtableText,'paint': 'eox_eolid'}, + '.hardware.support.cisco_eox:*.Last_checked': {'title': _('Last checked'), 'paint': 'last_checked_status'}, + + # SN2Info + '.hardware.support.cisco_contract:*.Last_checked': {'title': _('Last checked'), 'paint': 'last_checked_status'}, + '.hardware.support.cisco_contract:*.covered_product_line_end_date': {'title': _('Contract end date'), 'paint': 'date_status'}, + + # Bug + '.software.support.cisco_bug.bugs:*.bug_id': {'title': _('Bug ID'), 'paint': 'bug_bugid'}, + + # Psirt + '.software.support.cisco_psirt.advisories:*.advisoryId': {'title': _('Advisory ID'), 'paint': 'psirt_advisoryid'}, + '.software.support.cisco_psirt.advisories:*.bugIDs': {'title': _('Bug IDs'), 'paint': 'psirt_bugid'}, + '.software.support.cisco_psirt.Last_checked': {'title': _('Last checked'), 'paint': 'last_checked_status'}, + }) +else: + inventory_displayhints.update(({ + # EoX + '.hardware.support.cisco_eox:*.EOXExternalAnnouncementDate': {'title': _('EOL Announcement')}, + '.hardware.support.cisco_eox:*.EndOfSvcAttachDate': {'title': _('End of service attachment')}, + '.hardware.support.cisco_eox:*.EndOfSecurityVulSupportDate': {'title': _('End of service vulnerability support')}, + '.hardware.support.cisco_eox:*.EndOfSWMaintenanceReleases': {'title': _('End of software maintenace releases')}, + '.hardware.support.cisco_eox:*.EndOfRoutineFailureAnalysisDate': {'title': _('End of routine failure analysis')}, + '.hardware.support.cisco_eox:*.EndOfSaleDate': {'title': _('End of sale')}, + '.hardware.support.cisco_eox:*.LastDateOfSupport': {'title': _('End of support')}, + '.hardware.support.cisco_eox:*.ProductBulletinNumber': {'title': _('EOL bulletin ID')}, + '.hardware.support.cisco_eox:*.Last_checked': {'title': _('Last checked'),}, + + # SN2Info + '.hardware.support.cisco_contract:*.Last_checked': {'title': _('Last checked')}, + '.hardware.support.cisco_contract:*.covered_product_line_end_date': {'title': _('Contract end date')}, + + # Bug + '.software.support.cisco_bug.bugs:*.bug_id': {'title': _('Bug ID')}, + + # Psirt + '.software.support.cisco_psirt.advisories:*.advisoryId': {'title': _('Advisory ID')}, + '.software.support.cisco_psirt.advisories:*.bugIDs': {'title': _('Bug IDs')}, + '.software.support.cisco_psirt.Last_checked': {'title': _('Last checked')}, + })) + + +declare_invtable_view('invciscoeox', '.hardware.support.cisco_eox:', _('Cisco EoX status'), _('Cisco EoX status')) +declare_invtable_view('invciscocontract', '.hardware.support.cisco_contract:', _('Cisco contract status'), _('Cisco contract status')) +declare_invtable_view('invciscopsirt', '.software.support.cisco_psirt.advisories:', _('Cisco PSIRT advisories'), _('Cisco PSIRT advisories')) +declare_invtable_view('invciscobugs', '.software.support.cisco_bug.bugs:', _('Cisco BUG IDs'), _('Cisco Bug IDs')) diff --git a/gui/wato/inv_cisco_bug.py b/gui/wato/inv_cisco_bug.py new file mode 100644 index 0000000..c8c6b4d --- /dev/null +++ b/gui/wato/inv_cisco_bug.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 + +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : +# +# 2021-07-23: rewritten for CMK 2.0 +# + +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato.utils import ( + HostRulespec, + rulespec_registry, +) +from cmk.gui.valuespec import ( + Dictionary, + ListChoice, +) +from cmk.gui.plugins.wato.inventory import ( + RulespecGroupInventory, +) + +_removecolumns_inv_cisco_bug = [ + # ('status', 'Status'), + ('product', 'Product'), + ('description', 'Description'), + # ('headline', 'Headline'), + # ('support_case_count', 'Support case count'), + # ('last_modified_date', 'Last modified date'), + # ('behavior_changed', 'Behavior changed'), + # ('bug_id', 'Bug ID'), + ('base_pid', 'Base PID'), + ('known_fixed_releases', 'Known fixed releases'), + ('id', 'ID'), + ('known_affected_releases', 'known affected releases'), + # ('severity', 'Severity'), +] + + +def _valuespec_inv_cisco_bug(): + return Dictionary( + title=_('Cisco bugs'), + elements=[ + ('removecolumns', + ListChoice( + title=_('remove columns'), + help=_('remove information from report'), + choices=_removecolumns_inv_cisco_bug, + default_value=[ + 'base_pid', + 'description', + 'id', + 'known_affected_releases', + 'product', + ], + )), + ] + ) + + +rulespec_registry.register( + HostRulespec( + group=RulespecGroupInventory, + match_type='dict', + name='inv_parameters:inv_cisco_bug', + valuespec=_valuespec_inv_cisco_bug, + )) diff --git a/gui/wato/inv_cisco_contract.py b/gui/wato/inv_cisco_contract.py new file mode 100644 index 0000000..55dc4b3 --- /dev/null +++ b/gui/wato/inv_cisco_contract.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 + +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : +# +# 2021-07-23: rewritten for CMK 2.0 +# + +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato.utils import ( + HostRulespec, + rulespec_registry, +) +from cmk.gui.valuespec import ( + Dictionary, + ListChoice, + ListOfStrings, +) +from cmk.gui.plugins.wato.inventory import ( + RulespecGroupInventory, +) + +_removecolumns_inv_cisco_contract = [ + ('contract_site_address1', 'Address'), + ('contract_site_city', 'City'), + ('contract_site_country', 'Country'), + ('contract_site_customer_name', 'Customer name'), + ('parent_sr_no', 'Parent S/N'), + ('service_line_descr', 'Service description'), + ('contract_site_state_province', 'State/Province'), + ('warranty_type_description', 'Warranty Description'), + ('warranty_end_date', 'Warranty end date'), + ('warranty_type', 'Warranty type'), +] + + +def _valuespec_inv_cisco_contract(): + return Dictionary( + title=_('Cisco contract staus'), + elements=[ + ('removecolumns', + ListChoice( + title=_('remove columns'), + help=_('remove information from report'), + choices=_removecolumns_inv_cisco_contract, + default_value=[ + 'contract_site_state_province', + 'warranty_type_description', + 'warranty_end_date', + 'warranty_type', + ], + )), + ('PID_black_list', + ListOfStrings( + title=_('drop Product IDs beginning with'), + orientation='horizontal', + help=_('there will be no request for the following PID(s)'), + )), + ], + ) + + +rulespec_registry.register( + HostRulespec( + group=RulespecGroupInventory, + match_type='dict', + name='inv_parameters:inv_cisco_contract', + valuespec=_valuespec_inv_cisco_contract, + )) diff --git a/gui/wato/inv_cisco_eox.py b/gui/wato/inv_cisco_eox.py new file mode 100644 index 0000000..1475fbb --- /dev/null +++ b/gui/wato/inv_cisco_eox.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 + +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : +# +# 2021-07-23: rewritten for CMK 2.0 +# + +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato.utils import ( + HostRulespec, + rulespec_registry, +) +from cmk.gui.valuespec import ( + Dictionary, + FixedValue, + ListOfStrings, + ListChoice, +) +from cmk.gui.plugins.wato.inventory import ( + RulespecGroupInventory, +) + +_removecolumns_inv_cisco_eox = [ + # ('ProductIDDescription', 'PID Description'), + ('LinkToProductBulletinURL', 'EOL bulletin URL'), + ('EndOfSecurityVulSupportDate', 'End of service vulnerability support'), + ('EndOfSWMaintenanceReleases', 'End of software maintenance releases'), + ('EndOfRoutineFailureAnalysisDate', 'End of routine failure analysis'), + ('MigrationProductId', 'Migration PID'), + ('MigrationInformation', 'Migration information'), + ('MigrationProductInfoURL', 'Migration PID URL'), + ('MigrationProductName', 'Migration product name'), +] + + +def _valuespec_inv_cisco_eox(): + return Dictionary( + title=_('Cisco EoX staus'), + elements=[ + + ('always_use_serial', + FixedValue( + True, + title=_('always use serial number'), + help=_('if true, CMK will request Cisco EoX information via serial number (default is use PID)'), + )), + ('removecolumns', + ListChoice( + title=_('remove columns'), + help=_('remove information from EoX report'), + choices=_removecolumns_inv_cisco_eox, + default_value=[ + 'EndOfSecurityVulSupportDate', + 'EndOfSWMaintenanceReleases', + 'EndOfRoutineFailureAnalysisDate', + 'MigrationInformation', + 'MigrationProductInfoURL', + 'MigrationProductName', + ], + )), + ('PID_black_list', + ListOfStrings( + title=_('drop Product IDs beginning with'), + orientation='horizontal', + help=_('there will be no request for the following PID'), + )), + ('PID_bad_list', + ListOfStrings( + title=_('request EoX information for the following Product IDs via serial number'), + orientation='horizontal', + help=_('the EoX request for the following PID will by via serial number'), + )), + ('SN_black_list', + ListOfStrings( + title=_('drop entrys with the following serial number(s)'), + orientation='horizontal', + help=_('there will be noe EoX request for the following serial numbers'), + )), + ], + ) + + +rulespec_registry.register( + HostRulespec( + group=RulespecGroupInventory, + match_type='dict', + name='inv_parameters:inv_cisco_eox', + valuespec=_valuespec_inv_cisco_eox, + )) diff --git a/gui/wato/inv_cisco_psirt.py b/gui/wato/inv_cisco_psirt.py new file mode 100644 index 0000000..f213718 --- /dev/null +++ b/gui/wato/inv_cisco_psirt.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# License: GNU General Public License v2 + +# Author: thl-cmk[at]outlook[dot]com +# URL : https://thl-cmk.hopto.org +# Date : +# +# 2021-07-23: rewritten for CMK 2.0 +# + +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato.utils import ( + HostRulespec, + rulespec_registry, +) +from cmk.gui.valuespec import ( + Dictionary, + FixedValue, + TextAscii, + ListChoice, + Integer, + Alternative, +) +from cmk.gui.plugins.wato.inventory import ( + RulespecGroupInventory, +) + +_removecolumns_inv_cisco_psirt = [ + ('bugIDs', 'Cisco Bug IDs'), + ('firstFixed', 'First fixed in'), + ('firstPublished', 'First Published'), + ('installed_version', 'installed version'), + ('lastUpdated', 'Last Updated'), + ('publicationUrl', 'Public URL'), + ('summary', 'Summary'), + ('cwe', 'CWE'), + ('cves', 'CVEs'), + ('productNames', 'Product names'), + ('ipsSignatures', 'IPS signatures'), + ('iosRelease', 'IOS Releases'), + ('cvrfUrl', 'CVRF URL'), + ('ovalUrl', 'OVAL URL'), +] + + +def _valuespec_inv_cisco_psirt(): + return Dictionary( + title=_('Cisco PSIRT advisories'), + elements=[ + ('removecolumns', + ListChoice( + title=_('remove columns'), + help=_('remove information from report'), + choices=_removecolumns_inv_cisco_psirt, + default_value=[ + 'publicationUrl', + 'summary', + 'cwe', + 'cves', + 'productNames', + 'iosRelease', + 'ipsSignatures', + 'cvrfUrl', + 'ovalUrl', + ], + )), + ('psirt_type', + Alternative( + title=_('Cisco PSIRT advisory type'), + style='dropdown', + # default_value=True, + elements=[ + FixedValue( + 'IOS', + title=_('add Cisco PSIRT IOS security advisories'), + # tototext=_('Cisco PSIRT IOS security advisories info enabled'), + help=_('CMK will request Cisco IOS PSIRT security advisories'), + ), + FixedValue( + 'IOS-XE', + title=_('add Cisco PSIRT IOS-XE security advisories'), + # tototext=_('Cisco PSIRT IOS-XE security advisories info enabled'), + help=_('CMK will request Cisco IOS-XE PSIRT security advisories'), + ), + Dictionary( + title=_('add Cisco PSIRT advisories by base product id'), + elements=[ + ('product_family', + TextAscii( + title=_('product family'), + help=_('Product family for example: NEXUS, WSA, ESA or ASA'), + allow_empty=False, + size=24, + )), + ('not_updated', + Integer( + title=_('don\'t show advisories not updated for the last X days'), + default_value=730, + # allow_empty=False, + ) + ), + ('dont_show_older_then', + TextAscii( + title=_('don\'t show advisories from before YYYY-MM-DD'), + default_value='2000-01-01', + allow_empty=False, + )), + ]) + ] + )), + ]) + + +rulespec_registry.register( + HostRulespec( + group=RulespecGroupInventory, + match_type='dict', + name='inv_parameters:inv_cisco_psirt', + valuespec=_valuespec_inv_cisco_psirt, + )) diff --git a/inv_cisco_support-0.2.0-20230609.mkp b/inv_cisco_support-0.2.0-20230609.mkp new file mode 100644 index 0000000000000000000000000000000000000000..67db7b6f6e6bbea07cd0aa1337e0b9243ae32d33 GIT binary patch literal 26029 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 L#4<N!0MG&e-Qs)_ literal 0 HcmV?d00001 diff --git a/inv_cisco_support.mkp b/inv_cisco_support.mkp index 865d7a381c8f887616c722a162d3556e9267b80b..67db7b6f6e6bbea07cd0aa1337e0b9243ae32d33 100644 GIT binary patch literal 26029 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 L#4<N!0MG&e-Qs)_ literal 26335 zcmZ^qQ*b6+)MjJbwrzK;j%_C$+qT)Uopfy5cHY>w?RUO^YUXNcu6CU|m%HkmUF%t! zI0goWup+_~3}|KVZfI)dV(MV%;^ye+;OxrC#=^$N%F52d<j%sxYRY71;|Oxy;}fvS z)yT5d_e{HXbS+k~px@ifcX{K4UN+VhmAtC3arOKAOiDIoCQi;;Vltk)@5&;;910tZ zL|VD=il?SMUF*n*1q3Q&VA=>J^zba3-+$xo%GPe7?=Ayq@5X=az^B_&Gi&c*Q`Pn@ z-vzjF_2_ll*xedpPe6BoDyc3`v_9-jI!(TuNsh5Rc_DnAk$98<R8V0jkKR2D`7raw zolH<f3w9GrOq{cHR(Cu{AI5)q^1%Q5jS{9s2UGXO5@pUk`-dY-(bs}tCZ|k9AU20w zs3YE-I43ax6XCo4-RDe$;Lzz+s(6hza)*}%WkR5qe~3oTe22veY;R`0Gy-!uca=I~ z%z?nc6?1rj;STiX^u5D$z~ha<z1#6<`0>$2NWu4w9*=r;XS!__$n|2XaQkyPv{v!+ z^^(qqrskm#-x(n_XLvuk|GH`D&&%WGa#w9G^-Q4Sk2~X=>P-FOcS`pApHtuR!#~hw zrg$oUmq<>oLRDRNJ<S=euhX$!fMYnefhW>6&bz6D;P0G!hE7*Wk*i$!Jv%gBf9U|O z+yHSG>ZkouNYefp-*P{KoVlRkY)e6x#fI#G@evKtBSS=si@9?BeUvOu-dqXIAIcPH z4inFRA|j@IcD8Zcc>5XCWdO@GnrUaKp-jyk<w&FBxo#dDYpuWp@~ekJ@X)xEej(Fa zwv4T{50fM6pTCk)Ne187BLF_fD=9M(BZ#+#fJQ@)tQqr~a{1g|b|gVCcx73GpY3yw zwrt`W$}39{DPqYJZ!C&l@kP5<Y<CC?M_b4T{-^CgdD4D&STS}m1P;UEOGh-X;sIZ- z0a*Hs#JR^42@d%pa}Iv$GbfN(5F-+tSz}x1`Gp@C|Gu#(L6Ta+w}r8jC#Zo(Vn{u? zvp<s{+zjA?aUt5UUyl?S!!^w~r^F1Nm8q_C!GH1O9p_sZ@1^W1p~{0h5j=~_73J4> zu8Klw5e>KasW@)Ei^Xcc*)RT{oB0!kI$hG;)wy&o8pP0eUQ;SpZ3&;I(q2{Nu)$}t z)a?b&c1cSvu-GG->RR;czCN)lRLyriy<1ld&6mKRrh~KI^L4#Z!6w0C@}!XQVW!qU z++)hrrkdPyr2j@ktR!)2x+F_<Jf1V@E%Mmhwl4$Na4T~mF4xUWkq}IO1<Zut!a}yi zh$@9-cn=|y@JbRWm#zOatS^h4G&>`nkWNd2@JEXRAGxznV+1IqesHDs(*Z!A>l%GA z;H~S%CjK|j;0Y)t2Mk#9AYxnr5-;aUMSmoOrKtcNN-weLUaG9`>8q~V0CTdmhq5+1 zvK8xXixue_1=yBTAb;zinJb>qlIi%EovmtTWBQvPz%Zu6)Q-b;Z;3PMphb1n;l9BE zR!<(Has6%Y)@Q}4ZwQ^#%l3d@4W4;LncpkY6SL~1jKu0XHIt-?uy~}29a`k#0$~@1 zpu!u$BhmVxDH|mcberT#>p4>84NjnCAtZ1rb9a|~OHI|nLv-z}p{ZwZnGec<F&MP6 zOgNGy*7)!$Q@YT~I8Zg7e7X8(QF6afymQH4pT&aF!!|`^uD2hC7B=v}m1iNN*SPQ3 zyr%&D`JMYW4|kE(vVgWWFHJX}&0kAE{%eJ<Ho)a||J!RPf9@wiz}WkVEs>lgq+?8P zK<V6}gYKi}3Rr6KafIgV6BHM?aoxk3l`nF%9S7XtV=uenXdLmK{4uMq7wDuf&ZpmL zdU4w<v;Wy#chsY|KNl!xVt`9+Cq$>BPfTxeUnxjI+ZeNO%O!sYA6~z}qcAN*29u*g z@wScYW@rG8JXJwN>p{#}7IQE5j%}!HdE*R;l7@5F!gpdt0r^;84i)z-2aZUlBS%mg zSjLAV=0-l`ys#F^+*c-h&YJ;GK3k{|$A)g)OM+jaPmwheT=3T$x$o`cbx`30pL&kh z1cEzW+^Ayw8*2uHlZq;;?I~HtJ0OZXRA-KQ0awZM7__iHh5s}phDJdtz^OLm2rSji z;!K_jsh4Q))0(m7Fha6yHQfg%N__CZfKnooqkoSCF|=?~5@BiD@+juUsWN1LI)YWK zf~`My*7hj&l++H_T9Uh|7QTV!8QLy?n1$;Qe?FEe+e9--{DfLkvTR|;&edmyFchu< zup0ii=XxMMN}2W9yuT+{r8>^_Stcy58hzw|xj8ekFL`gVPA&50$Q&+q7<a%63jzHH zlov03ok~+F`(m={>zD8^U?n+=+Jr0j<6IE)7cRR<9zl=qH<Wkuw<sY1JoG9Zdk3M2 z^%I{`7mXage%Ha#D53b$yR;QeUfwcCC{W+NT9w*u&r~S<o4JTND4(x2bD`F4>1T{~ zdS+k=-7fH)FLH1I*YO8Bi=n%s@VW*h+^+!af*SnmuOGezYtCn^UMiSbqKG>%sCssc zx;sbk*a3MjAO9i|&NU}F7fbyO9rL;qJyCun8)S6ii!Ij1Zv}Ee%3|*QDbpXq)M$VT z4S-<6Ko#v`<ZC%Ar7D!NOMo0IWyo#_uKpeS3xDrjZ=3wb{s60!uhuH?XRJ_2KF6?U zVoNAvW?lw`-ydszTJfX_1-7n)-47$y!Ld~svWRegB>hcBC3|ov`}GDM8DimXkl6oD z>V_6We9t^*dQ@%l`-V8~mI;41Zj0}eElPN0p{%)6M}7?UP_f)BBr&?u=k8*}#{87W z_k_+>z}*1ql-(SkFI2xN+SZX)a9}zxiYLm>bN4sI9M0cpB8g>a;6J26><<h^y4#kW z>dvG}i|E@PdyQo9<IWbHZDAM;LSKj=u&Y+$aMW@BM(TaG>=9iEJyQ~KF9ZGkv*owp z>^I)Embub<eZ<$~VSED&(q$tVBS{T0EgHHUcB%sgY1nRGBmeN4mVAhP)C#nY!*tN5 zB)m}K9dSw6`4R?<Q?U({zTEf%uVQW%8wIpm=PZegAevw1KaNN?k27_Tu(#bs15M!R zwU=$_^)yuekyHRv;<T@|gvZLp!L*ul9F7f=r~IMnytyyG3<_>16zt~eg1{DqnngY3 zVVWf3Fa{h}vPPBYWwWq0PIWM%f9?-S<<Dqg<B*t2q7Tkii+>8))CmfO+-bkML*u{H z3|})qc1?i?c$jik9~ZFjJm4Sh$V!Rt$l^kleP&bK>L8bB-$ZaZ(K1UCa4+;GZD7lL zKM~C=OUCuN7Tn-3>bkwbj(mzx<?Gt3HDX_1P`#a2{2(3LXm~DQJ}fkMk2A39gQe*^ zU^>3IsNk)Ijm$&UIx4Kgq7%!oAjg#{uIVK&F?46<JAd>d3`o9v6ye@{a3Z`lX!&b+ z>+jiIH6?4gJ6`M$1_+)6cJh5@T^!IppVSf1h{@kxdbN6w!%8q@Di1^@M5)_K{3My6 zxT&?y(QGG=g7O=K{9+L<=EvU)%!XsF7JQjG47e(3sn^3J23US3d?T!~z0w<9+y&Il zY;ETHI^59tB$C%f)YLhsr6#f4G+Ng#hb$nEm+a(Pae#qg<tbZZ!tgDAh}g<JW$&jL zPf>@h!b$DEm&tkvqXi)GYbEj`XQf~e57S6ALNxtn{db4M5XW`fGEivv@$=^=^_8T9 zi{%Ha2$heMtTK$H6fItq0QY~TO-Ep3jAYotjYlUbvh*A>rT^8Xi9e-(oY2e$+k@$I zW0ej_O=wK?1#Jo|DBcy0neFl?Pcpy;3@wE%AEMUhqSv6Kp6M~zx?!vI_0(J25^@(9 zviL0Lz_=20sihjgC+FvQWOK0l?=JYlF0Dt+l#a`Hn<f>qL6nP2aR%g7MN_9@dOPAR z>h-piY$0mG?cL93BRuqG;p&Gtmw#=%o4Czu*;32dL|mbZi-zKRZHrqaS>cFW#f|BA z(~{aT7Kqq-11gB)EB}#Dl7gA$Fh+hQrcJKoTXAUrHWP+ihw{^Fj*=>%Hd<;S>!%1& z^ABKijTJ2WYSK8Nx}zqy!l*Kj3g>M|-|%y;+*JLU1*4OJg9e)EBTbMkcrf^x8VX+f zeETyc{AoDzD?}!fmkR>_Y;wl;c96QXD5R_pTf?0xOE!YpLalO+bm6KFzRRvl8mFUa z6-nFMOrhV$#>;XnM3$7dm#ph##Bc?dnDe`e1xJA+fOgae=KzdGuyi7+<C@|e#U7tT zmCvG(=$UbyHfPuR2k-i2d$AzvYoNb(M?QF!18x+Z%U9ETy0I{HAK_3LZ=&?lnwqo{ zqbhIPaZD~)T831#npj>mo0#$Bs$wNH$YkEJK33zEz*3wJ>f}V?W0LKf)k<j`s$IJr z{aG_WR~7Z_StMnO9i7R?B|xe;=wP{?4?MMz8o6(J->LQUZsYu_@lYB2{9LliP^h8D zrY32YF!Fgc2&tFX&32t<jDiYOoYE3i*P*IOD&g=NQ%V-^Qm&EVyVlkJM$mMvkaP~W z*U)srjn)b~pWC)Z?{&tBdt3TGQ2xOvw~w{8glq(-{hOA%sp9=jmTOS+)YN+}_N@Nx zOQ+|rhdo@#TJh<u3#PE}>D#I3lH#_tgR5W1l3x>cZqZY8!jYvB$5i_(uy>+(_3W>V zM5r{!;hi(zo)5evB~<Oz`53nTays+iuNSPYmoa~~?lp8!94qRA0oyq{_pF{1q^|(% zpSBiwpcF933bH%E@#*AlMhK4?IpF5CFtM(Xvpym7FC$-z1Q`9*`Ho@R)Fk~MOX5;+ zH$Ge%s|aoqAKdZueou6cm4E(-AF@38TFpSB7{=fDjAV+H-b437c2*qDm>r2wC1%mC z+NkgD@ISBQu7rmJcu-2?s0tmSBdH8{#~23j%_Ck$gU+O!(u@F~e8FsCSCt`)AeHq= zo76g*AJwShzSXz6Sq+~u`a>ZP+8>vyReOg6A@2M9w70q1SfDEwL?B)SGnaBfhjM`s zopQlCi4yZPEZEFw;ErI5s-HLu?XJxOxvXzr1(ch8RQ=pA@*|i~A@t$dq9N@EI`66c z6T=1n&lpLe^y=FCrQ!ZJ6~mKpL>L_P`L8gy3kIEIA^0JFUODB+vfxp?lL~)k{ELaH zYN0LmCVBj>$W4*0ctse&_x$Ze<wEO2I;dx7pPsbu>(eY;Idn-<T_Tz#D#;*{@EG{Q z2htqxwxwrKBZE&*E*NszD-{&*IlcD`=K&ps_dG_1cOIlbk%)quK#ZKkuQpS9x_X#j zt{c+{hYGmUXzrvlBF?JOYN5KJ2yUPmFHL83FcGER{WJ}KH!R~D3o!-=IAYK;AQPrT zhT)VO2kn_7OdB2F$IXtz1Tw&FZMYh?0`n0pINnH7r5K(THol6;*6D<)4hQ>B!Z3@W z5+y)7N2cME@uO9mOy@<g3UFeZ$1fz#IPSofMtag9KZ{T!b3|n^$PB@FgcUOKjH=y6 zF0_m8>8#9)2RrfyF%cReX$Wfx5Wa>cLG5i~r2#GZkPUxI2ZuxB+2PDHrCEPwuo(Y^ zl2zcC-DyqXjW-vRgaTyf@690p@T%E#pwJ8`lOa{=O)sh>N`LE0wK^78Y=PYJE;Hi} z4mh`z%E$yY9k|Rma%+Eu_>$lg<kD_A>*q=kQ5BDcnq(HQ>N=X28QU3C!pq-;%|M4Y zKKGj_S}6-L9MaC*`(wIVC|o7a;z`vxi-zC!NBuG8^#RdM+*L)0oO#ur1!{AUz5mQT z^eUERi9ha3FmiL7j3RgDFuU62I5LxeNf2rmk=q~kCE3i6Mybz6JuiRM$0joEkE2np zT+FD9zrpG0WLr=IiKm92As_mh+xVw}t2@`vt!<qtz|z&tHz3!KknT@9F<!2HaTocP z*HM@Ct1A#^PHEA@H60JnaRgp&*8MjxMi3cwo<guV;{{GHO(FCkgV(K!?LpvIL;=`6 zx{oCS?b}8PU1jaz3RdyUsHs*pT)i&H6I|Lu+;v881LV5`)xtY3pNzUt`pC?4aC}Ag z!6z9M=te$wPGIM`tV5#j^N4^+9a27FXtBf&>sL!YMbn;b-kZ~J4-GGmbyy>$xr)vY z{m(ha#0Zg`H%-9=j%m*&1Z>>Oa$s&e#<$S=xqn`MlRW$!HB5ENbf{wd_nsizFwO`# zk|v=+jL@ZmFFL(sn|Ec>9U>~f-zi%c8c0mi9omp7?(|69yUyBbAWUhe4!Xy(`s=wv zh#_*kB9bBDcR;<@#-_{0`3-#-l-@Zq>sUem=PWH<L49H7M-yEe>G^eF(FeS8_pJKh z*xR<U?ED*%9kA!#)yJak^QA?#Cs1GCnK0_6)bY0*!McMQVNh7vrr)Zkn~FS>jY2#4 zrS&%N{yIf2U619o(3e&ff1k5>*Nppf#D;R&Mo{_~G&9nRB2rGW=G^XAYk=>bK-@p> zroAsP9&;`5-wRNgn>VhxyI!dl+}$ha4_jkyMytjEFUbo3+;jsN0p4V{YhitT<yF9$ zGoF$8mVMP`IMCfa$G&XWekh>ycN}C`k%atZf2@T3G32Bx(_)6HwkTNFunj>$FU#=a z2~S6V?ma?UXme<tok?Sk<nFGGG%qzQ2Im^g?Tg~$^bK6+zs|U`uGr4+H!6Z&zKNSc zPPP=oTYlcu<1{6#sMl-CDf4wgZ&0o*a^g8x_YAJGaEK|96o58wh?|ob<t0Y{B(B?u z;li!#*5p1_sNcvoAv%u_S`98(RexZ?S%;syJDLGR122+DhML4T=P9#~75@j<VVn$# zOBRZ=K*BktC(^R@6bqcQf>TC`l`D({PSC+Vq7!WiK&aydx22f#;*pV$f>j;vL4<Rl z0y%=LA<&SnvS1aW_;2$=_7fv5Ldr{RYTtts4aSBeO@7=^aKT&w)MdLJ8cal;!Vckh z@bnR0_OeNsiGPb?RCKFNm4+OxSKwa<ASSszmgnF<f`V#w=Xs|{AX<FXSV3z%nUdqF zr8C6w$s#c%k)l&uKx4iNkqdeANqlk)Lw#r`MhMr0s)Z&%=-s=@cPT!gb8bQOlR#Z} z`TZGgw>Rq}tLq9;pB8cns!$3#8=zX2!&Zv*AYCeS884dj`zbSEUmtS8w%-LyV+Oks zb=0Sa7AX8-->imzgW4-**c7J)j!(Ys2^*R{?D!zxM9^G<mpsWWt#o>s-1%j&0G0_* zEuXvXXdL<OdEY=+po3(-W0}=EuC9J*Ddwd$%;mW-Q`3fqV=5k4;N6P$lX-OM7-jWZ znM<uk%tHoo=(Ddoj(mk6JQ)xc&{bBRspd-OK#~~6pQ)Pi5UAp7)<S{LU69L0YR*hP zv|b5%X!V7INhp6Cj6YTRG0hp|+GXuF+~dZgOn=0!Dowk`t~z%18#%}1!!l%Tjhqos zw8eKrO_?oSO5d2SXXCp_y<HUf>?pd5OQ}mkzZSL1&mXcqXyX@VR+DL$TOZIBmQWyy z6Ccr4AJkNXGR``Xa?RK_c7={7ORwQ-2cdblYX78hO9W9J&-lGsbL4q8+1z4V@=xQq zW{9KX{2#0w{$Ss7ga4@xu?_jfX8^yKTZy>c3#~1Cm7|Hct2%$;Ka?*!F7#-E_1Bwh zntLh&HJx@S>oe6Nk)_<$MA{=ZkZ+==F5BNfkdwY2IqQ+~Mmann$jjxV71y>Nv`Y_Q zcs*R=yF(h%WoVW$j1&hp#bT)9vem_5gUCE&hxnC2JWV145+IjEP;s9{i=gJTX!i!) zl3lx*0T`LOY|_?VrEqd3C1gN$xIt@P)AK>Tt5y{o&Jhhja8|o552noDTK=+Kc%=;Q z!gz)%z{b?l!%No5B;hc}pe5Ah>CM;pjy&YjeBai%P_aBl-(cXST4HW{iW{b2V@Otm zLeAWW_RF^(EtyQPb|Ka~klhJ{q<gn%-8(Cw$LxGQ@CyaBd=SZCD!G6SS3#1Fw4<PF zarYc%jRVvySq+~CQ?QvUw}4?Wq7a)PDo=&Op||~vA4|7cN#aixK`J1!qHtPERsxj~ zFKN(-%w~VtG^RPDsMI8ihz)rS3_C1%976VjtWpC`G<xxzc@zD1LWrI|6ms9aSWWzI z3OfS|&32KwSa74U2!=<oUt8#>Qdz@YI&t>49gz~i&c3NUw=9BM(U23#@CNAIFB+wy zDTft@7e6FGC4*22<zI=QoB6MrXkTsK_C&UN1qQciUD7nhAt;Qsr3qBgSIX<kP<wAG z^w;mRYlJ&M6z&=I$JLXK;6=W(e-sSu#J3Gxyh&<6jg#^^{=*H;kA(#_^N&Rr^lt2D zzPtBk_FKOEm*VWVqGzbk?GLqy={1+;*71MupH<?kR4C#%X|4SQ$HxDBF)&WVW7d^1 zzL3*iVixKR&I@wK7ZY?B$#P+M%ej_p4;Cy+2IIB1Gv-hv*wOyLWUM~%v7gtcpK=*^ zD^-K~3h8WHhLj+)^#^)G(-yUQH9Ts{2e5f7<1~CQ4WH2vUs=hraKOTzUI)u)B~5vy zIL$KnG}9e)biNL+&IPBlq(L`^5av{^){1D^8!N>Dy%P*9x1k!o-H=u@#7)_8ERlpK z8y3Z3;K>U~$K6mhARJnY)I1MElO+6CM*p-VFD4=61RMpGO=a+tjrJpVs#gBDE~|DK zzD$d;jNca*=(~CYHLDZfybPVWb|-(fUol7qhC-QTu&%nGQJ@HhG3KF^|0>eOlmnm8 z&pcHoa%CfnZm+Qu?ms_WF`GF_d$nu2HF;O{u$r8F8jz8VI`s`DfB~Mo;?v~=_%~Me z9qqTuWq8^IqgRcFi_R=EzwWhr9Hm*CX34@2ATJfbC{<zhI@yfM@{X>hFVb4#&ol6t zZ$pVvZn|XX^(CV&v2R^sK7yUL5j$s7j>5$iF*kljYIp9PN)WgmrP_<g;7dVV1J(?2 zv7c%vPVZ*`wcTPEdcC2TxULX2a$#$pS2&4ReanIiK#Szsm1ee4ta&;{;wz#92S}$z z|E2)11e*dKi)9HtW`D8x-J<W&oK6lbz8cR;vkNQMyukP<C$6l@QVIRMXG>ib7H?74 zWmu)#eS8Z`1f!oh)@HDdH^HGXp9nIT6UI}gMI6o39DMR5kcBIN;u<&*QosgQqP|3T zf=Cqg_xUy4b+%VIZ9ytGLM(fF7=aH<6Qs9FKoSF34qn6DXyskCQ|(2_j3F2036Ysb zSeWgTxDg&`b^`p~mT2W^MAJzh&-S);<K{<}&8mzj*SS&`b@y)EG~gy2m>GCpq=*8C zno+S8c|TSEK6oaiM0PVt?N9h)y<ZM_w2i>isCAR|da+I=$pgG}yfKi)!51fSqSH&2 zbGoNRoK)~e9K5-S!lM#ajf0{G2lvnG7m^PB-&EqeC@E$U$(dL@ZyqeUBK^8qt<KT0 zG3qR0oaK>Ks(mxam@V7)I3#{z^&TR_I@g#4)Cw4x80Pb(#-{=UmTMV@eriauA*E2O zV_rIu@XEdXHF#|Q;&<lwtdR(<dEFa_Y&B}wD}5DYIy$-))U@^Dkls$^@WMz!lKx0Y zA%C@t122ZnQi~QvuBIRxnF*(Rc?z~u6xnEY@j&JIuT@y*qmRu>_fEqW=42vSmsL3- z_jeW>Io!g|-E(_JE(~f^FlwedXYlIen(H#?Y-FH(8|8a*<(JkDJpH}SjV1Xh>sDIQ zzM}1F*%rpnQkQn1m8Y58mv$5mllKIkj9^^v_LpsK=h5`NlAo3<M(qC)!czF)_HC;5 zs@LE?Y@zYDo3T?AIF-bVowhq2O`SX~W|f#o3klypMPLaBGgnS>r@$5)W#DkDW`1X; z?WtRV)Z-jaSof64b4+S#vg=<+JC6<zn}fRm&|y;iWx4C=lCYk^){Yi4*hnJUh!9_p zrWC4)ma323(-4MN1WUXN*#a{tQ@DiPI!sj>-!TzF_69OMSvn$RA$OV{TIYQ}OkjHz zWobxXUm|@7)uNi(!qzYQ+1d?|(iWZ|ze|*>B@8Vh`W1YrFlq`47U9Ts=*v*vgI}f{ zsbW;}cTv*knklo4n3H6Ev2y;S7?p2<k{^Y`9SGltgNHa5Wm$Zno_CVdGaSC#=%*#= zJhcPn13E~yXJCyEPety&hYcr;dIx$?HP2PK{RKs(hv(BFsLx#pZGdE!M;@^ivBg3* zRwge-a;CGLwa7HkS`EO@<S{*WzxpmAj#F!;%h*M*tWPR)hg*fh5@%_lk;vSQqh_TC z<XnD7O(RpYXK=AR;sCHSjGCemru#2LHP>{SeC`rvul#aj5++?KH0vLa5pVUO`^8>f zb{zc_-sum66h%q1PV=zUJq_94QiW%g-`+L67Z84K?EJYBN)z=?u4SXYyTza09+K#F zeId^=bq3pRC4<i=iDn83Wc4R9?o|eM&HJ#Rd|VowfCJm~$c6@!Yy3KaD>&2ohLl;^ zR^+7z9qU^5WSzG(MLy`IJ$W>Csl~FxR`hy3|D^3i`l%vH(SBn0?Q7)PH+2sCvA2P( z+uLaOyPKPK)4=xaOS_wZ&EbI4k$|SpT{_QRbDqqEj9L}wKNf6Gv%VHYLqx$G#5(53 zaj^`<${#W(CcVYk`=0%diV&Eeyrd(ZpzE<Fvy#(0(nrS4mrs%<)f^UOl-_IOlRS?t z^#@yDv97r5gpe5N(aST4Y0I@B=P?ukW??OVaEFyS4$jw??NUD2@l3-RqyON|6j>en z7VHLyycFi|67_llcmxJhZ;$r@hni2T0LHbiUf;*Bb@tb#V5GP)3vNDQn5P{YiJzCV zqvrv@&}jtp9LnY6cYXqL{(&J*K+v+Q2VbrSGye7|^walDjE9izZEOCszyC*YpXbkz z`{~%~_7kM2D^KXiG7QOBfQ?b%D3kif$H;xxDl;CnB0jb5FKoXXnGgjXDQjR30q(X- z*5SA+do2Esz@LH%W_8!G80o4ZrVeHZdGNnpmAvkkq3*biH_Mc5eKJ`cZAFL>h&V$^ z>Q1;_m0OnrOL&a-{&6I~_;qVN^tyJv6hAX{N9A<V(ZOs<f155&9+Wt5jVS*3Wkb^t zE+AaTz?;;==BqJiL!KgDj;xG^foEo~vdgJ4%0x<iHKzyU{x*oFov}B8<!FJ{SGnV7 z;69=vc^HYK>nM1r#AIBB>{F$xuqI)n8ZGQkd_DNKVx&HWq@QuFJ=sT+q}{+9!;f`V z#=8caj+E@<jt+7pHTA4--jhP%IRq7|A-O`F)&!9SR$u&-L2mA)X-OOt%j?0sLrJ=& zfeJ9-piLj2FeSkV$Y?G_;0;f|N=9FDk7+SaY0Zh07*6Gd^rH=UEug|QX;%;Xq}N2< z&?0Y0Mk)uc_Wj;_E<;0;70q9Fh)#mNUq~OmyKmDsDE&SfAnV5{>yCLpaHzdntSZM& zzizMuOIl^$&BL*@=1ex|J<?nzj<Gy8!YeH*|7}sJlA#vOtjGa@7uMkasisvc#3?UK zrH<s{4Oqg*V0%_nbR|eqYQ+FgK$`y?n@&`JJ4dFCs744Ej46VxdRr)#l4F@qkua2G zIdnho)T-%7$k*i#DVYeJ`;n8+Oj#N1B|>+h1932SQL<^qW4X$xH~CuNq}u-?8OZmc zxq}o)8ki~|S%6FQdktB^`yESmvPYN;mbz<=855MDUIbE#I1YnN<8tnI2g=3O@Ncmn z?_MTO^_0aOiC7HaW&>-CJ!4kTZj-IdypJpp8fYb4um3ZjV2bQ5@{(ESig@xUpOUw% zWGJ3N@0Qjk*WSXR1KnG(QV%Rw&ebb`vsFBU92K)(pbQlxaDu2o2&VQ~E0K{r<-o~> zaOPTCRp-}9O*TG};r(}aG?!GSWmjFU*&iGl=%v935ZxPYUjOjNw!kRT<<Ngp@eW&` zC=z?r(^wZR5`l-nBt_Sux=4gF`qvbdz#coxEu>wVe=F?boxL9kON)K;C(A^8NC|VG z2iM<pW<IF-7b2}Ctyqk65x(^bE+f7I>bw@>?OQ%&3z(~|g1;^`t<650eki7CX#o?% z8HwAzoFb|`6;1$s(ZgD69n%xu8=n3rIhauc(IkAvF?%1c8SX{Z%_p#H_ifkxaBN`b zwq_T<Pw2hp{qwdj-?>5LAJIR`Dw?eOuj-3V3@DxC`$L@_*Y&PJqg#mNr~ssy=#a<1 zSQxW3(@=j}SR#iqlVTlcRXV~;0qM=pRqMPt0J2u#eCG7OAWe9^V)8{|*iR_QQ9|)s zEy19wes*&jXLD!JM~o?#X04oQSPMZ-iBopIq=b=QCkDeA!!RVm4H6hQLg!za#dIBn zW11E(u^K6V+ziSChaT6sOu=awW$g%y60DZt9SULwT?Qh8oA4B~`c|VuZ&bkAgv05q zf5&U3T}4(4o7vc5!U?g7Ec;ozY8cLc>R<8gT4969z=L3qu#ImSm(k3uJEcFa@x@RE ztwc>{lg%*x;S&>~_ZIgmodh-T=%q1Y3v99>qiD|g=>BRe^1uWo$@()M2w5_e@8@(( z(-2JAD3mK%ngNEID@E=%RyHf}E7M&Fe@E(h-l9fhjT369j6%Qi$xYGs-46J6c6WyD zBryqDu@<k0E9@l3A7w<RVDY1lGGp2a54S8^fbL%7`L8zGu5Td25GO)sLIyb@uW!Q> zn|tnLT_D@y(Kc_U$&2g%IUpZ)yv$`qad!duQr-f5p91*xKf0QhH!rT-+giOhcboaI zuMJKG4}n1`ZX7ChTbd7dJU1)qdxpEqyeMhFzFe1|;<la^u~Q-MamuT&G5fBjCSITu z@tmgD&wm-bMa*4t*ZJ`-;emmBXK)gBbjkK-9kOYeH%d<xKeg2MkR!qWag+A`uUYhV zQ9e2sz`$2q7}hj9H}{<p+$L`B*9{*iIyE7Ium6zUA$g{-YO|(cPpCwd({HH;IV|3U zJiAofl`pXp6`e*c_$$Xr5cy~w;Ue|PG4H>)`UshNCwA~H>H<`Bb)*ai9a|+*Xu0ZC zVf8ZdbcdoX?Q{SlQt-&F`(8*#2{M;hRM}c-34^f3nT92@4E^>545qaC0WUn}$e9gx zR(u`k_4ptm@q0@#-h8JGqel2Ngs|HO#OMfGF5rR^1L%sYOg0AZz%>Xf8geF#$jIfP zqvQlnXa9$Sz8Gun#^_lol^CsST*khr!v*#svS_!Sj`idqmIC!He<dlk!Bi2a->Dh+ z7!xvZ`8`z34LwF38T-|qK#%zdAbPzMbSJ9&EL`cEZN561`MJlZD1F$~{;66Kr{j3> z32gX2fRg;-hJs%5T%!u707ob_w*MM;_CYk3neymqz3ZoEEk-~Nw~VYpr}gBB4;|x+ z%_*a%M#`NlHAv+hSv+9pGs48@W}c)GjP)>5w+YI~VY0P8XSk3F^ZLC+zv5|6uMO8K zvq4dCQPzCA7(B#76!{d%gegHn`Ay4A2VGQ71Ge6nV@~SD5?Cd1C(UvN8Qna;p%R@> zC4&UYZ<uMjd}-cpUDX4|Ea0f2#JkVz13$(-1nM*8qR0wgGHrtEZ?SxDaqcGmlWe3W zui8@1ZH92d9*Z_8LGeqF!yS0QD?YGO9Fr+Tlp}I02&Ql3enah(z#`24bvN#m3I+q! zFS;K^rX}(lvHx$W#x#JbO*kElbbWB1<gH4gGj4pMdk)stPv-ac*BSNms-ez~qW5?l zy$iUgn2dWIv0o@qzCiH|Xu}5N`EEAk!2jH_^&E)S{<JOiZ+a%I`h6+jd%qem^SwT^ zLtV7@=)vlW@Tq!^o(o{0xMW>rK$C-}TMu&`VrH0keXiLEdupnOhdr?Gx?@nt8v4ya z>;<Tp4EHo<Kn)6L5k#dNH@-@cAgn;k+e}2WtgxzdEgjd+HKYYMU3ECrUy<~;WQB1u z29K@Mf2$A6ENT>0CQiT|#u)d;vMEM#;(U++&+_Sc?1b)&WCnx;Q;FBKdOltA?GG%l ze>D0NVsPrlTAb)YN4y|mex`}e$sKViKela2Lpe9zzAW1Y{r)R=hm|OjdivjhuzW>- z0}Z6giu=lR;#y4#HpiR)L&K2e+&}1cF2F78=kX>tQ4n(5k|RyKHTO6%42}32Nj|RO zdUvrsAcYe)pM4c!3?U}sb?F3*^F$h4sJf5hz8`_Fh5?4RSqVx&xp8hOqB(dD2sf$U zcRB3@g1IDlrmKQW{<b?gXoYeS9sp6cILyL`JLlszh^7vrCCI(KU!p@w<YU7zh{p=X zi<SG$@Iiy~rmz%w@xW69ZdITmlq&f}1|!rR6?-4x^ED{$e|G^H$?$?J&vtAx9&3tf zuGais8ggvoiMuBOH*dQjo2i_F;t6I9IUI35<NIEodiiND!cWrR+P4yT{IOdN3XnT4 z#(s~x#~`f(SIzr{1;tiIH+!?PLQ8lYv<FG1(tl5n?lN3TO6I^w1imIJuK6>KZCM*! zn19j&V|TE4ObJEyeMSNE4a#s}mZKCH1Cd#6!fQq4!){nIq0BHO4cGe&#|?Q;D9r=e z#TE=T3sU^Ko_?$}*$@T3RXBS{(fMSNqCQ=HNDC~Vtz53Wmn_aC9wTfD_j~zqmk+cv z4R`kSz%SuZ^veO)OmSZ}!};3qX6)UG_EwT)F|At%`SG@5Dx+0!NlP-;(jp{XD!HX! zF^*1@18*PJwbGE^r>7c#?2Tn}4(v77$)`*lT+PI$;=bYo1;2FoBHyNR#2*e(?j`w~ zTFr!NrmsSAx(o^uU-Y;B`0li=ue$l?cuNz(C-)?q26SSw4w9>aH?0j4{O`&VSZp!x zD;wh=9q5!vFTw#%G9_dG)WD>P#4$scRw`r7AH&tZ-F!|*%hJb=`*|LTOu-C+*m`LI zk@<1*B@_~<V{84By0E_SVL4t4aKkg0c%OZr3>}3TX(1dFIt(<Ax0mU@yV_t&u00Rk z^d*M+ATOPfBLDYZTELBY$-96-WlSG7wz1i0Zl3|tx~12)F{=tM6x1jX^)_A10#~w$ zxWfmJlmMzh4-?CPtx_0q6%}RHGAeVfYP>#Id0_6VtKHd8`zhsyx~l6ZR=+V+ZtqMm zbis|MpgWGF@hrL!;q`~m*wbDLwn>ju+gr0_so13)au_XxykDbKQ>G}^d1$wao(NL1 z+GU2^#tz_3AIR%beCL7v;tNu05D=*0<U}o;qf8>ZlGTGpqIi*B09Mm?JKmz6hcVUC zgnMkDF&Z>ws#(jdRiD#vuU^N_+)_)Mj<ts;kvX)ruTF5N_6w9VaD@r<Ioz7DO`O%{ z$Js{h-@-2UbLF+Z&tZLCl`Flic=Jlu$7dZibxO~)alU4iXcw;T5RjnHu>mu*O{C6% z$i9N?yIY5_b!y3_xjC(!&$cxBNp`hqD(|fo&;5{0K-JFJ$^KtZt$=rq@W`U4RqU64 zZ*0<lLEZSns<tHX+UWQ_In_%s;8&z3A?dTXNSDs<@C^0)OKh|(_@7I3nVEsU6^#od z_d4T$Qt|ohqdK5v?KlfL&V4t1T2K}pj2gUp7lZJZCwt@)K^dM>Q;eNcwLg=80;e>u z;?Oz99T6+gVrNh~{Y5BDUPRHIdH+orH>mfGqgpuCm$Qt&F!4@1wWy08Z_Pa~E?ek( zn%C^ZG)X$qV;dZS1zy(VMRb1g<=+vi!t&LgpvOuJ!c%!Vb+jMZ*-?DIBy}&58if1j z6nDW#^F;8acf%-^UEJG{p7CT+*Gu@_K%)PZ`s`=?A9v~r5R2(JS{-_LPj-1pwof>F zj+szl+*U(>={Z~B;74HOQH1TqzkV}MsEWbi+ZHNTCO#otMd>5b@kvt!ALXF)$hZ)% zRYq3c&hE&H8$IAw)6Agi)IiQOw^gg-=fW#?o|J!j3a3uVKrl^YxxsyM8cc7N%O)^g zo>=Q*pl30VMsrD%{z;k)!w#lG9QvRxCN^(wZRc)VrPA7MW@lr;uC)n(X+3E9ieWf; zZMF;;7<rFj95l3V=!ubdM#7%!GN8CM8|j*(yv|Fn?$XP;@j~s)b~|BOLGLiA7k^HB zbW!INO(Zjk36{9jF|)+GHw)0$RVy{BEAj@gRC_B=s}Hn=nceBWq&4WS!pAL15M!%^ z5EzQfUBK6F67)^-4`>Hef8`zQk^D2zhz~GrtG<ToKX}}|-q9#~mA9zs>Ds)ucksCX z0=~@N+?;&9uKN9aBE<)s5-dTrC<s;Rd-kt+_hzsq*o?&hsiTukyQN((kTbXP?+wA- zZ)pvZ?Q%GK2VLA?ii!ubr|(YOoEo?~yv`7aKI2|%1$rV2E+6K!$%DKisv$G`0y}ws zVX%bS^nXQt1>Xj74czO&3qk0rAE+rb_it9xA8;=^Zai&DBUdepu~67^j!HyTNME*& z_GpDrnBn?x#BpATq)kXJ^0cyqV;D$2jqCF}w8a|qJ6H{a0j&`wLB?}HAPl07rw%5< z;Xt(fJ%u-_WH6Erj4LB`zo3$I-C9x3+upr$Q@jAh7A2(D6QzkOS|gbV!+U9q-Lf4Q z*3*_mu)i&Wj?KBW$rTZq9hVq1oAlq3E5ItnubTuO*z@rrnj|9B0=hLh>=;IdYlfGK z)9_2Y0yE6A2te$(j{7k%=KiD;c=9$h?JCB3TtJ?eo!g!K6Sj@UFKi;?AOyIisQ7rX z$?nkJQVzkxp>MlL1xfz#u5s~()iuN?gVbVsHz4YJ+*}8iSZ%sI39aRCW&gARcFvu@ z>q7WQE&{r?XTIm~zi$;wfIWCkRnCXZWoQS8>lHH6@a{YBv;xLv$}g4nH&$W-m|4G! z$7L~bQ`Vgp*@z4fGnd7y_t<6z3vY7NoiMfhR6`-{)#Im}BT*ePxsaH$`Y#5=5%q*( zGc=)vY%MiPJo=tN3N?m#IJBz84jAWUFaK^Wa?YtzAj&Uh>-lis8zJFg^4FVNvz`+) zSte&}mKzk9v0ZtR7y65+6m~{=Xp<VCiMe8aPXn=C6%I-m)!Y`Y7<JU0R^|IIMrEtn z)K_A$ktM1|PqrnSplY?Xo0w9F!~R^E6qQRSa*Wej<WI*0&d}<i<;m|Ht9dhBwM;?S z#Zq8bkQoeSLlL5eN32fd4mjf;AP%?!8&dh_v+${wVt-+)h41Zp;;#%d?hPFcLbJ*~ zUQLfp>zTARbe*wy5$_y_Ez>2rDL=kHJJWL;pcpH-!z;@m{}CU+<Bw9D4({F0^;8Xx zwXL(lw%Uoe@D&)tZIW$tBjYTpbh@tfv{bgRavcuq87kWx+NMelsl4LShCAIjac>eD z`7vz&z99uV_*8A9e**36dJuQ{`MY{{%z=IDKp^WY@Zw~r5!hGv-RpWCBOkClGa&)$ zGr$21#dIAfL=?ykB@DN)7G}w89zd!%-VpXA?+bU7ub4odXDFl+*kehPLq0-j_~TKJ zW=he%N5sZh0m+=6zYPL+Y{tP7^Y7i1z$O?>R~?UGZc?dR0-I6KKfiV~WE#j|K)i64 z%3VG}kg8f9n2VKKZ$?Vj-?Pq?rAxL+bXc``1JJ5(r0bnM#Qz1Hs{uCQ^l*BC#5@BP zx&Z5}Ujh8vxwDeM-7{bS5|B6#d@~_J#Aug@AUrDIW^OIv8<voKfd1mvp-gyDc%ABQ z&1UhnWlgIvk8T|SgeG}FI83GqNm`l{H3%kt@(8QHI_{t}=__ieiWW><Pb&*c*p~Py z`KLWdN%1uX5kg2xk<{uyx@V16ujv{#Hx~Q0Hlr+&*m1S^Y%TMdBSj%k75jQAKF?Gj z!)Z$oOp^6g6J@Ci{gmgh2jg@)>Oa=3=ZgV7x^L<&?K{vK^k($_OHBD7QRuEV$lK<a zF=Qf#*C>hq(Vb}Z{XavxVr0QM$M~^m0fT88e1VrQ9b>kVcbBrQwo%pi6v2+4P$CP9 zH7P6(5a<3+D`!hi$v7fzhNiRiW`zh}?(gB>*-V1toUO)NVV<Mss+(t`ID(c^8nt0{ z4Bn>I#d?ZhV1Kari!KsZmh%@?i884*tGdh9Wn}(xGs2A5t?aAq;r2jfF1*C*W*;xi z>iUS*$cOiOt9#0=Xla<me;Xyqk*cuKn;n)0v_&ELbBFKI7w|Xwf3x&jh9MCfjf1T- zF)GB3S=H)V8cU!)iF>BbA)X$KLp&T5FJ>L6Kiy9E%yxquKB+&C?|du~ao^GbN9MO3 z+)>|bVF!D=KL#E)sFrr9km{NfgiZB;4*zbo^z>=IkZ{sTEoPVA)N*m`>#iR6Owok& z+xv~8TX>)KEUHs`Pl2+b-xGX~g>`<X#CIG9y|w~AtujJgo(Z<ah`K}ZKGX;X<1J-r z13ZlN#DaQLq(Z&gE4Jk{9r)S%ns0g(#g;g2;SJXhhMrwqpTu3j^%LN2e?a@U!+%mv zQ`k!%<C}#kuq&eM`4^+>Mmzs*8{I^C#ZLP;dQ6_z5Hd~RpfdPNm*s}fmOb+3cIzV} zJDZpBgCWwMK>oXbq-VL>Qv<4@XFcu(|Mj`lhavLMt0xaZLWJ17qN&h3vwLpQaCAa+ z%R>iQ)F3~|BQ4$#U{=ZXu(+R@tRjZOxJDKb(uR$-V45c3ZC+r)AcCNwd7t&5z@kA| zXM(2pQ9Y&(=JX!c(m8Oe4ldDk3!?vnpucaf%9Z5!{{uDue}wbDZ|ZJ&;RnqRW^S61 zD1VTNzjtr2zPHye?A+YDbaj7q0}Fe$dsaV*1OG3@QQFJrM$|b-crNu@=`{D?zWsWg zwNDrexVdum=^>m3HZR}U9SGeXH{=0N#`?aM_(6UQ?^;kp$%W+b7CZ?)fA6jrsqTX? zCpaX0(FdT(f8%x;mGQ<h>0+2nhsfghn>xkhAKhGWZS+XiZEHH0-1G^$=9wCOxN&?c zc~wJSCrJKCmwf%P=&c~wxpsT{!c48n`;79kQ<h(W0)98-$3!r7nP}Y!WMaqP%>%l? zTYSq3zM`b*ec6PBUQPL=;MTOxiSJxRJ+fs~xd!B%x>LYABM41SCHzas=JQ?IFaa3Q z=^1?C3M{+{YwM$##EQi%FWDyJIlspIK2N+3p<k7^2a+Qj9_M)AvXjU0M<mM@aS?7A zvZz^d(nZcF649m7Gd{686Z;?Mw%Eg+yl04wq~Aqc{H+<V`WnpdJ^t(B!R>EXf28I{ z_ABMB9Pf!Ro@%1uL!$#-p?Bgk7M91gd|2_vbu+Y_D8qaCSOPD>sM+o)m*W{Qfp;v; zS?D3ylQ>)m@+%^4KTyQn_b5-;o=?=de^<Quf<uavtiI^fKcx?l$hM6RI|3d3uiSTO zvxy~^V?ewxuycm!q=ES|)2Hrw2G}F=j0_RaEZ{V;!GBiB8o@hb(kvRzQPb3%<(#iW zsF&_&P<QhoD+1o7RyWii!9oDhtYJNKzy&bO;9ZCl$d(u3`fx){7rOU@AiW2^7>fH8 zyrT{c&cKJmqe#$pI|xLkp~m)E7{;FGoB_qaIZUZp$ucdlJQ`}I?$;?Lo9i-Jaxzys zlZN-Hh)sCr#Haj5Cr17L<37x``^Q7aW-tgKG`<fotP-Qqi7q}Uk+NBPGATYnQEX~| zaFo-J0SD%%<s;~>r{bK(WrHfN_t6<X+9Qqn?$8*1JWwE0YCuj9&>0dU!(7=wrzWk- z3%O)O0s7Kq5-+rb9*zPCJ^;go>`l;3xRFr8*A}d>L_;|KZ*6?;|4GPMC%D(_QQN@H zi7IkvdS~C^6_5XMWUpxHfu?`eY^=N;{gT)lce@bypDKLdgdhTavKXl(M3j}GN-!eg zvF~EJw^Vb))w2M@q1^WJx9}5P4|$$o?w^z_ezmDpyatsQ?e#ZnDqiu9)f)|F#@l@8 znJrn)`z3sCe`A_6>1@<TYTe4!aQsT19?VzI8aIQpI3+>Z+f~w^J76f3{<SwS9hoTY z^CUeH9jeuG-@D4NJW;tOmj4xXU2=;(Rm-{Xa6%M+_lPG`fPl+83SqRB>sLme92k{o z%s0R#?OesNgcLOuwj|Q+YLNKK&6w%7)?sk^CA2bIR2+YT7~xM4()loee0fDImm6>_ z59aRMcBm)*n5p`!3pGN*rrA;S54eV>a4A>SX-`djH)XPAhE*gVnVenG4dSREUEJNj znR(L#QNE36;T$SU{2N)YJyi{a%Tb?GrC5*z6)4HA_U?gdLcg)+f2xTFrOypcetz4+ z-<_Z-NpLfZP^1!oVTiIn?@iSsOFL8xz8GVPjo<eNSe%u(q`v8>(kAwfxWlZftLqrW z=xsj?IJ+>vlo_tD{wON&{FxZ?2S{WYO{pPt7cnF$=tQe7aEK@VCR;GA(qhP((wvp8 z6%~$3H3_N@hy|g6#zf*GZA8;w=u1K7l;w@jtumvlufg*1AQZzrrCniwbl-hvl62}R zcnKq<@sT!kYz)!j`Kx1WkSw9tq++7o)V>5fs$2vyFZe(=|MwTp-ViQw&biXktp=5o z3-hJ5fXz$ppR`0{ox)(J=~<MY%Rdltx{<r4uY2wB2@8?fqy<>f)|`DBXj2T4-XZnV zc3KYt+djmuqkZUGf@eM*HJ=&OAvP4sN@s~eSvCi=VC=INn|!9+`?bGCEQFI&0dF~U z8H$xWB-`hk48*kG>DGCbWXr2gG+F;rqE1YTWsNtphFS^|6FhX27AbVLu_<4BV+eb- z_VT4kex}l;zLJ;w#;6QZv8$@8<n-+fe(CF7b~CIxg_S}@%11CpCu5Y6h!4qASYg5( zuwh=`f7sTfyORmvFdRj`G^W*Zf7v}18ZJmFwUThssxwM6*mUaTrL0NO^^?2d8>OlF zbKQ$Sjz<g(b5KOrE4zk~nCVxpLN)RSa)*FiEG|zkmw;tuvVfYim{Ffic$b-Dy-=Yq z{Wr~0DT%Ekl3jtJRzx3+4Xh*fP0-ao1YANPMq=Y0Eq>An15v!I=0&}5RS^APr`2Yf zXTUw(kX$_H)~%-$=Q%?vgz#BnD1A5oDXtoTn542o_@ECILBfX!9S}zTW?Vn{+ZUOv zUFoAT>ixEEoD4ShcTZgwS#{!qMo?^*m{*ynqp(|~(z%A#p~w?~jxtsUq#7+Q7hORK zq;p|J;0xtY)-t=P>smVeu4f;zXUiI#o3cL0q(=lNarX_8GcYuLfr10X<)o4~V13HH zlBqV8$g7c>MaYS0nONNwfa1Q0z973uBcflXfp&Q|Q)n3+e;rY5t#ke9Uu;UT(h!%V z5Lr;n@c}1WmTHd)*X-X76Xd)B1vGE-e7=XWa)5e-u?XWce3xO0w8j?`2e(^X(>yZt zf;57p6ppq)i^zn8{X?0|#j_0g7!~%LNhXt$M#9!Odd$|^R`wkRJ*^9ez%^4zlqw)) zO8kR>+cu45Ec<@=wM?D&)JEgPvg$4M#AYgIffK7Px>h{I2<57jA&L%42lE*dTGmNS zkO|EH33WBliluT8h_DZgNPUE2h-YsWOug5a7&mR(tJ;k8iw~Ld{YquNH5NDbpuROc z)Q843-km|e5~dR+AIXxgE0VLgE+6o`H{|?#D`|1DHKU7!j)9tvd7*A6N*0etg;{-q zb`QuCkzlky>>o6NV-?5RcR*SN8_17&OhXG`5dWc;-*HJsNzaa!fYIlzV2$+gOQy*h zW;ANR8jmb#+2-?nzB$GB+O@9&I6q>c<T0V~YU_)+uC$jyLY$5lK~j{yCbWms{Q9dc zc9;FfSSjW%Ohg;|&c<DzW(!|n?N~%}8}r<+N_$Z4n0!NgL0q}{QTL_{4(rSq0^cSB z8~mi;iyU$C#=S!Xwrh>LrpjK{RWim+GRA@YZ6@PYh=KwB!(47HqkiX2Gz4u%(vzS6 ze+6SEoZ0jTF8hD#5edjAH4d=%Ww(5(VYv=r;oAr?+k|0#^F>7&i{k^dK@^KSPO9`0 zUVKfP!S!0n?!qopic5HQ`&OE(x&;IAS3WX(Zl$uK){JI2*aFJ2t)Zvn{b{N=qKdCD z0qy!YY}U7`n)aDSoLVJP9BApUlY%R3{#E5G`_lB{A^uyXAGA47f6`wyeDn>O7JQn! z0nqANW(Oerd3ubisby#TBKlx_&h3jjZdPyEDRpL7O=eY{&`ft_yUb!)BIlg<!B+!z zt2O6xo}lR$^Q4bG%n|NXDX@?tcAE}fG=g!v9o!8u&jSVYc3=LSr9l}mbz5SPrtD0L zxQ?Sj6#l4xUp-ye$ErQ<lPm6q>g#;yD)(0O9nDRoegeQSO33f7g}&LlLxgVjsV^H) zVMBA-BV|U|q~`s$V-*BEKz3fCtJD5mr8`C8Y>zHpS3mljmGz;ntRMXcwYEN-m2KgI zv0m~1kYwjae>LfO-$DuKTv;sX7YtSD9}4F98s*X-Of2E0bS*wWf93zKK{|;smeNTR zPy8Y$kW2z@CCS9l{2_%Bzb30M+jt-1h}E?Yd9;B1)g=)paamCmv!jasXb@bYWylLh zFd%fP<Jant{yH?J$BTs$KTg_;<#E#1Rj1ije#b58UgM^!m5_EdloEvFJ{-lZHE3nz zFScl8R^K*7`$(D=3%n@?-mW0+EOl5#o(*nwxo5*xYQs(Ah-aw#<>qc*pf{sE7fIPE zg*8*Jx>OD4>2-@&taTk?Ri*dV0+onbU66j|;<HQ?PUpU&aPBthaubE~wMOCG%i78( za=gB!RbpJ%(yC7?+v-{-TxjXlcEy;U5(^rPWLAcz%b2!Ya><;#n*Ko-`?W0b`a;eJ z^aaR8eI>v3LSDRL^3jIbGliLrEdG@*o~)((Xh<>`LSw=~91dVA4xq#E0C&~hKiS{j z?zFL|e{6QDyS2`C{fFij{LyRI@y$-H<6_boc{)Xaq2!~#o1N{ND38$m{Z6w(uLU-I zPk%Ri?V9^J4V|&kMcR`PVqze~ArqWF<|lSq(BUhiadc47&Lpy>Z1!D~R_Cf)=ZAU( zl{^`XJm43MnwYjJi7}@Ib*Gx+;4v)cUU=@~ZP$)4^^&&?d#GFMw9!>Ye>XernkFqx zlISRfNv19^1d)~ones<~zK0c%PRP|PM=ce6;!n{pHOt9Ley&4+)^HfQed|@l3TF4a zxcOaNBR`R(s(o5dNi^4eITrHlp4r;Ig!x<(XFF}9b$DzYt>0ce)ko`x=618wZ8vGS z;765yJh<^P20)Be6J#fj#n!^Hu#_w}eAN<zsQl=vKYD8!UUgq~f5V}%Q)j2yHZz<E zb?P#@@?mg1TDSl0&!X5t@E@D)4tdLWnji%#w;UR5_eX5#4B5EauIux$Ry_XiKb|V} zz^~>6bmU-Kb=x^?pz<XawjbEGF-_YoYAZCZ`Ohd&NYC%nWab4E?9~0n|9XvoX8^A+ zFvo~GeSocClUS``N4fhyKYe!a{FnbZ{M+AOygYjK`u{&ZdGq$y-`@S>f8Elvg|-of znIMlx+Pyp;f$iZHp03vf4}!ygw@wa_OM1;-b*pxA@TcaJQE(n4ady|IHB6B-{XCLB zHiMXuRcrag@Y-PK`!ToIp%S%2x~I*VKb?3(zsgF35@hKZn+&b-c!_)wDljUw-ClZf z{YVy76k}A#nEtDp^q79ee^}xTBg)7S|4D4~U(#C$;{zM}W*h&ITY34%fNb&UL3*|l z8VM0>`f#yg&H<GJG72x(x$MrW)K{31Ev_mXxikw0*Ujk?a;R$IwjZWUD^o-3yO>WP zB2!SMvE*`A0_ZiBoXi$o`?z3Vp0g*{P*^tO_??V|dxpguM?90J2BwO~_xHnSHp&#z zAlL^EYM`2UaWVjCk-{K@n&BfbOvU=q+@L2>;!QYgT#Q5mHSzFGKF-#v`}*)1i!S?B z4LW;+<sDw0y?TvCD;a-gu!B}#J#A_&W2Q-a;U}kKRk%~uvLzZx$6iA1sXu^`{|};P zUAUKB(d-U~3`+N}g;>b?hOLlmephKWP|?X*0w1e~(Yp54oodc&9UA}yv6j7qxvr8^ zTizyEcIK~PW?N_ZlM>T>F>@>{n>3RnJCK1ZCO~Ugluren;XxOeCE45DLTruGN?_WY zJaDqO8u0iow}6@JQyL3JPEV8>LT<2MFJOA4zuq3d=sUjyC;sj>oi~)ThW#QIRQjdP zJAg5wGRx`?ox)ha2O|HNP~N6Q!6#R#0dxknzFbr}kIU#I2PzS|Gy=@>*r(PkT@+gn zW$~ZSh7&Z;iJD)Bn-L1wHRih_1T?q$a`F{{>T6WrSVIa5icDmEF}9x<Iw8=A4Zn(< z(b=iL%yeI2ikF_&C8lzz>C1HwcmKr;x3bb)USZDWP0_oVmMcxgC8ys*n{uo4*b@De zJKgMlAF;jV`z9YFHE5&jjmQ2_@o-hA@nEyxeW3f@0UJ1Ft4kpVa>SY+{2SbNf)d}h zx}-S*7KLw=GLrEae1Scn6hVzm$5}HyNow^bHY6ZZLsr8ptu<$GDO$5eWj#~vEjG=| zs?e(_s<%|R&E0MJP4c2`Z_)O5*;)$s#diIH=WTJ=u&%UMUdR{)+h_Y$N?&FO{|8Y4 zE;&YxZia0!3!?e1F4Z^Dd!Ng6P+0|Q@gD20n^hPtxS(NBbjb(^LG9&eOb5$nmy9+4 zD3OENz`^bOxWVowZg3Mf_+{e;7b!w~zP7G9EoHhYhW{9+GN?q4*Mse$#0CmL%zg32 zf&+_%WpRk0tpWBJK`W&75R?t@7Du6SDO@g0uxu|a3>JKB4HwJ_Dg-)ng9^#YbH`DI zo0~k>asMMvySC;#AdByR>~`DT9>4#wv(xSL@czf$?cL4&k6+RKk5Hns-E40)@s>oV z@4UHazG%X~Cz(4A={m%fE{rs<4dg$g*mk$z%nL`+rFtcx<ubxyG`*33^NB!_yX1M} zI2c+~*y-(ny{)2VbQy&6#DCxhC`L6`s2TB1{0Fzjow~ErcD{Gi##A!y-|t4^Do6^p zm-rZ{;;J-1$yjLWJy@G~BdhT51c#mXTsXhP9Qp%#$KoKoB$-t?c-;~3B8aT+97Ulo z^&ED~N)W<S4u^hKLsv%|fEI3wqr|U+l8DKACH_Wqlryzr8o$rHI2f{9w5kNVC%*i) ze|&U!^h@1g3+|r#U6m(Wsm0EB444K5f5|^CLH)!nyy|MqGD58hB)>omRinDRTfgkI z8vSy&lxmgL%+Pq+jET(@vN^veaTLIY#Q#^*slKk^77N72$-(jA{tMWvR7aq|Cn8!n zi5y~vA%(;|cE11q11P}fF&ZYupoUx!AA<gXmoGRY4<vxRZvnJ)tZe|bRpEbiS@=Px zyHnf6O<Rm=exaQ58I-LyY~a3ke%RS+vn@D<Ln=kpoIh+Hse5ab*Vr|9?<$7B&5#Vx z-mx9@C&h6_ar4@nd_cgOkz|GTyAoPgp4r_q3N8GCS8T^6k;H4p?rCB7;C9s1)2nHV z8VX7@H)|Y_)mPJvC1yDS+%j^|9iW=pa<vNyM-fQ`V%d0p2DHU#)D*n^P!Za4jFF@i z6EUB{&S&O3Rp<gt)-?~Wy)f}d4(~@`V%!Y*$*|dM>aLPeoSh+1Ee?V|Oz2droGbL= z1||#46*)n?5EgM@z?yQ-hNw=N{tOS;q@(swSFZUP5}C`E;(H!j8m+vW@e)L22kr3H zNsDfe7N+3rxD`k=F?KvKSrb?fd)Tf-Ywt&zi0X_v{pDt`Ac#0(<WKy>&n{=iq9+(a z7*uNff@ci2*VikusyuieNJmXGC1vgwhB0T2#8D?fpUUDE%Hn}4i}@YHuTJQKVZ%b@ z#bOT92?a}!V_%EONuQncIu>hxQ`(BJqr*Rh(RH}CC0t+=W}URmQnO0Z!SP{*0(GV` zpq&4eU5+i%AY-Bgl|W(wwPIvn_&K+MfUqyR_%n9AiDVS)Q;^JDrKkgt|8WY_6@Zk9 zOLuE?(BXwY{7@&a0%<3p;rKJyPt>7=TpOMds5cr_9<z8>Vp_Zv(FqbjH<<|0fp~yb z6?q}l8>pVME&Hv>2?eOGDMWRLIGWY?(9hD!Hpq)N2guKA>uS6Oc+DE$rm}8&j2(@N zKTHGs|L>jd7W^M=c;WvVjh`I;p0wm#C0l*9q)x4jjL06HGe*k2dub6{_CjKQ1!!5- zTJ6&ud3z_SwwQ65KyGA1yQ|lF6Mc5&DA6K&9B;vDl37_S!h+^GwgD^lKf=t0X~nG( zKst#65Q7QM=w^DBo6T{x6fQ#qi3iZGJ^1}Wfv1asMFGUk3&C8_OX0WZ14#rT4LgLQ zsu&dyUvW&u>7FC_makh?b&60!`FGYCRavxFtZZOrWn8F~0)}?cH{vgq`_GjZ@>X&t zl$?v`67kZYfKaDxD^lvCgVBQ)$<gwaW&%WzC}T=vr;0iJzlS|$qTzeWh{*eq-T$~X z<yKz!s2<Avk9s1Lw)=Y$-4lB8zSBRgsH4s*013}9v@#f;>|xhyWvp{S=~!h{U03rf zArK8Rtu5?=yagI9V^gHf7Q0bvGu2ufir2funUwT!xGa&>DDdB`y`f-s?v^FJ-v z0kw255<0ob{hk50@QD1(v1>u0X)RaYEUiUxmAYyn5m2Zlix744Yq-<;(%~oVEt=wK zT=~@ji8?lJh_bCmEKWqd47WP2<@t)WR<V-rU9~l*n&UJDck5XcMvgSLn*EBW{b)hM z2Kf0Gd5bviu^We%Q_i`5^s&!^gZqCwd38it-9>!&C;zisFh-i#%8-g2roH71k~2tI z1h&DlRFfUF3~OlPB!fO+)6Juc&?(d$`Ti9DU`Y!rdT4;a^)2$5vXAj^C@xh4;^C~e ze1}T4nVk#=!NPq@0D&whbRZ1aQBItu?J-nH8Cn9&MvWEob+uqLJ$0=gqS_OLdM!o6 zAtU>#s2(&Bj6k|OmJdACuO`k>7cc~<f7h<b6~q9e`Q<bdbUD`I3I-<Y@YAxn0bSN& z>EQ*~lA6t@z-3BVh$~f^J5gPQtXNe`hkrRuZdCKIYRjOH17W26dJMnZ?ptiVPVdwu z5K1<}fESpYE>$#06_v^}G+8965v}5dq-wAoI}7h+n+hJ?o2jbqn8#kF{ffpE)`kRF zpCSw6Xg(XV;;^L{VJ0yiz>Ed}ekEcQVxklDUZUv2@L<4_mL!%ShQOAm@9QSMj4$jC zy-uc)-#XYSt=e!8((xYX1I`R3mfg0}k55Font44ol{Stnu;cnvn5(JkVpoio_BBcu zTM!r{kcWkmg9mh>Cu^f6Z5<<pugEINPpC*XtA|oGFO?xSr|4o!gF;Qsy7yn%tWeH4 zK>J38h<c{EH5OP&La$2ZFm2+&lpe5)SI7WJi)sl=LXP6K@E{$Jcj=31+V{B#sq>*^ zf@Q=~2~sQM@d3Z?6t`5aDYjKXonw{CCMfPt8~@jU4Zxa%{}#vp^?Exz{%?1u)7{y@ z_`gnXYZL$X#p3^ZJOEG&|D#AhjNE$>41Ee9{^jVc^NSz)GjHO&o}W#Eq4?%pbqSOO z-}jy5V$v91et7RCr_nr_MA3)$qbNBYMwbBe_V@)zroIda<iO2~XqrULXm$=T&(LrN zFc@CgXm2+<+baeUJ_Z!sPNTis==LyF=z10aW>V>?=}xct1Ar|GHB_Sr)v!Y~iZ&Bj zs01TYkRhEYneyO!@?DXca2~S1VJ^~1T^@Xh&~$2p?vH-h3Ors~%^D%yk@yN)Cf`YA zPKLt=?C$Gy<ve)x?%1ABM}Bctha`Vq%M*Ubyig97Jir{J{vn<`1T1;wBvuA#Lxbrp z%At{vdDI-sLyn8-ITnW*v#97~Vp&`&u&e3L=~xBK6M+Gm{fq+p`tTW!vxK%L_W>X= z%rE%K%EM=FttiuvWH{k&0vu(I@2NGQd|~QT4gt#?Q%u=?X^P$*hSPaMhCCZZ%4b)< zIsTI?yTR?JT~c>kPL1+~(e_ypMDiZn)W0pf)d$f#N3aOFR?!}gFVAjxF-1g)dvPDW zluGXoR^<_AQBxEte6p9vAQk(NvU+$oA4DJf%+llVv|2d6DxR7u02N-?%jE*wVfV3& zZQ&CxEl_02V3eIuK|vftW0;#Gajh|@i>#%2FwzKSP~PXbjia`FV8qNx@f4jEV>BaM z3cA&DTF4#``61p%I|;I~I5Q4gJ=zHaYeTd2c#quqz>YTH>;2b<&UUk1SA$8?Fixn9 zhMk${@F$wwv4M5x;rsd+`FXQt<;n6`F97QYvuGlEwWrC>E0iPrK*|y9eQDDjG3U4! zhQPr?e`KsqDn;34B%fk!o>gp42xcRxswlJw1PVZ~8lrV$G;*6`xQ?jD#Wc`mgvQVa zwve6qbY&Nt-vwizD4}%3+sab#wemL$9<;EyE}0paooRF}NhxO0XEJ=HmB{5Hos$?b zi<n8lYv(318GAuk6@F^%h@j@QCK3E#z19?vh!%Kmd^Oz3?mc+5Rj*lk1bz~XeQrT* z%ajJ`mPyl3G+(oDZ$B1>QW0s$4!VfNS6ETSR;<^_d=m0>&)Kb{=@-0lf{7W>JPTha z*hOS*P}4KOE?kVBIJ)I>v!?rS{O|YV8P6JpF~_x@n?>b9;r>6LL}%Vac>X1XTTh{N z<dzY#<1zsOrTNOpw`Pp59jyihIJMsy4NWkH9hU%b(=&cXm)ZDO;DT=gQ3eb!X5Q6u zF{jgD0dBF2s@sa^9mhkn)GSDXp*N|z>_{G4m8I9$!8GJeR9Y3uVGTknmc&LUB8{Bg zQr5#mRj{J-$QgLUog8~9ID#;5bPAMuLkvLqK>_C85)+ia=E6aR9MA<adc({}IEfWe zVcxu&(S%Y+SQ{)TWIIZsNxLIOm2Fi_i(2_s0erSqWssz#7%0+Kl|iGoilO2w4Ub;b zGT@|sF{zNxW7M0})l91~<CQg;Ks$fTw{|!l;$)08iKLM^G7`d@;9-OtZc8N!<)M#U zimZn|SgecAS+}8#Xc6S`0-$SJw>0y0P^~fZl9U^&1*09;45!y-A@i`C;3udH2(%nZ z$dYE2qjiV11Qm}JdVM)^a!An#A<?^9FB;QE+k`EeK&b+_#2kV`HcD~!XDH<r6iy>S zgwB&Wq*>WgS{8Mgc1tP+1*^`g)mxy6)a6@YOfT7vq8XM;?BYoVMRO*ZY*pSoTRa*| z)0IP$Nm1^EE77$qe3I^_dw)tEHA$r&45?%GCj8KsH{&sNP3IXMal^yZ>V-Zcj{zG; zRDIP{A$%Z7y`E2<D(w{FDKe=HBsJCWlqOxXGZT&|YPNFfiE535a0qlyqu~X`MX{4m zC4;bJdNF;YN)hx9``f$nf(hmsLrWVTT!_(r6wLgQXjE0-<l-)u63oe6?q5<$)SyY3 z4ND6~Rgo!nWn&|4+(xZ@boKjxl&y9)C#d)cWqLd9w&)e5OxlG{($@``t&-)kjW5^( zjG-@m3M<0#)5x~7nc!(7oEKfhM!(^#Dig3P+&+jx7z;5Eu3V|I4r#X75vW0d?wr<Z zL5rkX9yvQL&(~_z6rOIn*jB4%SH$=sRCY*GiUfJulrY<;mF-xyqB?2HsM!*|FP3PF z*f`em6}0>oDYdSlO_{oga9SN%HP?9X9nYQeg_9*HAb#bPtVD#;TzR7?m&~R4T9}mO zN};p2U9=-QW&l@QLtkPpa1UT=o=@3TWE90fUKU6{^@RtoVmQWREsB9XGiz!l>zefn zWTY+`UKLx7sztw0rL#D-0WbFs^jTF_ZDP@s7LL2p%%_v7X~+wQMU3?N21bg2dmI{z zuDV#vth1jpHER2b^~;-F&a70JGh-9qS`hykhh0kNxz-ra;`q-_Z@04}<3D>lTa^E? zv&sLsiT})t|J-VHw)<28^G}}~bq|l8zak&?b94e;N3##~eYyD1$4q!Mn02O+L=EK_ z+(QZ;B@a8Bsspy*3_%dWUaZRB^2OwV``dX7IPHq#lYfB4OZ#Sx8P!{?T(GIW&ukUh zNDsIj&aG9}8a{f<u+dwFi>@I|blu^h`d}1>h^9enS|MwsD330q%7a)il1ullyZ<;i z$%z&<7*A#nv&5L?#F}w|Be>JBL~$0I0Rt}=HJWAo)oG*L2b{iz<jDw9qiZliPk&Q) z+$@y}So-p_DX26pmA#C=*0>-}zrv=f=n;XDn2S!RRCb<_Ok64&h=G<21L74I#{pU6 zBmZatK=%;f^H`=7M##8a%+WHTL4x5{3I-BWP?+1(Rh5SXnE=|#yIN$>CNQXEU{L9J zo&~W%`GYLbaR`AgrvPBj`I29k#xEtSQ<jf<0m4;!YN^PlwMIRy7V)$=Y-pSpH}px) zjL$XINu_dG*>E8-2rQwK9x)^aGaEIO8&RU7lMzKNYjDakXk;M`RT^<r8WAtja+i13 z3nj~R*h?W@-eGU`a&kObPbd3exWdKM%W>7)iLyJoh0c(U@EYh-Rnc|RD7j%ZI!$Lh zFJL(3J9ejh9?=V(@<sAaek<_Hm+Jnd$;%-vOIN;kzFdE&8$#}+zh9sH#tXmk!msLu z7kq0tEOjH4*^4xTymQ$7&Mz;$*^TDN6qBlqBQu>}`Xye5)z2ggyRMA2(B@hy*u3j% zoix5=7mw9_NVg+N(J*WF<zgL_**#lgy({!>aiCjyw-nD-<<|b6X&kKm`maU)pWW?V z`<u>Ir`>6{w|m>W=>OT;-r4wn9;pB4j+{EXjZXU(Nj*A0G}=4x-#+Y+KEnKhX*QbR zMT{u4>7Xc^d8DjgIx?@5xRcPS@>v{BMh;B_4#SK*itA@fxPB5?V=~U4JoitSP&(PQ zJA+8rBsWuPDtr9`wQ6>=gqL{3i5KII>HMR71!CjB(;xKz&TF(?^|_Ig?p%x$wzH9P zGkwJcA$ws#cFgog4IQldCOUjoshkg_1rXf=-1<16ffz_hIAGa9=_Ph2)m>-9H3%4O zIksf~ftVrFpex2>BDb1m!_rp$v8$B`7)!#u`IL49CQRm?pqiZ0B<WUAZg|MagU&M& zD&v4=Wqt#BRg4V&Fh)T9%#4t_ein_@0~spC{BG;LMaf0A4jR`gwz!*g{1>sh=(oBu zdf7|%1}xjPT6fM!X=B27e%adi06lv2h*j2~deCEzin5>m5ZGR(73F;toWVZxQ|2iF z5B$0tZ*&#J5l?`t^bg+@v9FZ4R=tdnTS(;XLQ22^(<9S@>F$fy)SO0BnHKj5uV-=9 zixIP+!8ASz&O^{cXNzH@_!wB#i~R+F!>if&?QF6DD!TF}i=p`bC<B$i-hUuFcrPtw zWF4VUL7#>|E5frdXVNUo?>I&P!|TQJ@87#e`|c@Ba{MRj6wnAyi&oUhyjWhAoQ1ZT zP8pjy8h-qXrwAT<m%-#lNM2gf*~%_2g6WIJx#l!}B7NaK0G}F@{lEDP)zNC7Qsho_ z_D_G9Fri735>rn=i^5S$nyXvsA)VJJhsSTU5L%AYBvC`mOtJD}mopzXua2i-b3)%| znJ><Kfek98{Ha8QB!^YYy0x#JY&3+9VHZAp=4#epR=aunQ5~C77GYeh#Zgeb(;0^g z9ZBj1Od(K=ZJ)GCKf5^xyO?52tuRg+7SU`!a;TQz+}0E)NbjT(sqf?N8lWD7T~{Lv zOEW9M?2=?&&K#;NZc2_3w}^H*{Zp8FZkpQS$LO`xP`&E#uG-o~EnR0T6|?us3?Qx+ zQ9lZa&XQzFp{^|%F_TZANKyky`LIa%T0c!uuaq`*5+zmzRzU?Nt$gm`s}nTb!at37 z2j=1@$iu4+GHMRL`%v{mW`dmx5inZRR7-gDsZgDw$QiMXHzH`3(uXyMQagpNp<T>G zY>VHF{i&^dQI{;EFJvb9@|2p3O#!W%=w(!-W)Y`)L19F(bA92<Yaci|^i8mGBW7}e ziQ<?!5Oa1t3r51&z-oGt2w0+7N}hK)S?@fENikylS;Cl>31M0$VoC4?6Z|4Zpkw(a z4S4u@t$|96MD-KnkWB=W5y5opv_^mEz#iom4JgcaHB}?-85np^UI>N!Ew6&gP6R0c zwYN20?8_CR3|hH*)^jFT+7er^lGKJ)4_8_}$C1W-d7(cB&++9p8|K9NNJbU0kazgE zTG59pMW6&Wr2~+5zN14bu}k%eai5ccR7NLNShuTcGTq%|u>f$*ZK`edYkJASM?b$X z>U%}y`O<DL&vmzKc^R9akdOZJ8Xc+CDU(SX^a^!%yP<w>;zTxaBI}D2$x5@;@-?XZ zYhJ^q?!_|Ku+c}%;h^nlt1800aFOCYBy_SqM{5SPd=joiEtLJ}5BLVKs^VLjSY04l zh+Ub|ES<L@Qlao`J7jFce}FW-@B3eRo$gkb;y>EETU$Hq{4f02#DDyW;y-?9v^$M1 zyVM2mPmWk*1-o;XFR%_yWC!H&B<3H`q5v<QZnKRSW@Cv<^arR;wAlCi%N_#;y!vV1 zE!%0|qQkyrqBj=DfTV|gwPRS~w&Bpf7Ax+TUZGVb1y<S;i#A)24fS8GurBM^WS_pD z?9&<hjA?%9YHx3Cc-b5gTVP;G4Y1sj3BnVYHr^OsN*i7t%S9rd`omxx3^Sc*a=EAl zh03XZ%c(Gu=!TaJCS_(6AXAJkc5}-eo(Qv=HJ7rw%+Kwar6s0vXT&6)H0AWN=fgA< zX)@~N&W347M4D;^#bpr37-Cr#1&5bOq0x1j20xI9Ro)BZpz>BZZ_exnBKQFLnP;A3 zF$h$+FyV6MQe87ZKle&&mA?1zBfZ&VfC@p&t(_tR)!pVlBhXMvoO+{LpHRA8^^(^; zeM(6-6)u0y25RG@$p{wh1U*mLImO~blo*XF%NwFtv@~Mn-N@!aEj{+mN^&}DEyG3W z!-QXWg;6RvCCEC1dz|t9n7EcrhP-HKIZLi*a5*|0-dZ_|$<#Mvfo3}SDQ)S-A8%+@ zS(3+f(S6Niyh^|GvMNNsqMBU!GN8;e!Eqy4Cj8N0q$XQ?>~sJUv#dD3`ThsxeOJ8i zwP8mCm2nT6x(4+G%#P!{j6&gAV;{G~$A9?VOnJWz@AoGY3(X>VgR9ZH*HII<EQId4 zbp0{%M?$v+pKCesK-R-bbK{8Spa~JSUNxgfP6S-X=o0y%YxINs&@=i#e%LZPL4Md) zKJ2I;cC?7fOv6QE_PVZ~_>V<w6*Tb`)ba|DD6gKwtLJ#cKza8DHC=ypiMesK4>n?` z(!Ey(DG}MG)cYA3p!!A|KnHeMLqEh2;u(r23){(2)ZVcfJQ(7#Jp{Rx<W?~NhMWW> zJ>_3iTWvuTS!4D@^?Fk+1*Uj7F2M9f`VjFcpO&!P%&tvk_pi7mbN>$)SxUiz=nEZ# ztJ1o3a_5CEgXP@@>oNJ4FnpU^#k#shGz<=vWst6BPd9DqtRIN~q(4>B2ecqyWyLer zicAc!Y~xQEONJ%tw`}+5@iW*;MrxYllM0g2K))ggw8$I-HhyBMod>9N@>R1+x|G&Z z+-BesTsP>oO$X6qEVU{0nL<Fe{p@BGg5`eXU}V|9#?d%|t*|d>jP*j9&o&e!{m7QW zZA>bH^do30U2*~yXT@T@Pl3Mt+ReIi!uI6!_pi(!AQTYRsY*5VY?~}~E^<R_SG8yw z7e+PFO@SGlj~$9(LskV{cjjZP75=}6x)U=a)aj5LOs0rVwD}+DT4`&-G__avu;=WF z$e_E7F~c{RxO@XU!t{}1luQ0~OPqBjY-z-`?LijAF=7lD_X|VV$V-9e1x9`hLWNkG zX)`=fO?h8fBOgj~!J5M><b@LdjVXYUNYd>o3otJv5G|rwAV(i%ratUu{-aiiD9hRG zSdC<CesCk!xmn;YMxJhTmBAOg**fe{i;JTS<jlkKRIO#iQwAF-V}^V{^ihsrSZ~Mv z5cDratn^Do{tBXnJ_3Imw~%EgC7K+S6QYICnLKV{C~|VRnUB?Yna^T8TV<|M(|TWZ zNkeBIKkCNMPrz`W`=59-Klc@5D3I7kG8xO}*h3dE4(4>`OqRA-5mU8=j3;p*8>I*r zw-*a@cEO^8bINrT##)QIB)NltC#>YS^bh^gq3bDTe%Ug|M{<$9qO;9NIXg?^U(;z| zr>e^7TImYqtl>vYMSW#wefhpi2S}Zv*e>)HVmt6#rQFPT&v*n)8ww}U+Cgh^D&b`> z$>SwuuqT)VRcKID4Q^r5s%mUW#~xRXGl_#x3#5)(kr2vQg^=1xGsG))_+7R|dtF&c zY!&98%+E)e(vHVrX?H~)RmPjDZEsO8@NFS)iZt<jyXUm~3yPN3(nLR%*3+`}+`9iB zTY8bxW$VH+W1F9Qb{U<hvOvnEK1rB4vAOCkW*R6zr#+b2ampFXKdc#Vdnq$scVoue znDM@V8Sm?{;$`hYx^LhMScGn8<Pqd=%sgMn%=5LGWbV@HQu*Sxm9^Ma7IxJ`H@t{z ztcXp{Zc16CIE>g!f1+vLM@C)l#B^q+yM3v$Er*C$x(}+{lI27{^+h*+{#tBL>KKTv zsb+^N9(1vFr!)u(<LJB4n$u|f<j_0K2Z85i(x?=3#>JKLgxhz$w#F^6?%ah5{SG&^ zY6~gL)1s}>Y8%)4*|x5&e{#31xh0GDrFP*fW8P8$Y+c{W$=_1lt0mW1zJv8Pr1Xnh zDRKVZEBjK7g#)8!gUc60-1CyjYqpX-J3H2T+O)L{xRM-C4HC>5SeAM~v0_vfn9?)( z^cMFjh;zUlIv82N`;!hcI->w2YeM-hiu2YII7X6TOjcaAC#)@7utc6*Td|TUc5Owu zq^PACZ7ZAW8aL18**u$P^K72Yvw1ep=Gi=(XY*{H&9iwn&*s@Yn`iTEo~57v2e_jp I(g4r`07u-cdjJ3c diff --git a/packages/inv_cisco_support b/packages/inv_cisco_support index 967067e..47f89b5 100644 --- a/packages/inv_cisco_support +++ b/packages/inv_cisco_support @@ -13,7 +13,10 @@ 'v.0.0.11: fixes for CMK 1.5.x\n' '\n' 'v0.1: rewrite for CMK 2.0\n' - ' - suggestion and api_status removed\n', + ' - suggestion and api_status removed\n' + '\n' + 'v0.2.0: moved to CMK 2.1\n' + ' - changed to new rest API (apix.cisco.com)\n', 'download_url': 'https://thl-cmk.hopto.org', 'files': {'agent_based': ['inv_cisco_eox.py', 'inv_cisco_contract.py', @@ -21,22 +24,21 @@ 'inv_cisco_psirt.py', 'utils/inv_cisco_support.py'], 'bin': ['ciscoapi/ciscoapi.py', - 'ciscoapi/ciscosupport.py', 'ciscoapi/cisco-eox.py', 'ciscoapi/cisco-sn2info.py', 'ciscoapi/cisco-psirt.py', - 'ciscoapi/cisco-bug.py'], - 'web': ['plugins/views/inv_cisco_support.py', - 'plugins/wato/inv_cisco_bug.py', - 'plugins/wato/inv_cisco_eox.py', - 'plugins/wato/inv_cisco_contract.py', - 'plugins/wato/inv_cisco_psirt.py', - 'htdocs/css/inv_cisco_support.css']}, + 'ciscoapi/cisco-bug.py', + 'ciscoapi/cisco_live_cycle_utils.py'], + 'gui': ['views/inv_cisco_livecycle.py', + 'wato/inv_cisco_bug.py', + 'wato/inv_cisco_contract.py', + 'wato/inv_cisco_eox.py', + 'wato/inv_cisco_psirt.py'], + 'web': ['htdocs/css/inv_cisco_support.css']}, 'name': 'inv_cisco_support', - 'num_files': 17, 'title': 'Inventory for Cisco Bug, EoX, contract status, PSIRT advisories and ' 'suggested software', - 'version': '20221130.v0.1c', - 'version.min_required': '2.0.0', - 'version.packaged': '2021.09.20', - 'version.usable_until': None} \ No newline at end of file + 'version': '0.2.0-20230609', + 'version.min_required': '2.1.0b1', + 'version.packaged': '2.1.0p21', + 'version.usable_until': '2.2.0b1'} \ No newline at end of file -- GitLab