Extending Cloud Assembly with vRealize Orchestrator for ServiceNow Integration
Continuing from our previous blog post on Extending Cloud Assembly with Action Based eXtensibility for ServiceNow Integration we are going to demonstrate in this blog post extensibility with vRealize Orchestrator (vRO), typically used for integration with on-premise platforms. E.g. ServiceNow, Active Directory, DNS, IPAM, etc.
vRO is more of a traditional approach with workflow based execution using JavaScript rather than ABX which leverages serverless functions. vRO has rich extensibility capabilities and can be leveraged by most customers due to its entitlement through vSphere licensing.
Prerequisites
Depending on the circumstances, you can deploy a standalone or clustered vRO appliance which can be downloaded from the CAS portal. The integration with vRO residing on-premise is proxied through the Cloud Automation Services Data Collector, simple and lightweight.
Once your data collector is deployed, you then need to configure the integration in Cloud Assembly. For more detailed steps, refer to the VMware’s official documentation on How Can I Use vRealize Orchestrator with Cloud Assembly
Something worth mentioning is “Capability Tags” which can be applied to an vRO instance. This can become useful when you have multiple instances, perhaps for different environments such as development, test and production.
Subscriptions
We previously discussed event subscriptions in our previous blog post, so we won’t go into too much detail here except demonstrate the difference by selecting a vRO workflow as a runnable item.
Conditional Based Event Filtering
For those familiar with vRealize Automation and using custom properties to drive Event Broker Subscriptions, a similar concept can be applied to Cloud Assembly.
Filters can be applied on subscriptions to set boolean based conditions for the event, I.e. the event and action would only trigger if the boolean expression is ‘true’.
Below is an example of the property enable_servicenow: true
defined in the blueprint’s YAML.
Example Blueprint Properties for ServiceNow Integration
Example Subscription Event Filter
event.data["customProperties"]["enable_servicenow"] === "true"
Other Custom Properties
Additional to using properties defined in the blueprints YAML as part of your extensibility, there are also options to inherit properties from other constructs such as project, this fits well with the ServiceNow use case, as often there is a scenario where you may need to associate some business metadata with the instance in the CMDB, this might include things like a cost center or business unit identifier. See the example below where you can define properties to be inherited.
Projects > Specific Project > Provisioning tab
The above examples of conditional based event filtering are only some examples, the capabilities available today are much more extensive. For example, you can define a event filter to execute only for a specific user that performs the deployment by using event.userName == 'username@example.com'
Hint, hit “⌥ + Space” on Mac or “Alt + Space” on Windows to show the options available in the filter input, useful to see what options are available when creating the event filter.
vRealize Orchestrator Workflow
When writing your top level workflow for Cloud Assembly, like vRealize Automation there is an expected input structure that is used to populate the event schema data. In this case we need to use an input name inputProperties
and of type Properties
Workflow Inputs
A simple way of debugging during development with an existing run is to output the inputProperties to the vRO client logs using System.log
and JSON.stringify
to perform and pretty print of the object.
Example of Logging inputProperties (payload)
System.log(JSON.stringify(inputProperties, null, 2))
The output of this javascript can be seen below.
Example System log output with event topic payload
Reading Execution Context
As part of the workflow run, additional parameters are passed into the execution context when a vRO workflow is executed. See the example below, usually these contexts are passed starting with __
so they are easily identifiable.
In the below example we can easily obtain the request user from the execution context for use in a downstream integrations like ServiceNow.
var userName = System.getContext().getParameter("__metadata_userName");
Workflow Outputs
Below is an example of updating properties via vRO workflow outputs. In this case the implementation is slightly different to ABX, your output type and name should match the item your updating.
Example of updating properties
//Workflow Output customProperties Type:Properties
customProperties = new Properties();
customProperties = inputProperties['customProperties'];
customProperties['serviceNowSysId'] = "SuccessfulUpdatedProp";
Finding vCenter VM
A common need for on-premise provisioning can be to find the vCenter VM to then perform a subsequent downstream action. Below is an example of finding the VC:VirtualMachine
within vRealize Orchestrator using VM ID
and vCenter UUID
//Action to get VC:VirtualMachine in vRO
//Input: inputProperties
//Return: vCenterVM Type:VC:VirtualMachine
if (inputProperties["customProperties"]["vcUuid"]) {
var sdkConnection = VcPlugin.findSdkConnectionForUUID(
inputProperties["customProperties"]["vcUuid"]
);
var vCenterVM = sdkConnection.searchIndex.findByUuid(
null,
inputProperties["externalIds"][0],
true,
true
);
}
return vCenterVM;
Enterprise ITSM Use Case (ServiceNow)
We thought it would be good to show the same use case of integrating Cloud Automation Services into ServiceNow but instead with vRealize Orchestrator. We will share our workflow package at the end of the article so you can grab a copy and understand how the pieces fit together.
In the example below we have broken down the integration again into four top level workflows, each with a separate subscriptions, breaking up the workflows in this manor allows us to update and iterate on each component individually.
We have also implemented a basic tiered workflow structure. In this workflow structure we have standardised inputs to minimise binding issues, this could perhaps be reduced to two levels, the key point of the layers is separation of technical workflow and data to allow for independent tests to be executed on technical workflow, without the need spin up a machine. This separation can be implemented in different ways, below is an example.
Workflows have been broken down to the following
- Event subscription entry point - at the top level we have some basic logging, lookup of requesting user and vCenter VM if relevant.
- Integration workflow - to separate objects and feed inputs into technical workflow, handle logging and property updates to be passed up and output if needed.
- Technical workflow - downstream system integration, in this case ServiceNow API to create CMDB CI, CR and CAS IaaS API to get additional VM properties not available in the payload. These workflows are executable standalone without provisioning an instance.
To contrast the integration in ABX vs VRO we have tried to keep the code structure in a similar format. We will just show below the CAS integration to get additional data and update properties, along with the CMDB integration and updating properties. If you want to go through the end to end process, the vRO package is available at the end of the article.
Configuration Information
We are centralising configuration in a configuration item where we can reference common configuration used by multiple workflows, allowing us to update in a central location if needed, we are also storing our CAS API token here. This configuration is referenced via common actions rather than linked to workflow.
Note: The CAS API token has an expiry
Get VM Details
- In this case, rather than using AWS SSM parameter store for storing config and secrets, we are centralising configuration in vRO configuration items and looking up via common actions (functions). Secrets are stored as secure strings in vRO, for example data such as the CAS API token.
- vRO rest operations are generated dynamically so we are not bound to a particular instance of vRO, and a REST Host is referenced by the action to look up dynamically.
- We have a standardised REST actions that takes an optional parameter of a token which we add in as an additional authorization header.
var configPath = "CS"
var configName = "environmentConfig"
var attributeName = "CASRestHost"
//get REST Host from configuration element
var restHost = System.getModule("au.com.cs.example").getRestHostFromConfig(configPath,configName,attributeName)
var ConfigurationElement = System.getModule("au.com.cs.example").getConfigurationElementByName(configName,configPath);
System.debug("ConfigurationElement:" + ConfigurationElement);
var casToken = ConfigurationElement.getAttributeWithKey("CASToken")["value"]
if(!casToken){
throw "no CAS Token";
}
//REST Template
var opName = "casLogin";
var opTemplate = "/iaas/login";
var opMethod = "POST";
// create the REST operation:
var opLogin = System.getModule("au.com.cs.example").createOp(restHost,opName,opMethod,opTemplate);
//cas API Token
var contentObject = {"refreshToken":casToken}
postContent = JSON.stringify(contentObject);
var loginResponse = System.getModule("au.com.cs.example").executeOp(opLogin,null,postContent,null) ;
try{
var tokenResponse = JSON.parse(loginResponse)['token']
System.debug("token: " + tokenResponse);
} catch (ex) {
throw ex + " No valid token";
}
//REST Template Machine Details
var opName = "machineDetails";
var opTemplate = "/iaas/machines/" + resourceId;
var opMethod = "GET";
var bearer = "Bearer " + tokenResponse;
var opMachine = System.getModule("au.com.cs.example").createOp(restHost,opName,opMethod,opTemplate);
// (Rest Operation, Params, Content, Auth Token)
var vmResponse = System.getModule("au.com.cs.example").executeOp(opMachine,null,"",bearer) ;
try{
var vm = JSON.parse(vmResponse);
} catch (ex) {
throw ex + " failed to parse vm details"
}
System.log("cpuCount: " + vm["customProperties"]["cpuCount"]);
System.log("memoryInMB: " + vm["customProperties"]["memoryInMB"]);
cpuCount = vm["customProperties"]["cpuCount"];
memoryMB = vm["customProperties"]["memoryInMB"];
We pass the output of cpuCount
and memoryMB
up to the parent workflow and update the existing customProperties properties, then pass out with a output name of customProperties
, this allows us to use these value in subsequent workflow when creating the CMDB item.
Property Updates for CMDB Data Prerequisites
ServiceNow CMDB Create CI
Elements considered as part of the scripting element:
- Lookup of ServiceNow REST Host via a configuration item.
- Dynamic creation of a REST Operation for the
cmdb_ci_vmware_instance
table - Creation of a content object based on workflow inputs which is then stringified to form the post data.
- Outputting the returned
sys_id
to be used for property updated in upstream workflow.
var configPath = "CS"
var configName = "environmentConfig"
var attributeName = "serviceNowRestHost"
var tableName = "cmdb_ci_vmware_instance"
//get REST Host from configuration element
var restHost = System.getModule("au.com.cs.example").getRestHostFromConfig(configPath,configName,attributeName)
//REST Template
var opName = "serviceNowCreatCI";
var opTemplate = "/api/now/table/" + tableName;
var opMethod = "POST";
// create the REST operation:
var opCI = System.getModule("au.com.cs.example").createOp(restHost,opName,opMethod,opTemplate);
//cmdb_ci_vm_vmware table content to post;
var contentObject = {};
contentObject["name"] = hostname;
contentObject["cpus"] = cpuTotalCount;
contentObject["memory"] = MemoryInMB;
contentObject["correlation_id"]= deploymentId
contentObject["disks_size"]= diskProvisionGB
contentObject["location"] = "Sydney";
contentObject["vcenter_uuid"] = vcUuid;
contentObject["state"] = "On";
contentObject["owned_by"] = owner;
postContent = JSON.stringify(contentObject);
System.log("JSON: " + postContent);
// (Rest Operation, Params, Content, Auth Token)
var ciResponse = System.getModule("au.com.cs.example").executeOp(opCI,null,postContent,null) ;
try{
var cmdbCI = JSON.parse(ciResponse);
} catch (ex) {
throw ex + " failed to parse ServiceNow CMDB response";
}
serviceNowSysId = cmdbCI['result']['sys_id'];
In the example below we take the output from the child workflow then create a properties object taking in the existing customProperties
and we add or overwrite the serviceNowSysId
property with the value from ServiceNow, this is the unique id used within the CMDB, and is subsequently used for marking the instance as retired on destroy.
Property Updates to store the CMDB SysId
Note: These examples are provided as a means to demonstrate the use case, but whilst functional should not be considered production ready, additional considerations should be taken for production including error handling.
You can get access to the workflows and actions used in the above use case below.
servicenow.cloudbrokers.blog.package