ServiceNow's Configuration Management Database (CMDB) provides an integral resource for the support of many IT Service Management activities. Without a complete and accurate understanding of the devices within the organization, troubleshooting incidents, discovering the root cause of problems, understanding the impact of changes, and many other activities become much less efficient and much more challenging. Often, achieving a complete and accurate CMDB requires pulling data from outside sources.

I've recently been working to import a lot of data into ServiceNow's CMDB as a part of an on-going integration with an external source. The data is coming from a single CSV file, but it comprises a variety of different types of devices. Using as few transform maps as possible will benefit both performance and maintainability, but having several target tables complicates that goal. To minimize the number of maps, the data is transformed into the lowest level table that is still shared amongst the classes. This maximizes the number of available fields. The initial transform is supplemented by other transform maps on lower tables. These use fewer field mappings since most of the data has already been mapped, and only the data mapping into the fields unavailable on the higher table needs to be handled.

Because the transforms are run against a higher level table in the CMDB hierarchy than the one in which the data actually belongs, I need to take some action to make sure the data ends up in the correct place. This is handled by an explicit transform map script that sets the target record's sys_class_name value. This can get a bit messy with tons of if-else if blocks, especially when considering multiple fields in the condition, e.g. type and role. The class below, stored in a Script Include by the same name, provides a function for checking the loaded record against a list set in the script to determine whether or not it should be transformed. This function can be included in the transform map script, but placing it in a Script Include makes the code a bit easier to follow and allows for the addition of related utility functions, should they be needed in the future. The parameters and condition can be modified to match your data.

var CMDBImportUtility = Class.create(); CMDBImportUtility.prototype = { initialize: function() { },

//function accepts an array of devices and a specific device and role //returns -1 if the device and role are not in the array, otherwise, returns the array index findEntry: function(deviceArray, device, role) { //utilitizes a system property com.re.log_level to determine logging (http://wiki.servicenow.com/index.php?title=GSLog) var logger = new GSLog('com.re.log_level', '[TM] Client CMDB');

try { for (var i = 0; i deviceArray.length; i++) { if (deviceArray[i]['device'] == device && (deviceArray[i]['role'] == '' || deviceArray[i]['role'] == role)) { return i; }
}

return -1; } catch (err) { logger.logError(err.message); return -1; } },

type: 'CMDBImportUtility'
}

The explicit transform map script below can be easily reused between transform maps. You will want to modify the attributes in the array objects to match your data and update the parameters being passed to the findEntry() function. Once you have done that, the "deviceArray.push(..." lines are the only ones that need to be updated before reusing the script across your transform maps for the data load. These should reflect the device types and classifications that you are importing along with the table into which they are to be transformed.

var cmdbUtil = new CMDBImportUtility(); var deviceArray = [];

//set devices to be imported in this map deviceArray.push({device:'Wireless Access Point', table:'cmdb_ci_wap_network', role:''}); deviceArray.push({device:'Server', table:'cmdb_ci_terminal_transport', role:'Terminal Server'}); deviceArray.push({device:'Server', table:'cmdb_ci_terminal_transport', role:'Windows Server'});

//determine if current record type exists in the array of acceptable types var arrayIndex = cmdbUtil.findEntry(deviceArray, source.u_device, source.u_type);

//ignore record if class not included, otherwise, map the class arrayIndex == -1 ? ignore = true : target.sys_class_name = deviceArray[arrayIndex]['table'];