Send vRA Deployment Lease Expiration Mails [CB10109]

  1. Steps
  2. Final Script

A simple use-case where we want to notify the vRA users about the deployments lease expiration over the email via vRO script. Use this script and modify accordingly. Let me quickly explain what I am doing here.

Steps

  • Get all the deployments by hitting the vRA Deployments API (/deployment/api/deployments) available at https://{{vRA_FQDN}}/automation-ui/api-docs
// typeof(host) == vRA:Host
var restClient = host.createRestClient();
//Assuming there are less than 10000 deployments in your environment
var request = restClient.createRequest("GET", "/deployment/api/deployments?$limit=10000", null);
request.setHeader("Content-Type", "application/json");
var response = restClient.execute(request);

System.log("****Status Code****");
var statusCode = response.statusCode;
System.log("Status code: " + statusCode);

System.log("****Content As String****");
var contentAsString = response.contentAsString;
System.log("Content as string: " + contentAsString);

Note Make sure to deal with pagination in REST API if you have a large number of deployments. Find the pagination code in final script.


We will get response something like,

{
   "content":[
      {
         "id":"xyz123-abcd-efgh-9ijk-lmnop123rst",
         "name":"new_deploy01-2023-03-1T21:13:55.799Z",
         "orgId":"xyz123-abcd-efgh-9ijk-lmnop123rst",
         "catalogItemId":"xyz123-abcd-efgh-9ijk-lmnop123rst",
         "catalogItemVersion":"1",
         "blueprintId":"xyz123-abcd-efgh-9ijk-lmnop123rst",
         "blueprintVersion":"1",
         "iconId":"xyz123-abcd-efgh-9ijk-lmnop123rst",
         "createdAt":"2023-03-1T21:14:11.773990Z",
         "createdBy":"mayank.goyal",
         "ownedBy":"mayank.goyal",
         "lastUpdatedAt":"2023-03-1T21:23:42.518212Z",
         "lastUpdatedBy":"new_deploy01",
         "leaseExpireAt":"2023-05-19T21:23:00Z",
         "inputs":{
            "name":"vm_sql01",
            "vm-size":"Small (1vCPU|2Gb)",
            "os-image":"Win2016",
            "leaseDays":60,
            "textField_b8ad97ef":"2023-03-1T21:13:55.799Z"
         },
         "projectId":"xyz123-abcd-efgh-9ijk-lmnop123rst",
         "status":"CREATE_SUCCESSFUL"
      },
.
.
.
.
  ]
}
  • Parse the response JSON to get properties like lease expiration date, deployment owner & deployment name.
