Name

sn_agent.AgentDiscoveryClassification

Description

No description available

Script

var AgentDiscoveryClassification = Class.create();
AgentDiscoveryClassification.prototype = {
  
  initialize: function(iType) {
  	this.DISCOVERY_CLASSY = 'discovery_classy';
  	this.setType(iType);
  },
  
  setType: function(iType) {
  	this.type = this.DISCOVERY_CLASSY;
  	if (iType)
  		this.type += '_' + iType;
  },
  
  setFullType: function(fullType) {
  	this.type = fullType;
  },
  
  loadClassification: function() {
  	var grClassy = new GlideRecord(this.type);
  	
  	// Falling back to discovery_classy in a case we couldn't find the table resulted in unexpected behavior
  	if (!grClassy.isValid())
  		throw "Cannot use discovery classifiers, " + this.type + " table do not exist";
  		
  	grClassy.addActiveQuery();
  	grClassy.orderBy('order');
  	grClassy.query();
  	
  	return grClassy;
  },
  
  findCiType: function(map){
  	try {
  		var sdc = new global.ScopedDiscoveryClassification();
  		var rtn = sdc.findCiType(this.type, map);
  		
  		// Hyper-V Server should have 2 conditions at-least
  		// If there is only 1 condition it is: name, does not contain, without Hyper-V - which is too generic and matches non-Hyper-V servers (like Windows 2012)
  		// This is present in Q patch 0 and patch 1.
  		// We moved the discovery_class_criteria_0d797153c313200031e65ad8cbba8fc3 which adds the condition: name, contains, Hyper V - in Q patch 2
  		// Should be able to remove lines 86-87 onces discovery_class_criteria_0d797153c313200031e65ad8cbba8fc3 there are no customer on QP2.
  		// This check simply adds the condition: name, contains, Hyper V in an hard coded fashion, until we can remove it.
  		// Just coping the discovery_class_criteria_0d797153c313200031e65ad8cbba8fc3 record to the scoped app cause that record to be own by ACC-F
  		if (rtn != 'cmdb_ci_hyper_v_server' || !this.shouldSkipHyperV(map.name))
  			return rtn;
  		
  		// As we need to skip Hyper-V, we will evalute the classification in JavaScript with an additional condition
  		gs.warn("AgentDiscoveryClassification: Cannot use Hyper-V Classification, a critical missing condition is missing. Will try again");
  		var grClassy = this.find(map);
  		if (grClassy)
  			return grClassy.getValue('table');
  	} catch (e) {
  		gs.warn("AgentDiscoveryClassification: Could not find global.ScopedDiscoveryClassification() wrapper, using internal JS. Original exception = " + e);
  		try {
  			// Could not use wrapper - use internal JS
  			var gr = this.find(map);
  			if (gr)
  				return gr.getValue('table');
  		} catch (e) {
  			gs.error("AgentDiscoveryClassification: Could not classify. Original exception = " + e);
  			return "";
  		}
  	}
  	
  	return "";
  },
  
  find : function(map) {
  	var grClassy = this.loadClassification();
  	if (grClassy.getRowCount() < 1)
  		return "";
  	
  	// Search for the best match
  	while (grClassy.next()) {
  		var classySysId = grClassy.getValue('sys_id');
  		var isAllMatched = ("All" == grClassy.getValue('match_criteria'));
  		var classyConditions = this.getConditions(classySysId);
  		
  		// Hyper-V Server should have 2 conditions at-least
  		// If there is only 1 condition it is: name, does not contain, without Hyper-V - which is too generic and matches non-Hyper-V servers (like Windows 2012)
  		// This is present in Q patch 0 and patch 1.
  		// We moved the discovery_class_criteria_0d797153c313200031e65ad8cbba8fc3 which adds the condition: name, contains, Hyper V - in Q patch 2
  		// Should be able to remove lines 86-87 onces discovery_class_criteria_0d797153c313200031e65ad8cbba8fc3 there are no customer on QP2.
  		// This check simply adds the condition: name, contains, Hyper V in an hard coded fashion, until we can remove it.
  		// Just coping the discovery_class_criteria_0d797153c313200031e65ad8cbba8fc3 record to the scoped app cause that record to be own by ACC-F
  		if (classySysId == 'c759f913c313200031e65ad8cbba8fd7' && this.shouldSkipHyperV(map.name))
  			continue;
  		
  		if (this.matched(classyConditions, isAllMatched, map)) {
  			return grClassy;
  		}
  	}
  	
  	return "";
  },
  
  shouldSkipHyperV: function(osName) {
  	// First evaluate the name contains "Hyper-V" then the condition to not contain "without Hyper-V" will be evluated in the classifier
  	if (osName && osName.indexOf('Hyper-V') > -1)
  		return false;
  	
  	return true;
  },
  
  matched: function (conditions, isAll, map){
  	var numMatches = 0;
  	var processed = 0 ;
  	
  	for (var i=0; i < conditions.length; i++) {
  		processed++;
  		var op = conditions[i].operator;
  		var name = conditions[i].name;
  		var dbValue = conditions[i].value;
  		var inputValue = map[name];
  		
  		// We do want to have empty values...
  		if (inputValue == null || inputValue == undefined)
  			continue;
  		
  		switch(op) {
  			case 'equals':
  				if (inputValue == dbValue)
  					numMatches++;
  				break;
  			case 'does not equals':
  				if (inputValue != dbValue)
  					numMatches++;
  				break;
  			case 'contains':
  				if (inputValue.indexOf(dbValue) > -1 )
  					numMatches++;
  				break;
  			case 'does not contain':
  				if (inputValue.indexOf(dbValue) < 0 )
  					numMatches++;
  				break;
  			case 'startsWith':
  				if (inputValue.startsWith(dbValue))
  					numMatches++;
  				break;
  			case 'does not start with':
  				if (!inputValue.startsWith(dbValue))
  					numMatches++;
  				break;
  			case 'endsWith':
  				if (inputValue.endsWith(dbValue))
  					numMatches++;
  				break;
  			case 'does not end with':
  				if (!inputValue.endsWith(dbValue))
  					numMatches++;
  				break;
  			case 'regex matches':
  				dbValue = this.replaceToDoAll(dbValue);
  				var regex = new RegExp(dbValue, "gm"); // global, multiline, dotall
  				if (inputValue.match(regex))
  					numMatches++;
  				break;
  			case 'regex does not match':
  				dbValue = this.replaceToDoAll(dbValue);
  				var regex2 = new RegExp(dbValue, "gm"); // global, multiline, dotall
  				if (inputValue.match(regex2))
  					numMatches++;
  				break;
  			case 'in IPs':
  				if (this.inIps(inputValue, dbValue))
  					numMatches++;
  				break;
  			default:
  				// Nothing here
  		}
  		
  		if (!isAll && numMatches > 0)
  			return true;	
  	}
  	
  	if (processed > 0 && processed == numMatches)
  		return true;
  	
  	return false;
  },
  
  getConditions: function(classySysId) {
  	var grClassyCriteria = new GlideRecord('discovery_class_criteria');
  	grClassyCriteria.addQuery('classy', classySysId);
  	grClassyCriteria.addActiveQuery();
  	grClassyCriteria.query();
  	
  	var conditions = [];
  	
  	while (grClassyCriteria.next()) {
  		var condition = {};
  		condition.name = grClassyCriteria.getValue('name');
  		condition.operator = grClassyCriteria.getValue('operator');
  		condition.value = grClassyCriteria.getValue('value');
  		
  		conditions.push(condition);
  	}
  	
  	return conditions;
  },
  
  /*
  * JS does not have dot ALL
  * Workaround: convert . to [\s\S] - not a space + is a space (including new lines)
  * We need to preserve \.
  */
  replaceToDoAll: function(str){
  	if (str.indexOf('.') > -1){
  		str = str.replaceAll('.', '[\\s\\S]'); // dot all in JS
  		str = str.replaceAll('\\[\\s\\S]', '\\.'); // return the original \.
  	}
  	
  	return str;
  },
  
  /*
  	This method supports the following input:
  	ip: a string representaion of an IPv4 IP Address, i.e.: a.b.c.d
  	ipColl: a string represenation of IP Collection in the following structure (in parentheses = structure as is supported today)
  		1) single IP: 1.2.3.4 (IPNetworkV4.getIPNetworkInstance)
  		2) CIDR: 1.2.3.4/16 (IPNetworkV4.getIPNetworkInstance)
  		3) Netowrk and Mask: 1.2.3.4 / 255.254.0.0 (IPNetworkV4.getIPNetworkInstance)
  		4) Hexdecimal Netowrk and mask: 0x01010101/FF000000 (IPNetworkV4.getIPNetworkInstance)
  		5) IP Range: a.b.c.d-e.f.g.h (IPRangeV4.getIPRangeV4Instance)
  		6) Comma separated list of IPs: a.b.c.d[,e.f.g.h]... (IPList.getIPListInstance)
  */
  inIps: function (ip, ipColl){
  	var arr;
  	if (ipColl.indexOf('/') > -1) {
  		arr = ipColl.split('/');
  		if (!arr[0] || !arr[1])
  			return false;
  		
  		// Assume this is a network mask
  		var netmask = arr[1].trim();

  		if (arr[1].indexOf('.') < 0) {
  			// This is CIDR
  			netmask = global.NetworkDiscoveryUtil.netmaskFromCidrBitCount(netmask);
  		}
  		
  		var ipAsInt = global.NetworkDiscoveryUtil.stringIpToInt(ip);
  		var networkAsInt;
  		var netmaskAsInt;
  		
  		if (ipColl.startsWith('0x')){
  			// This is if the netowrk and mask are coded in hexdecimal fashion just like in IPNetworkV4.getIPNetworkInstance
  			// This should NOT be a common case
  			networkAsInt = parseInt(Number(arr[0]), 10);
  			netmaskAsInt = parseInt(Number('0x' + arr[1]), 10); // needs to add 0x
  		} else {
  			networkAsInt = global.NetworkDiscoveryUtil.stringIpToInt(arr[0].trim());
  			netmaskAsInt = global.NetworkDiscoveryUtil.stringIpToInt(netmask);
  		}

  		return (global.NetworkDiscoveryUtil.doesIpContainedInNetwork(ipAsInt, netmaskAsInt, networkAsInt));
  	}
  	else if (ipColl.indexOf('-') > -1){
  		// This is a range
  		arr = ipColl.split('-');
  		if (!arr[0] || !arr[1])
  			return false;

  		var startRange = arr[0].trim();
  		var endRange = arr[1].trim();
  		var intIp = global.NetworkDiscoveryUtil.stringIpToInt(ip);
  		var intStartRange = global.NetworkDiscoveryUtil.stringIpToInt(startRange);
  		var intEndRange = global.NetworkDiscoveryUtil.stringIpToInt(endRange);

  		// If ip is between the ranges - then true
  		if (intStartRange < intIp && intIp < intEndRange)
  			return true;
  	} else {
  		// This is a list of IPs or a single ip
  		
  		// split and trim
  		var ips = ipColl.split(",").map(function(item) {
  			return item.trim();
  		});
  		
  		if (ips.indexOf(ip) > -1)
  			return true;
  	}
  	
  	return false;
  },
  
  loadWindowsClassificationMap: function(checkValues, clientIP) {
  	var map = {};
  	
  	if (!checkValues.osInfo)
  		return map;
  	
  	var osName = checkValues.osInfo.caption;

  	// Windows computer must starts with "Windows", for Windows 10 we remove Microsoft as prefix
  	if (osName && osName.startsWith('Microsoft'))
  		osName = osName.replaceAll('Microsoft', '').trim();

  	map.isVIP = 'false'; // For Discovery Calssification this is mandatory
  	map.name = osName;
  	map.osFullName = checkValues.osInfo.name;
  	
  	var keys = Object.keys(checkValues.osInfo);
  	
  	map.version = checkValues.osInfo.version;
  	map.buildNumber = checkValues.osInfo.buildNumber;
  	map.osArchitecture = checkValues.osInfo.osArchitecture;
  	map.osLanguage = checkValues.osInfo.osLanguage;
  	
  	return map;
  },

  type: 'AgentDiscoveryClassification'
};

Sys ID

663f003affb820108ec45897d53bf1cf

Offical Documentation

Official Docs: