Name

sn_cmp.CmpTagUtil

Description

No description available

Script

var CmpTagUtil = Class.create();
/* eslint-disable no-dupe-keys, no-new-wrappers */
CmpTagUtil.prototype = {

  CMDB_KEY_VALUE_TABLE: 'cmdb_key_value',
  COST_TABLE: 'sn_cmp_cost',
  EMPTY_TAG_VALUE_SET_ID: 'cf35ebb5936c32004a9032bfa67ffbc8',
  INTERACTIVE_FILTER_REF_TABLE: 'sys_ui_hp_reference',
  INTERACTIVE_FILTER_TABLE: 'sys_ui_hp_publisher',
  ORDER_TABLE: 'sn_cmp_order',
  POOL_FILTER_TABLE: 'sn_cmp_rp_filter',
  POOL_TABLE: 'sn_cmp_resource_pool',
  PROVIDER_PROPERTY_TABLE: 'sn_capi_provider_property',
  RESOURCE_TAG_HISTORY_TABLE: 'sn_cmp_rsrc_tag_history',
  TAG: '[CmpTagUtil]',
  TAG_NAME_TABLE: 'sn_cmp_tag_name',
  TAG_VALUE_SET_TABLE: 'sn_cmp_tag_value_set',
  TAG_VALUE_TABLE: 'sn_cmp_tag_value',
  TLR_OP_ATTR_TABLE: 'sn_cmp_tlr_op_attr',
  EMPTY: '(empty)',

  tagNames: {},
  tagLabels: {},

  initialize: function () {

  	// Keep tag names in memory for repeated use
  	var gr = new GlideRecord(this.TAG_NAME_TABLE);
  	gr.query();
  	while (gr.next()) {
  		if (gr.active && (gr.tag_type == 'ServiceNow' || gr.tag_type == 'Custom'))
  			this.tagNames[gr.getValue('name').toLowerCase()] = {
  				'label': gr.getValue('label'),
  				'table': gr.getValue('table'),
  				'id': gr.getValue('sys_id')
  			};
  		this.tagLabels[gr.getValue('label')] = true;
  	}
  },

  associateStackTagValuesWithCi: function (stackId, ciId) {
  	var stackHistGr = new GlideRecord(this.RESOURCE_TAG_HISTORY_TABLE);
  	stackHistGr.addQuery('cmdb_ci', stackId);
  	stackHistGr.addQuery('current', true);
  	stackHistGr.query();
  	if (stackHistGr.next()) {
  		var ciHistGr = new GlideRecord(this.RESOURCE_TAG_HISTORY_TABLE);
  		ciHistGr.addQuery('cmdb_ci', ciId);
  		ciHistGr.addQuery('current', true);
  		ciHistGr.query();
  		if (ciHistGr.next()) {
  			// if CI already has a more or equally recent tag set, do not replace
  			if (ciHistGr.tag_values.tag_id >= stackHistGr.tag_values.tag_id) {
  				return ciHistGr.getUniqueValue();
  			}
  			ciHistGr.current = false;
  			ciHistGr.update();
  		}
  		var histGr = new GlideRecord(this.RESOURCE_TAG_HISTORY_TABLE);
  		histGr.initialize();
  		histGr.current = true;
  		histGr.cmdb_ci = ciId + '';
  		histGr.tag_values = stackHistGr.tag_values;
  		return histGr.insert();
  	}
  },

  associateTagValuesWithCi: function (tagValues, ciId) {
  	var tagValuesObj = global.JSON.parse(tagValues);
  	if (Array.isArray(tagValuesObj))
  		tagValuesObj = this.flatten(tagValuesObj);
  	this.setCiKeyValuePairs(tagValuesObj, ciId);
  	var tagValueSetId = this.getOrCreateTagValueSet(tagValuesObj);

  	if (gs.nil(tagValueSetId) || gs.nil(ciId)) {
  		return;
  	}
  	var histGr = new GlideRecord(this.RESOURCE_TAG_HISTORY_TABLE);
  	histGr.addQuery('cmdb_ci', ciId);
  	histGr.addQuery('current', true);
  	histGr.query();
  	if (histGr.next()) {
  		if (histGr.tag_values == tagValueSetId) {
  			return histGr.getUniqueValue();
  		} else {
  			var tagId = this.getOrGenerateTagId(tagValuesObj);

  			// Do not replace if tag id indicates that the instance-side tags are more or equally recent
  			var histTagId = histGr.tag_values.tag_id + '';
  			if (tagId.substring(0, 8) != '10000000' && histTagId.substring(0, 8) != '10000000'
  				&& histGr.tag_values.tag_id + '' >= tagId)
  				return histGr.getUniqueValue();

  			histGr.current = false;
  			histGr.update();
  		}
  	}
  	histGr.initialize();
  	histGr.current = true;
  	histGr.cmdb_ci = ciId + '';
  	histGr.tag_values = tagValueSetId;
  	return histGr.insert();
  },

  flatten: function (tagValuesArray) {
  	var tagValues = {};
  	for (var i = 0; i < tagValuesArray.length; i++)
  		tagValues[tagValuesArray[i].key] = tagValuesArray[i].value;
  	return tagValues;
  },

  setCiKeyValuePairs: function (tagValues, ciId) {
  	var kvGr = new GlideRecord(this.CMDB_KEY_VALUE_TABLE);
  	var keyVals = {};
  	kvGr.addQuery('configuration_item', ciId);
  	kvGr.query();

  	while (kvGr.next()) {
  		var key = kvGr.getValue('key');
  		if (this._hasOwnPropertyCaseInsensitive(tagValues, key)) {
  			// update value for keys that already exist
  			var tagVal = this._getPropertyCaseInsensitive(tagValues, key);
  			if (tagVal != kvGr.getValue('value')) {
  				kvGr.value = tagVal;
  				kvGr.update();
  			}
  			keyVals[key] = tagVal;
  		} else {
  			// clear value in kv records for keys no longer on the resource
  			kvGr.deleteRecord();
  		}
  	}
  	// add kv records for new keys
  	for (var k in tagValues) {
  		if (!this._hasOwnPropertyCaseInsensitive(keyVals, k)) {
  			kvGr.initialize();
  			kvGr.configuration_item = ciId;
  			kvGr.key = k;
  			kvGr.value = tagValues[k];
  			kvGr.insert();
  		}
  		if (!this._hasOwnPropertyCaseInsensitive(this.tagLabels, k)) {
  			this.createUserDefinedTag(k);
  		}
  	}
  },

  createUserDefinedTag: function (label) {
  	var tagGr = new GlideRecord(this.TAG_NAME_TABLE);
  	tagGr.initialize();
  	tagGr.label = label;
  	tagGr.name = "u_" + label.toLowerCase().replace(/[ :-]/g, "_");
  	tagGr.tag_type = 'User-defined';
  	tagGr.active = false;
  	tagGr.visible = false;
  	tagGr.insert();
  },

  associateEmptyTagValuesWithCi: function (ciId) {
  	var gr = new GlideRecord(this.RESOURCE_TAG_HISTORY_TABLE);
  	gr.addEncodedQuery('cmdb_ci=' + ciId + '^tag_values=' + this.EMPTY_TAG_VALUE_SET_ID);
  	gr.setLimit(1);
  	gr.query();
  	if (gr.next()) {
  		return gr.sys_id;
  	} else {
  		var histGr = new GlideRecord(this.RESOURCE_TAG_HISTORY_TABLE);
  		histGr.initialize();
  		histGr.current = true;
  		histGr.cmdb_ci = ciId;
  		histGr.tag_values = this.EMPTY_TAG_VALUE_SET_ID;
  		return histGr.insert();
  	}
  },

  getOrCreateTagValueSet: function (tagValues) {
  	var tagId = this.getOrGenerateTagId(tagValues);

  	var tvsGr = new GlideRecord(this.TAG_VALUE_SET_TABLE);
  	if (!tvsGr.get('tag_id', tagId)) {
  		tvsGr.initialize();
  		tvsGr.tag_id = tagId;
  	}
  	this.matchGroupUserStack(tvsGr, tagValues, tagId);
  	var customValues = '';
  	for (var tagName in this.tagNames) {
  		if (gs.nil(tvsGr[tagName]) && this._hasOwnPropertyCaseInsensitive(tagValues, this.tagNames[tagName].label)) {
  			tvsGr[tagName] = this.getOrCreateTagValue(tagName,
  				this._getPropertyCaseInsensitive(tagValues, this.tagNames[tagName].label));
  		}

  		if (this._isCustomTag(tagName)) {
  			var tagKey = this.tagNames[tagName].label;
  			var tagVal = tagValues[tagKey];
  			if (!gs.nil(customValues))
  				customValues += ', ';
  			customValues += tagKey + ":" + tagVal;

  		}
  	}
  	tvsGr.custom_values = customValues;
  	tvsGr.update();
  	return tvsGr.getUniqueValue();
  },

  _isCustomTag: function (tagName) {
  	var tagGr = new GlideRecord(this.TAG_NAME_TABLE);
  	tagGr.addQuery('tag_type', 'Custom');
  	tagGr.addQuery('name', tagName);
  	tagGr.query();
  	return tagGr.hasNext();

  },


  getOrCreateTagValue: function (tagName, strValue, sysId) {
  	var tvGr = new GlideRecord(this.TAG_VALUE_TABLE);
  	tvGr.addQuery('display_value', strValue);
  	tvGr.addQuery('tag', this.tagNames[tagName].id);
  	if (!gs.nil(this.tagNames[tagName].table))
  		tvGr.addQuery('value_table', this.tagNames[tagName].table);
  	else
  		tvGr.addNullQuery('value_table');
  	if (sysId == this.EMPTY)
  		tvGr.addNullQuery('value_id');
  	else if (!gs.nil(sysId))
  		tvGr.addQuery('value_id', sysId);

  	tvGr.query();
  	if (!tvGr.next()) {
  		tvGr.initialize();
  		tvGr.display_value = strValue;
  		if (!gs.nil(this.tagNames[tagName].table)) {
  			tvGr.value_table = this.tagNames[tagName].table;
  			var refGr = new GlideRecord(tvGr.value_table);
  			if (refGr.isValid() && sysId != this.EMPTY) {
  				// Get by display name.
  				// First record is chosen if multiple records have the same name
  				if (refGr.get(strValue))
  					tvGr.value_id = refGr.getUniqueValue();
  			}
  		}
  		tvGr.tag = this.tagNames[tagName].id;
  		tvGr.insert();

  	}
  	return tvGr.getUniqueValue();
  },

  /**
   * Given a new tag value set record and a map of discovered tag value strings,
   * try to set the User, User Group, and Stack tags appropriately given the
   * existing User, Group, and Stack assignments in ServiceNow.
   */
  matchGroupUserStack: function (tvsGr, tagValues, tagId) {
  	if (this._hasOwnPropertyCaseInsensitive(tagValues, 'User') && this._hasOwnPropertyCaseInsensitive(tagValues, 'User Group')) {
  		var ugmGr = new GlideRecord('sys_user_grmember');
  		var tagValUser = this._getPropertyCaseInsensitive(tagValues, 'User');
  		var tagValUserGroup = this._getPropertyCaseInsensitive(tagValues, 'User Group');

  		ugmGr.addQuery('user.name', tagValUser);
  		ugmGr.addQuery('group.name', tagValUserGroup);
  		ugmGr.query();
  		if (ugmGr.next()) {
  			tvsGr.user = this.getOrCreateTagValue('user', tagValUser, ugmGr.getValue('user'));
  			tvsGr.user_group = this.getOrCreateTagValue('user_group', tagValUserGroup,
  				ugmGr.getValue('group'));
  		} else {
  			if (ugmGr.get('user.name', tagValUser))
  				tvsGr.user = this.getOrCreateTagValue('user', tagValUser, ugmGr.getValue('user'));
  			tagValues['User Group'] = 'Unmanaged';
  		}
  	}

  	// If User and/or User Group is set but Stack isn't, set a Stack name from those.
  	if (!this._hasOwnPropertyCaseInsensitive(tagValues, 'Stack')) {
  		if (this._hasOwnPropertyCaseInsensitive(tagValues, 'User')) {
  			tagValues['Stack'] = this._getPropertyCaseInsensitive(tagValues, 'User');
  			if (this._hasOwnPropertyCaseInsensitive(tagValues, 'User Group'))
  				tagValues['Stack'] += ' (' + this._getPropertyCaseInsensitive(tagValues, 'User Group') + ')';
  		} else if (this._hasOwnPropertyCaseInsensitive(tagValues, 'User Group'))
  			tagValues['Stack'] = this._getPropertyCaseInsensitive(tagValues, 'User Group');
  	}

  	if (this._hasOwnPropertyCaseInsensitive(tagValues, 'Stack')) {
  		var stackGr = new GlideRecord('sn_cmp_stack');
  		if (stackGr.isValid()) {
  			var tagValStack = this._getPropertyCaseInsensitive(tagValues, 'Stack');
  			stackGr.addQuery('tag_values.tag_id', tagId);
  			stackGr.addQuery('name', tagValStack);
  			stackGr.query();
  			if (stackGr.next())
  				tvsGr.stack = this.getOrCreateTagValue('stack', tagValStack, stackGr.getUniqueValue());
  			else
  				tvsGr.stack = this.getOrCreateTagValue('stack', tagValStack, this.EMPTY);
  		}
  	}
  },

  setCustomTagValuesDisplay: function (tvsGr) {
  	var tagGr = new GlideRecord(this.TAG_NAME_TABLE);
  	tagGr.addQuery('tag_type', 'Custom');
  	tagGr.query();
  	while (tagGr.next()) {
  		var tagName = tagGr.getValue('name').toLowerCase();
  		if (!gs.nil(tvsGr[tagName])) {
  			var tagVal = tvsGr[tagName].display_value;
  			if (!gs.nil(tagVal)) {
  				if (!gs.nil(tvsGr.custom_values))
  					tvsGr.custom_values += ', ';
  				tvsGr.custom_values += tagGr.label + ':' + tagVal;
  			}
  		}
  	}
  },

  supportCustomTag: function (tagGr) {
  	this.tagNames[tagGr.getValue('name')] = {
  		'label': tagGr.getValue('label'),
  		'table': tagGr.getValue('table'),
  		'id': tagGr.getValue('sys_id')
  	};

  	this.setCustomTagColumn(tagGr.name, tagGr.label);
  	if (tagGr.value_type == 'Variable')
  		this.setTLROperationAttribute(tagGr);
  	if (GlidePluginManager.isActive("com.snc.pa.premium"))
  		this.setInteractiveFilter(tagGr);
  },

  setCustomTagColumn: function (name, label) {
  	var updater = new global.CmpImportHelper();
  	updater.setCustomTagColumn(name, label);

  	this.migrateKeyValuesToTagColumn(name, label);
  },

  migrateKeyValuesToTagColumn: function (name, label) {
  	var kvGr = new GlideRecord(this.CMDB_KEY_VALUE_TABLE);
  	kvGr.addQuery('key', label);
  	kvGr.query();

  	while (kvGr.next()) {
  		var tagHistGr = new GlideRecord(this.RESOURCE_TAG_HISTORY_TABLE);
  		tagHistGr.addQuery('cmdb_ci', kvGr.getValue('configuration_item'));
  		tagHistGr.addQuery('current', true);
  		tagHistGr.query();
  		if (tagHistGr.next()) {
  			var tvSet = new GlideRecord(this.TAG_VALUE_SET_TABLE);
  			if (tvSet.get(tagHistGr.getValue('tag_values')) && tvSet.getValue('tag_id') > '01') {
  				var val = this.getOrCreateTagValue(name, kvGr.getValue('value'));
  				tvSet.setValue(name, val);
  				tvSet.update();
  			}
  		}
  	}
  },

  removeTLROperationAttribute: function (varName) {
  	var opAttrGr = new GlideRecord(this.TLR_OP_ATTR_TABLE);
  	if (opAttrGr.isValid()) {
  		opAttrGr.addNullQuery('template_type');
  		opAttrGr.addQuery('operation', 'Provision');
  		opAttrGr.addQuery('attribute', varName);
  		opAttrGr.query();

  		if (opAttrGr.next()) {
  			opAttrGr.deleteRecord();

  			// remove parameter from existing blueprints
  			var oiParamGr = new GlideRecord('sn_cmp_rb_op_impl_param');
  			oiParamGr.addQuery('name', varName);
  			oiParamGr.addQuery('operation.operation.name', 'Provision');
  			oiParamGr.addQuery('operation.resource.is_tlr', true);
  			oiParamGr.query();
  			while (oiParamGr.next()) {
  				oiParamGr.deleteRecord();
  			}

  			var attrGr = new GlideRecord('sn_cmp_bp_stage_res_op_attr');
  			attrGr.addQuery('name', varName);
  			attrGr.addQuery('stage_resource_operation.alias_name',
  				'Blueprint Container Resource.Provision');
  			attrGr.query();
  			while (attrGr.next()) {
  				var attrID = attrGr.getUniqueValue();

  				var propGr = new GlideRecord('sn_cmp_bp_global_prop');
  				propGr.addQuery('bp_stage_res_op_attr', attrID);
  				propGr.query();
  				if (propGr.next())
  					propGr.deleteRecord();

  				attrGr.deleteRecord();
  			}
  		}
  	}
  },

  setTLROperationAttribute: function (tagGr) {
  	var opAttrGr = new GlideRecord(this.TLR_OP_ATTR_TABLE);
  	if (opAttrGr.isValid()) {
  		opAttrGr.addNullQuery('template_type');
  		opAttrGr.addQuery('operation', 'Provision');
  		opAttrGr.addQuery('attribute', tagGr.variable_name + '');
  		opAttrGr.query();

  		// If not found, create new one.
  		if (!opAttrGr.next()) {
  			opAttrGr.initialize();
  			opAttrGr.operation = 'Provision';
  			opAttrGr.attribute = tagGr.variable_name + '';
  			opAttrGr.mandatory = false;
  			opAttrGr.visible = true;
  			opAttrGr.defaultvalue = '${parameter.' + opAttrGr.attribute + '}';
  		}

  		if (gs.nil(tagGr.table))
  			opAttrGr.data_source_type = 'Text';
  		else {
  			opAttrGr.data_source_type = 'Pools';
  			opAttrGr.data_source_value = this.getOrCreatePool(tagGr.table, opAttrGr.attribute);
  		}
  		opAttrGr.update();
  	}
  },

  getOrCreatePool: function (table, varName) {
  	var poolGr = new GlideRecord(this.POOL_TABLE);
  	if (poolGr.get('lookup_table', table)) {
  		var filterGr = new GlideRecord(this.POOL_FILTER_TABLE);
  		filterGr.addQuery('resource_pool', poolGr.getUniqueValue());
  		filterGr.addQuery('name', "All");
  		filterGr.query();

  		if (!filterGr.next()) {
  			filterGr = new GlideRecord(this.POOL_FILTER_TABLE);
  			filterGr.initialize();
  			filterGr.name = 'All';
  			filterGr.resource_pool = poolGr.getUniqueValue();
  			filterGr.type = 'query';
  			filterGr.insert();
  		}
  		var datasourceValue = 'ServiceNow::Pools::' + poolGr.getValue('name') + '.' + filterGr.getValue('name');
  		return datasourceValue;
  	}

  	// If no resource pool is found for the table,
  	// create a new one along with an 'All' filter
  	poolGr.initialize();
  	poolGr.name = varName + 'Pool';
  	poolGr.lookup_table = table;
  	poolGr.lookup_value = 'sys_id';

  	// Get display field (scope friendly)
  	var dictGr = new GlideRecord('sys_dictionary');
  	dictGr.addQuery('name', table);
  	dictGr.addQuery('display', true);
  	dictGr.query();
  	if (dictGr.next())
  		poolGr.lookup_label = dictGr.getValue('element');
  	else
  		poolGr.lookup_label = 'name';

  	poolGr.type = 'static';
  	var poolId = poolGr.insert();

  	var filterGr = new GlideRecord(this.POOL_FILTER_TABLE);
  	filterGr.initialize();
  	filterGr.name = 'All';
  	filterGr.resource_pool = poolId;
  	filterGr.type = 'query';
  	filterGr.insert();

  	return 'ServiceNow::Pools::' + poolGr.getValue('name') + '.' + filterGr.getValue('name');
  },

  setInteractiveFilter: function (tagGr) {
  	var ifGr = new GlideRecord(this.INTERACTIVE_FILTER_TABLE);
  	var lookUpName = 'Cloud Cost ' + tagGr.label + ' - Single';
  	if (!ifGr.get('look_up_name', lookUpName)) {
  		ifGr.initialize();
  		ifGr.type = 2;
  		ifGr.name = tagGr.label;
  		ifGr.look_up_name = lookUpName;
  		ifGr.ui_control_type = 2;
  		ifGr.reference_table = 'sn_cmp_tag_value';
  		ifGr.reference_conditions = 'tag=' + tagGr.sys_id + '^EQ';

  		var updater = new global.CmpImportHelper();
  		var ifId = updater.updateRecord(ifGr);

  		var ifRefGr = new GlideRecord(this.INTERACTIVE_FILTER_REF_TABLE);
  		ifRefGr.initialize();
  		ifRefGr.publisher_reference = ifId;
  		ifRefGr.reference_table = this.COST_TABLE;
  		ifRefGr.reference_field = 'tag_values.' + tagGr.name;
  		updater.updateRecord(ifRefGr);
  	}
  },

  getProviderProperty: function (providerId, propKey) {
  	var ppGr = new GlideRecord(this.PROVIDER_PROPERTY_TABLE);
  	ppGr.addQuery('provider', providerId);
  	ppGr.addQuery('key', propKey);
  	ppGr.query();
  	if (ppGr.next()) {
  		return ppGr.value + '';
  	}
  	return '';
  },

  getTagValuesFromOrder: function (orderId) {

  	var result = {};

  	var orderGr = new GlideRecord(this.ORDER_TABLE);
  	if (orderGr.get(orderId + '')) {
  		if (!gs.nil(orderGr.tag_values)) {
  			return orderGr.getValue('tag_values');
  		}

  		var formData = global.JSON.parse(orderGr.order_form_data + '');
  		result["Tag Id"] = this.generateTagId();

  		var tagNameGr = new GlideRecord(this.TAG_NAME_TABLE);
  		tagNameGr.addQuery('active', true);
  		tagNameGr.query();
  		while (tagNameGr.next()) {
  			if (tagNameGr.value_type == 'Variable') {
  				var varName = tagNameGr.variable_name + '';
  				if (!gs.nil(formData[varName])) {
  					var opAttrGr = new GlideRecord(this.TLR_OP_ATTR_TABLE);
  					opAttrGr.addNullQuery('template_type');
  					opAttrGr.addQuery('operation', 'Provision');
  					opAttrGr.addQuery('attribute', varName);
  					opAttrGr.query();

  					// If value is from a pool or direct reference, convert from ref
  					// to display name. Otherwise assume the name is already given
  					if (opAttrGr.next()
  						&& (opAttrGr.data_source_type == 'Pools'
  							|| opAttrGr.data_source_type == 'Reference')) {
  						var valRefGr = new GlideRecord(tagNameGr.table + '');
  						if (valRefGr.get(formData[varName] + ''))
  							result[tagNameGr.label] = valRefGr.getDisplayValue();
  					} else {
  						result[tagNameGr.label] = formData[varName] + '';
  					}
  				}
  			} else if (tagNameGr.value_type == 'Order Field') {
  				var fieldName = tagNameGr.order_field + '';
  				if (!gs.nil(orderGr.getElement(fieldName)))
  					result[tagNameGr.label] = orderGr.getElement(fieldName) + '';
  			} else if (tagNameGr.value_type == 'Script') {
  				var script = tagNameGr.script + '';
  				var evaluator = new GlideScopedEvaluator();
  				var res = evaluator.evaluateScript(tagNameGr, 'script', null);
  				if (!gs.nil(res))
  					result[tagNameGr.label] = res;
  			}
  		}
  		orderGr.tag_values = global.JSON.stringify(result);
  		orderGr.update();
  	}
  	return global.JSON.stringify(result);
  },

  /**
   * Given a new resource-tag association (sn_cmp_rsrc_tag_history), make sure the
   * resource's owned_by and assigned_to user fields are set to the user in the
   * resource's "User" tag, and the assignment_group and/or owner_group fields are set
   * to the user group matching the "User Group" tag.
   *
   * Also, find or create a stack for this resource that matches the Stack tag.
   */
  setOwnersFromTags: function (current) {
  	var ciGr = new GlideRecord(current.cmdb_ci.sys_class_name);
  	if (ciGr.get(current.cmdb_ci)) {
  		var updated = false;

  		// Assign a user based on "User" tag value
  		if (!gs.nil(current.tag_values.user) && !gs.nil(current.tag_values.user.value_id)
  			&& ciGr.owned_by != current.tag_values.user.value_id) {
  			ciGr.assigned_to = current.tag_values.user.value_id;
  			ciGr.owned_by = current.tag_values.user.value_id;
  			updated = true;
  		}

  		// Assign a user group based on "User Group" tag value
  		if (!gs.nil(current.tag_values.user_group) && !gs.nil(current.tag_values.user_group.value_id)
  			&& ciGr.assignment_group != current.tag_values.user_group.value_id) {
  			ciGr.assignment_group = current.tag_values.user_group.value_id;
  			// Also assign owner group (for stacks)
  			if (ciGr.getRecordClassName() == 'sn_cmp_stack') {
  				ciGr.owner_group = current.tag_values.user_group.value_id;
  			}
  			updated = true;
  		}

  		// Assign a stack based on "Stack" tag value
  		if (!gs.nil(current.tag_values.stack) && current.cmdb_ci.sys_class_name != 'sn_cmp_stack') {
  			var stackId = this.setStackFromTags(current);
  			this.setStackItem(current.cmdb_ci, stackId);
  		}

  		if (updated)
  			ciGr.update();
  	}
  },

  /**
   * Given a new resource-tag association (sn_cmp_rsrc_tag_history),
   * Find or create a stack to for this resource that matches the Stack tag.
   * If a new stack must be created, make it unmanaged since no blueprint
   * is available.
   */
  setStackFromTags: function (current) {
  	var stackGr = new GlideRecord('sn_cmp_stack');

  	// ignore in discovery-only environments
  	if (!stackGr.isValid())
  		return '';

  	var stackId = current.tag_values.stack.value_id;

  	if (gs.nil(stackId)) {
  		if (stackGr.get('tag_values.tag_id', current.tag_values.tag_id))
  			stackId = stackGr.getUniqueValue();

  		if (gs.nil(stackId)) {
  			// Create stack
  			stackGr.initialize();
  			stackGr.name = current.tag_values.stack.display_value;
  			stackGr.stack_type = 1;   // Workload
  			stackGr.stack_tier = 1;   // Simple
  			stackGr.stack_status = 7; // Unmanaged

  			// Get Cloud Account for this CI
  			var relGr = new GlideRecord('cmdb_rel_ci');
  			relGr.addQuery('type.name', 'Hosted on::Hosts');
  			relGr.addQuery('parent', current.cmdb_ci);
  			relGr.query();
  			if (relGr.next()) {
  				var asscGr = new GlideRecord('sn_cmp_ca_assc_datacenter');
  				asscGr.addQuery('ldc_id', relGr.child);
  				asscGr.query();
  				if (asscGr.getRowCount == 1 && asscGr.next()) {
  					stackGr.cloud_account = asscGr.cloud_account;
  				}
  			}
  			var isDomSepPluginInstalled = new global.CMPDomainSeparationUtil().isDomainSeparationPluginInstalled();
  			if (isDomSepPluginInstalled && stackGr.isValidField('sys_domain'))
  				stackGr.sys_domain = current.cmdb_ci.sys_domain;

  			stackId = stackGr.insert();

  			// Associate the resource's tag values to its stack
  			var tagHistoryGr = new GlideRecord('sn_cmp_rsrc_tag_history');
  			tagHistoryGr.initialize();
  			tagHistoryGr.cmdb_ci = stackId;
  			tagHistoryGr.current = true;
  			tagHistoryGr.tag_values = current.tag_values;
  			tagHistoryGr.insert();
  		}

  		// Fix tag value to include ref to the new stack
  		var tagValueGr = new GlideRecord(this.TAG_VALUE_TABLE);
  		tagValueGr.initialize();
  		tagValueGr.tag = current.tag_values.stack.tag;
  		tagValueGr.display_value = current.tag_values.stack.display_value;
  		tagValueGr.value_table = 'sn_cmp_stack';
  		tagValueGr.value_id = stackId;
  		var newStackTv = tagValueGr.insert();
  		var tagValueSetGr = new GlideRecord(this.TAG_VALUE_SET_TABLE);
  		if (tagValueSetGr.get(current.tag_values)) {
  			tagValueSetGr.stack = newStackTv;
  			tagValueSetGr.update();
  		}
  	}
  	return stackId;
  },

  /**
   * Given a cmdb_ci resource  and a stack, find or create a stack item connecting
   * this resource to the stack. If a new stack must be created, make it unmanaged
   * since no blueprint is available.
   */
  setStackItem: function (cmdb_ci, stackId) {
  	var itemGr = new GlideRecord('sn_cmp_stack_item');

  	var isDomSepPluginInstalled = new global.CMPDomainSeparationUtil().isDomainSeparationPluginInstalled();

  	// ignore in discovery-only environments
  	if (!itemGr.isValid())
  		return;

  	if (itemGr.get('ci_instance_id', cmdb_ci)) {
  		if (itemGr.stack != stackId) {
  			itemGr.stack = stackId;
  			if (isDomSepPluginInstalled && itemGr.isValidField('sys_domain'))
  				itemGr.sys_domain = cmdb_ci.sys_domain;

  			itemGr.update();
  		}
  	} else {
  		// create item only if Stack is 'Unmanaged'
  		var stackGR = new GlideRecord('sn_cmp_stack');
  		if (stackGR.get(stackId) && stackGR.stack_status != 7) {
  			return;
  		}

  		itemGr.initialize();
  		itemGr.ci_instance_id = cmdb_ci;
  		itemGr.stack = stackId;
  		itemGr.status = 2;
  		itemGr.alias = cmdb_ci.name;
  		var rbGr = new GlideRecord('sn_cmp_rb_resourceblock');
  		if (rbGr.get('refcitype', cmdb_ci.sys_class_name)) {
  			itemGr.resource_block_id = rbGr.getUniqueValue();
  		}
  		var relGr = new GlideRecord('cmdb_rel_ci');
  		relGr.addQuery('type.name', 'Hosted on::Hosts');
  		relGr.addQuery('parent', cmdb_ci);
  		relGr.query();
  		if (relGr.next()) {
  			itemGr.parent_ci_instance_id = relGr.child;
  			if (rbGr.get('refcitype', relGr.child.sys_class_name))
  				itemGr.parent_block_id = rbGr.sys_id;
  		}

  		itemGr.setWorkflow(false);
  		if (isDomSepPluginInstalled && itemGr.isValidField('sys_domain'))
  			itemGr.sys_domain = cmdb_ci.sys_domain;

  		itemGr.insert();
  	}
  },

  /**
   * Generate an 11-character hex-code from the current time (in ms), and append 13 more
   * random hex digits to reduce any same-millisecond collisions to near zero (1 in 450 trillion)
   */
  generateTagId: function () {
  	var timeStamp = new Date().getTime().toString(16);
  	var counter = Math.floor(Math.random() * 0xfffffffffffff).toString(16);
  	var paddedCounter = Array(Math.max(13 - counter.length + 1, 0)).join(0) + counter;
  	return timeStamp + paddedCounter;
  },

  /**
   * For tagValues that don't include a Tag Id, generate one based on a hash of the tag values
   */
  getOrGenerateTagId: function (tagValues) {
  	if (this._hasOwnPropertyCaseInsensitive(tagValues, 'Tag Id'))
  		return this._getPropertyCaseInsensitive(tagValues, 'Tag Id');

  	var tvStr = new global.JSON().encode(tagValues);
  	var hash = 0;
  	for (i = 0; i < tvStr.length; i++) {
  		var ch = tvStr.charCodeAt(i);
  		hash = ((hash << 5) - hash) + ch;
  		hash = hash & hash; // Convert to 32bit integer
  	}
  	hash += 2147483648;
  	var tagId = '1000000000000000' + hash.toString(16);
  	return tagId;
  },

  _hasOwnPropertyCaseInsensitive: function (obj, prop) {
  	return Object.keys(obj).filter(function (v) {
  		return v.toLowerCase() === prop.toLowerCase();
  	}).length > 0;
  },

  _getPropertyCaseInsensitive: function (obj, prop) {
  	for (var i in obj)
  		if (i.toLowerCase() === prop.toLowerCase())
  			return obj[i];
  	return null;
  },

  type: 'CmpTagUtil'
};

Sys ID

30209de0935b22004a9032bfa67ffb38

Offical Documentation

Official Docs: