222 lines
6.3 KiB
Python
222 lines
6.3 KiB
Python
import logging
|
|
import re as _re
|
|
import subprocess as _subprocess
|
|
import sys
|
|
|
|
from netbox_agent.misc import is_tool
|
|
|
|
_handle_re = _re.compile('^Handle\\s+(.+),\\s+DMI\\s+type\\s+(\\d+),\\s+(\\d+)\\s+bytes$')
|
|
_in_block_re = _re.compile('^\\t\\t(.+)$')
|
|
_record_re = _re.compile('\\t(.+):\\s+(.+)$')
|
|
_record2_re = _re.compile('\\t(.+):$')
|
|
|
|
_type2str = {
|
|
0: 'BIOS',
|
|
1: 'System',
|
|
2: 'Baseboard',
|
|
3: 'Chassis',
|
|
4: 'Processor',
|
|
5: 'Memory Controller',
|
|
6: 'Memory Module',
|
|
7: 'Cache',
|
|
8: 'Port Connector',
|
|
9: 'System Slots',
|
|
10: ' On Board Devices',
|
|
11: ' OEM Strings',
|
|
12: ' System Configuration Options',
|
|
13: ' BIOS Language',
|
|
14: ' Group Associations',
|
|
15: ' System Event Log',
|
|
16: ' Physical Memory Array',
|
|
17: ' Memory Device',
|
|
18: ' 32-bit Memory Error',
|
|
19: ' Memory Array Mapped Address',
|
|
20: ' Memory Device Mapped Address',
|
|
21: ' Built-in Pointing Device',
|
|
22: ' Portable Battery',
|
|
23: ' System Reset',
|
|
24: ' Hardware Security',
|
|
25: ' System Power Controls',
|
|
26: ' Voltage Probe',
|
|
27: ' Cooling Device',
|
|
28: ' Temperature Probe',
|
|
29: ' Electrical Current Probe',
|
|
30: ' Out-of-band Remote Access',
|
|
31: ' Boot Integrity Services',
|
|
32: ' System Boot',
|
|
33: ' 64-bit Memory Error',
|
|
34: ' Management Device',
|
|
35: ' Management Device Component',
|
|
36: ' Management Device Threshold Data',
|
|
37: ' Memory Channel',
|
|
38: ' IPMI Device',
|
|
39: ' Power Supply',
|
|
40: ' Additional Information',
|
|
41: ' Onboard Devices Extended Information',
|
|
42: ' Management Controller Host Interface'
|
|
}
|
|
_str2type = {}
|
|
for type_id, type_str in _type2str.items():
|
|
_str2type[type_str] = type_id
|
|
|
|
|
|
def parse(output=None):
|
|
"""
|
|
parse the full output of the dmidecode
|
|
command and return a dic containing the parsed information
|
|
"""
|
|
if output:
|
|
buffer = output
|
|
else:
|
|
buffer = _execute_cmd()
|
|
if isinstance(buffer, bytes):
|
|
buffer = buffer.decode('utf-8')
|
|
_data = _parse(buffer)
|
|
return _data
|
|
|
|
|
|
def get_by_type(data, type_id):
|
|
"""
|
|
filter the output of dmidecode per type
|
|
0 BIOS
|
|
1 System
|
|
2 Baseboard
|
|
3 Chassis
|
|
4 Processor
|
|
5 Memory Controller
|
|
6 Memory Module
|
|
7 Cache
|
|
8 Port Connector
|
|
9 System Slots
|
|
10 On Board Devices
|
|
11 OEM Strings
|
|
12 System Configuration Options
|
|
13 BIOS Language
|
|
14 Group Associations
|
|
15 System Event Log
|
|
16 Physical Memory Array
|
|
17 Memory Device
|
|
18 32-bit Memory Error
|
|
19 Memory Array Mapped Address
|
|
20 Memory Device Mapped Address
|
|
21 Built-in Pointing Device
|
|
22 Portable Battery
|
|
23 System Reset
|
|
24 Hardware Security
|
|
25 System Power Controls
|
|
26 Voltage Probe
|
|
27 Cooling Device
|
|
28 Temperature Probe
|
|
29 Electrical Current Probe
|
|
30 Out-of-band Remote Access
|
|
31 Boot Integrity Services
|
|
32 System Boot
|
|
33 64-bit Memory Error
|
|
34 Management Device
|
|
35 Management Device Component
|
|
36 Management Device Threshold Data
|
|
37 Memory Channel
|
|
38 IPMI Device
|
|
39 Power Supply
|
|
40 Additional Information
|
|
41 Onboard Devices Extended Information
|
|
42 Management Controller Host Interface
|
|
"""
|
|
if isinstance(type_id, str):
|
|
type_id = _str2type.get(type_id)
|
|
if type_id is None:
|
|
return None
|
|
|
|
result = []
|
|
for entry in data.values():
|
|
if entry['DMIType'] == type_id:
|
|
result.append(entry)
|
|
|
|
return result
|
|
|
|
|
|
def _execute_cmd():
|
|
if not is_tool('dmidecode'):
|
|
logging.error('Dmidecode does not seem to be present on your system. Add it your path or '
|
|
'check the compatibility of this project with your distro.')
|
|
sys.exit(1)
|
|
return _subprocess.check_output(['dmidecode', ], stderr=_subprocess.PIPE)
|
|
|
|
|
|
def _parse(buffer):
|
|
output_data = {}
|
|
# Each record is separated by double newlines
|
|
split_output = buffer.split('\n\n')
|
|
|
|
for record in split_output:
|
|
record_element = record.splitlines()
|
|
|
|
# Entries with less than 3 lines are incomplete / inactive; skip them
|
|
if len(record_element) < 3:
|
|
continue
|
|
|
|
handle_data = _handle_re.findall(record_element[0])
|
|
|
|
if not handle_data:
|
|
continue
|
|
handle_data = handle_data[0]
|
|
|
|
dmi_handle = handle_data[0]
|
|
|
|
output_data[dmi_handle] = {}
|
|
output_data[dmi_handle]['DMIType'] = int(handle_data[1])
|
|
output_data[dmi_handle]['DMISize'] = int(handle_data[2])
|
|
|
|
# Okay, we know 2nd line == name
|
|
output_data[dmi_handle]['DMIName'] = record_element[1]
|
|
|
|
in_block_elemet = ''
|
|
in_block_list = ''
|
|
|
|
# Loop over the rest of the record, gathering values
|
|
for i in range(2, len(record_element), 1):
|
|
if i >= len(record_element):
|
|
break
|
|
# Check whether we are inside a \t\t block
|
|
if in_block_elemet != '':
|
|
in_block_data = _in_block_re.findall(record_element[i])
|
|
|
|
if in_block_data:
|
|
if not in_block_list:
|
|
in_block_list = [in_block_data[0]]
|
|
else:
|
|
in_block_list.append(in_block_data[0])
|
|
|
|
output_data[dmi_handle][in_block_elemet] = in_block_list
|
|
continue
|
|
else:
|
|
# We are out of the \t\t block; reset it again, and let
|
|
# the parsing continue
|
|
in_block_elemet = ''
|
|
|
|
record_data = _record_re.findall(record_element[i])
|
|
|
|
# Is this the line containing handle identifier, type, size?
|
|
if record_data:
|
|
output_data[dmi_handle][record_data[0][0]] = record_data[0][1]
|
|
continue
|
|
|
|
# Didn't findall regular entry, maybe an array of data?
|
|
|
|
record_data2 = _record2_re.findall(record_element[i])
|
|
if record_data2:
|
|
# This is an array of data - let the loop know we are inside
|
|
# an array block
|
|
in_block_elemet = record_data2[0]
|
|
in_block_list = ''
|
|
|
|
continue
|
|
|
|
if not output_data:
|
|
raise ParseError("Unable to parse 'dmidecode' output")
|
|
|
|
return output_data
|
|
|
|
|
|
class ParseError(Exception):
|
|
pass
|