Join the #BuildWithBuildAgent Challenge! Get recognized, earn exclusive swag, and inspire the ServiceNow Community with what you can build using Build Agent.  Join the Challenge.

How to use CMDBTransformUtil in Transform Maps where it requires a Dependency?

Wallace Roets
ServiceNow Employee
ServiceNow Employee

Good day all,

I would like to find out if anyone used the CMDBTransformUtil in transform maps for sub-components?

Apply CI Identification and Reconciliation to Import Sets

NOTE: I've set up the identification rules, precedence etc, accordingly.

This works great for Parent components like Servers etc. However, trying to make use of this for components like IP Address, I get the "MISSING_DEPENDENCY" error

How do I set up the transform map to provide the relationship/"relations"?

I have a transform map where i set all the mappings for 1 table

The payload that gets generated look like this:

{  

    "items":[  

          {  

                "className":"cmdb_ci_ip_address",

                "values":{  

                      "nic":"3f493de24f8c5300c512ecfe0310c72b",

                      "name":"10.52.34.58",

                      "correlation_id":"ab3c16636f8593c4f412e79c6711a95b",

                      "ip_address":"10.52.34.58"

                },

                "lookup":[],

                "related":[]

          }

    ],

    "relations":[]

}

However, it is expecting the payload to contain the dependencies, i.e. something like this (even though the IP is associated with the NIC and not the host directly):

{

    "items":[

          {

                "className":"cmdb_ci_ip_address",

                "lookup":[],

                "values":{

                      "nic":"3f493de24f8c5300c512ecfe0310c72b",

                      "discovery_source":"MySource",

                      "correlation_id":"ab3c16636f8593c4f412e79c6711a95b",

                      "ip_address":"10.52.34.58"

                }

          },

          {

                "className":"cmdb_ci_solaris_server",

                "lookup":[],

                "values":{

                      "correlation_id":"ab3c16636f8593c4f412e79c6711a95b",

                      "discovery_source":"MySource"

                }

          }

    ],

    "relations":[

          {

                "type":"Owns::Owned by",

                "parent":1,

                "child":0

          }

    ]

}

In summary: How does one set up the transform maps to ensure it generates the required "parent", "child" and "relations" values when using CMDBTransformUtil

I've tried to map it in the transform:

find_real_file.png

But that just generates errors like:

Invalid map target parent does not exist in table cmdb_ci_ip_address

Invalid map target child does not exist in table cmdb_ci_ip_address

2 REPLIES 2

Hadyn
Tera Expert

Yeah I recently needed to import some CI's via import sets and decided to use the createOrUpdateCIscript to do this. I notice it is just a wrapper for SNC.IdentificationEngineScriptableApi.createOrUpdateCI().

I had the exact same issue as you as there is no obvious way to include relationships using purely CMDBTransformUtil.

So I updated the CMDBTransformUtil class to be capable of adding relationships.

These are the changes I made:

Add the last line below to the initialize function.

initialize: function() {
	this.json = new JSON();
	this.dataSource = 'ImportSet';
	this.transformEntryTable = 'sys_transform_entry';
	this.cmdbTransformStatsTable = 'cmdb_import_set_run';
	this.CmdbAPI = SNC.IdentificationEngineScriptableApi;
	this.CmdbUtil = SNC.CMDBUtil;
	this.outputPayload = '';
	this.outputRecordSysId = '';
	this.error = '';
	this.transformResult = 'e';
	this.relationships = []; //Add this
},

Add the below function to the class.

/**New function to store some relationships to build later in our payload
*@param {string} relation_class The ci class you are going to relate to
*@param {string} relation_sys_id The sys_id of the ci instance you are going to relate to
*@param {string} relation_type The CI relationship type you want to use to represent the relation
*@param {boolean} im_the_parent Indicates if the CI we are building a payload for is the parent in this relationsip ie. the relationship direction
*/
addRelationship: function(relation_class, relation_sys_id, relation_type, im_the_parent){//ie'cmdb_ci_solaris_server', sys_id, 'Instantiates::Instantiated by', false		
	this.relationships.push({
		relation_class: relation_class,
		relation_sys_id: relation_sys_id.toString(),//just in case we sent a glideelement
		relation_type: relation_type,
		im_the_parent: im_the_parent
	});
},

Change the buildInputPayload function to include building the relationships as below.

// Builds inputPayload for given source record and transform map
buildInputPayload: function(source, map, log) {
	var inputPayload = {};
	inputPayload.items = [];
	var item = {};
	item.className = map.target_table;
	var values = this.getTransformValues(source, map, log);
	var isEmpty = !Object.keys(values).length;
	if (!this.hasError() && !isEmpty) {
		item.values = values;
		inputPayload.items.push(item);
	}
	//Add our code for adding relationships now.
	if(this.relationships.length>0){
		inputPayload.relations = [];
		for(var i in this.relationships){
			var offset = parseInt(i)+1;
			var rel = this.relationships[i];
			//Add the relation to our items array
			inputPayload.items.push({
				"className": rel.relation_class,
				"values": {
					"sys_id": rel.relation_sys_id
				}
			});
			inputPayload.relations.push({
				"parent": rel.im_the_parent ? "0" : offset.toString(),
				"child": !rel.im_the_parent ? "0" : offset.toString(),
				"type": rel.relation_type
			});
		}
	}
	return inputPayload;
},

 

So then when I Import dependant classes I can use the following example code in my OnBefore script in the transform map:

var cmdbUtil = new CMDBTransformUtil();
cmdbUtil.setDataSource('ImportSet');
//Go find if we have a parent we can relate to, since it's mandatory for this class
var parent = new GlideRecord('cmdb_ci_solaris_server');
parent.addQuery('host_name',host);
parent.query();
if(parent.next()) { //If we found a valid parent, continue...
	//Add our relationship which will be added to the payload later in identifyAndReconcile() function call
	cmdbUtil.addRelationship('cmdb_ci_solaris_server', parent.sys_id.toString(), 'Instantiates::Instantiated by', false);
	//Add more than 1 relationship if you want
	cmdbUtil.addRelationship('cmdb_ci_solaris_server', parent.sys_id.toString(), 'Owns::Owned by', false);
	cmdbUtil.identifyAndReconcile(source, map, log);

 

I also had an issue where I actually have more than 1 transform map for my import set, and that does not play nicely with the CMDBTransformUtil class, so I had to make the following adjustment to fix that:

// Get values that need to be transformed for given source record
getTransformValues: function(source, map, log) {
	var values = {};
	var td = GlideTableDescriptor.get(map.target_table);
	var entryGr = new GlideRecord(this.transformEntryTable);
	entryGr.addQuery('source_table', map.source_table);
	entryGr.addQuery('map', map.sys_id); //If you have more than 1 transform map for a table then this will cause issues if this line is not present.
	entryGr.query();

Thanks for posting this, I found very useful.  However, I am having an issue when I try to add more than one relationship.  I get a MULTIPLE_DEPENDENCIES error.  Were you able to add multiple relationships successfully?