.
.
.
for (var i = 0; i < contentAsString.content.length; i++){
    System.log("//------ Deployment "+contentAsString.content[i].inputs.name+" ------//");
    System.log("Lease expiring at "+contentAsString.content[i].leaseExpireAt);
.
.
var toAddress = contentAsString.content[i].ownedBy + "@email.domain";
var emailBody = "Deployment "+ contentAsString.content[i].inputs.name +" is about to expire";
.
.
.
  • Find out how many days remaining for the deployment lease to expire. Using some date and time operations, we are doing the calculation of days difference between today and leaseDate.
//2023-05-19T21:23:00Z -> vRO doesn't understand this format. So, we are converting it.
var dateString = contentAsString.content[i].leaseExpireAt;
var dateFromUI = dateString.split("Z")[0].split("T")[0];
var timeFromUI = dateString.split("Z")[0].split("T")[1];
var dateParts = dateFromUI.split("-");
var timeParts = timeFromUI.split(":");
var date = new Date(dateParts[0], dateParts[1]-1, dateParts[2], timeParts[0], timeParts[1], timeParts[2]);
System.warn("Expiration Date: "+date);
var todayDate = new Date();
System.warn("Present Date: "+todayDate);
System.log("How many days remaining for lease to expire? "+ getDaysDiffBetweenDates(todayDate, date));
if((getDaysDiffBetweenDates(todayDate, date) == 14) || (getDaysDiffBetweenDates(todayDate, date) == 7) || (getDaysDiffBetweenDates(todayDate, date) == 1)){
var toAddress = contentAsString.content[i].ownedBy + "@email.domain";
var emailBody = "Deployment "+ contentAsString.content[i].inputs.name +" is about to expire in "+getDaysDiffBetweenDates(todayDate, date)+" days. Take action now!";

function getDaysDiffBetweenDates(initialDate, finalDate){
    return Math.floor((finalDate - initialDate) / (1000 * 3600 * 24));
}
  • And then we have to send notification mails to the users at different time interval, lets say at 14 days , 7 days and 1 day before expiration.
if((getDaysDiffBetweenDates(todayDate, date) == 14) || (getDaysDiffBetweenDates(todayDate, date) == 7) || (getDaysDiffBetweenDates(todayDate, date) == 1)){
        var toAddress = contentAsString.content[i].ownedBy + "@email.domain";
        var emailBody = "Deployment "+ contentAsString.content[i].inputs.name +" is about to expire in "+getDaysDiffBetweenDates(todayDate, date)+" days. Take action now!";
        sendMail(toAddress,emailBody);

function sendMail(toAddress,emailBody){
    var message = new EmailMessage();
    var smtpHost = "[INPUT]your SMTP server fqdn/relay address";
    message.smtpHost = smtpHost;
    message.subject = "Take Action now! Deployment Expiring Soon";
    message.toAddress = toAddress;
    message.fromAddress = "[INPUT] from address";
    message.addMimePart(emailBody, "text/html; charset=UTF-8");
    message.sendMessage();    
    System.log("Mail sent to user "+toAddress);
}

Final Script

Create vRO Action with an input host of type vRA:Host and edit values like smtp server address, email domain, fromAddress.

// Input typeof(host) == vRA:Host

var restClient = host.createRestClient();
var items = [];
var path = "/deployment/api/deployments"; 
var page = 0; 
var page_size = 200; 
var base_path = path + "?$top=" + page_size; 
while (true) {    
    var skipFilter = page * page_size; 
    System.log(base_path + "&$skip=" + skipFilter);
    var request = restClient.createRequest("GET", base_path + "&$skip=" + skipFilter, null);
    request.setHeader("Content-Type", "application/json");
    var response = restClient.execute(request);
    var statusCode = response.statusCode;
    System.log("Status code: " + statusCode);
    var contentAsString = response.contentAsString;
    contentAsString = JSON.parse(contentAsString);

    for (var i = 0; i< contentAsString.content.length; i++)
        items.push(contentAsString.content[i]); 
    page++; 
    if (page >= contentAsString.totalPages) break  
};


var completedDeploymentCount = 0;
System.log("Total number of records fetched: "+ items.length);

for (var i = 0; i < items.length; i++) {
    if (items[i].inputs.name &&items[i].status != "CREATE_INPROGRESS" && items[i].status != "CREATE_FAILED"  && items[i].status != "UPDATE_FAILED" && items[i].status != "UPDATE_INPROGRESS") {
        completedDeploymentCount++; 
        System.log("*** Deployment #" + completedDeploymentCount + " " + items[i].inputs.name + " ***");
        if (items[i].leaseExpireAt) { 
            var dateString = items[i].leaseExpireAt; 
            var dateFromUI = dateString.split("Z")[0].split("T")[0];
            var timeFromUI = dateString.split("Z")[0].split("T")[1];
            var dateParts = dateFromUI.split("-");
            var timeParts = timeFromUI.split(":");
            var date = new Date(dateParts[0], dateParts[1] - 1, dateParts[2], timeParts[0], timeParts[1], timeParts[2]);
            System.log("Lease expiring on " + date);
            var todayDate = new Date();
            System.log("How many days remaining for the lease to expire? " + getDaysDiffBetweenDates(todayDate, date));  
            if ((getDaysDiffBetweenDates(todayDate, date) == 30) || (getDaysDiffBetweenDates(todayDate, date) == 14) || (getDaysDiffBetweenDates(todayDate, date) == 7) || (getDaysDiffBetweenDates(todayDate, date) == 1)) {
              if(items[i].ownedBy){  
                var toAddress =items[i].ownedBy + "@domain.com";
                var emailBody = "Deployment " + items[i].inputs.name + "'s lease is about to expire in " + getDaysDiffBetweenDates(todayDate, date) + " days.";
                sendMail(toAddress, emailBody);
              } else {
                 System.error("\"ownedBy\" attribute not found in response JSON for deployment: " + items[i].inputs.name + ". Cannot send mail!"); 
              }
            }
        } else {
            System.error("\"leaseExpireAt\" attribute not found in response JSON for deployment: " + items[i].inputs.name +". Skipping...");
        }
    }
}

function sendMail(toAddress, emailBody) {
    var message = new EmailMessage();
    var smtpHost = "x";
    message.smtpHost = smtpHost;
    message.subject = "Take Action Now!";
    message.toAddress = toAddress;
    message.fromAddress = "fromAddress@domain.com";
    message.addMimePart(emailBody, "text/html; charset=UTF-8");
    message.sendMessage();
    System.log("Mail sent to user " + toAddress);
}

function getDaysDiffBetweenDates(initialDate, finalDate) {
    return Math.ceil((finalDate - initialDate) / (1000 * 3600 * 24));
}

Over 100+ people have signed up.
Join the COMMUNITY.

Enter your mail to get the latest to your inbox, delivered weekly.

Advertisement

Workflow to run a Workflow over RESTAPI Dynamically [CB10107]

  1. Approach
    1. 1. Trigger WF in same vRO
    2. 2. Trigger WF in Master-Slave setup using Multi-Node Plugin
    3. 3. Trigger WF using Transient REST Host mechanism
  2. Steps
    1. Part 1 Get the Input parameters JSON of the WF that you want to trigger
    2. Part 1 Manually run the WF(to be triggered) just once
    3. Part 2 Time to trigger it remotely

In this post, the idea is to trigger a workflow from a vRO to another vRO over the REST API dynamically (no need to add the remote vRO as Inventory REST Host as we will be using transient hosts, you must have guessed). I have created a WF that you can use to get that functionality. Link to the package here.

Approach

There are multiple scenarios where we have to trigger a vRO workflow from another workflow. And for that, depending on the scope and requirement, We have 3 basic approaches to this.

1. Trigger WF in same vRO

This one is most commonly used through the Workflow editor. What you have to do is to simply add a WF item from the Generic Tab in the WF editor inside another WF. You can then will be able to run the WF synchronously (means the WF will wait for the sub-WF to execute completely and return its status) or asynchronously (that means the WF will run in a different thread and will not return the execution result).

If you want to run a workflow through action, you can also do that using this script in a asynchronous manner. Learn more about it in a post by Umit Demirtas here.

//Auto generated script, cannot be modified !
var workflowToLaunch = Server.getWorkflowWithId("d7c058fb-3f95-4d92-ad17-5d6a856343d4");
if (workflowToLaunch == null) {
	throw "Workflow not found";
}
var workflowParameters = new Properties();
workflowParameters.put("input1",input1Value);
workflowParameters.put("input2",input2Value);
//Add all the inputs needed..//
wfToken = workflowToLaunch.execute(workflowParameters); 

2. Trigger WF in Master-Slave setup using Multi-Node Plugin

The Multi-Node plug-in creates a primary-secondary relation between vRealize Orchestrator servers, which extends in the areas of package management and workflow execution. This plugin comes with vRO out-of-the-box. Learn how to setup Multi-Node plug-in here .

A typical script to trigger a WF in a proxy vRO Server synchronously.

// Id can be viewed in the scripting of the proxy workflows generated for a server
var workflowId = "BD808080808080808080808080808080F0C280800122528313869552e41805bb1";
var tokens = [];
for(var i = 0; i < servers.length; i++) {
	var parameters = new Properties();
	parameters.put("vm", vms[i].id);
	parameters.put("newName", newNames[i]);
	var tmpResult = VCOProxyWorkflowManager.executeSynchronousProxy(servers[i].connectionId, workflowId, parameters);
	tokens.push(tmpResult.get("remoteToken"));
}

Other way would be to trigger them asynchronously. Make sure the proxy workflows exists in local vRO server. Learn more about that here.

VCOProxyWorkflowManager.executeAsynchronousProxies(workflowId, params);

3. Trigger WF using Transient REST Host mechanism

This one is what I would like to share in today’s post. I have created a custom WF through which you can use this approach. Basically, the idea is to utilize the vRO REST API to trigger workflows asynchronously (workflows/{{workflowId}}/executions) and that too using the transient REST host which will not require any configuration to be done earlier. You just need the HTTP-REST plug-in that comes out-of-the-box.

Steps

Part 1 Get the Input parameters JSON of the WF that you want to trigger

Here, in this demo WF, there is one single input input1 with type=string, probably the simplest one. However, head to William Lam’s post on how create a complex input JSON with SDK objects here.

Part 1 Manually run the WF(to be triggered) just once

As you can see above, I have modified the sequence of steps. I have actually created a new action that will prepare the input parameter JSON body by itself. You will find that action (in.co.cloudblogger.customclasses/getWorkflowLastExecutionBody()) in the package itself.

Just 1 thing is needed to be done which is to run the WF to be triggered at least once manually. So that a JSON structure will prepared for the input set of the triggered WF and the action I have created will fetch the last run and automatically prepopulate the “Start WF Execution using REST” WF with the input set of last run.

Part 2 Time to trigger it remotely

  • Download the vRO package from here.
  • Once the package is imported, Go to the Workflow “Start WF Execution using REST” which will be considered as “master wf” here.
  • Provide inputs to this workflow
    • vRO FQDN – Name of the remote vRO where you want to trigger the WF.
    • Username – Username to access remote vRO (User should have permission to Run)
    • Password – Password for the user provided
    • Workflow ID – ID of the Workflow that you want to trigger (find it in your WF URL)
    • Parameter JSON – A structured JSON object with input parameters required to run the remote WF. This will be auto-populated using the new action.
  • Click Run

Critical Values of Certain Datatypes like SecureString or EncryptedString will be hidden in the Payload body. You will have to manually add that missing JSON. For eg. if you see here, the payload has no value field for SecureString.

{
   "parameters":[
      {
         "value":{
            "string":{
               "value":"mayank.goyal"
            }
         },
         "type":"string",
         "name":"username",
         "scope":"local"
      },
      {
         "type":"SecureString",
         "name":"password",
         "scope":"local"
      }
   ]
}

To fix this, you can add something like,

"value":{
            "string":{
               "value":"pa$$w0rd!"
            }
         },

to make the final payload complete to be sent over REST.

{
   "parameters":[
      {
         "value":{
            "string":{
               "value":"mayank.goyal"
            }
         },
         "type":"string",
         "name":"username",
         "scope":"local"
      },
      {
	"value":{
            "string":{
               "value":"Mar#2023"
            }
         },
         "type":"SecureString",
         "name":"password",
         "scope":"local"
      }
   ]
}

  • Once the master WF executed successfully, Login to remote vRO.
  • Go to the WF that was triggered by master WF, click ALL RUNS, you should see the executed run there.
  • In our test run, we passed the Input value as “Triggered over REST by Mayank Goyal” which can be validated here.

You can use this WF inside a Wrapper WF to prepare the inputs or probably inside another WF where you are performing other tasks and that way, you can use it in your automations easily.

That’s it in this post. I hope it will be of some help for you. Thanks for reading. See you on other post. Please subscribe to my newsletter and get latest posts directly in your inbox.

Differences between VMware Aria Automation Orchestrator Forms and VMware Aria Automation Service Broker Forms

Starting with vRealize Automation 8.2, Service Broker is capable of displaying input forms designed in vRealize Orchestrator with the custom forms display engine. However, there are some differences in the forms display engines.

Orchestrator and Service Broker forms

Amongst the differences, the following features supported in vRealize Orchestrator are not yet supported in Service Broker:

  • The inputs presentations developed with the vRealize Orchestrator Legacy Client used in vRealize Orchestrator 7.6 and earlier, are not compatible. vRealize Orchestrator uses a built-in legacy input presentation conversion that is not available from Service Broker yet.
  • The inputs presentation in vRealize Orchestrator has access to all the workflow elements in the workflow. The custom forms have access to the elements exposed to vRealize Automation Service Broker through the VRO-Gateway service, which is a subset of what is available on vRealize Orchestrator.
    • Custom forms can bind workflow inputs to action parameters used to set values in other inputs.
    • Custom forms cannot bind workflows variables to action parameters used to set values in other inputs.

Note You might have noticed VRO-Gateway service when you use WFs as a WBX (Workflow Based Extensibility) in Event Subscriptions where these WFs get triggered by this service.

Basically, It provides a gateway to VMware Realize Orchestrator (vRO) for services running on vRealize Automation. By using the gateway, consumers of the API can access a vRO instance, and initiate workflows or script actions without having to deal directly with the vRO APIs.


It is possible to work around vRealize Automation not having access to workflow variables by one of the following options :

  • Using a custom action returning the variable content.
  • Binding to an input parameter set to not visible instead of a variable.
  • Enabling custom forms and using constants.

The widgets available in vRealize Orchestrator and in vRealize Automation vary for certain types. The following table describes what is supported.

vRAvRO
Input Data TypePossible Form Display TypesAction return type for Default ValueAction return type for Value OptionsPossible Form Display TypesAction return type for Default ValueAction return type for Value Options
StringText, TextField, Text AreaDropdown, Radio GroupStringArray of StringPropertiesArray of Properties (value, label)Text, TextFIeld, Text AreaDropdown, Radio GroupStringArray of String
Array of StringArray Input (vRA 8.2), Dual List, Multi SelectArray of StringPropertiesArray of PropertiesArray of StringDatagrid, Multi Value PickerArray of StringPropertiesArray of PropertiesArray of String
IntegerIntegerNumberArray of NumberNot supportedNot supportedNot supported
Array of IntegerArray Input (vRA 8.2), Datagrid (vRA 8.1)Array of NumberArray of NumberNot supportedNot supportedNot supported
NumberDecimalNumberArray of NumberDecimalNumberArray of Number
Array/NumberArray Input (vRA 8.2), Datagrid (vRA 8.1)Array of NumberArray of NumberDatagridArray of NumberArray of Number
BooleanCheckboxBooleanNot supportedCheckboxBoolean
DateDate TimeDateArray of DateDate TimeDateArray of Date
Array of DateArray Input (vRA 8.2), Datagrid (vRA 8.1)Array of DateArray of DateDatagridArray of DateArray of Date
Composite/ComplexDatagrid, Object Field (vRA 8.3)Composite, Properties, Array/Composite, Array/PropertiesArray of CompositeDatagridComposite(columns…)Array/PropertiesArray of Composite
Array of CompositeDatagrid, Multi Value PickerComposite, Properties, Array/Composite, Array/PropertiesArray of CompositeDatagrid, Multi Value PickerArray/Composite(columns…)Array/PropertiesArray of Composite
Reference / vRO SDK Object typeValue PickerSDK ObjectArray of SDK Object (vRA 8.2)Value PickerSDK ObjectArray of SDK Object
Array of ReferenceMulti Value Picker (vRA 8.3)Array of SDK ObjectArray of SDK Object (vRA 8.3)DatagridArray of SDK ObjectArray of SDK Object
Secure StringPasswordStringNot supportedPasswordStringNot supported
FileNot supportedNot supportedNot supportedFile UploadNot supportedNot supported

For use cases where the widget specified in vRealize Orchestrator is not available from Service Broker, a compatible widget is used.

Because the data being passed to and from the widget might expect different types, formats, and values in the case they are unset, the best practice to develop workflows targeting Service Broker is to:

  1. Develop the vRealize Orchestrator workflow. This can include both the initial development of the workflow or changes of inputs.
  2. Version the workflow manually.
  3. In Cloud Assembly, navigate to Infrastructure > Connections > Integrations and select your vRealize Orchestrator integration.
  4. Start the data collection for the vRealize Orchestrator integration. This step, along with versioning up your workflow, ensure that the VRO-Gateway service used by vRealize Automation has the latest version of the workflow.
  5. Import content into Service Broker. This step generates a new default custom form.
  6. In addition to the input forms designed in vRealize Orchestrator, you can, if needed, develop workflow input forms with the custom forms editor.
  7. If these forms call actions, develop or run these from the vRealize Orchestrator workflow editor.
  8. Test the inputs presentation in Service Broker.
  9. Repeat from step 5 as many times as needed.
  10. Repeat from step 1, in case workflows inputs or forms need to be changed.

Either distribute and maintain the custom forms or alternatively, design vRealize Orchestrator inputs by using the same options or actions as in the custom forms (the above step 1), and then repeat the steps 2 to 8 to validate that the process works.

Using this last option means that:

  • Running the workflow from vRealize Orchestrator can lead to the input presentation not working as expected when started in vRealize Orchestrator.
  • For some cases, you must modify the return type of the actions used for default value or value options so these values can be set from the vRealize Orchestrator workflow editor and, when the workflow is saved, revert the action return types.

Designing the form in the workflow has the following advantages:

  • Form is packaged and delivered as part of the workflow included in a package.
  • Form can be tested in vRealize Orchestrator as long as the compatible widgets are applied.
  • The form can optionally be versioned and synchronized to a Git repository with the workflow.

Designing the custom forms separately has the following advantages:

  • Being able to customize the form without changing the workflow.
  • Being able to import and export the form as a file and reusing it for different workflows.

For example, a common use case is to have a string based drop-down menu.

Returning a Properties type can be used in both the vRealize Orchestrator input form presentation and vRealize Automation custom forms presentation. With the Property type you can display a list of values in the drop-down menu. After being select by the user, these values pass an ID to the parameter (to the workflow and the other input fields that would bind to this parameter). This is very practical to list objects when there is no dedicated plug-in for them as this avoids you having to select object names and having to find object IDs by name.

Returning an array of Properties types has the same goal as returning Properties but does give control on the ordering of the element. It is done by setting for each property in the array the label and value keys. For example, it is possible to sort ascending or descending properties by label or by keys within the action.

All the workflows included in the “drop down” folder of the sample package include drop down menus created with actions that have array of Properties set as the return type.