diff --git a/agent_based/inv_cisco_bug.py b/agent_based/inv_cisco_bug.py index 111928277f8a754d98955722f3a7abe933b7e56e..65d869bdddf1bfec15b971a1bcb67169ed6de638 100644 --- a/agent_based/inv_cisco_bug.py +++ b/agent_based/inv_cisco_bug.py @@ -11,12 +11,15 @@ # #bug['known_fixed_releases'] = bug['known_fixed_releases'].split(' ') # #bug['known_affected_releases'] = bug['known_affected_releases'].split(' ') # 2021-07-24: rewritten for CMK 2.0 +# 2021-07-26: some cleanup +# import os import json import logging import time -from typing import List +from typing import List, Optional +from dataclasses import dataclass from cmk.base.plugins.agent_based.agent_based_api.v1 import ( register, SNMPTree, @@ -33,187 +36,150 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( InventoryResult, ) -# from cmk.base.plugins.agent_based.utils import ciscoapi as utils from cmk.base.plugins.agent_based.utils.ciscoapi import ( set_loglevel, expand_path, - compact_info_by_serial, - check_bad_serial, - pid_on_black_list, - sn_on_black_list, - pid_on_bad_list, - set_PID_black_list, - set_PID_bad_list, - set_SN_black_list, + get_base_path, ) -def parse_inv_cisco_bug(string_table: List[StringTable]): - section = string_table - return section - -def inventory_cisco_bug(params, section) -> InventoryResult: - - def get_OS_Version(cw_version, sysdescription, inventory): - version = None - pid = '' - # cw_version = None - sys_version = None - phy_version = None - - if str(cw_version).find('CW_VERSION$') == 0: - cw_version = cw_version.split('$')[1] # .upper() - - sysdescription = sysdescription.split(',') - # sys description from N5K-C5672UP (has two version entrys :-( ) - # Cisco NX-OS(tm) n6000, Software (n6000-uk9), Version 7.0(8)N1(1), RELEASE SOFTWARE Copyright (c) 2002-2012 \ - # by Cisco Systems, Inc. Device Manager Version 6.0(2)N1(1), Compiled 2/20/2016 21:00:00 - for entry in sysdescription: - if not sys_version and 'version' in entry.lower(): # get only the first 'version' entry - sys_version = entry[entry.lower().find('version') + 7:].strip() - if sys_version.startswith(':'): # AsyncOS - sys_version = sys_version[1:].strip() - sys_version = sys_version.split(' ')[0].strip() - sys_version = sys_version # .upper() - - low_index = 1000000 - for phyentry in inventory: - phyindex, physoftwarerev, phymodelname, physicalclass = phyentry - if physicalclass == '3': # chassis - if int(phyindex) <= low_index: - if phymodelname != '': # PID at all - if phymodelname.find(' ') == -1: # no spaces in PID - low_index = int(phyindex) - pid = phymodelname - phy_version = physoftwarerev.split(',')[0] - - version = cw_version - if version == '' and sys_version: - version = sys_version - if version == '' and phy_version: - version = phy_version - - # remove leading '0' in IOSXE version numbers - if version and version[0] == '0': - version = version.replace('.0', '.') - version = version.lstrip('0') - version = version.rstrip(';') - - # todo: cut intermediate for ASA version as option - - return version, pid - - def get_bug_status(bug_status): - _bug_status = { - 'F': 'Fixed', - 'O': 'Open', - 'T': 'Terminated' - } - return _bug_status.get(bug_status, bug_status) - - def get_bug_behavior_changed(behavior_changed): - _behavior_changed = { - 'Y': 'yes', - 'N': 'no', - } - return _behavior_changed.get(behavior_changed, behavior_changed) +@dataclass +class SnmpBug: + version: str + pid: str + + +def _get_os_version(cw_version, sysdescription, inventory): + pid = '' + sys_version = None + phy_version = None + + if str(cw_version).find('CW_VERSION$') == 0: + cw_version = cw_version.split('$')[1] # .upper() + + sysdescription = sysdescription.split(',') + # sys description from N5K-C5672UP (has two version entries :-( ) + # Cisco NX-OS(tm) n6000, Software (n6000-uk9), Version 7.0(8)N1(1), RELEASE SOFTWARE Copyright (c) 2002-2012 \ + # by Cisco Systems, Inc. Device Manager Version 6.0(2)N1(1), Compiled 2/20/2016 21:00:00 + for entry in sysdescription: + if not sys_version and 'version' in entry.lower(): # get only the first 'version' entry + sys_version = entry[entry.lower().find('version') + 7:].strip() + if sys_version.startswith(':'): # AsyncOS + sys_version = sys_version[1:].strip() + sys_version = sys_version.split(' ')[0].strip() + sys_version = sys_version # .upper() + + low_index = 1000000 + for phyentry in inventory: + phyindex, physoftwarerev, phymodelname, physicalclass = phyentry + if physicalclass == '3': # chassis + if int(phyindex) <= low_index: + if phymodelname != '': # PID at all + if phymodelname.find(' ') == -1: # no spaces in PID + low_index = int(phyindex) + pid = phymodelname + phy_version = physoftwarerev.split(',')[0] + + version = cw_version + if version == '' and sys_version: + version = sys_version + if version == '' and phy_version: + version = phy_version + + # remove leading '0' in IOSXE version numbers + if version and version[0] == '0': + version = version.replace('.0', '.') + version = version.lstrip('0') + version = version.rstrip(';') + + # todo: cut intermediate for ASA version as option + + return version, pid + + +def _get_bug_status(bug_status): + _bug_status = { + 'F': 'Fixed', + 'O': 'Open', + 'T': 'Terminated' + } + return _bug_status.get(bug_status, bug_status) + + +def _get_bug_behavior_changed(behavior_changed): + _behavior_changed = { + 'Y': 'yes', + 'N': 'no', + } + return _behavior_changed.get(behavior_changed, behavior_changed) + + +def parse_inv_cisco_bug(string_table: List[StringTable]) -> Optional[SnmpBug]: + cw_version, sysdescription = string_table[0][0] + pids = string_table[1] + + # get OS version + version, pid = _get_os_version(cw_version, sysdescription, pids) + + if version == '' or pid == '': + return + else: + return SnmpBug( + version=version, + pid=pid + ) + + +def inventory_cisco_bug(params, section: SnmpBug) -> InventoryResult: set_loglevel() - # disable_bug = False optionalcolumns = [ 'base_pid', - # 'bug_id', - # 'behavior_changed', 'description', - # 'headline', 'id', 'known_affected_releases', - # 'known_fixed_releases', - # 'last_modified_date', 'product', - # 'severity', - # 'status', - # 'support_case_count', ] - # version = '' - not_updated = 10000000 - dont_show_older_then = '0000-00-00' - conf_file = '~/etc/ciscoapi/ciscoapi.json' - conf_file = os.path.expanduser(conf_file) - bug_status = {} - # prepare path variables (expand homedir and add '/') - base_path = '~/var/ciscoapi' - cw_version, sysdescription = section[0][0] - pids = section[1] - # get parameters from wato if params: optionalcolumns = params.get('removecolumns', optionalcolumns) - # get OS version - version, pid = get_OS_Version(cw_version, sysdescription, pids) - - if version == '' or pid == '': - return - - # check for conf_file and read parameters - if os.path.isfile(conf_file): - with open(conf_file) as f: - try: - config = json.load(f) - base_path = config['global']['base_dir'] - except ValueError as e: - logging.warning(f'snmp_cisco_eox:status:JSON load error: {e}') - + base_path = get_base_path() bug_dir = base_path + '/bug' path_found = expand_path(bug_dir + '/found/') path_not_found = expand_path(bug_dir + '/not_found/') path_request = expand_path(bug_dir + '/request/') - status_path = expand_path(base_path + '/status') - # node = inv_tree_list('software.support.cisco_bug.') - path = ['software','support','cisco_bug'] - yield Attributes( - path=path, - inventory_attributes={ - 'os_version': version, - 'PID': pid, - }) + path = ['software', 'support', 'cisco_bug'] + attribute = { + 'os_version': section.version, + 'PID': section.pid, + } - pid = pid.replace('/', '_') + pid = section.pid.replace('/', '_') # bug report found - if os.path.isfile(path_found + pid + '/' + version): - logging.info('snmp_cisco_bug:report found: %s, %s' % (pid, version)) - modifytime = os.path.getmtime(path_found + pid + '/' + version) - yield Attributes( - path=path, - inventory_attributes={ + if os.path.isfile(path_found + pid + '/' + section.version): + modifytime = os.path.getmtime(path_found + pid + '/' + section.version) + attribute.update({ 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), 'status': 'found' }) bug_record = '' - with open(path_found + pid + '/' + version) as f: + with open(path_found + pid + '/' + section.version) as f: try: bug_record = json.load(f) except ValueError as e: - logging.warning(f'{host_name}:snmp_cisco_bug:bug_found:JSON load error: [e]') + logging.warning(f'inv_cisco_bug:bug_found:JSON load error: {e}') + exit() if bug_record != '': - yield Attributes( - path=path, - inventory_attributes={ - 'Total_records': bug_record.get('total_records', 0), - }) + attribute['Total_records'] = bug_record.get('total_records', 0) missing = bug_record.get('missing', {}) if len(missing.keys()) != 0: - yield Attributes( - path=path, - inventory_attributes={ - 'missing_records': len(missing.keys()) * 10, - }) + attribute['missing_records'] = len(missing.keys()) * 10 bugs = bug_record.get('bugs') bug_ids = [] @@ -223,12 +189,10 @@ def inventory_cisco_bug(params, section) -> InventoryResult: bug_ids.append(bug.get('bug_id')) bug['known_fixed_releases'] = ', '.join(bug['known_fixed_releases'].split(' ')) bug['known_affected_releases'] = ', '.join(bug['known_affected_releases'].split(' ')) - bug['status'] = get_bug_status(bug['status']) - bug['behavior_changed'] = get_bug_behavior_changed(bug['behavior_changed']) - logging.info('remove columns: %s' % optionalcolumns) + bug['status'] = _get_bug_status(bug['status']) + bug['behavior_changed'] = _get_bug_behavior_changed(bug['behavior_changed']) if optionalcolumns is not None: for column in optionalcolumns: - logging.info('remove column: %s' % column) bug.pop(column, None) clean_bugs.append(bug) key_columns = {'bug_id': bug.get('bug_id')} @@ -238,91 +202,31 @@ def inventory_cisco_bug(params, section) -> InventoryResult: key_columns=key_columns, inventory_columns=bug, ) - yield Attributes( - path=path, - inventory_attributes={ - 'duplicate_records':len(bugs) - len(clean_bugs), - }) - + attribute['duplicate_records'] = len(bugs) - len(clean_bugs) # bug report not found - elif os.path.isfile(path_not_found + pid + '/' + version): - logging.info('snmp_cisco_bug:report not found: %s, %s' % (pid, version)) - modifytime = os.path.getmtime(path_not_found + pid + '/' + version) - yield Attributes( - path=path, - inventory_attributes={ - 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), - 'status': 'Not found', - }) + elif os.path.isfile(path_not_found + pid + '/' + section.version): + modifytime = os.path.getmtime(path_not_found + pid + '/' + section.version) + attribute.update({ + 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), + 'status': 'Not found', + }) else: # create new request path_request = expand_path(path_request + pid) - if not os.path.isfile(path_request + '/' + version): - logging.info('snmp_cisco_bug:create request: %s, %s' % (pid, version)) - with open(path_request + '/' + version, 'w+') as f: + if not os.path.isfile(path_request + '/' + section.version): + with open(path_request + '/' + section.version, 'w+') as f: # only create file (touch) pass - modifytime = os.path.getmtime(path_request + '/' + version) - yield Attributes( - path=path, - inventory_attributes={ - 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), - 'status': 'requested', - }) - - # # create and write back api status, will be used for active check cisco_api_status - # apistatus = {} - # lastrun = {} - # laststatus = {} - # thisrun = [] - # apiname = 'bug' - # rewrite = False - # use_keys = ['sir', 'advisoryTitle', 'lastUpdated'] - # - # if bug_status != {}: - # - # # in CMK v1.5.x global variable g_hostname was replaced by API call host_name() - # try: - # host_name = host_name() - # except NameError: - # host_name = g_hostname - # - # if os.path.isfile(status_path + host_name): - # with open(status_path + host_name) as f: - # try: - # apistatus = json.load(f) - # except ValueError, e: - # logging.warning('snmp_cisco_bug:status:JSON load error: %s' % e) - # - # if apistatus.get(apiname, None) is not None: - # lastrun = apistatus.get(apiname).get('lastrun', {}) - # laststatus.update({'status': apistatus.get(apiname).get('status', '')}) - # laststatus.update({'product_family': apistatus.get(apiname).get('product_family', '')}) - # laststatus.update({'Last_checked': apistatus.get(apiname).get('Last_checked', '')}) - # else: - # apistatus.update({apiname: {}}) - # - # if bug_status != laststatus: - # apistatus.get(apiname).update(bug_status) - # rewrite = True - # - # if bug_status != node: - # # cleanup bug infos for status - # advisories = node.get('advisories', {}) - # if advisories != {}: - # for entry in advisories: - # this_entry = {} - # for key in entry.keys(): - # if key in use_keys: - # this_entry.update({key: entry.get(key)}) - # thisrun.append(this_entry) - # - # if lastrun != thisrun: - # apistatus.get(apiname).update({'lastrun': thisrun}) - # rewrite = True - # if rewrite: - # with open(status_path + host_name, 'w') as f: - # json.dump(apistatus, f) + modifytime = os.path.getmtime(path_request + '/' + section.version) + attribute.update({ + 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), + 'status': 'requested', + }) + + yield Attributes( + path=path, + inventory_attributes=attribute + ) register.snmp_section( @@ -354,7 +258,6 @@ register.snmp_section( register.inventory_plugin( name='inv_cisco_bug', inventory_function=inventory_cisco_bug, - inventory_default_parameters={ - }, + inventory_default_parameters={}, inventory_ruleset_name='inv_cisco_bug', ) diff --git a/agent_based/inv_cisco_contract.py b/agent_based/inv_cisco_contract.py index 1bff9836cfd2da7336cc9d878cc0b4b6a5b76dc3..52d543f02209b5451ef4db550f3f199def93ff57 100644 --- a/agent_based/inv_cisco_contract.py +++ b/agent_based/inv_cisco_contract.py @@ -18,12 +18,22 @@ # 2020-08-04: moved node tree from hardware.system.support to hardware.system # cleanup create_sn2info_record # 2021-07-24: rewritten for CMK 2.0 +# 2021-07-25: some cleanup +# +# ToDo: parse function and SNMP section can be shared between inv_cisco_eox and inv_cisco_contract +# pro: only one section/parse function +# con: with only one pare function these plugins can not disabled separately +# solution: separate SNMP section, common parse function +# ToDo: error handling for json.load -> in eox/contract pass, in psirt/bug -> exit +# ToDo: exit codes ?? # -import logging import os import json import time +from typing import Dict +from dataclasses import dataclass + from cmk.base.plugins.agent_based.agent_based_api.v1 import ( register, SNMPTree, @@ -37,20 +47,21 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( InventoryResult, ) -# from cmk.base.plugins.agent_based.utils import ciscoapi as utils from cmk.base.plugins.agent_based.utils.ciscoapi import ( - set_loglevel, expand_path, - compact_info_by_serial, check_bad_serial, pid_on_black_list, - sn_on_black_list, - pid_on_bad_list, - set_PID_black_list, - set_PID_bad_list, - set_SN_black_list, + set_pid_black_list, + get_base_path, ) + +@dataclass +class SnmpContractEntry: + phydescr: str + pid: str + + def _create_sn2info_record(sn2infofile, optional_columns): sn2info = {} if os.path.isfile(sn2infofile): @@ -58,10 +69,9 @@ def _create_sn2info_record(sn2infofile, optional_columns): sn2inforecord = json.load(f) modifytime = os.path.getmtime(sn2infofile) - sn2info.update({'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)) - }) + sn2info.update({'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime))}) - # add nonempty values to sn2inforecord + # add nonempty values to sn2info record for key, value in [ ('serial_number', sn2inforecord.get('sr_no')), ('is_covered', sn2inforecord.get('is_covered')), @@ -77,200 +87,93 @@ def _create_sn2info_record(sn2infofile, optional_columns): ('warranty_type', sn2inforecord.get('warranty_type')), ('warranty_type_description', sn2inforecord.get('warranty_type_description')), ('warranty_end_date', sn2inforecord.get('warranty_end_date')), - ]: + ]: value = value.strip() if value != '' and key not in optional_columns: sn2info[key] = value - logging.info('create_sn2info_record sn2info: %s' % sn2info) - # print(sn2info) return sn2info -def parse_inv_cisco_contract(string_table: StringTable): - section = string_table - return section - -def inventory_cisco_contract(params, section) -> InventoryResult: - set_loglevel() - disable_contract = False - optionalcolumns = ['contract_site_state_province', - 'warranty_type_description', - 'warranty_end_date', - 'warranty_type', - ] - base_path = '~/var/ciscoapi' - conf_file = '~/etc/ciscoapi/ciscoapi.json' - conf_file = os.path.expanduser(conf_file) +def parse_inv_cisco_contract(string_table: StringTable) -> Dict[str, SnmpContractEntry]: + serials: Dict[str, SnmpContractEntry] = {} + for phydescr, physerialnum, phymodelname in string_table: + if not check_bad_serial(physerialnum): + if not pid_on_black_list(phymodelname): + phymodelname = phymodelname.strip() + physerialnum = physerialnum.strip() + if phymodelname == '': + phymodelname = 'UNSPECIFIED' + pid = phymodelname.split()[0].upper() + serial = physerialnum.split()[0].upper() + if serial not in serials.keys(): + serials.update({serial: SnmpContractEntry( + phydescr=phydescr, + pid=pid + )}) + else: + temp = serials[serial] + temp.phydescr = ', '.join(list(set(temp.phydescr.split(', ') + [phydescr]))) + temp.pid = ', '.join(list(set(temp.pid.split(', )') + [pid]))) + serials[serial] = temp + + return serials + + +def inventory_cisco_contract(params, section: Dict[str, SnmpContractEntry]) -> InventoryResult: + optionalcolumns = [ + 'contract_site_state_province', + 'warranty_type_description', + 'warranty_end_date', + 'warranty_type', + ] # get parameters from wato if params: optionalcolumns = params.get('removecolumns', optionalcolumns) - set_PID_black_list(params.get('PID_black_list')) - - # check for conf_file and read parameters - if os.path.isfile(conf_file): - with open(conf_file) as f: - try: - config = json.load(f) - base_path = config['global']['base_dir'] - except ValueError as e: - logging.warning(f'snmp_cisco_eox:status:JSON load error: {e}') - # exit() + set_pid_black_list(params.get('PID_black_list')) + base_path = get_base_path() sn2info_dir = 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/') - status_path = expand_path(base_path + '/status') path = ['hardware', 'support', 'cisco_contract'] - serials = [] - serialscontract = [] - nodes ={} - - logging.info('raw info: %s' % section) - - # if no serial or no PID no service ;-) - # remove all empty serials - info = [i for i in section if not check_bad_serial(i[1])] - # remove all empty PIDs - #info = [i for i in info if i[2] != ''] - - logging.info('info no empty pid/serail: %s' % info) - - info = compact_info_by_serial(info) - - for phydescr, physerialnum, phymodelname in info: - - logging.info('raw pid : %s' % phymodelname) - logging.info('raw sn : %s' % physerialnum) - logging.info('raw desc : %s' % phydescr) - - # cut PID/SN at first space sign, change to all Uppercase - pid = phymodelname.split()[0].upper() - physerialnum = physerialnum.split()[0].upper() - - # drop all PIDs on Black List, create default node entry for SN - if not pid_on_black_list(pid): - - logging.info('PID not on blacklist: %s' % pid) - if phydescr == "": - phydescr = 'requested' - - # create list of serial numbers to request contract info for - serials.append(physerialnum) - key_columns = {'serial_number': physerialnum} + for serial in section.keys(): + if not pid_on_black_list(section[serial].pid): + key_columns = {'serial_number': serial} + serialscontract = ({ + 'pid': section[serial].pid, + 'ProductIDDescription': section[serial].phydescr, + 'is_covered': 'requested', + }) + # find serial in SER-sn2info-covered + if os.path.isfile(path_found + serial): + serialscontract.update(_create_sn2info_record(path_found + serial, optionalcolumns)) + serialscontract.pop('serial_number') + # find serial in SER-sn2info-notcovered + elif os.path.isfile(path_not_found + serial): + modifytime = os.path.getmtime(path_not_found + serial) + serialscontract.update({ + 'is_covered': 'NO', + 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), + }) + else: + # create new request in SER-sn2info-request + if not os.path.isfile(path_request + serial): + with open(path_request + serial, 'w+') as f: # only create file (touch) + pass + modifytime = os.path.getmtime(path_request + serial) + serialscontract.update({ + 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), + }) yield TableRow( path=path, key_columns=key_columns, - inventory_columns={ - 'pid': pid, - 'ProductIDDescription': phydescr, - } + inventory_columns=serialscontract ) - nodes[physerialnum] = { - 'pid': pid, - 'ProductIDDescription': phydescr, - } - - - logging.info("serial numbers : %s" % serials) - - for serial in serials: - logging.info("Serialnumber: %s " % serial) - # find serial in SER-sn2info-covered - # print(serial) - if os.path.isfile(path_found + serial): - logging.info("Serial number covered: %s" % serial) - # print(f'found: {serial}') - serialscontract.append(_create_sn2info_record(path_found + serial, optionalcolumns)) - logging.info("Serial number Record: %s" % serialscontract) - # find serial in SER-sn2info-notcovered - elif os.path.isfile(path_not_found + serial): - logging.info("Serial number not covered: %s" % serial) - modifytime = os.path.getmtime(path_not_found + serial) - serialscontract.append({"serial_number": serial, - "is_covered": "NO", - "Last_checked": time.strftime('%Y-%m-%d', time.localtime(modifytime)), - }) - else: - # create new request in SER-sn2info-request - logging.info("SER request: %s" % serial) - if not os.path.isfile(path_request + serial): - with open(path_request + serial, 'w+') as f: - pass - modifytime = os.path.getmtime(path_request + serial) - serialscontract.append({"serial_number": serial, - "is_covered": "requested", - "Last_checked": time.strftime('%Y-%m-%d', time.localtime(modifytime)), - }) - - logging.info("Serials contract : %s" % serialscontract) - # print(serialscontract) - # update default entrys with data - for physerialnum in nodes.keys(): - #print(f'serial from nodes: {physerialnum}') - for serial in serialscontract: - #print(f'serial from serialscontract {serial}') - if physerialnum == serial['serial_number']: - key_columns = {'serial_number': physerialnum} - # print(f'serial_number from serial {serial["serial_number"]}, serial from nodes: {physerialnum}') - temp_ser = serial.copy() - temp_ser.pop('serial_number') - # print(f'inventory_columns {temp_ser}, key_columns: {key_columns}, path: {path}') - yield TableRow( - path=path, - key_columns=key_columns, - inventory_columns=temp_ser - ) - - - # # create and write back api status, will be used for active check cisco_api_status - # apistatus = {} - # lastrun = {} - # apiname = 'sn2info' - # use_keys = ['serial_number', - # 'covered_product_line_end_date', - # 'service_contract_number', - # 'Last_checked', - # 'pid', - # 'ProductIDDescription', - # 'Last_checked', - # 'is_covered', - # ] - # thisrun = [] - # - # # add only used keys to status - # for entry in node: - # this_entry = {} - # for key in entry.keys(): - # if key in use_keys: - # this_entry.update({key: entry.get(key)}) - # thisrun.append(this_entry) - # - # # in CMK v1.5.x global variable g_hostname was replaced by API call host_name() - # try: - # _hostname = host_name() - # except NameError: - # _hostname = g_hostname - # - # if os.path.isfile(status_path + _hostname): - # with open(status_path + _hostname) as f: - # try: - # apistatus = json.load(f) - # except ValueError as e: - # logging.warning(f'snmp_cisco_contract:status:JSON load error: {e}') - # - # if apistatus.get(apiname, None) is not None: - # lastrun = apistatus.get(apiname).get('lastrun', {}) - # - # if lastrun != thisrun: - # apistatus.update({apiname: {}}) - # apistatus.get(apiname).update({'lastrun': thisrun}) - # with open(status_path + _hostname, 'w') as f: - # json.dump(apistatus, f) register.snmp_section( @@ -284,7 +187,6 @@ register.snmp_section( '13', # entPhysicalModelName ] ), - detect=any_of( contains('.1.3.6.1.2.1.1.1.0', 'cisco'), startswith('.1.3.6.1.2.1.1.2.0', '.1.3.6.1.4.1.9.1') @@ -294,9 +196,6 @@ register.snmp_section( register.inventory_plugin( name='inv_cisco_contract', inventory_function=inventory_cisco_contract, - inventory_default_parameters={ - }, + inventory_default_parameters={}, inventory_ruleset_name='inv_cisco_contract', ) - - diff --git a/agent_based/inv_cisco_eox.py b/agent_based/inv_cisco_eox.py index ee287720ffdcbce1362350837b7bd1616d87aa4a..6827dc1b52c4c6338a802cf89191d32e059457a0 100644 --- a/agent_based/inv_cisco_eox.py +++ b/agent_based/inv_cisco_eox.py @@ -18,12 +18,15 @@ # 2020-08-04: code cleanup (_create_eox_record function) # moved node tree from hardware.system.support to hardware.system # 2021-07-23: rewrite for CMK 2.0 +# 2021-07-26: cleanup import os import json -import logging import time +from typing import Dict +from dataclasses import dataclass + from cmk.base.plugins.agent_based.agent_based_api.v1 import ( register, SNMPTree, @@ -39,19 +42,23 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( # from cmk.base.plugins.agent_based.utils import ciscoapi as utils from cmk.base.plugins.agent_based.utils.ciscoapi import ( - set_loglevel, expand_path, - compact_info_by_serial, check_bad_serial, pid_on_black_list, sn_on_black_list, pid_on_bad_list, - set_PID_black_list, - set_PID_bad_list, - set_SN_black_list, + set_pid_bad_list, + set_sn_black_list, + get_base_path, ) +@dataclass +class SnmpContractEntry: + phydescr: str + pid: str + + def _create_eox_record(eoxfile, optional_columns): eox = {} if os.path.isfile(eoxfile): @@ -64,7 +71,7 @@ def _create_eox_record(eoxfile, optional_columns): eox['pid'] = eoxrecord.get('EOLProductID') # Migration Info - EOXMigrationDetails = eoxrecord.get('EOXMigrationDetails') + eox_migration_details = eoxrecord.get('EOXMigrationDetails') for key, value in [ ('ProductIDDescription', eoxrecord.get('ProductIDDescription', '')), @@ -80,31 +87,49 @@ def _create_eox_record(eoxfile, optional_columns): ('EndOfRoutineFailureAnalysisDate', eoxrecord.get('EndOfRoutineFailureAnalysisDate').get('value', '')), # Migration Info - ('MigrationProductId', EOXMigrationDetails.get('MigrationProductId', '')), - ('MigrationInformation', EOXMigrationDetails.get('MigrationInformation', '')), - ('MigrationProductInfoURL', EOXMigrationDetails.get('MigrationProductInfoURL', '')), - ('MigrationProductName', EOXMigrationDetails.get('MigrationProductName', '')), + ('MigrationProductId', eox_migration_details.get('MigrationProductId', '')), + ('MigrationInformation', eox_migration_details.get('MigrationInformation', '')), + ('MigrationProductInfoURL', eox_migration_details.get('MigrationProductInfoURL', '')), + ('MigrationProductName', eox_migration_details.get('MigrationProductName', '')), ]: value = value.strip() if value != '' and key not in optional_columns: eox[key] = value # add serial if EoX from get_eox_by_serial - EOXInputType = eoxrecord.get('EOXInputType') - if EOXInputType == 'ShowEOXBySerialNumber': + eox_input_type = eoxrecord.get('EOXInputType') + if eox_input_type == 'ShowEOXBySerialNumber': eox['serial_number'] = eoxrecord.get('EOXInputValue') return eox -def parse_inv_cisco_eox(string_table: StringTable): - section = string_table - return section - - -def inventory_cisco_eox(params, section) -> InventoryResult: - set_loglevel() - +def parse_inv_cisco_eox(string_table: StringTable) -> Dict[str, SnmpContractEntry]: + serials: Dict[str, SnmpContractEntry] = {} + for phydescr, physerialnum, phymodelname in string_table: + if not check_bad_serial(physerialnum): + if not pid_on_black_list(phymodelname): + phymodelname = phymodelname.strip() + physerialnum = physerialnum.strip() + if phymodelname == '': + phymodelname = 'UNSPECIFIED' + pid = phymodelname.split()[0].upper() + serial = physerialnum.split()[0].upper() + if serial not in serials.keys(): + serials.update({serial: SnmpContractEntry( + phydescr=phydescr, + pid=pid + )}) + else: + temp = serials[serial] + temp.phydescr = ', '.join(list(set(temp.phydescr.split(', ') + [phydescr]))) + temp.pid = ', '.join(list(set(temp.pid.split(', )') + [pid]))) + serials[serial] = temp + + return serials + + +def inventory_cisco_eox(params, section: Dict[str, SnmpContractEntry]) -> InventoryResult: always_use_serial = False optionalcolumns = ['EndOfSecurityVulSupportDate', 'EndOfSWMaintenanceReleases', @@ -114,32 +139,15 @@ def inventory_cisco_eox(params, section) -> InventoryResult: 'MigrationProductName', ] - base_path = '~/var/ciscoapi' - conf_file = '~/etc/ciscoapi/ciscoapi.json' - conf_file = os.path.expanduser(conf_file) - # get parameters from wato if params: always_use_serial = params.get('always_use_serial', always_use_serial) optionalcolumns = params.get('removecolumns', optionalcolumns) - set_SN_black_list(params.get('SN_black_list')) - set_SN_black_list(params.get('PID_black_list')) - set_PID_bad_list(params.get('PID_bad_list')) - - - - - # check for conf_file and read parameters - if os.path.isfile(conf_file): - with open(conf_file) as f: - try: - config = json.load(f) - base_path = config['global']['base_dir'] - except ValueError as e: - logging.warning(f'snmp_cisco_eox:status:JSON load error: {e}') - # exit() - + set_sn_black_list(params.get('SN_black_list')) + set_sn_black_list(params.get('PID_black_list')) + set_pid_bad_list(params.get('PID_bad_list')) + base_path = get_base_path() eox_path = base_path + '/EoX' path_found = eox_path + '/found' path_not_found = eox_path + '/not_found' @@ -153,209 +161,109 @@ def inventory_cisco_eox(params, section) -> InventoryResult: path_found_ser = expand_path(path_found + '/ser') path_not_found_ser = expand_path(path_not_found + '/ser') - status_path = expand_path(base_path + '/status') - path = ['hardware', 'support', 'cisco_eox'] pids = [] - pidseox = [] + pidseox = {} serials = [] - serialseox = [] - nodes ={} - - logging.info('raw info: %s' % section) - - # if no serial or no PID no service ;-) - # remove all empty serials - info = [i for i in section if not check_bad_serial(i[1])] - # remove all empty PIDs - # info = [i for i in info if i[2] != ''] - - logging.info('info no empty pid/serail: %s' % info) - - # compact info will remove PIDs on black list and join entry's with identical serial number - info = compact_info_by_serial(info) - - for phydescr, physerialnum, phymodelname in info: - - logging.info('raw pid : %s' % phymodelname) - logging.info('raw sn : %s' % physerialnum) - logging.info('raw desc : %s' % phydescr) - - # cut PID/SN at first space sign, change to all Uppercase - pid = phymodelname.split()[0].upper() - physerialnum = physerialnum.split()[0].upper() + serialseox = {} + nodes = {} + for serial in section.keys(): # drop all PIDs/SNs on Black List - if not pid_on_black_list(pid) and not sn_on_black_list(physerialnum): - logging.info('PID not on blacklist: %s' % pid) - - if phydescr == "": - phydescr = 'requested' + if not pid_on_black_list(section[serial].pid) and not sn_on_black_list(serial): + if section[serial].phydescr == '': + section[serial].phydescr = 'requested' # check if PID on bad list, if so try serial number - if pid_on_bad_list(pid) or always_use_serial: - logging.info('PID on bad list: %s' % pid) - logging.info('use SN: %s' % physerialnum) - serials.append(physerialnum) + if pid_on_bad_list(section[serial].pid) or always_use_serial: + serials.append(serial) else: - pids.append(pid) - logging.info('PID not on bad list: %s' % pid) + pids.append(section[serial].pid) # create default node entry for PID/SN - key_columns = {'serial_number': physerialnum} + key_columns = {'serial_number': serial} yield TableRow( path=path, key_columns=key_columns, inventory_columns={ - 'pid': pid, - 'ProductIDDescription': phydescr, + 'pid': section[serial].pid, + 'ProductIDDescription': section[serial].phydescr, } ) - nodes[physerialnum] = { - 'pid': pid, - 'ProductIDDescription': phydescr, + nodes[serial] = { + 'pid': section[serial].pid, + 'ProductIDDescription': section[serial].phydescr, } - logging.info("PIDs : %s" % pids) - # remove duplicates pids = list(set(pids)) - logging.info("PIDs : %s" % pids) - for pid in pids: - logging.info("PID: %s " % pid) pidfile = pid.replace('/', '_') # find pid in PID-EOX-known if os.path.isfile(path_found_pid + pidfile): - logging.info("PID known: %s" % pid) - pidseox.append(_create_eox_record(path_found_pid + pidfile, optionalcolumns)) - logging.info("EOX Record: %s" % pidseox) + pidseox.update({pid: _create_eox_record(path_found_pid + pidfile, optionalcolumns)}) # find pid in PID-EOX-unknown elif os.path.isfile(path_not_found_pid + pidfile): - logging.info("PID unknown: %s" % pid) modifytime = os.path.getmtime(path_not_found_pid + pidfile) - logging.info("PID unknown modify time: %s" % time.strftime('%Y-%m-%d', time.localtime(modifytime))) - pidseox.append({"pid": pid, - "EOXExternalAnnouncementDate": "None found", - "Last_checked": time.strftime('%Y-%m-%d', time.localtime(modifytime)), - }) + pidseox.update({pid: { + 'pid': pid, + 'EOXExternalAnnouncementDate': 'None found', + 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), + }}) else: # create new request in PID-EOX-request - logging.info("PID request: %s" % pid) if not os.path.isfile(path_request_pid + pidfile): - with open(path_request_pid + pidfile, 'w+') as f: + with open(path_request_pid + pidfile, 'w+') as f: # only create file # only create file pass modifytime = os.path.getmtime(path_request_pid + pidfile) - pidseox.append({"pid": pid, - "EOXExternalAnnouncementDate": 'requested', - "Last_checked": time.strftime('%Y-%m-%d', time.localtime(modifytime)), - }) + pidseox.update({pid: { + 'pid': pid, + 'EOXExternalAnnouncementDate': 'requested', + 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), + }}) for serial in serials: - logging.info("Serialnumber: %s " % serial) # find serial in SER-EOX-known if os.path.isfile(path_found_ser + serial): - logging.info("Serialnumber known: %s" % serial) - serialseox.append(_create_eox_record(path_found_ser + serial, optionalcolumns)) - logging.info("Serialnumber Record: %s" % serialseox) + serialseox.update({serial: _create_eox_record(path_found_ser + serial, optionalcolumns)}) # find serial in SER-EOX-unknown elif os.path.isfile(path_not_found_ser + serial): - logging.info("Serialnumber unknown: %s" % serial) modifytime = os.path.getmtime(path_not_found_ser + serial) - serialseox.append({"serial_number": serial, - "EOXExternalAnnouncementDate": "None found", - "Last_checked": time.strftime('%Y-%m-%d', time.localtime(modifytime)) - }) + serialseox.update({serial: { + 'serial_number': serial, + 'EOXExternalAnnouncementDate': 'None found', + 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)) + }}) else: # create new request in SER-EOX-request - logging.info("SER request: %s" % serial) if not os.path.isfile(path_request_ser + serial): - with open(path_request_ser + serial, 'w+') as f: - # only create file + with open(path_request_ser + serial, 'w+') as f: # only create file pass modifytime = os.path.getmtime(path_request_ser + serial) - serialseox.append({"serial_number": serial, - "EOXExternalAnnouncementDate": 'requested', - "Last_checked": time.strftime('%Y-%m-%d', time.localtime(modifytime)), - }) - - logging.info("PID EoX : %s" % pidseox) - logging.info("Serials EoX : %s" % serialseox) + serialseox.update({serial: { + 'serial_number': serial, + 'EOXExternalAnnouncementDate': 'requested', + 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), + }}) for physerialnum in nodes.keys(): # create default node entry for PID/SN key_columns = {'serial_number': physerialnum} - for pid in pidseox: - if nodes[physerialnum]['pid'] == pid['pid']: - yield TableRow( - path=path, - key_columns=key_columns, - inventory_columns=pid - ) - for serial in serialseox: - if physerialnum == serial['serial_number']: - temp_ser = serial.copy() - temp_ser.pop('serial_number') - yield TableRow( - path=path, - key_columns=key_columns, - inventory_columns=temp_ser - ) - - - # create and write back api status, will be used for active check cisco_api_status - # apistatus = {} - # lastrun = {} - # apiname = 'eox' - # use_keys = ['EndOfSaleDate', - # 'ProductIDDescription', - # 'ProductBulletinNumber', - # 'pid', - # 'UpdatedTimeStamp', - # 'EOXExternalAnnouncementDate', - # 'Last_checked', - # 'EndOfSvcAttachDate', - # 'LastDateOfSupport', - # ] - # thisrun = [] - # unique_entrys = {} - - # add only used keys to status - # for entry in node: - # this_entry = {} - # for key in entry.keys(): - # if key in use_keys: - # this_entry.update({key: entry.get(key)}) - # # add only unique entries to status - # unique_entrys.update({this_entry.get('pid'): this_entry}) - - # create list for unique entries by PID - # for key in unique_entrys.keys(): - # thisrun.append(unique_entrys.get(key)) - - # in CMK v1.5.x global variable g_hostname was replaced by API call host_name() - # try: - # _hostname = host_name() - # except NameError: - # _hostname = g_hostname - - # if os.path.isfile(status_path + _hostname): - # with open(status_path + _hostname) as f: - # try: - # apistatus = json.load(f) - # except ValueError: - # logging.warning(f'snmp_cisco_eox:status:JSON load error: {status_path + _hostname}') - # - # if apistatus.get(apiname, None) is not None: - # lastrun = apistatus.get(apiname).get('lastrun', {}) - # - # if lastrun != thisrun: - # apistatus.update({apiname: {}}) - # apistatus.get(apiname).update({'lastrun': thisrun}) - # with open(status_path + _hostname, 'w') as f: - # json.dump(apistatus, f) + if nodes[physerialnum]['pid'] in pidseox.keys(): + yield TableRow( + path=path, + key_columns=key_columns, + inventory_columns=pidseox[nodes[physerialnum]['pid']] + ) + if physerialnum in serialseox.keys(): + serialseox[physerialnum].pop('serial_number') + yield TableRow( + path=path, + key_columns=key_columns, + inventory_columns=serialseox[physerialnum] + ) register.snmp_section( @@ -379,7 +287,6 @@ register.snmp_section( register.inventory_plugin( name='inv_cisco_eox', inventory_function=inventory_cisco_eox, - inventory_default_parameters={ - }, + inventory_default_parameters={}, inventory_ruleset_name='inv_cisco_eox', ) diff --git a/agent_based/inv_cisco_psirt.py b/agent_based/inv_cisco_psirt.py index f337914e8cd91fb58f95c91b286779143a88e57c..26955bd8d2b9453b74fc332a12db5237ba1ffff4 100644 --- a/agent_based/inv_cisco_psirt.py +++ b/agent_based/inv_cisco_psirt.py @@ -18,19 +18,22 @@ # node = inv_tree('software.support.cisco_psirt.') # changed variable host_name to _hostname for cmk 1.5 # 2021-07-24: rewritten for CMK 2.0 +# 2021-07-26: cleanup +# +# ToDo: add number of psirt records to status information # import os import json -import logging import time from typing import List +from dataclasses import dataclass + from cmk.base.plugins.agent_based.agent_based_api.v1 import ( register, SNMPTree, contains, any_of, - OIDEnd, startswith, TableRow, Attributes, @@ -41,70 +44,90 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( InventoryResult, ) -# from cmk.base.plugins.agent_based.utils import ciscoapi as utils from cmk.base.plugins.agent_based.utils.ciscoapi import ( - set_loglevel, expand_path, - compact_info_by_serial, - check_bad_serial, - pid_on_black_list, - sn_on_black_list, - pid_on_bad_list, - set_PID_black_list, - set_PID_bad_list, - set_SN_black_list, + get_base_path, ) -def parse_inv_cisco_psirt(string_table: List[StringTable]): - section = string_table - return section - - -def inventory_cisco_psirt(params, section) -> InventoryResult: - def create_psirt_record(filepath, filename, not_updated, dont_show_older_then, optionalcolumns): - psirtfile = filepath + filename - advisories = {} - if os.path.isfile(psirtfile): - with open(psirtfile) as f: - psirtrecord = json.load(f) - advisories = psirtrecord.get('advisories') - remove_advisories = [] - for advisory in advisories: - lastUpdated = advisory.get('lastUpdated', 'T1980-01-01').split('T')[0] - if lastUpdated == '': - lastUpdated = '1980-01-01' - if (not_updated > int( - (time.time() - time.mktime(time.strptime(lastUpdated, '%Y-%m-%d'))) / 86400)) and ( - lastUpdated >= dont_show_older_then): - advisory.update({'installed_version': filename}) - advisory.update({'lastUpdated': advisory.get('lastUpdated', 'T1980-01-01').split('T')[0]}) - advisory.update({'firstPublished': advisory.get('firstPublished', 'T1980-01-01').split('T')[0]}) - advisory.update({'bugIDs': ', '.join(advisory.get('bugIDs', ''))}) - advisory.update({'firstFixed': ', '.join(advisory.get('firstFixed', ''))}) - advisory.update({'cves': ', '.join(advisory.get('cves', ''))}) - advisory.update({'cwe': ', '.join(advisory.get('cwe', ''))}) - - keys = advisory.keys() - for column in optionalcolumns: - if column in keys: - advisory.pop(column) - else: - # add old advisories to remove list - remove_advisories.insert(0, advisories.index(advisory)) - - - - # remove advisories older then not_updated - # if len(remove_advisories) > 0: - # node['removed_advisories'] = len(remove_advisories) - # for index in remove_advisories: - # advisories.pop(index) - - logging.info('snmp_cisco_psirt:create_psirt_recort:advisories: %s' % advisories) - return advisories - - def get_OS_Version(cw_version, sysdescription, pids): +@dataclass +class SnmpPsirt: + version: str + product_family: str + + +def _create_psirt_record(filepath, filename, not_updated, dont_show_older_then, optionalcolumns): + psirtfile = filepath + filename + advisories = {} + if os.path.isfile(psirtfile): + with open(psirtfile) as f: + psirtrecord = json.load(f) + advisories = psirtrecord.get('advisories') + remove_advisories = [] + for advisory in advisories: + last_updated = advisory.get('lastUpdated', 'T1980-01-01').split('T')[0] + if last_updated == '': + last_updated = '1980-01-01' + if (not_updated > int( + (time.time() - time.mktime(time.strptime(last_updated, '%Y-%m-%d'))) / 86400)) and ( + last_updated >= dont_show_older_then): + advisory.update({'installed_version': filename}) + advisory.update({'lastUpdated': advisory.get('lastUpdated', 'T1980-01-01').split('T')[0]}) + advisory.update({'firstPublished': advisory.get('firstPublished', 'T1980-01-01').split('T')[0]}) + advisory.update({'bugIDs': ', '.join(advisory.get('bugIDs', ''))}) + advisory.update({'firstFixed': ', '.join(advisory.get('firstFixed', ''))}) + advisory.update({'cves': ', '.join(advisory.get('cves', ''))}) + advisory.update({'cwe': ', '.join(advisory.get('cwe', ''))}) + + keys = advisory.keys() + for column in optionalcolumns: + if column in keys: + advisory.pop(column) + else: + # add old advisories to remove list + remove_advisories.insert(0, advisories.index(advisory)) + + # remove advisories older then not_updated + # if len(remove_advisories) > 0: + # node['removed_advisories'] = len(remove_advisories) + # for index in remove_advisories: + # advisories.pop(index) + return advisories + + +def _get_profuct_family(sysdescription: str) -> str: + if ' IOS ' in sysdescription: + if ' IOS-XE ' in sysdescription or 'IOSXE' in sysdescription or 'IOS Software [' in sysdescription: + return 'IOS-XE' + else: + return 'IOS' + elif 'NX-OS' in sysdescription: + if ' aci' in sysdescription or 'APIC ' in sysdescription: + return 'APIC' + else: + return 'NEXUS' + elif 'APIC ' in sysdescription: + return 'APIC' + elif 'Cisco Controller' in sysdescription: + return 'WLC' + elif 'Cisco Adaptive Security Appliance Version' in sysdescription: + return 'ASA' + elif 'Cisco PIX Security Appliance Version' in sysdescription: + return 'PIX' + elif ' AsyncOS ' in sysdescription: + if 'Model C' in sysdescription: + return 'ESA' + elif 'Model M' in sysdescription: + return 'SMA' + elif 'Model S' in sysdescription: + return 'WSA' + elif 'Integrated Management Controller' in sysdescription: + return 'IMC' + else: + return 'no_family_found_or_set' + + +def _get_os_version(cw_version, sysdescription, pids): version = None if str(cw_version).find('CW_VERSION$') == 0: @@ -131,110 +154,63 @@ def inventory_cisco_psirt(params, section) -> InventoryResult: return version - set_loglevel() - - # disable_psirt = False - optionalcolumns = ['publicationUrl', - 'summary', - 'cwe', - 'cves', - 'productNames', - 'iosRelease', - 'ipsSignatures', - 'cvrfUrl', - 'ovalUrl', - ] + +def parse_inv_cisco_psirt(string_table: List[StringTable]) -> SnmpPsirt: + + cw_version, sysdescription = string_table[0][0] + + pids = string_table[1] + + return SnmpPsirt( + version=_get_os_version(cw_version, sysdescription, pids), + product_family=_get_profuct_family(sysdescription) + ) + + +def inventory_cisco_psirt(params, section: SnmpPsirt) -> InventoryResult: + optionalcolumns = [ + 'publicationUrl', + 'summary', + 'cwe', + 'cves', + 'productNames', + 'iosRelease', + 'ipsSignatures', + 'cvrfUrl', + 'ovalUrl', + ] product_family = None psirt_type = None - # version = '' not_updated = 10000000 - dont_show_older_then = '0000-00-00' - conf_file = '~/etc/ciscoapi/ciscoapi.json' - conf_file = os.path.expanduser(conf_file) - psirt_status = {} - # prepare path variables (expand homedir and add '/') - base_path = '~/var/ciscoapi' - cw_version, sysdescription = section[0][0] - pids = section[1] + dont_show_older_then = '2000-01-01' # get parameters from wato if params: optionalcolumns = params.get('removecolumns', optionalcolumns) psirt_type = params.get('psirt_type', psirt_type) if type(psirt_type) == dict: - product_family = psirt_type.get('product_family', product_family) # .upper() + product_family = psirt_type.get('product_family') # .upper() not_updated = psirt_type.get('not_updated', not_updated) - dont_show_older_then = psirt_type.get('dont_show_older_then', dont_show_older_then) - - # get OS version - version = get_OS_Version(cw_version, sysdescription, pids) - logging.info('snmp_cisco_psirt:version: %s' % version) + dont_show_older_then = psirt_type.get('dont_show_older_then') if not product_family: # try to get product family form system description - if ' IOS ' in sysdescription: - if ' IOS-XE ' in sysdescription or 'IOSXE' in sysdescription or 'IOS Software [' in sysdescription: - product_family = 'IOS-XE' - else: - product_family = 'IOS' - elif 'NX-OS' in sysdescription: - if ' aci' in sysdescription or 'APIC ' in sysdescription: - product_family = 'APIC' - else: - product_family = 'NEXUS' - elif 'APIC ' in sysdescription: - product_family = 'APIC' - elif 'Cisco Controller' in sysdescription: - product_family = 'WLC' - elif 'Cisco Adaptive Security Appliance Version' in sysdescription: - product_family = 'ASA' - elif 'Cisco PIX Security Appliance Version' in sysdescription: - product_family = 'PIX' - elif ' AsyncOS ' in sysdescription: - if 'Model C' in sysdescription: - product_family = 'ESA' - elif 'Model M' in sysdescription: - product_family = 'SMA' - elif 'Model S' in sysdescription: - product_family = 'WSA' - elif 'Integrated Management Controller' in sysdescription: - product_family = 'IMC' - else: - product_family = 'no_family_found_or_set' - logging.info('snmp_cisco_psirt:no_family_found_or_set: %s' % sysdescription) + product_family = section.product_family elif psirt_type in ['IOS', 'IOS-XE']: product_family = psirt_type - logging.info('snmp_cisco_psirt:product_family: %s' % product_family) - - # check for conf_file and read parameters - if os.path.isfile(conf_file): - with open(conf_file) as f: - try: - config = json.load(f) - base_path = config['global']['base_dir'] - except ValueError as e: - logging.warning(f'snmp_cisco_eox:status:JSON load error: {e}') - + # set paths + base_path = get_base_path() psirt_dir = base_path + '/psirt' - status_path = expand_path(base_path + '/status') - - path = ['software', 'support', 'cisco_psirt'] - yield Attributes( - path=path, - inventory_attributes={ - 'os_version': version, - 'Product_family': product_family, - }) - path_found = psirt_dir + '/found/' path_not_found = psirt_dir + '/not_found/' path_request = psirt_dir + '/request/' + if product_family in ['IOS', 'IOS-XE']: path_found = path_found + product_family path_not_found = path_not_found + product_family path_request = path_request + product_family - psirt = version + psirt = section.version else: path_found = path_found + 'family' path_not_found = path_not_found + 'family' @@ -245,36 +221,31 @@ def inventory_cisco_psirt(params, section) -> InventoryResult: path_not_found = expand_path(path_not_found) path_request = expand_path(path_request) + # set defaults for inventory entries + path = ['software', 'support', 'cisco_psirt'] + attribute = { + 'os_version': section.version, + 'Product_family': product_family, + } + # psirt record found if os.path.isfile(path_found + psirt): - logging.info('PSIRT:found: %s' % psirt) modifytime = os.path.getmtime(path_found + psirt) - yield Attributes( - path=path, - inventory_attributes={ - 'status': 'found', - 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), - }) - - # psirt_status.update(node) + attribute.update({ + 'status': 'found', + 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), + }) # create psirt advisory list - if product_family in ['IOS', 'IOS-XE']: # do not remove entrys from IOS(XE) - advisories = create_psirt_record(path_found, psirt, 10000000, '2000-01-01', optionalcolumns) + if product_family in ['IOS', 'IOS-XE']: # do not remove entry's from IOS(XE) + advisories = _create_psirt_record(path_found, psirt, 10000000, '2000-01-01', optionalcolumns) else: - advisories = create_psirt_record(path_found, psirt, not_updated, dont_show_older_then, optionalcolumns) + advisories = _create_psirt_record(path_found, psirt, not_updated, dont_show_older_then, optionalcolumns) if dont_show_older_then != '0000-00-00': - yield Attributes( - path=path, - inventory_attributes={ - 'dont_show_older_than': dont_show_older_then, - }) + attribute['dont_show_older_than'] = dont_show_older_then if not_updated != 10000000: - yield Attributes( - path=path, - inventory_attributes={ - 'dont_show_not_updated_since': not_updated, - }) + attribute['dont_show_not_updated_since'] = not_updated + for advisory in advisories: key_columns = {'advisoryId': advisory.get('advisoryId')} advisory.pop('advisoryId') @@ -283,92 +254,29 @@ def inventory_cisco_psirt(params, section) -> InventoryResult: key_columns=key_columns, inventory_columns=advisory, ) - # if optionalcolumns is not None: - # for advisory in node: - # # remove optional columns - # logging.info('remove columns: %s' % optionalcolumns) - # for column in optionalcolumns: - # logging.info('remove column: %s' % column) - # advisory.pop(column, None) + # psirt record not found exists elif os.path.isfile(path_not_found + psirt): - logging.info('PSIRT:notfound: %s' % psirt) modifytime = os.path.getmtime(path_not_found + psirt) - yield Attributes( - path=path, - inventory_attributes={ - 'status': 'not found', - 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), - }) - # psirt_status.update(node) + attribute.update({ + 'status': 'not found', + 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), + }) elif psirt != 'no_family_found_or_set': # create new request - logging.info('PSIRT:request: %s' % psirt) if not os.path.isfile(path_request + psirt): - with open(path_request + psirt, 'w+') as f: + with open(path_request + psirt, 'w+') as f: # only create file (touch) pass modifytime = os.path.getmtime(path_request + psirt) - yield Attributes( - path=path, - inventory_attributes={ - 'status': 'requested', - 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), - }) - # psirt_status.update(node) - - # # create and write back api status, will be used for active check cisco_api_status - # apistatus = {} - # lastrun = {} - # laststatus = {} - # thisrun = [] - # apiname = 'psirt' - # rewrite = False - # use_keys = ['sir', 'advisoryTitle', 'lastUpdated'] - # - # if psirt_status != {}: - # - # # in CMK v1.5.x global variable g_hostname was replaced by API call host_name() - # try: - # _hostname = host_name() - # except NameError: - # _hostname = g_hostname - # - # if os.path.isfile(status_path + _hostname): - # with open(status_path + _hostname) as f: - # try: - # apistatus = json.load(f) - # except ValueError as e: - # logging.warning(f'{_hostname}:snmp_cisco_psirt:status:JSON load error: {e}') - # - # if apistatus.get(apiname, None) is not None: - # lastrun = apistatus.get(apiname).get('lastrun', {}) - # laststatus.update({'status': apistatus.get(apiname).get('status', '')}) - # laststatus.update({'product_family': apistatus.get(apiname).get('product_family', '')}) - # laststatus.update({'Last_checked': apistatus.get(apiname).get('Last_checked', '')}) - # else: - # apistatus.update({apiname: {}}) - # - # if psirt_status != laststatus: - # apistatus.get(apiname).update(psirt_status) - # rewrite = True - # - # if psirt_status != node: - # # cleanup psirt infos for status - # advisories = node.get('advisories', {}) - # if advisories != {}: - # for entry in advisories: - # this_entry = {} - # for key in entry.keys(): - # if key in use_keys: - # this_entry.update({key: entry.get(key)}) - # thisrun.append(this_entry) - # - # if lastrun != thisrun: - # apistatus.get(apiname).update({'lastrun': thisrun}) - # rewrite = True - # if rewrite: - # with open(status_path + _hostname, 'w') as f: - # json.dump(apistatus, f) + attribute.update({ + 'status': 'requested', + 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), + }) + + yield Attributes( + path=path, + inventory_attributes=attribute, + ) register.snmp_section( @@ -384,7 +292,6 @@ register.snmp_section( SNMPTree( base='.1.3.6.1.2.1.47.1.1.1.1', # ENTITY-MIB::entPhysicalEntry oids=[ - # OIDEnd(), # index of entry '10', # entPhysicalSoftwareRev '13', # entPhysicalModelName '5', # entPhysicalClass @@ -400,7 +307,6 @@ register.snmp_section( register.inventory_plugin( name='inv_cisco_psirt', inventory_function=inventory_cisco_psirt, - inventory_default_parameters={ - }, + inventory_default_parameters={}, inventory_ruleset_name='inv_cisco_psirt', ) diff --git a/agent_based/utils/ciscoapi.py b/agent_based/utils/ciscoapi.py index 4ae674c10c2445bd11fbfa8f060fa10a1a829bea..8d879e489004237ddc19f75dc13b6f788ea43beb 100644 --- a/agent_based/utils/ciscoapi.py +++ b/agent_based/utils/ciscoapi.py @@ -17,35 +17,52 @@ import os import logging import re +import json from typing import List + # # global variables # # list of PIDs to drop g_PID_black_list = ['BUILT-IN', 'MICRON', 'C400-MTFDD'] # list of PIDs to try by serial number -g_PID_bad_list = ['UNSPECIFIED', 'FABRIC', 'ASA', 'C2611XM-2FE', 'FTLX8570D3BCL', 'FTLF8519P2BCL', 'FTLX8571D3BCL', 'FTRJ-8519-7D', 'PLRXPL-SC-S43'] # +g_PID_bad_list = ['UNSPECIFIED', 'FABRIC', 'ASA', 'C2611XM-2FE', 'FTLX8570D3BCL', 'FTLF8519P2BCL', 'FTLX8571D3BCL', + 'FTRJ-8519-7D', 'PLRXPL-SC-S43'] # # list of S/Ns to drop g_SN_black_list = [] -def set_PID_black_list(PID_black_list: List[str]): +def get_base_path() -> str: + conf_file = os.path.expanduser('~/etc/ciscoapi/ciscoapi.json') + base_path = '~/var/ciscoapi' + # check for conf_file and read parameters + if os.path.isfile(conf_file): + with open(conf_file) as f: + try: + config = json.load(f) + base_path = config['global'].get('base_dir', base_path) + except ValueError as e: + logging.warning(f'inv_cisco_contract:status:JSON load error: {e}') + return base_path + + +def set_pid_black_list(pid_black_list: List[str]): global g_PID_black_list - if PID_black_list: - g_PID_black_list = list(set(g_PID_black_list + PID_black_list)) - logging.info(f'PID_black_list: {g_PID_black_list}') + if pid_black_list: + g_PID_black_list = list(set(g_PID_black_list + pid_black_list)) + + +def set_pid_bad_list(pid_bad_list: List[str]): + global g_PID_bad_list + if pid_bad_list: + g_PID_bad_list = list(set(g_PID_bad_list + pid_bad_list)) -def set_PID_bad_list(PID_bad_list: List[str]): - global g_PID_bad_list - if PID_bad_list: - g_PID_bad_list = list(set(g_PID_bad_list + PID_bad_list)) - logging.info(f'PID_bad_list: {g_PID_bad_list}') -def set_SN_black_list(SN_black_list: List[str]): +def set_sn_black_list(sn_black_list: List[str]): global g_SN_black_list - if SN_black_list: - SN_black_list = list(set(g_SN_black_list + SN_black_list)) - logging.info((f'SN_black_list: {SN_black_list}')) + if sn_black_list: + g_SN_black_list = list(set(g_SN_black_list + sn_black_list)) + # check if dir exists, if not try to create it. # return True if dir exists or creation was ok. @@ -73,9 +90,9 @@ def expand_path(path): # returns True if SN on black list -def sn_on_black_list(SN): +def sn_on_black_list(serial): global g_SN_black_list - if SN.upper() in g_SN_black_list: + if serial.upper() in g_SN_black_list: return True return False @@ -117,7 +134,7 @@ def check_bad_serial(serial): # remove all chars from string, except allowedchars allowedchars = re.compile('[^a-zA-Z0-9_=\-\+\.\\\]') - cleanserial = allowedchars.sub('',serial).strip() + cleanserial = allowedchars.sub('', serial).strip() logging.info('Check_bad_serial:cleanserial: %s' % cleanserial) @@ -136,39 +153,3 @@ def set_loglevel(): logging.basicConfig(level=logging.WARNING) # has no effect as long previous command is active (is by design) logging.basicConfig(level=logging.INFO) - - - -# compact info will remove PIDs on black list and join entry's with identical serial number -# PIDs will by all uppercase and cut at the first space sign -def compact_info_by_serial(info): - # compress module list by serial - # (base found on http://stackoverflow.com/questions/19811418/finding-duplicates-in-a-list-of-lists, - # answer from Mark Reed (thanks) http://stackoverflow.com/users/797049/mark-reed, a 'little' bit extended for my needs ;-) ) - # objective: join module info for modules with identical serial number, if PIDs different then join them as comma separated - # string - compact_info = {} # empty dictionary - for description, serial, pid in info: # for every module in list - if len(pid.split()) != 0: - logging.info('compact_info_by_serial:pid_not_empty: pid %s' % pid) - pid = pid.split()[0].upper() # cut PID at first space sign, change to all Uppercase - else: - pid = 'UNSPECIFIED' - if not pid_on_black_list(pid): # drop blacklisted PIDs - if compact_info.get(serial) != None: # if module already in compact list - compact_info[serial] = pid + ',' + compact_info.get(serial, '') + ', ' + description # update module info, append new PID + already covered module info + new description (pid/description are comma separated) - else: # for not already covered modules - compact_info[serial] = pid + '|' + description # add key : value 'serial' : 'pid|description', pid and description are '|' separated - compact_info = map(list, compact_info.items()) # change dictionary to list of lists , every entry ['serial', 'pid(s)|description(s)'] - info = [] # empty module list - for serial, summary in compact_info: # for every entry in compact_modules (summary is 'pid(s)|description(s)') - summary = summary.split('|') # split pid and description (summary becomes list ['pid(s)', 'description(s)'] - pid = ','.join(set(summary[0].split(','))) # split pid(s), remove duplicates, join as comma separated string - description = summary[1] # description is comma separated string of description(s) - entry = [] # create entry per module - entry.append(description) # 1. description - entry.append(serial) # 2. serial - entry.append(pid) # 3. pid - info.append(entry) # recreate the list of modules - - return info diff --git a/inv_cisco_support.mkp b/inv_cisco_support.mkp index 98beed0b82237bae5dcfe94088455d38b073dd97..cdf4854337fad3c970f66c317fac6bc40f6cca77 100644 Binary files a/inv_cisco_support.mkp and b/inv_cisco_support.mkp differ