lib/utils/job-utils/command-parser.js
// Copyright 2015, EMC, Inc.
// *** KEEP ONLY THOSE THAT ARE APPLICABLE HERE AND SET TO TRUE ***
'use strict';
var di = require('di'),
xmlParser = require('xml2js').parseString;
module.exports = commandParserFactory;
di.annotate(commandParserFactory, new di.Provide('JobUtils.CommandParser'));
di.annotate(commandParserFactory, new di.Inject('Logger', 'Promise', '_'));
function commandParserFactory(Logger, Promise, _) {
var logger = Logger.initialize(commandParserFactory);
function CommandParser() { }
// -------- commands ----------
var ohai = 'sudo ohai --directory /etc/ohai/plugins',
dmi = 'sudo dmidecode',
megaraidControllerCount = 'sudo /opt/MegaRAID/storcli/storcli64 show ctrlcount J',
megaraidAdapterInfo = 'sudo /opt/MegaRAID/storcli/storcli64 /c0 show all J',
megaraidDriveInfo = 'sudo /opt/MegaRAID/storcli/storcli64 /c0 /eall /sall show all J',
megaraidVirtualDiskInfo = 'sudo /opt/MegaRAID/storcli/storcli64 /c0 /vall show all J',
perccliControllerCount = 'sudo /opt/MegaRAID/perccli/perccli64 show ctrlcount J',
perccliAdapterInfo = 'sudo /opt/MegaRAID/perccli/perccli64 /c0 show all J',
perccliDriveInfo = 'sudo /opt/MegaRAID/perccli/perccli64 /c0 /eall /sall show all J',
perccliVirtualDiskInfo = 'sudo /opt/MegaRAID/perccli/perccli64 /c0 /vall show all J',
perccliVersionInfo = 'sudo /opt/MegaRAID/perccli/perccli64 -v',
mpt2fusionAdapterInfo = 'sudo /opt/mpt/mpt2fusion/sas2flash -s -list',
mellanoxInfo = 'sudo mlxfwmanager --query-xml',
lshw = 'sudo lshw -json',
lspci = 'sudo lspci -nn -vmm',
lsscsiPlusRotational = 'sudo lsblk -o KNAME,TYPE,ROTA; echo BREAK; sudo lsscsi --size',
ipmiMcInfo = 'sudo ipmitool mc info',
ipmiSelInformation = 'sudo ipmitool sel',
ipmiSel = 'sudo ipmitool sel list -c',
ipmiFru = 'sudo ipmitool fru',
testEsesR = 'sudo test_eses -R --xml',
testEsesQ = 'sudo test_eses -q std --xml',
amiBios = 'cd /opt/ami; sudo ./afulnx_64 /S',
flashupdt = 'sudo /opt/intel/flashupdt -i',
smart = 'sudo bash get_smart.sh',
driveid = 'sudo node get_driveid.js',
lldp = 'sudo /usr/sbin/lldpcli show neighbor -f keyvalue',
ip = 'sudo ip -d addr show; sudo ip -d link show';
var matchParsers = {};
matchParsers.esxcliNetworkDriverVersion = {
regex: /^esxcli software vib get --vibname=net-ixgbe/,
parsefunction: function (data) {
if (data.error) {
return Promise.resolve({ source: 'esxcli-network-driver-version',
error: data.error });
}
try {
var lines = data.stdout.split('\n');
_.remove(lines, function(line) {
if (!line) {
return true;
} else {
return false;
}
});
lines.splice(0,1);
var parsed = {};
var split1 = lines[1].split(':');
var split0 = lines[0].split(':');
var split5 = lines[5].split(':');
var split3 = lines[3].split(':');
parsed['version'] = split1[1].trim();
parsed['name'] = split0[1].trim();
parsed['description'] = split5[1].trim();
parsed['vendor'] = split3[1].trim();
return Promise.resolve({ data: parsed, source: 'esxcli-network-driver-version',
store: true });
} catch (e) {
return Promise.resolve({ source: 'esxcli-network-driver-version', error: e });
}
}
};
matchParsers.ipmiUserList = {
regex: /^sudo ipmitool -c user list \d+$/,
parsefunction: function (data) {
var channel = data.cmd.match(/\d+/);
var userListSource = 'ipmi-user-list';
if (channel) {
if (channel[0] === '3') {
userListSource = 'rmm-user-list';
} else {
userListSource = userListSource + '-' + channel;
}
}
if (data.error) {
// We catalog all 15 channels, only really fail if the first
// one isn't present
if (channel && channel[0] !== '1') {
return Promise.resolve({ source: userListSource, data: '', store: false });
} else {
return Promise.resolve({ source: userListSource, error: data.error });
}
}
try {
if (data.stdout.length === 0 ) {
return Promise.resolve({ source: userListSource, data: '', store: false });
}
var lines = data.stdout.split('\n');
_.remove(lines, function (line) {
if (!line) {
return true;
} else {
return false;
}
});
var parsed = {},
header = ['ID', 'Name', 'Callin', 'Link Auth', 'IPMI Msg', 'Channel Priv Limit'],
columns = _.map(lines, function (line) {
return line.split(',');
});
var ids = _.map(lines, function (line) {
return line.split(',')[0];
});
var i = 0;
_.forEach(ids, function (id) {
_.forEach(header, function (title) {
parsed[id] = parsed[id] || {};
parsed[id][title] = columns[i].shift();
});
i += 1;
});
var store = true;
return Promise.resolve({data: parsed, source: userListSource, store: store});
} catch (e) {
return Promise.resolve({source: userListSource, error: e});
}
}
};
matchParsers.ipmiUserSummary = {
regex: /^sudo ipmitool user summary \d+$/,
parsefunction: function(data) {
var channel = data.cmd.match(/\d+/);
var userSummarySource = 'ipmi-user-summary';
if (channel) {
if (channel[0] === '3') {
userSummarySource = 'rmm-user-summary';
} else {
userSummarySource = userSummarySource + '-' + channel;
}
}
if (data.error) {
// We catalog all 15 channels, only really fail if the first
// one isn't present
if (channel && channel[0] !== '1') {
return Promise.resolve({ source: userSummarySource, data: '', store: false });
} else {
return Promise.resolve({ source: userSummarySource, error: data.error });
}
}
try {
if (data.stdout.length === 0 ) {
return Promise.resolve({ source: userSummarySource, data: '', store: false });
}
var split = data.stdout.split('\n');
_.remove(split, function(line) {
if (!line) {
return true;
} else if (line.indexOf(' : ') === -1) {
logger.warning("ipmitool parser: ignoring line " + line);
return true;
} else {
return false;
}
});
split = _.map(split, function(line) {
var sep = line.split(' : ');
sep[0] = sep[0].trim();
sep[1] = sep[1].trim();
return sep;
});
var parsed = {};
_.forEach(split, function(line) {
parsed[line[0]] = line[1];
});
var store = true;
return Promise.resolve({ data: parsed, source: userSummarySource, store: store });
} catch (e) {
return Promise.resolve({ source: userSummarySource, error: e });
}
}
};
matchParsers.ipmiLanPrint = {
regex: /^sudo ipmitool lan print\s?\d?\d?$/,
parsefunction: function(data) {
var channel = data.cmd.match(/\d+/);
var bmcsource = 'bmc';
if (channel) {
if (channel[0] === '3') {
bmcsource = 'rmm';
} else {
bmcsource = bmcsource + '-' + channel;
}
}
if (data.error) {
// We catalog all 15 channels, only really fail if the first
// one isn't present
if (channel && channel[0] !== '1') {
return Promise.resolve({ source: bmcsource, data: '', store: false });
} else {
return Promise.resolve({ source: bmcsource, error: data.error });
}
}
try {
if (data.stdout.length === 0 ) {
return Promise.resolve({ source: bmcsource, data: '', store: false });
}
var split = data.stdout.split('\n');
_.remove(split, function(line) {
if (!line) {
return true;
} else if (line.indexOf(' : ') === -1) {
logger.warning("ipmitool parser: ignoring line " + line);
return true;
} else {
return false;
}
});
split = _.map(split, function(line) {
var sep = line.split(' : ');
sep[0] = sep[0].trim();
sep[1] = sep[1].trim();
return sep;
});
var parsed = {};
_.forEach(split, function(line) {
if (line.length === 3) {
if (line[0] === 'Auth Type Enable') {
parsed['Auth Type Enable'] = parsed['Auth Type Enable'] || {};
parsed['Auth Type Enable'][line[1]] = line[2];
} else if (line[0] === '' && _.has(parsed, 'Auth Type Enable')) {
parsed['Auth Type Enable'][line[1]] = line[2];
} else {
logger.warning("Skipping subsection of IPMI data: " + line);
}
} else if (line[0] === 'Cipher Suite Priv Max') {
parsed['Cipher Suite Priv Max'] = parsed['Cipher Suite Priv Max'] || [];
parsed['Cipher Suite Priv Max'].push(line[1]);
} else if (line[0] === '' && _.has(parsed, 'Cipher Suite Priv Max')) {
parsed['Cipher Suite Priv Max'].push(line[1]);
} else {
parsed[line[0]] = line[1];
}
});
var store = (parsed['MAC Address'] !== '00:00:00:00:00:00');
// If the BMC IP is statically configured at time of discovery,
// or assigned by external dhcp-server,
// we should seed the lookup table with the BMC mac -> IP since
// we won't be able to get from the DHCP lease table.
if (parsed['IP Address'] !== '0.0.0.0') {
var lookups = [
{
ip: parsed['IP Address'],
mac: parsed['MAC Address']
}
];
return Promise.resolve({
data: parsed,
source: bmcsource,
store: store,
lookups: lookups
});
} else {
return Promise.resolve({ data: parsed, source: bmcsource, store: store });
}
} catch (e) {
return Promise.resolve({ source: bmcsource, error: e });
}
}
};
// -------- parsers ----------
CommandParser.prototype[ohai] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'ohai', error: data.error });
}
try {
var parsed = JSON.parse(data.stdout);
return Promise.resolve({ data: parsed, source: 'ohai', store: true });
} catch (e) {
return Promise.resolve({ source: 'ohai', error: e });
}
};
CommandParser.prototype[smart] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'smart', error: data.error });
}
try {
var parsed = [];
var smartEachDrive = data.stdout.split(/^#{4,}\s*([^\n\r]+)[\r\n]+/m);
var drive = {'OS Device Name' : '',
'smartctl Version' : '',
'SMART' : [],
'Controller' : {}};
_.forEach(smartEachDrive, function(smart) {
if (smart === '') {
return;
}
else if (smart[0] === '/') { //This is the device name
drive['OS Device Name'] = smart;
}
else { //this is the smart data for above device
//extract the smartctl version
var ver = smart.match(/^smartctl\s([\d\.]+)/);
if (ver && ver.length > 1) {
drive['smartctl Version'] = ver[1];
}
//further split the data into the smart part and the controller part
var splitData = smart.split(/^#{3,}\s*[^\n\r]+[\r\n]+/m);
_.forEach(splitData, function (elem) {
if (elem.match(/^controller_name/)) { //the controller part
//code to find out the HBA controller that the drive is connected to
drive.Controller = _parseControllerInfoOneDriveData(elem);
return;
}
else { //This is the smart data part
drive.SMART = _parseSmartOneDriveData(elem);
}
});
// Append the an entry to drive smart catalog
parsed.push(drive);
drive = {
'OS Device Name': '',
'smartctl Version': '',
'SMART': [],
'Controller': {}};
}
});
return Promise.resolve({ data: parsed, source: 'smart', store: true });
} catch (e) {
return Promise.resolve({ source: 'smart', error: e });
}
};
CommandParser.prototype[dmi] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'dmi', error: data.error });
}
try {
// Slice head of file until first 'Handle...' entry and
// tail of file to before 'End Of Table'
var lines = data.stdout.split('\n');
var start = _.findIndex(lines, function(line) {
return line.startsWith('Handle');
});
var end = _.findLastIndex(lines, function(line) {
return line === 'End Of Table';
});
lines = lines.slice(start, end);
var key = null;
var subKey = null;
var curr;
var parsed = _.transform(lines, function(result, line) {
if (line.startsWith('Handle')) {
/* Skip lines that look like:
*
* Handle 0x0012, DMI type 8, 9 bytes
*/
return;
} else if (!line) {
/* Along with the !key block below, handle cases where we
* have a break between top-level items:
*
* Handle 0x0012, DMI type 8, 9 bytes
* Port Connector Information
* Internal Reference Designator: J3A1
*
* Handle 0x0013, DMI type 8, 9 bytes
* Port Connector Information
* Internal Reference Designator: J3A1
*/
key = null;
} else if (!key) {
// See comments on previous conditional block
key = line.trim();
/* If this key already exists, that means we have duplicate keys,
* so we should change to an array of objects, e.g. with:
*
* Handle 0x0012, DMI type 8, 9 bytes
* Port Connector Information
* Internal Reference Designator: J3A1
*
* Handle 0x0013, DMI type 8, 9 bytes
* Port Connector Information
* Internal Reference Designator: J3A1
*
* We want the result to be:
* result['Port Connector Information'] = [ { ... } , { ... } ]
*
* All _.isArray checks in below blocks accomodate this case as well.
*/
if (_.has(result, key)) {
if (_.isArray(result[key])) {
result[key].push({});
} else {
result[key] = [result[key], {}];
}
} else {
result[key] = {};
}
} else if (_.last(line) === ':') {
/* Handle cases where we have a subkey that is an array of items,
* as in 'Characteristics' below:
*
* BIOS Information
* Characteristics:
* PCI is supported
* BIOS is upgradeable
*/
subKey = line.split(':')[0].trim();
if (_.isArray(result[key])) {
curr = _.last(result[key]);
curr[subKey] = [];
} else {
result[key][subKey] = [];
}
} else if (line[0] !== '\s' && line[0] !== '\t') {
/* Handle cases where we don't have a blank line in between
* top level categories:
*
* On Board Device 1 Information
* Type: Video
* On Board Device 2 Information
* Type: Ethernet
*/
key = line.trim();
result[key] = {};
} else {
/* Handle sub-objects and sub-arrays
*
* Cache Information
* Socket Designation: L2 Cache
* Supported SRAM Types:
* Unknown
* Installed SRAM Type: Unknown
*/
var split = line.split(':');
if (split.length === 1) {
if (_.isArray(result[key])) {
curr = _.last(result[key]);
curr[subKey].push(split[0].trim());
} else {
/* Handle corner case where the value looks like
* an object initially, but is actually an array
* with a pre-specified length. In this case,
* ditch the length value and just provide an array.
*
* Examples include 'Installable Languages' and 'Contained Elements':
*
* BIOS Language Information
* Language Description Format: Long
* Installable Languages: 1
* en|US|iso8859-1
* Currently Installed Language: en|US|iso8859-1
*
* ---
*
* Chassis Information
* Contained Elements: 1
* <OUT OF SPEC> (0)
* SKU Number: To be filled by O.E.M.
*
*/
if (parseInt(result[key][subKey])) {
result[key][subKey] = [];
}
result[key][subKey].push(split[0].trim());
}
} else {
subKey = split[0].trim();
if (_.isArray(result[key])) {
curr = _.last(result[key]);
curr[subKey] = split[1].trim();
} else {
result[key][subKey] = split[1].trim();
}
}
}
}, {});
return Promise.resolve({ data: parsed, source: 'dmi', store: true });
} catch (e) {
return Promise.reject({ source: 'dmi', error: e });
}
};
CommandParser.prototype[flashupdt] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'flashupdt', error: data.error });
}
try {
// Slice head of file until first 'BIOS Version Information' entry and
// tail of file to before 'Successfully Completed'
var lines = data.stdout.split('\n');
var start = _.findIndex(lines, function(line) {
return line.startsWith('BIOS Version Information');
});
var end = _.findLastIndex(lines, function(line) {
return line === 'Successfully Completed';
});
lines = lines.slice(start, end);
var key = null;
var subkey = null;
var split = null;
var parsed = _.transform(lines, function(result, line) {
if (line.startsWith('------')) {
// Skip lines that look like "------"
return;
} else if (line) {
// Handle lines which is not empty, empty line is skipped
if (line.match(/^\s+/)) {
// Handle subkey cases started with at least one space
// delimiter is ":...." (at least one '.')
split = line.split(/:\.+/);
subkey = split[0].trim();
if (split.length === 2) {
result[key][subkey] = split[1].trim();
} else if (split.length === 1) {
result[key][subkey] = '';
}
} else {
if (_.last(line.trim()) === ':') {
/* Handle key cases with subkeys followed, like:
* System Information:
* ----------------------
* Manufacturer Name:. EMC
*/
key = line.split(':')[0];
result[key] = {};
} else {
// Handle key cases without subkeys
split = line.split(/:\.+/);
key = split[0].trim();
if (split.length === 2) {
result[key] = split[1].trim();
} else if (split.length === 1) {
result[key] = '';
}
}
}
}
},{});
return Promise.resolve({ data: parsed, source: 'flashupdt', store: true });
} catch (e) {
return Promise.reject({ source: 'flashupdt', error: e });
}
};
CommandParser.prototype[lshw] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'lshw', error: data.error });
}
return Promise.resolve()
.then(function() {
return JSON.parse(data.stdout);
})
.then(function(parsed) {
// Grab any mac addresses out of lshw output, and return them in a
// lookups object, which will be used to populate the lookups
// collection in the DB.
var macs = _.compact(_.map(parsed.children[0].children, function(child) {
if (child.id.startsWith('pci')) {
return _.compact(_.map(child.children, function(pciChild) {
if (pciChild.id && pciChild.id.startsWith('network')) {
return pciChild.serial;
}
if (pciChild.id && pciChild.id.startsWith('pci')) {
return _.compact(_.map(pciChild.children, function(_child) {
if (_child.class && _child.class.startsWith('network')) {
return _child.serial;
}
}));
}
}));
}
}));
var lookups = _.map(_.flattenDeep(macs), function(mac) {
return { mac: mac };
});
return Promise.resolve({ data: parsed, source: 'lshw', store: true, lookups: lookups });
})
.catch(function(e) {
return Promise.resolve({ source: 'lshw', error: e });
});
};
CommandParser.prototype[lspci] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'lspci', error: data.error });
}
try {
var lines = data.stdout.split('\n\n');
var parsed = _.map(lines, function(line) {
var split = line.split('\n');
return _.transform(split, function(result, pair) {
var sep = pair.split(/:[\t\s]?/, 2);
result[sep[0]] = sep[1];
}, {});
});
return Promise.resolve({ data: parsed, source: 'lspci', store: true });
} catch (e) {
return Promise.resolve({ source: 'lspci', error: e });
}
};
CommandParser.prototype[lsscsiPlusRotational] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'lsscsi', error: data.error });
}
try {
var lines = data.stdout.trim().split('\n');
// Remove KNAME,ROTA header from lsblk output
lines.shift();
var lsblk = true;
var split;
var rotationalData = {};
var parsed = _.compact(_.map(lines, function(line) {
if (line === 'BREAK') {
lsblk = false;
} else if (lsblk) {
// Grab rotational data information
split = line.replace(/\s+/g, ',').split(',');
// Our current overlay version of lsblk does not support the
// --scsi flag, otherwise we could just use that instead
// of checking the TYPE field
if (split[1] === 'disk') {
rotationalData[split[0]] = Boolean(parseInt(split[2]));
}
} else {
// Grab lsscsi information
split = _.compact(line.split(' '));
// Combine phrases in the same item after being splited by space
if (split.length > 7) {
for (var i = 1; 3 + i < split.length - 3; i+=1) {
split[3] = split[3] + ' ' + split[3+i];
}
split = split.slice(0, 4)
.concat(split.slice(split.length - 3, split.length));
}
var curr = {
scsiInfo: split[0],
peripheralType: split[1],
vendorName: split[2],
modelName: split[3],
revisionString: split[4],
devicePath: split[5],
size: split[6]
};
var device = _.last(curr.devicePath.split('/'));
if (_.has(rotationalData, device)) {
curr.rotational = rotationalData[device];
}
return curr;
}
}));
return Promise.resolve({ data: parsed, source: 'lsscsi', store: true });
} catch (e) {
return Promise.resolve({ source: 'lsscsi', error: e });
}
};
CommandParser.prototype[megaraidControllerCount] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'megaraid-controller-count', error: data.error });
}
try {
var store = true;
var parsed = JSON.parse(data.stdout);
if (parsed.Controllers[0]['Response Data']['Controller Count'] === 0) {
store = false;
}
return Promise.resolve({
data: parsed,
source: 'megaraid-controller-count',
store: store
});
} catch (e) {
return Promise.resolve({ source: 'megaraid-controllers', error: e });
}
};
CommandParser.prototype[megaraidAdapterInfo] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'megaraid-controllers', error: data.error });
}
try {
var store = true;
var parsed = JSON.parse(data.stdout);
if (parsed.Controllers[0]['Command Status'].Status === 'Failure') {
store = false;
}
return Promise.resolve({ data: parsed, source: 'megaraid-controllers', store: store });
} catch (e) {
return Promise.resolve({ source: 'megaraid-controllers', error: e });
}
};
CommandParser.prototype[megaraidVirtualDiskInfo] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'megaraid-virtual-disks', error: data.error });
}
try {
var store = true;
var parsed = JSON.parse(data.stdout);
if (parsed.Controllers[0]['Command Status'].Status === 'Failure') {
store = false;
}
return Promise.resolve({
data: parsed,
source: 'megaraid-virtual-disks',
store: store
});
} catch (e) {
return Promise.resolve({ source: 'megaraid-virtual-disks', error: e });
}
};
CommandParser.prototype[megaraidDriveInfo] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'megaraid-physical-drives', error: data.error });
}
try {
var store = true;
var parsed = JSON.parse(data.stdout);
if (parsed.Controllers[0]['Command Status'].Status === 'Failure') {
store = false;
}
return Promise.resolve({
data: parsed,
source: 'megaraid-physical-drives',
store: store
});
} catch (e) {
return Promise.resolve({ source: 'megaraid-physical-drives', error: e });
}
};
CommandParser.prototype[perccliVersionInfo] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'perccli-version', error: data.error });
}
try {
var lines = data.stdout.split('\n');
_.remove(lines, function(line) {
if (!line) {
return true;
} else {
return false;
}
});
lines.splice(2,1);
var parsed = {};
_.forEach(lines, function(line){
if(line.includes('Ver')){
var split = line.split(' ');
var version = split.splice(11,1);
parsed['version'] = version[0];
var key2 = 'description';
var line1 = line.replace(/ {2,}/g,'');
parsed[key2] = line1;
}
else {
var key3 = "copyright" ;
var line2 = line.replace(/ {2,}/g,'');
parsed[key3] = line2;
}
});
return Promise.resolve({ data: parsed, source: 'perccli-version', store: true });
} catch (e) {
return Promise.resolve({ source: 'perccli-version', error: e });
}
};
CommandParser.prototype[perccliDriveInfo] = CommandParser.prototype[megaraidDriveInfo];
CommandParser.prototype[perccliVirtualDiskInfo] =
CommandParser.prototype[megaraidVirtualDiskInfo];
CommandParser.prototype[perccliAdapterInfo] = CommandParser.prototype[megaraidAdapterInfo];
CommandParser.prototype[perccliControllerCount] =
CommandParser.prototype[megaraidControllerCount];
CommandParser.prototype[mpt2fusionAdapterInfo] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'mpt2fusion-adapters', error: data.error });
}
try {
var lines = data.stdout.split('\n');
lines = lines.slice(5, lines.length-4);
_.remove(lines, function (line) {
if (!line) {
return true;
} else {
return false;
}
});
var parsed = _.transform(lines, function(result, line) {
var split = line.split(':');
split[0] = split[0].trim();
if (split.length > 2) {
split[1] = split.slice(1).join(':').trim();
} else {
split[1] = split[1].trim();
}
result[split[0]] = split[1];
}, {});
return Promise.resolve({ data: parsed, source: 'mpt2fusion-adapters', store: true });
} catch (e) {
return Promise.resolve({ source: 'mpt2fusion-adapters', error: e });
}
};
CommandParser.prototype[mellanoxInfo] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'mellanox', error: data.error });
} else if (!data.stdout) {
return Promise.resolve({ source: 'mellanox', error: new Error("No data") });
}
var resolve;
var deferred = new Promise(function(_resolve) {
resolve = _resolve;
});
xmlParser(data.stdout, function(err, out) {
if (err) {
resolve({ source: 'mellanox', error: err });
} else {
resolve({
data: out,
source: 'mellanox',
store: true
});
}
});
return deferred;
};
CommandParser.prototype[ipmiSelInformation] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'ipmi-sel-information', error: data.error });
}
try {
var lines = data.stdout.trim().split('\n');
lines.shift();
var parsed = _.transform(lines, function(result, line) {
var split = line.split(' : ');
result[split.shift().trim()] = split.shift().trim();
}, {});
return Promise.resolve({ data: parsed, source: 'ipmi-sel-information', store: true });
} catch (e) {
return Promise.resolve({ source: 'ipmi-sel-information', error: e });
}
};
CommandParser.prototype[ipmiSel] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'ipmi-sel', error: data.error });
}
try {
var lines = data.stdout.split('\n');
_.remove(lines, function(line) {
if (!line) {
return true;
} else {
return false;
}
});
var parsed = _.transform(lines, function(result, line) {
var split = line.split(',');
result[split.shift()] = split;
}, {});
return Promise.resolve({ data: parsed, source: 'ipmi-sel', store: true });
} catch (e) {
return Promise.resolve({ source: 'ipmi-sel', error: e });
}
};
CommandParser.prototype[ipmiFru] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'ipmi-fru', error: data.error });
}
try {
var split = data.stdout.split('\n');
_.remove(split, function(line) {
if (!line) {
return true;
} else {
return false;
}
});
split = _.map(split, function(line) {
var sep = line.split(':');
sep[0] = sep[0].trim();
sep[1] = sep[1] ? sep[1].trim() : '';
return sep;
});
var parsed = {};
var key;
_.forEach(split, function(line) {
if (line[0] === 'FRU Device Description') {
key = line[1];
parsed[key] = {};
} else {
parsed[key][line[0]] = line[1];
}
});
return Promise.resolve({ data: parsed, source: 'ipmi-fru', store: true });
} catch (e) {
return Promise.resolve({ source: 'ipmi-fru', error: e });
}
};
CommandParser.prototype[ipmiMcInfo] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'ipmi-mc-info', error: data.error });
}
if (!data.stdout) {
return Promise.resolve({
source: 'ipmi-mc-info',
error: new Error("no stdout returned from command. Data is "+data)
});
}
try {
var split = data.stdout.split('\n');
_.remove(split, function(line) {
if (!line) {
return true;
} else {
return false;
}
});
split = _.map(split, function(line) {
var sep = line.split(':');
sep[0] = sep[0].trim();
sep[1] = sep[1] ? sep[1].trim() : '';
return sep;
});
var parsed = {};
_.forEach(split, function(line) {
if (line[0] === 'Additional Device Support') {
parsed['Additional Device Support'] = [];
} else if (line[0] === 'Aux Firmware Rev Info') {
parsed['Aux Firmware Rev Info'] = [];
} else if (parsed['Aux Firmware Rev Info']) {
parsed['Aux Firmware Rev Info'].push(line[0]);
} else if (parsed['Additional Device Support']) {
parsed['Additional Device Support'].push(line[0]);
} else {
parsed[line[0]] = line[1];
}
});
return Promise.resolve({ data: parsed, source: 'ipmi-mc-info', store: true });
} catch (e) {
return Promise.resolve({ source: 'ipmi-mc-info', error: e });
}
};
CommandParser.prototype[amiBios] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'ami', error: data.error });
}
try {
var lines = data.stdout.split('\n');
var parsed = {};
_.forEach(lines, function(line) {
if (line.match(/System ROM ID/)) {
parsed.systemRomId = _.last(line.split(' = '));
} else if (line.match(/System ROM GUID/)) {
parsed.systemRomGuid = _.last(line.split(' = '));
} else if (line.match(/System ROM Secure Flash/)) {
parsed.systemRomSecureFlash = _.last(line.split(' = '));
}
});
return Promise.resolve({ data: parsed, source: 'ami', store: true });
} catch (e) {
return Promise.resolve({ source: 'ami', error: e });
}
};
CommandParser.prototype[testEsesR] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'test_eses', error: data.error });
} else if (!data.stdout) {
return Promise.resolve({ source: 'test_eses', error: new Error("No data") });
}
var resolve;
var deferred = new Promise(function(_resolve) {
resolve = _resolve;
});
xmlParser(data.stdout, function(err, out) {
if (err) {
resolve({ source: 'test_eses', error: err });
} else {
resolve({
data: out,
source: 'test_eses',
store: true
});
}
});
return deferred;
};
CommandParser.prototype[testEsesQ] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'test_eses', error: data.error });
} else if (!data.stdout) {
return Promise.resolve({ source: 'test_eses', error: new Error("No data") });
}
var resolve;
var deferred = new Promise(function(_resolve) {
resolve = _resolve;
});
xmlParser(data.stdout, function(err, out) {
if (err) {
resolve({ source: 'test_eses', error: err });
} else {
resolve({
data: out,
source: 'test_eses',
store: true
});
}
});
return deferred;
};
CommandParser.prototype[lldp] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'lldp', error: data.error });
} else if (!data.stdout) {
return Promise.resolve({ source: 'lldp', error: new Error("No data") });
}
try {
var lines = data.stdout.split('\n');
var parsed = {};
_.forEach(lines, function(line) {
var keys = line.split("=")[0].split(".");
var value = line.split("=")[1];
if( keys[0] === "lldp" ) {
//TODO: Replace with actual _.set in lodash 3.x when it is available.
var index = 0,
length = keys.length,
lastIndex = length - 1,
nested = parsed;
while( nested != null && ++index < length ) { // jshint ignore:line
var key = keys[index];
if( index === lastIndex) {
nested[key] = value;
} else if (nested[key] == null) { // jshint ignore:line
nested[key] = {};
}
nested = nested[key];
}
}
});
return Promise.resolve({ data: parsed, source: 'lldp', store: true });
} catch (e) {
return Promise.resolve({ source: 'lldp', error: e });
}
};
CommandParser.prototype[ip] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'ip', error: data.error });
} else if (!data.stdout) {
return Promise.resolve({ source: 'ip', error: new Error("No data") });
}
try {
var interfaces = _.transform(data.stdout.split('\n'), function(result, line) {
//divide the output of ip addr show; ip link show into an array of arrays
// where each sub-array represents the details of an interface and
// each element is one line of output
if (line.startsWith(':', 1)) {
//if we're looking at the first line of a new interface
// i.e. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc....
// push an array containing that line into the result
result.push([line]);
} else {
//otherwise push the line into the last interface array
result[result.length - 1].push(line);
}
}, []);
var parsed = _.transform(interfaces, function(res, info) {
//iterate over the interface arrays to parse them into key value pairs
var nameLine = info[0].substring(3).split(': ');
//remove the 'n: ' from the start of the first line the interface description
// where n = some integer and split the rest of the line into the interface
// name and other details
var name = nameLine[0];
info[0] = nameLine[1].split(' ');
var flags = info[0].shift().replace('<', '').replace('>','').split(',');
info[0] = info[0].join(' ');
//remove the flag portion of the line i.e. <BROADCAST,MULTICAST,UP,LOWER_UP>
// and then split the flags into an array of strings
// finally join the rest of the line back into a string
res[name] = (res[name] || {});
//each interface will appear in ip addr show and ip link show so we don't
// want to overwrite the object
_.defaults(res[name], {flags: flags});
var detailObj = _.transform(_.compact(info), function(result, line) {
//iterate over the lines of each interface array and parse to key/value
var words = _.compact(line.split(' '));
if(words[0].match('vlan')) {
//handle vlan detail line
// i.e. vlan protocol 802.1Q id 99 <REORDER_HDR>
words = words.slice(1, words.length - 1);
return _.defaults(result, {vlan: _.zipObject(_.chunk(words, 2))});
} else if(words.length % 2 !== 0) {
//pop off the last element of a line like
// ['inet', '10.0.2.15/24', 'brd', '10.0.2.255', 'scope', 'global', 'eth0']
// and add it to the last element because it is part of the
// 'scope' info i.e.
// ['inet', '10.0.2.15/24', 'brd', '10.0.2.255', 'scope', 'global eth0']
var last = words.pop();
words[words.length - 1] = words[words.length - 1].concat(' '+last);
}
_.defaults(result, _.zipObject(_.chunk(words,2)));
//chunk the array into an array of subarrays where each array holds
// 2 strings i.e. [inet, 172.31.128.1/24, brd, 172.31.128.255, scope, global]
// to [[inet, 172.31.128.1/24], [brd, 172.31.128.255], [scope, global]]
// and zip them into an object then assign that object to the result
},{});
_.defaults(res[name], detailObj);
}, {});
var lookups = _.transform(parsed, function(result, intrface, name) {
if (name !== 'lo' && !intrface.vlan) {
result.push({ip: intrface.inet.split('/')[0], mac: intrface['link/ether']});
}
}, []);
return Promise.resolve({ data: parsed, source: 'ip', lookups: lookups, store: true });
} catch (e) {
return Promise.resolve({ source: 'ip', error: e });
}
};
CommandParser.prototype.parseUnknownTasks = function(tasks) {
return Promise.all(_.map(tasks, function(data) {
var out;
if (data.error) {
return Promise.resolve({ source: data.source, error: data.error });
} else if (!data.stdout) {
return Promise.resolve({ source: data.source, error: new Error("No data") });
}
if (data.format === 'json') {
try {
out = JSON.parse(data.stdout);
} catch (e) {
return Promise.resolve({ source: data.source, error: e });
}
return Promise.resolve({
source: data.source,
data: out,
store: true
});
} else if (data.format === 'xml') {
xmlParser(data.stdout, function(err, out) {
if (err) {
return Promise.resolve({ source: data.source, error: err });
} else {
return Promise.resolve({
data: out,
source: data.source,
store: true
});
}
});
} else {
return Promise.resolve({
source: data.source,
data: { data: data.stdout },
store: true
});
}
}));
};
CommandParser.prototype.validateParser = function validateParser(cmd) {
var self = this;
if (self[cmd] !== undefined) {
return Promise.resolve(cmd);
}
return Promise.reject(new Error("Command parser does not exist"));
};
CommandParser.prototype.parseTasks = function parseTasks(tasks) {
var self = this;
return Promise.all(_.map(tasks, function(task) {
// if there's an explicit match parser listed, use it
if (self[task.cmd]) {
return self[task.cmd](task);
}
// if there's a match on a regex parser listed, use it
for(var parsername in matchParsers) {
if (matchParsers.hasOwnProperty(parsername)) {
if (task.cmd.match(matchParsers[parsername].regex)) {
return matchParsers[parsername].parsefunction(task);
}
}
}
// otherwise return an error for no parser existing
var error = new Error("No parser exists for command " + task.cmd);
return Promise.resolve({ source: task.cmd, error: error });
}));
};
/**
* Parse the whole controller info of one drive.
*
* @param {String} dataBlock - The controller info output data for one drive.
* @returns {Object} The object with well classfied controller information.
*/
function _parseControllerInfoOneDriveData(dataBlock) {
//split output by empty line
var dataSegments = dataBlock.split(/\n/); //Divided by line
var drvObj = {};
_.forEach(dataSegments, function(data) {
data = data.trim();
// Ignore empty line
if (data.length === 0) {
return;
}
//Split key and value by "="
var fields = data.split("="); //Divided by "="
drvObj[fields[0]] = fields[1].trim();
});
return drvObj;
}
/**
* Parse the whole SMART data of one drive.
*
* The data catalog is refer to the smartctl GUI.
*
* @param {String} dataBlock - The whole smartctl output data for one drive.
* @returns {Object} The object with well classfied SMART information.
*/
function _parseSmartOneDriveData(dataBlock) {
//split output by empty line
var dataSegments = dataBlock.split(/\r?\n\s*\r?\n/); //Divided by empty line
var drvObj = {};
_.forEach(dataSegments, function(data) {
data = data.trim();
//remove the empty data segment and the segment with smartctl version
if (data.length === 0 || data.match(/^smartctl\s([\d\.]+)\s/)) {
return;
}
var lines = data.split(/\r?\n/); //Divided by line
if (data.startsWith('=== START OF INFORMATION SECTION ===')) {
drvObj.Identity = _parseSmartInfoSection(lines);
}
else if (data.startsWith('=== START OF READ SMART DATA SECTION ===')) {
drvObj['Self-Assessment'] = _parseSmartSelfAssessment(lines);
}
else if (data.startsWith('General SMART Values')) {
drvObj.Capabilities = _parseSmartCapabilities(lines);
}
else if (data.startsWith('SMART Attributes Data Structure revision number:')) {
drvObj.Attributes = _parseSmartAttributes(lines);
}
else if (data.startsWith('SMART Error Log Version:')) {
drvObj['Error Log'] = _parseSmartErrorLog(lines);
}
else if (data.startsWith('SMART Self-test log structure revision number')) {
drvObj['Self-test Log'] = _parseSmartSelfTestLog(lines);
}
else if (data.startsWith(
'SMART Selective self-test log data structure revision number')) {
drvObj['Selective Self-test Log'] = _parseSmartSelectiveSelfTestLog(lines);
}
else {
//I don't find the regular pattern for some data segment, or I don't know how to
//well classify some data, so just put them in the 'Un-parsed Raw Data'.
if (_.isArray(drvObj['Un-parsed Raw Data'])) {
drvObj['Un-parsed Raw Data'].push(data);
}
else {
drvObj['Un-parsed Raw Data'] = [data];
}
}
});
return drvObj;
}
/*
* Parse the regular line that sperated by colon
*
* @param {String} The string line that is used to parse
* @param {Object} The target object that will be added the property to it by the parse result.
* @return {Object} The object with newly parsed property.
*/
function _parseSmartColonLine(line, obj) {
//some of the value contains colon, so this is why I don't split the string using colon.
//Lie this:
// Local Time is: Fri Jun 12 23:55:27 2015 CST
var pos = line.indexOf(':');
var key, val;
if (pos < 0) {//If no colon, then put this into key 'Others'
key = 'Others';
val = line;
}
else {
key = line.substring(0, pos);
val = line.substring(pos + 1).trim();
}
//Some of identity occurs multi times
//Example data:
//SMART support is: Available - device has SMART capability.
//SMART support is: Enabled
if (obj.hasOwnProperty(key)) {
if (_.isArray(obj[key])) {
obj[key] = obj[key].push(val);
}
else {
obj[key] = [obj[key], val];
}
}
else {
obj[key] = val;
}
return obj;
}
/**
* Parse the SMART information section
*
* @param {Array} The data to parse, it has been splitted by lines
* @return {Object}
*/
function _parseSmartInfoSection(lines) {
/*
*Example data:
=== START OF INFORMATION SECTION ===
Device Model: Micron_P400e-MTFDDAK200MAR
Serial Number: 1144031E6FB3
LU WWN Device Id: 5 00a075 1031e6fb3
Firmware Version: 0135
User Capacity: 200,049,647,616 bytes [200 GB]
*/
var result = {};
//ignore the first line
for (var i = 1; i < lines.length; i+=1) {
_parseSmartColonLine(lines[i], result);
}
return result;
}
/**
* Parse the SMART self-assessment result
*
* @param {Array} The data to parse, it has been splitted by lines
* @return {Object}
*/
function _parseSmartSelfAssessment(lines) {
/*
Example 1:
=== START OF READ SMART DATA SECTION ===
SMART overall-health self-assessment test result: PASSED
Example 2:
=== START OF READ SMART DATA SECTION ===
SMART Health Status: OK
*/
var result = {};
//ignore the first line
for (var i = 1; i < lines.length; i+=1) {
_parseSmartColonLine(lines[i], result);
}
return result;
}
/**
* Parse SMART capabilities information from smartctl output
*
* @param {Array} The data to parse, it has been splitted by lines
* @return {Array} All parsed capabilities entries,
* each entry contains properties 'Name', 'Value' and 'Annotation'.
*/
function _parseSmartCapabilities(lines) {
var combineLines = [];
var strBuf = '';
/*
The first line is 'General SMART Values:', it should be discarded.
Some of the entry information is divided into multiline,
so first we try to combine these lines to achieve a regular patter.
Below is the example data:
Offline data collection status: (0x84) Offline data collection activity
was suspended by an interrupting command from host.
Auto Offline Data Collection: Enabled.
*/
for (var i = 1; i < lines.length; i+=1) {
var firstChar = lines[i][0];
if (firstChar >= 'A' && firstChar <= 'Z') {
if (strBuf !== '') {
combineLines.push(strBuf);
}
strBuf = lines[i];
}
else {
if (strBuf[strBuf.length-1] !== '.') {
strBuf += ' ';
}
strBuf += lines[i].trim();
}
}
if (strBuf !== '') { // if this is ignored, the last capabilities entry will be missed.
combineLines.push(strBuf);
}
//After combination, each line will become something like this:
//name: (0x84) annotation1.annotation2.annotation3
var result = [];
_.forEach(combineLines, function(line) {
var arr = line.split(/:\s*\(\s*(\w+)\)\s*/);
var annotation = (arr[2] ? arr[2].split('.') : []);
if (annotation.length > 0 && annotation[annotation.length-1].length === 0) {
annotation.pop();
}
result.push({
'Name' : arr[0],
'Value' : arr[1] || '',
'Annotation' : annotation
});
});
return result;
}
/**
* Parse the SMART attribute table
*
* @param {Array} The input data that has been splitted by line.
* @param {Object} The object with SMART attribute revision and table.
*/
function _parseSmartAttributes(lines) {
var attrObj = {};
/* Example data:
SMART Attributes Data Structure revision number: 1
Vendor Specific SMART Attributes with Thresholds:
ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED
1 Raw_Read_Error_Rate 0x0003 100 100 006 Pre-fail Always
3 Spin_Up_Time 0x0003 100 100 000 Pre-fail Always
4 Start_Stop_Count 0x0002 100 100 020 Old_age Always
*/
//Parse first line to get revision number
//Example data: "SMART Attributes Data Structure revision number: 16"
if (lines.length > 0) {
attrObj.Revision = (lines[0].substring(lines[0].lastIndexOf(' ') + 1)).trim();
}
attrObj['Attributes Table'] = _parseSmartTable(lines, 2, /\s+/);
return attrObj;
}
/**
* Parse SMART error log
*
* @param {Array} The data to parse, it has been splitted by line.
* @return {Object} The object with error log revision and table.
*/
function _parseSmartErrorLog(lines) {
var resultObj = {};
//Parse first line to get revision number
//Example data:
//SMART Error Log Version: 1
if (lines.length > 0) {
resultObj.Revision = (lines[0].substring(
lines[0].lastIndexOf(' ') + 1)).trim();
}
//TODO: need to implement the error log parser
resultObj['Error Log Table'] = [];
if (lines.length > 1 && lines[1].startsWith('No ')) {
return resultObj;
}
//resultObj['Error Log Table'] = _parseSmartTable(lines, 1);
return resultObj;
}
/**
* Parse SMART Self-test log
*
* @param {Array} The data to parse, it has been splitted by line.
* @return {Object} The object with self-test log revision and table.
*/
function _parseSmartSelfTestLog(lines) {
/* Example data:
SMART Self-test log structure revision number 1
Num Test_Description Status Remaining LifeTime
# 1 Vendor (0xff) Completed 00% 463
# 2 Vendor (0xff) Completed 00% 288
# 3 Extended offline Completed 00% 263
*/
var resultObj = {};
//Parse first line to get revision number
if (lines.length > 0) {
resultObj.Revision = (lines[0].substring(lines[0].lastIndexOf(' ') + 1)).trim();
}
resultObj['Self-test Log Table'] = [];
if (lines.length > 1 && lines[1].startsWith('No self-tests have been logged')) {
return resultObj;
}
resultObj['Self-test Log Table'] = _parseSmartTable(lines, 1);
return resultObj;
}
/**
* Parse the Selective Self-test Log from the smartctl output
*
* The caller should make sure the input is the right block data for selective self-test log,
* this function will not validate whether the input is valid.
*
* @param {Array} The data to parse, it has been splitted by line
* @return {Object} The object with selective self-test log revision and table.
* @api {private}
*/
function _parseSmartSelectiveSelfTestLog(lines) {
/* Below is the example data for Selective Self-test Log:
SMART Selective self-test log data structure revision number 1
SPAN MIN_LBA MAX_LBA CURRENT_TEST_STATUS
1 0 0 Not_testing
2 0 0 Not_testing
3 0 0 Not_testing
4 0 0 Not_testing
5 0 0 Not_testing
Selective self-test flags (0x0):
After scanning selected spans, do NOT read-scan remainder of disk.
If Selective self-test is pending on power-up, resume after 0 minute delay.
*/
var resultObj = {};
if (lines.length > 0) {
resultObj.Revision = (lines[0].substring(lines[0].lastIndexOf(' ') + 1)).trim();
}
resultObj['Selective Self-test Log Table'] = [];
if (lines.length > 1 && !lines[1].startsWith('No ')) {
resultObj['Selective Self-test Log Table'] = _parseSmartTable(lines, 1, /\s{2,}/,
function (line) {
return line.startsWith('Selective self-test flags');
}
);
}
var flagRow = resultObj['Selective Self-test Log Table'].length + 2;
if (lines.length > flagRow) {
var arr = lines[flagRow].split(/\s*\(\s*(\w+)\)\s*:/);
var annotation = [];
for (var i = flagRow + 1; i < lines.length; i+=1) {
var trimLine = lines[i].trim();
if (trimLine.length !== 0) {
annotation.push(trimLine);
}
}
resultObj[arr[0]] = {
'Value' : arr[1],
'Annotation' : annotation};
}
return resultObj;
}
/**
* Parse the table data to an array of object
*
* The object properties (column header) is extracted from the first
* valid line (line index = ignoreLinesCount)
*
* @param {Array} The data to parse, it has been splitted by lines
* @param {Number} The number of lines that needed be discarded before parsing.
* @param {RegExp} The regular expression that tells how to split a row
* (not including the column header)
* @param {Function (String)} The function that tells whether the line has been the end of table
* @return {Array}
* @api private
*/
function _parseSmartTable(lines,
ignoreLinesCount,
regSplitRow,
funcCheckTableEnd)
{
/* Example 1:
ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED
1 Raw_Read_Error_Rate 0x0003 100 100 006 Pre-fail Always
3 Spin_Up_Time 0x0003 100 100 000 Pre-fail Always
4 Start_Stop_Count 0x0002 100 100 020 Old_age Always
5 Reallocated_Sector_Ct 0x0003 100 100 036 Pre-fail Always
9 Power_On_Hours 0x0003 100 100 000 Pre-fail Always
12 Power_Cycle_Count 0x0003 100 100 000 Pre-fail Always
190 Airflow_Temperature_Cel 0x0003 069 069 050 Pre-fail Always
Example 2:
Num Test_Description Status Remaining LifeTime
# 1 Vendor (0xff) Completed 00% 463
# 2 Vendor (0xff) Completed 00% 288
# 3 Extended offline Completed 00% 263
# 4 Short offline Completed 00% 263
*/
var resultObj = [];
var colHeaders;
regSplitRow = regSplitRow || /\s{2,}/; //default to split with more than 2 spaces
if (lines.length <= ignoreLinesCount + 1) { //should contains at least the column header row
return resultObj;
}
//The first line should be the column header
colHeaders = lines[ignoreLinesCount].split(/\s+/);
if (colHeaders[0].trim().length === 0) {
colHeaders.shift();
}
for (var i = ignoreLinesCount + 1; i < lines.length; i+=1) {
if (funcCheckTableEnd && funcCheckTableEnd(lines[i])) {
break; //skip early if reach the end of table
}
var arr = lines[i].split(regSplitRow);
if (arr[0].trim().length === 0) {
arr.shift();
}
var entry = {};
var j;
for (j = 0; j < colHeaders.length - 1; j += 1) {
entry[colHeaders[j]] = arr[j]; //Fill each column data
}
//There maybe more cells than the header, we append these cells to the last column.
//For example, the 'RAW_VALUE' in the attributes table may be like '31 (Min/Max 31/31)'
entry[colHeaders[j]] = arr.slice(j, arr.length).join(' ');
resultObj.push(entry);
}
return resultObj;
}
CommandParser.prototype[driveid] = function(data) {
if (data.error) {
return Promise.resolve({ source: 'driveId', error: data.error });
} else if (!data.stdout) {
return Promise.resolve({ source: 'driveId', error: new Error("No data") });
}
try {
var parsed = JSON.parse(data.stdout);
return Promise.resolve({ data: parsed, source: 'driveId', store: true });
} catch (e) {
return Promise.resolve({ source: 'driveId', error: e });
}
};
return new CommandParser();
}