Name

sn_itom_pattern.UpdateContainers

Description

This script grabs a batch of newly created containers and associates them with software packages and application CIs based on the results of container image scanning

Script

var UpdateContainers = Class.create();
UpdateContainers.prototype = {
  initialize: function() {
  	this.LAST_CONTAINER_PROCESSED_TIME = 'last_container_processed_time';
  	this.logger = new Logger('UpdateContainers');
  },

  /*
  	process a batch of newly created containers. Per each container, add the installed packages and application CIs
  */
  run: function() {
  	if (!gs.getProperty('sn_itom_pattern.container_image_scan', 'true') == 'true')
  		return;
  	
  	var lastContainerProcessedTime = this._getLastContainerProcessedTime();
  	var batchSize = gs.getProperty('sn_itom_pattern.container_batch_size',50);
  	var lastContainerTimestamp;
  	
  	var containers = [];
  	var containerGr = new GlideRecord('cmdb_ci_oslv_container');
  	containerGr.addQuery('sys_created_on','>=', lastContainerProcessedTime);
  	containerGr.orderBy('sys_created_on');
  	containerGr.setLimit(batchSize);
  	containerGr.query();
  	while (containerGr.next()) {
  		lastContainerTimestamp = containerGr.sys_created_on + '';
  		containers.push(containerGr.getUniqueValue());
  		this.logger.debug('processing container ' + containerGr.name + ' ' + containerGr.getUniqueValue());
  	}
  	
  	// Now get additional containers created on the same second. We do this since we want to avoid a situation where the next
  	// iteration will process the same containers
  	this._getContainersCreatedAt(lastContainerTimestamp, containers);
  	
  	// If the current time is identical to the recent timestamp, then abort. Ideally, we could wait here one second, but gs.sleep
  	// is not available for scoped apps. Again, this is in order to avoid a situation where the next iteration will processs the same 
  	// containers
  	var now = new GlideDateTime();
  	if (now.compareTo(new GlideDateTime(lastContainerTimestamp)) == 0) {
  		this.logger.debug('Current time is identical to the timestamp of the last container we found. Will read from the same marker in the next iteration');
  		return;
  	}

  	// If no containers fetched, return here
  	if (containers.length == 0)
  		return;
  	
  	// Add packages and application CIs to containers
  	this._processContainers(containers);
  	
  	// Add one second to lastContainerTimestamp, so the next iteration will not get containers already processed
  	var lastCreated = new GlideDateTime(lastContainerTimestamp);
  	lastCreated.addSeconds(1);

  	// Record the timestamp for the next iteration
  	this._saveLastProcessedTime(lastCreated);

  },
  
  // Get containers created at given time (time is give as string). The result is pushed to an array called containers
  _getContainersCreatedAt: function(lastContainerTimestamp, containers) {
  	containerGr = new GlideRecord('cmdb_ci_oslv_container');
  	containerGr.addQuery('sys_created_on', lastContainerTimestamp);
  	containerGr.addQuery('sys_id','NOT IN', containers);
  	containerGr.query();
  	while (containerGr.next()) {
  		containers.push(containerGr.getUniqueValue());
  		this.logger.debug('processing container ' + containerGr.name + ' ' + containerGr.getUniqueValue());
  	}
  },
  
  // Process the containers in the input array. Per each container, add the installed packages and application CIs
  _processContainers: function(containers) {
  	var imageToContainers = {};
  	// Find the images associated with the containers. Prepare a map between image ID and array of containers
  	var relGr = new GlideRecord('cmdb_rel_ci');
  	relGr.addQuery('child', containers);
  	relGr.addQuery('type','1bb40e370a0a0b323d85a1ce84f8feae'); // Instantiates::Instantiated by
  	relGr.query();
  	while (relGr.next()) {
  		if (imageToContainers[relGr.parent + '']) {
  			imageToContainers[relGr.parent + ''].push(relGr.child + '');
  		} else {
  			imageToContainers[relGr.parent + ''] = [relGr.child + ''];
  		}
  	}
  	
  	// Find which images are scanned. Per each scanned image do the processing
  	var imageScanGr = new GlideRecord('sn_itom_pattern_container_image_scan_status');
  	imageScanGr.addQuery('image', Object.keys(imageToContainers));
  	imageScanGr.addQuery('scan_status','scanned');
  	imageScanGr.query();
  	while (imageScanGr.next()) {
  		this.processImage(imageScanGr.image + '', imageToContainers[imageScanGr.image]);
  	}
  }, 
  
  // Process all containers that use a given image. Per each container, add the installed packages and application CIs
  processImage: function(imageId, containers) {
  	this.logger.debug('Processing image ' + imageId + ' used by ' + containers.length + ' containers');
  	
  	// Prepare a map of software packages for this image based on sn_itom_pattern_container_image_os_packages
  	var packages = this._preparePackagesPerImage(imageId);

  	var imageGr = new GlideRecord('cmdb_ci_oslv_image');
  	if (!imageGr.get(imageId))
  		return;
  	var appHandler = new ContainerizedAppHandler();

  	// Apply the packages to each of the containers
  	var packagesPayload;
  	if (Object.keys(packages).length > 0) 
  		packagesPayload = { cmdb_ci_spkg : { data: packages}};
  	
  	var containerGr = new GlideRecord('cmdb_ci_oslv_container');
  	containerGr.addQuery('sys_id', containers);
  	containerGr.query();
  	while (containerGr.next()) {
  		// Create installed packages
  		if (packagesPayload)
  			new global.DiscoveryReconciler(containerGr, packagesPayload).process();
  			
  		// Create application Cis
  		appHandler.createApp(containerGr.getUniqueValue(), imageGr);
  	}

  },
  
  // Prepare a data structure describing the installed packages in a given image
  _preparePackagesPerImage: function(imageId) {
  	var imagePackagesGr = new GlideRecord('sn_itom_pattern_container_image_os_packages');
  	imagePackagesGr.addQuery('image', imageId);
  	imagePackagesGr.query();
  	
  	var packages = [];
  	while (imagePackagesGr.next()) {
  		packages.push({ name: imagePackagesGr.package_name + '' , version: imagePackagesGr.package_version + '', vendor: imagePackagesGr.package_maintainer + ''});
  	}
  	return packages;
  },
  
  // Record a timestamp to be used as input in the next iteration
  _saveLastProcessedTime: function(timestamp) {
  	var grHash = new GlideRecord('sa_hash');
  	grHash.addQuery('name', this.LAST_CONTAINER_PROCESSED_TIME);
  	grHash.query();
  	if (grHash.next()) {
  		grHash.setValue('hash',timestamp);
  		grHash.update();
  	} else {
  		grHash = new GlideRecord('sa_hash');
  		grHash.setValue('name', this.LAST_CONTAINER_PROCESSED_TIME);
  		grHash.setValue('hash',timestamp);
  		grHash.insert();
  	}
  	
  },
  
  // Get the last saved timestamp recorded by the previous iteration
  _getLastContainerProcessedTime: function() {
  	var grHash = new GlideRecord('sa_hash');
  	grHash.addQuery('name', this.LAST_CONTAINER_PROCESSED_TIME);
  	grHash.query();
  	if (grHash.next()) 
  		return new GlideDateTime(grHash.hash + '');
  	else 
  		return new GlideDateTime('1970-01-01 12:00:00');
  },
  
  type: 'UpdateContainers'
};

Sys ID

ef2be385a1e71910f8775b4b0fc68db9

Offical Documentation

Official Docs: