Collection of CheckMK checks (see https://checkmk.com/). All checks and plugins are provided as is. Absolutely no warranty. Send any comments to thl-cmk[at]outlook[dot]com

Skip to content
Snippets Groups Projects
Commit 23a9eb23 authored by thl-cmk's avatar thl-cmk :flag_na:
Browse files

update project

parent 6bb6244d
No related branches found
No related tags found
No related merge requests found
...@@ -11,12 +11,15 @@ ...@@ -11,12 +11,15 @@
# #bug['known_fixed_releases'] = bug['known_fixed_releases'].split(' ') # #bug['known_fixed_releases'] = bug['known_fixed_releases'].split(' ')
# #bug['known_affected_releases'] = bug['known_affected_releases'].split(' ') # #bug['known_affected_releases'] = bug['known_affected_releases'].split(' ')
# 2021-07-24: rewritten for CMK 2.0 # 2021-07-24: rewritten for CMK 2.0
# 2021-07-26: some cleanup
#
import os import os
import json import json
import logging import logging
import time 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 ( from cmk.base.plugins.agent_based.agent_based_api.v1 import (
register, register,
SNMPTree, SNMPTree,
...@@ -33,187 +36,150 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( ...@@ -33,187 +36,150 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
InventoryResult, InventoryResult,
) )
# from cmk.base.plugins.agent_based.utils import ciscoapi as utils
from cmk.base.plugins.agent_based.utils.ciscoapi import ( from cmk.base.plugins.agent_based.utils.ciscoapi import (
set_loglevel, set_loglevel,
expand_path, expand_path,
compact_info_by_serial, get_base_path,
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,
) )
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() set_loglevel()
# disable_bug = False
optionalcolumns = [ optionalcolumns = [
'base_pid', 'base_pid',
# 'bug_id',
# 'behavior_changed',
'description', 'description',
# 'headline',
'id', 'id',
'known_affected_releases', 'known_affected_releases',
# 'known_fixed_releases',
# 'last_modified_date',
'product', '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 # get parameters from wato
if params: if params:
optionalcolumns = params.get('removecolumns', optionalcolumns) optionalcolumns = params.get('removecolumns', optionalcolumns)
# get OS version base_path = get_base_path()
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}')
bug_dir = base_path + '/bug' bug_dir = base_path + '/bug'
path_found = expand_path(bug_dir + '/found/') path_found = expand_path(bug_dir + '/found/')
path_not_found = expand_path(bug_dir + '/not_found/') path_not_found = expand_path(bug_dir + '/not_found/')
path_request = expand_path(bug_dir + '/request/') 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']
path = ['software','support','cisco_bug'] attribute = {
yield Attributes( 'os_version': section.version,
path=path, 'PID': section.pid,
inventory_attributes={ }
'os_version': version,
'PID': pid,
})
pid = pid.replace('/', '_') pid = section.pid.replace('/', '_')
# bug report found # bug report found
if os.path.isfile(path_found + pid + '/' + version): if os.path.isfile(path_found + pid + '/' + section.version):
logging.info('snmp_cisco_bug:report found: %s, %s' % (pid, version)) modifytime = os.path.getmtime(path_found + pid + '/' + section.version)
modifytime = os.path.getmtime(path_found + pid + '/' + version) attribute.update({
yield Attributes(
path=path,
inventory_attributes={
'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
'status': 'found' 'status': 'found'
}) })
bug_record = '' bug_record = ''
with open(path_found + pid + '/' + version) as f: with open(path_found + pid + '/' + section.version) as f:
try: try:
bug_record = json.load(f) bug_record = json.load(f)
except ValueError as e: 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 != '': if bug_record != '':
yield Attributes( attribute['Total_records'] = bug_record.get('total_records', 0)
path=path,
inventory_attributes={
'Total_records': bug_record.get('total_records', 0),
})
missing = bug_record.get('missing', {}) missing = bug_record.get('missing', {})
if len(missing.keys()) != 0: if len(missing.keys()) != 0:
yield Attributes( attribute['missing_records'] = len(missing.keys()) * 10
path=path,
inventory_attributes={
'missing_records': len(missing.keys()) * 10,
})
bugs = bug_record.get('bugs') bugs = bug_record.get('bugs')
bug_ids = [] bug_ids = []
...@@ -223,12 +189,10 @@ def inventory_cisco_bug(params, section) -> InventoryResult: ...@@ -223,12 +189,10 @@ def inventory_cisco_bug(params, section) -> InventoryResult:
bug_ids.append(bug.get('bug_id')) bug_ids.append(bug.get('bug_id'))
bug['known_fixed_releases'] = ', '.join(bug['known_fixed_releases'].split(' ')) bug['known_fixed_releases'] = ', '.join(bug['known_fixed_releases'].split(' '))
bug['known_affected_releases'] = ', '.join(bug['known_affected_releases'].split(' ')) bug['known_affected_releases'] = ', '.join(bug['known_affected_releases'].split(' '))
bug['status'] = get_bug_status(bug['status']) bug['status'] = _get_bug_status(bug['status'])
bug['behavior_changed'] = get_bug_behavior_changed(bug['behavior_changed']) bug['behavior_changed'] = _get_bug_behavior_changed(bug['behavior_changed'])
logging.info('remove columns: %s' % optionalcolumns)
if optionalcolumns is not None: if optionalcolumns is not None:
for column in optionalcolumns: for column in optionalcolumns:
logging.info('remove column: %s' % column)
bug.pop(column, None) bug.pop(column, None)
clean_bugs.append(bug) clean_bugs.append(bug)
key_columns = {'bug_id': bug.get('bug_id')} key_columns = {'bug_id': bug.get('bug_id')}
...@@ -238,91 +202,31 @@ def inventory_cisco_bug(params, section) -> InventoryResult: ...@@ -238,91 +202,31 @@ def inventory_cisco_bug(params, section) -> InventoryResult:
key_columns=key_columns, key_columns=key_columns,
inventory_columns=bug, inventory_columns=bug,
) )
yield Attributes( attribute['duplicate_records'] = len(bugs) - len(clean_bugs)
path=path,
inventory_attributes={
'duplicate_records':len(bugs) - len(clean_bugs),
})
# bug report not found # bug report not found
elif os.path.isfile(path_not_found + pid + '/' + version): elif os.path.isfile(path_not_found + pid + '/' + section.version):
logging.info('snmp_cisco_bug:report not found: %s, %s' % (pid, version)) modifytime = os.path.getmtime(path_not_found + pid + '/' + section.version)
modifytime = os.path.getmtime(path_not_found + pid + '/' + version) attribute.update({
yield Attributes( 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
path=path, 'status': 'Not found',
inventory_attributes={ })
'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
'status': 'Not found',
})
else: else:
# create new request # create new request
path_request = expand_path(path_request + pid) path_request = expand_path(path_request + pid)
if not os.path.isfile(path_request + '/' + version): if not os.path.isfile(path_request + '/' + section.version):
logging.info('snmp_cisco_bug:create request: %s, %s' % (pid, version)) with open(path_request + '/' + section.version, 'w+') as f: # only create file (touch)
with open(path_request + '/' + version, 'w+') as f:
pass pass
modifytime = os.path.getmtime(path_request + '/' + version) modifytime = os.path.getmtime(path_request + '/' + section.version)
yield Attributes( attribute.update({
path=path, 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
inventory_attributes={ 'status': 'requested',
'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)), })
'status': 'requested',
}) yield Attributes(
path=path,
# # create and write back api status, will be used for active check cisco_api_status inventory_attributes=attribute
# 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)
register.snmp_section( register.snmp_section(
...@@ -354,7 +258,6 @@ register.snmp_section( ...@@ -354,7 +258,6 @@ register.snmp_section(
register.inventory_plugin( register.inventory_plugin(
name='inv_cisco_bug', name='inv_cisco_bug',
inventory_function=inventory_cisco_bug, inventory_function=inventory_cisco_bug,
inventory_default_parameters={ inventory_default_parameters={},
},
inventory_ruleset_name='inv_cisco_bug', inventory_ruleset_name='inv_cisco_bug',
) )
...@@ -18,12 +18,22 @@ ...@@ -18,12 +18,22 @@
# 2020-08-04: moved node tree from hardware.system.support to hardware.system # 2020-08-04: moved node tree from hardware.system.support to hardware.system
# cleanup create_sn2info_record # cleanup create_sn2info_record
# 2021-07-24: rewritten for CMK 2.0 # 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 os
import json import json
import time import time
from typing import Dict
from dataclasses import dataclass
from cmk.base.plugins.agent_based.agent_based_api.v1 import ( from cmk.base.plugins.agent_based.agent_based_api.v1 import (
register, register,
SNMPTree, SNMPTree,
...@@ -37,20 +47,21 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( ...@@ -37,20 +47,21 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
InventoryResult, InventoryResult,
) )
# from cmk.base.plugins.agent_based.utils import ciscoapi as utils
from cmk.base.plugins.agent_based.utils.ciscoapi import ( from cmk.base.plugins.agent_based.utils.ciscoapi import (
set_loglevel,
expand_path, expand_path,
compact_info_by_serial,
check_bad_serial, check_bad_serial,
pid_on_black_list, pid_on_black_list,
sn_on_black_list, set_pid_black_list,
pid_on_bad_list, get_base_path,
set_PID_black_list,
set_PID_bad_list,
set_SN_black_list,
) )
@dataclass
class SnmpContractEntry:
phydescr: str
pid: str
def _create_sn2info_record(sn2infofile, optional_columns): def _create_sn2info_record(sn2infofile, optional_columns):
sn2info = {} sn2info = {}
if os.path.isfile(sn2infofile): if os.path.isfile(sn2infofile):
...@@ -58,10 +69,9 @@ def _create_sn2info_record(sn2infofile, optional_columns): ...@@ -58,10 +69,9 @@ def _create_sn2info_record(sn2infofile, optional_columns):
sn2inforecord = json.load(f) sn2inforecord = json.load(f)
modifytime = os.path.getmtime(sn2infofile) 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 [ for key, value in [
('serial_number', sn2inforecord.get('sr_no')), ('serial_number', sn2inforecord.get('sr_no')),
('is_covered', sn2inforecord.get('is_covered')), ('is_covered', sn2inforecord.get('is_covered')),
...@@ -77,200 +87,93 @@ def _create_sn2info_record(sn2infofile, optional_columns): ...@@ -77,200 +87,93 @@ def _create_sn2info_record(sn2infofile, optional_columns):
('warranty_type', sn2inforecord.get('warranty_type')), ('warranty_type', sn2inforecord.get('warranty_type')),
('warranty_type_description', sn2inforecord.get('warranty_type_description')), ('warranty_type_description', sn2inforecord.get('warranty_type_description')),
('warranty_end_date', sn2inforecord.get('warranty_end_date')), ('warranty_end_date', sn2inforecord.get('warranty_end_date')),
]: ]:
value = value.strip() value = value.strip()
if value != '' and key not in optional_columns: if value != '' and key not in optional_columns:
sn2info[key] = value sn2info[key] = value
logging.info('create_sn2info_record sn2info: %s' % sn2info)
# print(sn2info)
return 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 def parse_inv_cisco_contract(string_table: StringTable) -> Dict[str, SnmpContractEntry]:
optionalcolumns = ['contract_site_state_province', serials: Dict[str, SnmpContractEntry] = {}
'warranty_type_description', for phydescr, physerialnum, phymodelname in string_table:
'warranty_end_date', if not check_bad_serial(physerialnum):
'warranty_type', if not pid_on_black_list(phymodelname):
] phymodelname = phymodelname.strip()
base_path = '~/var/ciscoapi' physerialnum = physerialnum.strip()
conf_file = '~/etc/ciscoapi/ciscoapi.json' if phymodelname == '':
conf_file = os.path.expanduser(conf_file) 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 # get parameters from wato
if params: if params:
optionalcolumns = params.get('removecolumns', optionalcolumns) optionalcolumns = params.get('removecolumns', optionalcolumns)
set_PID_black_list(params.get('PID_black_list')) 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()
base_path = get_base_path()
sn2info_dir = base_path + '/sn2info' sn2info_dir = base_path + '/sn2info'
path_found = expand_path(sn2info_dir + '/found/') path_found = expand_path(sn2info_dir + '/found/')
path_not_found = expand_path(sn2info_dir + '/not_found/') path_not_found = expand_path(sn2info_dir + '/not_found/')
path_request = expand_path(sn2info_dir + '/request/') path_request = expand_path(sn2info_dir + '/request/')
status_path = expand_path(base_path + '/status')
path = ['hardware', 'support', 'cisco_contract'] path = ['hardware', 'support', 'cisco_contract']
serials = [] for serial in section.keys():
serialscontract = [] if not pid_on_black_list(section[serial].pid):
nodes ={} key_columns = {'serial_number': serial}
serialscontract = ({
logging.info('raw info: %s' % section) 'pid': section[serial].pid,
'ProductIDDescription': section[serial].phydescr,
# if no serial or no PID no service ;-) 'is_covered': 'requested',
# remove all empty serials })
info = [i for i in section if not check_bad_serial(i[1])] # find serial in SER-sn2info-covered
# remove all empty PIDs if os.path.isfile(path_found + serial):
#info = [i for i in info if i[2] != ''] serialscontract.update(_create_sn2info_record(path_found + serial, optionalcolumns))
serialscontract.pop('serial_number')
logging.info('info no empty pid/serail: %s' % info) # find serial in SER-sn2info-notcovered
elif os.path.isfile(path_not_found + serial):
info = compact_info_by_serial(info) modifytime = os.path.getmtime(path_not_found + serial)
serialscontract.update({
for phydescr, physerialnum, phymodelname in info: 'is_covered': 'NO',
'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
logging.info('raw pid : %s' % phymodelname) })
logging.info('raw sn : %s' % physerialnum) else:
logging.info('raw desc : %s' % phydescr) # create new request in SER-sn2info-request
if not os.path.isfile(path_request + serial):
# cut PID/SN at first space sign, change to all Uppercase with open(path_request + serial, 'w+') as f: # only create file (touch)
pid = phymodelname.split()[0].upper() pass
physerialnum = physerialnum.split()[0].upper() modifytime = os.path.getmtime(path_request + serial)
serialscontract.update({
# drop all PIDs on Black List, create default node entry for SN 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
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}
yield TableRow( yield TableRow(
path=path, path=path,
key_columns=key_columns, key_columns=key_columns,
inventory_columns={ inventory_columns=serialscontract
'pid': pid,
'ProductIDDescription': phydescr,
}
) )
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( register.snmp_section(
...@@ -284,7 +187,6 @@ register.snmp_section( ...@@ -284,7 +187,6 @@ register.snmp_section(
'13', # entPhysicalModelName '13', # entPhysicalModelName
] ]
), ),
detect=any_of( detect=any_of(
contains('.1.3.6.1.2.1.1.1.0', 'cisco'), 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') 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( ...@@ -294,9 +196,6 @@ register.snmp_section(
register.inventory_plugin( register.inventory_plugin(
name='inv_cisco_contract', name='inv_cisco_contract',
inventory_function=inventory_cisco_contract, inventory_function=inventory_cisco_contract,
inventory_default_parameters={ inventory_default_parameters={},
},
inventory_ruleset_name='inv_cisco_contract', inventory_ruleset_name='inv_cisco_contract',
) )
...@@ -18,12 +18,15 @@ ...@@ -18,12 +18,15 @@
# 2020-08-04: code cleanup (_create_eox_record function) # 2020-08-04: code cleanup (_create_eox_record function)
# moved node tree from hardware.system.support to hardware.system # moved node tree from hardware.system.support to hardware.system
# 2021-07-23: rewrite for CMK 2.0 # 2021-07-23: rewrite for CMK 2.0
# 2021-07-26: cleanup
import os import os
import json import json
import logging
import time import time
from typing import Dict
from dataclasses import dataclass
from cmk.base.plugins.agent_based.agent_based_api.v1 import ( from cmk.base.plugins.agent_based.agent_based_api.v1 import (
register, register,
SNMPTree, SNMPTree,
...@@ -39,19 +42,23 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( ...@@ -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 import ciscoapi as utils
from cmk.base.plugins.agent_based.utils.ciscoapi import ( from cmk.base.plugins.agent_based.utils.ciscoapi import (
set_loglevel,
expand_path, expand_path,
compact_info_by_serial,
check_bad_serial, check_bad_serial,
pid_on_black_list, pid_on_black_list,
sn_on_black_list, sn_on_black_list,
pid_on_bad_list, pid_on_bad_list,
set_PID_black_list, set_pid_bad_list,
set_PID_bad_list, set_sn_black_list,
set_SN_black_list, get_base_path,
) )
@dataclass
class SnmpContractEntry:
phydescr: str
pid: str
def _create_eox_record(eoxfile, optional_columns): def _create_eox_record(eoxfile, optional_columns):
eox = {} eox = {}
if os.path.isfile(eoxfile): if os.path.isfile(eoxfile):
...@@ -64,7 +71,7 @@ def _create_eox_record(eoxfile, optional_columns): ...@@ -64,7 +71,7 @@ def _create_eox_record(eoxfile, optional_columns):
eox['pid'] = eoxrecord.get('EOLProductID') eox['pid'] = eoxrecord.get('EOLProductID')
# Migration Info # Migration Info
EOXMigrationDetails = eoxrecord.get('EOXMigrationDetails') eox_migration_details = eoxrecord.get('EOXMigrationDetails')
for key, value in [ for key, value in [
('ProductIDDescription', eoxrecord.get('ProductIDDescription', '')), ('ProductIDDescription', eoxrecord.get('ProductIDDescription', '')),
...@@ -80,31 +87,49 @@ def _create_eox_record(eoxfile, optional_columns): ...@@ -80,31 +87,49 @@ def _create_eox_record(eoxfile, optional_columns):
('EndOfRoutineFailureAnalysisDate', eoxrecord.get('EndOfRoutineFailureAnalysisDate').get('value', '')), ('EndOfRoutineFailureAnalysisDate', eoxrecord.get('EndOfRoutineFailureAnalysisDate').get('value', '')),
# Migration Info # Migration Info
('MigrationProductId', EOXMigrationDetails.get('MigrationProductId', '')), ('MigrationProductId', eox_migration_details.get('MigrationProductId', '')),
('MigrationInformation', EOXMigrationDetails.get('MigrationInformation', '')), ('MigrationInformation', eox_migration_details.get('MigrationInformation', '')),
('MigrationProductInfoURL', EOXMigrationDetails.get('MigrationProductInfoURL', '')), ('MigrationProductInfoURL', eox_migration_details.get('MigrationProductInfoURL', '')),
('MigrationProductName', EOXMigrationDetails.get('MigrationProductName', '')), ('MigrationProductName', eox_migration_details.get('MigrationProductName', '')),
]: ]:
value = value.strip() value = value.strip()
if value != '' and key not in optional_columns: if value != '' and key not in optional_columns:
eox[key] = value eox[key] = value
# add serial if EoX from get_eox_by_serial # add serial if EoX from get_eox_by_serial
EOXInputType = eoxrecord.get('EOXInputType') eox_input_type = eoxrecord.get('EOXInputType')
if EOXInputType == 'ShowEOXBySerialNumber': if eox_input_type == 'ShowEOXBySerialNumber':
eox['serial_number'] = eoxrecord.get('EOXInputValue') eox['serial_number'] = eoxrecord.get('EOXInputValue')
return eox return eox
def parse_inv_cisco_eox(string_table: StringTable): def parse_inv_cisco_eox(string_table: StringTable) -> Dict[str, SnmpContractEntry]:
section = string_table serials: Dict[str, SnmpContractEntry] = {}
return section for phydescr, physerialnum, phymodelname in string_table:
if not check_bad_serial(physerialnum):
if not pid_on_black_list(phymodelname):
def inventory_cisco_eox(params, section) -> InventoryResult: phymodelname = phymodelname.strip()
set_loglevel() 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 always_use_serial = False
optionalcolumns = ['EndOfSecurityVulSupportDate', optionalcolumns = ['EndOfSecurityVulSupportDate',
'EndOfSWMaintenanceReleases', 'EndOfSWMaintenanceReleases',
...@@ -114,32 +139,15 @@ def inventory_cisco_eox(params, section) -> InventoryResult: ...@@ -114,32 +139,15 @@ def inventory_cisco_eox(params, section) -> InventoryResult:
'MigrationProductName', 'MigrationProductName',
] ]
base_path = '~/var/ciscoapi'
conf_file = '~/etc/ciscoapi/ciscoapi.json'
conf_file = os.path.expanduser(conf_file)
# get parameters from wato # get parameters from wato
if params: if params:
always_use_serial = params.get('always_use_serial', always_use_serial) always_use_serial = params.get('always_use_serial', always_use_serial)
optionalcolumns = params.get('removecolumns', optionalcolumns) optionalcolumns = params.get('removecolumns', optionalcolumns)
set_SN_black_list(params.get('SN_black_list')) set_sn_black_list(params.get('SN_black_list'))
set_SN_black_list(params.get('PID_black_list')) set_sn_black_list(params.get('PID_black_list'))
set_PID_bad_list(params.get('PID_bad_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()
base_path = get_base_path()
eox_path = base_path + '/EoX' eox_path = base_path + '/EoX'
path_found = eox_path + '/found' path_found = eox_path + '/found'
path_not_found = eox_path + '/not_found' path_not_found = eox_path + '/not_found'
...@@ -153,209 +161,109 @@ def inventory_cisco_eox(params, section) -> InventoryResult: ...@@ -153,209 +161,109 @@ def inventory_cisco_eox(params, section) -> InventoryResult:
path_found_ser = expand_path(path_found + '/ser') path_found_ser = expand_path(path_found + '/ser')
path_not_found_ser = expand_path(path_not_found + '/ser') path_not_found_ser = expand_path(path_not_found + '/ser')
status_path = expand_path(base_path + '/status')
path = ['hardware', 'support', 'cisco_eox'] path = ['hardware', 'support', 'cisco_eox']
pids = [] pids = []
pidseox = [] pidseox = {}
serials = [] serials = []
serialseox = [] serialseox = {}
nodes ={} 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()
for serial in section.keys():
# drop all PIDs/SNs on Black List # drop all PIDs/SNs on Black List
if not pid_on_black_list(pid) and not sn_on_black_list(physerialnum): if not pid_on_black_list(section[serial].pid) and not sn_on_black_list(serial):
logging.info('PID not on blacklist: %s' % pid) if section[serial].phydescr == '':
section[serial].phydescr = 'requested'
if phydescr == "":
phydescr = 'requested'
# check if PID on bad list, if so try serial number # check if PID on bad list, if so try serial number
if pid_on_bad_list(pid) or always_use_serial: if pid_on_bad_list(section[serial].pid) or always_use_serial:
logging.info('PID on bad list: %s' % pid) serials.append(serial)
logging.info('use SN: %s' % physerialnum)
serials.append(physerialnum)
else: else:
pids.append(pid) pids.append(section[serial].pid)
logging.info('PID not on bad list: %s' % pid)
# create default node entry for PID/SN # create default node entry for PID/SN
key_columns = {'serial_number': physerialnum} key_columns = {'serial_number': serial}
yield TableRow( yield TableRow(
path=path, path=path,
key_columns=key_columns, key_columns=key_columns,
inventory_columns={ inventory_columns={
'pid': pid, 'pid': section[serial].pid,
'ProductIDDescription': phydescr, 'ProductIDDescription': section[serial].phydescr,
} }
) )
nodes[physerialnum] = { nodes[serial] = {
'pid': pid, 'pid': section[serial].pid,
'ProductIDDescription': phydescr, 'ProductIDDescription': section[serial].phydescr,
} }
logging.info("PIDs : %s" % pids)
# remove duplicates # remove duplicates
pids = list(set(pids)) pids = list(set(pids))
logging.info("PIDs : %s" % pids)
for pid in pids: for pid in pids:
logging.info("PID: %s " % pid)
pidfile = pid.replace('/', '_') pidfile = pid.replace('/', '_')
# find pid in PID-EOX-known # find pid in PID-EOX-known
if os.path.isfile(path_found_pid + pidfile): if os.path.isfile(path_found_pid + pidfile):
logging.info("PID known: %s" % pid) pidseox.update({pid: _create_eox_record(path_found_pid + pidfile, optionalcolumns)})
pidseox.append(_create_eox_record(path_found_pid + pidfile, optionalcolumns))
logging.info("EOX Record: %s" % pidseox)
# find pid in PID-EOX-unknown # find pid in PID-EOX-unknown
elif os.path.isfile(path_not_found_pid + pidfile): elif os.path.isfile(path_not_found_pid + pidfile):
logging.info("PID unknown: %s" % pid)
modifytime = os.path.getmtime(path_not_found_pid + pidfile) 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.update({pid: {
pidseox.append({"pid": pid, 'pid': pid,
"EOXExternalAnnouncementDate": "None found", 'EOXExternalAnnouncementDate': 'None found',
"Last_checked": time.strftime('%Y-%m-%d', time.localtime(modifytime)), 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
}) }})
else: else:
# create new request in PID-EOX-request # create new request in PID-EOX-request
logging.info("PID request: %s" % pid)
if not os.path.isfile(path_request_pid + pidfile): 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 # only create file
pass pass
modifytime = os.path.getmtime(path_request_pid + pidfile) modifytime = os.path.getmtime(path_request_pid + pidfile)
pidseox.append({"pid": pid, pidseox.update({pid: {
"EOXExternalAnnouncementDate": 'requested', 'pid': pid,
"Last_checked": time.strftime('%Y-%m-%d', time.localtime(modifytime)), 'EOXExternalAnnouncementDate': 'requested',
}) 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
}})
for serial in serials: for serial in serials:
logging.info("Serialnumber: %s " % serial)
# find serial in SER-EOX-known # find serial in SER-EOX-known
if os.path.isfile(path_found_ser + serial): if os.path.isfile(path_found_ser + serial):
logging.info("Serialnumber known: %s" % serial) serialseox.update({serial: _create_eox_record(path_found_ser + serial, optionalcolumns)})
serialseox.append(_create_eox_record(path_found_ser + serial, optionalcolumns))
logging.info("Serialnumber Record: %s" % serialseox)
# find serial in SER-EOX-unknown # find serial in SER-EOX-unknown
elif os.path.isfile(path_not_found_ser + serial): elif os.path.isfile(path_not_found_ser + serial):
logging.info("Serialnumber unknown: %s" % serial)
modifytime = os.path.getmtime(path_not_found_ser + serial) modifytime = os.path.getmtime(path_not_found_ser + serial)
serialseox.append({"serial_number": serial, serialseox.update({serial: {
"EOXExternalAnnouncementDate": "None found", 'serial_number': serial,
"Last_checked": time.strftime('%Y-%m-%d', time.localtime(modifytime)) 'EOXExternalAnnouncementDate': 'None found',
}) 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime))
}})
else: else:
# create new request in SER-EOX-request # create new request in SER-EOX-request
logging.info("SER request: %s" % serial)
if not os.path.isfile(path_request_ser + serial): if not os.path.isfile(path_request_ser + serial):
with open(path_request_ser + serial, 'w+') as f: with open(path_request_ser + serial, 'w+') as f: # only create file
# only create file
pass pass
modifytime = os.path.getmtime(path_request_ser + serial) modifytime = os.path.getmtime(path_request_ser + serial)
serialseox.append({"serial_number": serial, serialseox.update({serial: {
"EOXExternalAnnouncementDate": 'requested', 'serial_number': serial,
"Last_checked": time.strftime('%Y-%m-%d', time.localtime(modifytime)), 'EOXExternalAnnouncementDate': 'requested',
}) 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
}})
logging.info("PID EoX : %s" % pidseox)
logging.info("Serials EoX : %s" % serialseox)
for physerialnum in nodes.keys(): for physerialnum in nodes.keys():
# create default node entry for PID/SN # create default node entry for PID/SN
key_columns = {'serial_number': physerialnum} key_columns = {'serial_number': physerialnum}
for pid in pidseox: if nodes[physerialnum]['pid'] in pidseox.keys():
if nodes[physerialnum]['pid'] == pid['pid']: yield TableRow(
yield TableRow( path=path,
path=path, key_columns=key_columns,
key_columns=key_columns, inventory_columns=pidseox[nodes[physerialnum]['pid']]
inventory_columns=pid )
) if physerialnum in serialseox.keys():
for serial in serialseox: serialseox[physerialnum].pop('serial_number')
if physerialnum == serial['serial_number']: yield TableRow(
temp_ser = serial.copy() path=path,
temp_ser.pop('serial_number') key_columns=key_columns,
yield TableRow( inventory_columns=serialseox[physerialnum]
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)
register.snmp_section( register.snmp_section(
...@@ -379,7 +287,6 @@ register.snmp_section( ...@@ -379,7 +287,6 @@ register.snmp_section(
register.inventory_plugin( register.inventory_plugin(
name='inv_cisco_eox', name='inv_cisco_eox',
inventory_function=inventory_cisco_eox, inventory_function=inventory_cisco_eox,
inventory_default_parameters={ inventory_default_parameters={},
},
inventory_ruleset_name='inv_cisco_eox', inventory_ruleset_name='inv_cisco_eox',
) )
...@@ -18,19 +18,22 @@ ...@@ -18,19 +18,22 @@
# node = inv_tree('software.support.cisco_psirt.') # node = inv_tree('software.support.cisco_psirt.')
# changed variable host_name to _hostname for cmk 1.5 # changed variable host_name to _hostname for cmk 1.5
# 2021-07-24: rewritten for CMK 2.0 # 2021-07-24: rewritten for CMK 2.0
# 2021-07-26: cleanup
#
# ToDo: add number of psirt records to status information
# #
import os import os
import json import json
import logging
import time import time
from typing import List from typing import List
from dataclasses import dataclass
from cmk.base.plugins.agent_based.agent_based_api.v1 import ( from cmk.base.plugins.agent_based.agent_based_api.v1 import (
register, register,
SNMPTree, SNMPTree,
contains, contains,
any_of, any_of,
OIDEnd,
startswith, startswith,
TableRow, TableRow,
Attributes, Attributes,
...@@ -41,70 +44,90 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( ...@@ -41,70 +44,90 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
InventoryResult, InventoryResult,
) )
# from cmk.base.plugins.agent_based.utils import ciscoapi as utils
from cmk.base.plugins.agent_based.utils.ciscoapi import ( from cmk.base.plugins.agent_based.utils.ciscoapi import (
set_loglevel,
expand_path, expand_path,
compact_info_by_serial, get_base_path,
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,
) )
def parse_inv_cisco_psirt(string_table: List[StringTable]): @dataclass
section = string_table class SnmpPsirt:
return section version: str
product_family: str
def inventory_cisco_psirt(params, section) -> InventoryResult:
def create_psirt_record(filepath, filename, not_updated, dont_show_older_then, optionalcolumns): def _create_psirt_record(filepath, filename, not_updated, dont_show_older_then, optionalcolumns):
psirtfile = filepath + filename psirtfile = filepath + filename
advisories = {} advisories = {}
if os.path.isfile(psirtfile): if os.path.isfile(psirtfile):
with open(psirtfile) as f: with open(psirtfile) as f:
psirtrecord = json.load(f) psirtrecord = json.load(f)
advisories = psirtrecord.get('advisories') advisories = psirtrecord.get('advisories')
remove_advisories = [] remove_advisories = []
for advisory in advisories: for advisory in advisories:
lastUpdated = advisory.get('lastUpdated', 'T1980-01-01').split('T')[0] last_updated = advisory.get('lastUpdated', 'T1980-01-01').split('T')[0]
if lastUpdated == '': if last_updated == '':
lastUpdated = '1980-01-01' last_updated = '1980-01-01'
if (not_updated > int( if (not_updated > int(
(time.time() - time.mktime(time.strptime(lastUpdated, '%Y-%m-%d'))) / 86400)) and ( (time.time() - time.mktime(time.strptime(last_updated, '%Y-%m-%d'))) / 86400)) and (
lastUpdated >= dont_show_older_then): last_updated >= dont_show_older_then):
advisory.update({'installed_version': filename}) advisory.update({'installed_version': filename})
advisory.update({'lastUpdated': advisory.get('lastUpdated', 'T1980-01-01').split('T')[0]}) 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({'firstPublished': advisory.get('firstPublished', 'T1980-01-01').split('T')[0]})
advisory.update({'bugIDs': ', '.join(advisory.get('bugIDs', ''))}) advisory.update({'bugIDs': ', '.join(advisory.get('bugIDs', ''))})
advisory.update({'firstFixed': ', '.join(advisory.get('firstFixed', ''))}) advisory.update({'firstFixed': ', '.join(advisory.get('firstFixed', ''))})
advisory.update({'cves': ', '.join(advisory.get('cves', ''))}) advisory.update({'cves': ', '.join(advisory.get('cves', ''))})
advisory.update({'cwe': ', '.join(advisory.get('cwe', ''))}) advisory.update({'cwe': ', '.join(advisory.get('cwe', ''))})
keys = advisory.keys() keys = advisory.keys()
for column in optionalcolumns: for column in optionalcolumns:
if column in keys: if column in keys:
advisory.pop(column) advisory.pop(column)
else: else:
# add old advisories to remove list # add old advisories to remove list
remove_advisories.insert(0, advisories.index(advisory)) remove_advisories.insert(0, advisories.index(advisory))
# remove advisories older then not_updated
# if len(remove_advisories) > 0:
# remove advisories older then not_updated # node['removed_advisories'] = len(remove_advisories)
# if len(remove_advisories) > 0: # for index in remove_advisories:
# node['removed_advisories'] = len(remove_advisories) # advisories.pop(index)
# for index in remove_advisories: return advisories
# advisories.pop(index)
logging.info('snmp_cisco_psirt:create_psirt_recort:advisories: %s' % advisories) def _get_profuct_family(sysdescription: str) -> str:
return advisories if ' IOS ' in sysdescription:
if ' IOS-XE ' in sysdescription or 'IOSXE' in sysdescription or 'IOS Software [' in sysdescription:
def get_OS_Version(cw_version, sysdescription, pids): 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 version = None
if str(cw_version).find('CW_VERSION$') == 0: if str(cw_version).find('CW_VERSION$') == 0:
...@@ -131,110 +154,63 @@ def inventory_cisco_psirt(params, section) -> InventoryResult: ...@@ -131,110 +154,63 @@ def inventory_cisco_psirt(params, section) -> InventoryResult:
return version return version
set_loglevel()
def parse_inv_cisco_psirt(string_table: List[StringTable]) -> SnmpPsirt:
# disable_psirt = False
optionalcolumns = ['publicationUrl', cw_version, sysdescription = string_table[0][0]
'summary',
'cwe', pids = string_table[1]
'cves',
'productNames', return SnmpPsirt(
'iosRelease', version=_get_os_version(cw_version, sysdescription, pids),
'ipsSignatures', product_family=_get_profuct_family(sysdescription)
'cvrfUrl', )
'ovalUrl',
]
def inventory_cisco_psirt(params, section: SnmpPsirt) -> InventoryResult:
optionalcolumns = [
'publicationUrl',
'summary',
'cwe',
'cves',
'productNames',
'iosRelease',
'ipsSignatures',
'cvrfUrl',
'ovalUrl',
]
product_family = None product_family = None
psirt_type = None psirt_type = None
# version = ''
not_updated = 10000000 not_updated = 10000000
dont_show_older_then = '0000-00-00' dont_show_older_then = '2000-01-01'
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]
# get parameters from wato # get parameters from wato
if params: if params:
optionalcolumns = params.get('removecolumns', optionalcolumns) optionalcolumns = params.get('removecolumns', optionalcolumns)
psirt_type = params.get('psirt_type', psirt_type) psirt_type = params.get('psirt_type', psirt_type)
if type(psirt_type) == dict: 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) not_updated = psirt_type.get('not_updated', not_updated)
dont_show_older_then = psirt_type.get('dont_show_older_then', dont_show_older_then) dont_show_older_then = psirt_type.get('dont_show_older_then')
# get OS version
version = get_OS_Version(cw_version, sysdescription, pids)
logging.info('snmp_cisco_psirt:version: %s' % version)
if not product_family: if not product_family:
# try to get product family form system description # try to get product family form system description
if ' IOS ' in sysdescription: product_family = section.product_family
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)
elif psirt_type in ['IOS', 'IOS-XE']: elif psirt_type in ['IOS', 'IOS-XE']:
product_family = psirt_type product_family = psirt_type
logging.info('snmp_cisco_psirt:product_family: %s' % product_family) # set paths
base_path = get_base_path()
# 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}')
psirt_dir = base_path + '/psirt' 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_found = psirt_dir + '/found/'
path_not_found = psirt_dir + '/not_found/' path_not_found = psirt_dir + '/not_found/'
path_request = psirt_dir + '/request/' path_request = psirt_dir + '/request/'
if product_family in ['IOS', 'IOS-XE']: if product_family in ['IOS', 'IOS-XE']:
path_found = path_found + product_family path_found = path_found + product_family
path_not_found = path_not_found + product_family path_not_found = path_not_found + product_family
path_request = path_request + product_family path_request = path_request + product_family
psirt = version psirt = section.version
else: else:
path_found = path_found + 'family' path_found = path_found + 'family'
path_not_found = path_not_found + 'family' path_not_found = path_not_found + 'family'
...@@ -245,36 +221,31 @@ def inventory_cisco_psirt(params, section) -> InventoryResult: ...@@ -245,36 +221,31 @@ def inventory_cisco_psirt(params, section) -> InventoryResult:
path_not_found = expand_path(path_not_found) path_not_found = expand_path(path_not_found)
path_request = expand_path(path_request) 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 # psirt record found
if os.path.isfile(path_found + psirt): if os.path.isfile(path_found + psirt):
logging.info('PSIRT:found: %s' % psirt)
modifytime = os.path.getmtime(path_found + psirt) modifytime = os.path.getmtime(path_found + psirt)
yield Attributes( attribute.update({
path=path, 'status': 'found',
inventory_attributes={ 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
'status': 'found', })
'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
})
# psirt_status.update(node)
# create psirt advisory list # create psirt advisory list
if product_family in ['IOS', 'IOS-XE']: # do not remove entrys from IOS(XE) 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) advisories = _create_psirt_record(path_found, psirt, 10000000, '2000-01-01', optionalcolumns)
else: 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': if dont_show_older_then != '0000-00-00':
yield Attributes( attribute['dont_show_older_than'] = dont_show_older_then
path=path,
inventory_attributes={
'dont_show_older_than': dont_show_older_then,
})
if not_updated != 10000000: if not_updated != 10000000:
yield Attributes( attribute['dont_show_not_updated_since'] = not_updated
path=path,
inventory_attributes={
'dont_show_not_updated_since': not_updated,
})
for advisory in advisories: for advisory in advisories:
key_columns = {'advisoryId': advisory.get('advisoryId')} key_columns = {'advisoryId': advisory.get('advisoryId')}
advisory.pop('advisoryId') advisory.pop('advisoryId')
...@@ -283,92 +254,29 @@ def inventory_cisco_psirt(params, section) -> InventoryResult: ...@@ -283,92 +254,29 @@ def inventory_cisco_psirt(params, section) -> InventoryResult:
key_columns=key_columns, key_columns=key_columns,
inventory_columns=advisory, 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 # psirt record not found exists
elif os.path.isfile(path_not_found + psirt): elif os.path.isfile(path_not_found + psirt):
logging.info('PSIRT:notfound: %s' % psirt)
modifytime = os.path.getmtime(path_not_found + psirt) modifytime = os.path.getmtime(path_not_found + psirt)
yield Attributes( attribute.update({
path=path, 'status': 'not found',
inventory_attributes={ 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
'status': 'not found', })
'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
})
# psirt_status.update(node)
elif psirt != 'no_family_found_or_set': elif psirt != 'no_family_found_or_set':
# create new request # create new request
logging.info('PSIRT:request: %s' % psirt)
if not os.path.isfile(path_request + 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 pass
modifytime = os.path.getmtime(path_request + psirt) modifytime = os.path.getmtime(path_request + psirt)
yield Attributes( attribute.update({
path=path, 'status': 'requested',
inventory_attributes={ 'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
'status': 'requested', })
'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime)),
}) yield Attributes(
# psirt_status.update(node) path=path,
inventory_attributes=attribute,
# # 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)
register.snmp_section( register.snmp_section(
...@@ -384,7 +292,6 @@ register.snmp_section( ...@@ -384,7 +292,6 @@ register.snmp_section(
SNMPTree( SNMPTree(
base='.1.3.6.1.2.1.47.1.1.1.1', # ENTITY-MIB::entPhysicalEntry base='.1.3.6.1.2.1.47.1.1.1.1', # ENTITY-MIB::entPhysicalEntry
oids=[ oids=[
# OIDEnd(), # index of entry
'10', # entPhysicalSoftwareRev '10', # entPhysicalSoftwareRev
'13', # entPhysicalModelName '13', # entPhysicalModelName
'5', # entPhysicalClass '5', # entPhysicalClass
...@@ -400,7 +307,6 @@ register.snmp_section( ...@@ -400,7 +307,6 @@ register.snmp_section(
register.inventory_plugin( register.inventory_plugin(
name='inv_cisco_psirt', name='inv_cisco_psirt',
inventory_function=inventory_cisco_psirt, inventory_function=inventory_cisco_psirt,
inventory_default_parameters={ inventory_default_parameters={},
},
inventory_ruleset_name='inv_cisco_psirt', inventory_ruleset_name='inv_cisco_psirt',
) )
...@@ -17,35 +17,52 @@ ...@@ -17,35 +17,52 @@
import os import os
import logging import logging
import re import re
import json
from typing import List from typing import List
# #
# global variables # global variables
# #
# list of PIDs to drop # list of PIDs to drop
g_PID_black_list = ['BUILT-IN', 'MICRON', 'C400-MTFDD'] g_PID_black_list = ['BUILT-IN', 'MICRON', 'C400-MTFDD']
# list of PIDs to try by serial number # 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 # list of S/Ns to drop
g_SN_black_list = [] 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 global g_PID_black_list
if PID_black_list: if pid_black_list:
g_PID_black_list = list(set(g_PID_black_list + 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}')
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 global g_SN_black_list
if SN_black_list: if sn_black_list:
SN_black_list = list(set(g_SN_black_list + SN_black_list)) g_SN_black_list = list(set(g_SN_black_list + sn_black_list))
logging.info((f'SN_black_list: {SN_black_list}'))
# check if dir exists, if not try to create it. # check if dir exists, if not try to create it.
# return True if dir exists or creation was ok. # return True if dir exists or creation was ok.
...@@ -73,9 +90,9 @@ def expand_path(path): ...@@ -73,9 +90,9 @@ def expand_path(path):
# returns True if SN on black list # returns True if SN on black list
def sn_on_black_list(SN): def sn_on_black_list(serial):
global g_SN_black_list global g_SN_black_list
if SN.upper() in g_SN_black_list: if serial.upper() in g_SN_black_list:
return True return True
return False return False
...@@ -117,7 +134,7 @@ def check_bad_serial(serial): ...@@ -117,7 +134,7 @@ def check_bad_serial(serial):
# remove all chars from string, except allowedchars # remove all chars from string, except allowedchars
allowedchars = re.compile('[^a-zA-Z0-9_=\-\+\.\\\]') 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) logging.info('Check_bad_serial:cleanserial: %s' % cleanserial)
...@@ -136,39 +153,3 @@ def set_loglevel(): ...@@ -136,39 +153,3 @@ def set_loglevel():
logging.basicConfig(level=logging.WARNING) logging.basicConfig(level=logging.WARNING)
# has no effect as long previous command is active (is by design) # has no effect as long previous command is active (is by design)
logging.basicConfig(level=logging.INFO) 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
No preview for this file type
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment