Upload a vRO package using vRO Node.js action [CB10108]

  1. Thought Process
  2. Steps
  3. The JavaScript Code that didn’t work
    1. Choice 1 Using Simple REST operations
    2. Choice 2 Generating Multi-Part Content line by line

Thought Process

Recently, I had a requirement to upload a vRO package from one vRO to other. I was very sure that vRO offers the REST APIs to do so and it just the matter of writing a simple JavaScript code to fetch the package, save it in local file system and upload it to other vRO. Clearly, I was not thinking straight. I immediately realized that vRO doesn’t work really well with Multi-Part MIME Type which is what the /packages/ API is using.

I was struggling through it when I found a tweet by Joerg Lew from 2017 where he is uploading a workflow using same Multi-Part MIME type. From what I see in the replies, that code worked. Now I had to make it work with a package. After many attempts and modifications, I failed badly. You can check what I did here.

That was a lot of efforts and I dropped that idea. But I started wondering maybe I can find my answer in Node.js or Python and of course there it is. One thing constantly biting me was whether Polyglot Scripts can access the local filesystem just as JavaScript and clearly, they don’t. Another setback.

In my last attempt, I created a node.js script which was working fine on my local machine but I had to make it work inside vRO. Then, I thought what if I use vRO Polyglot bundles as a local filesystem. It kinda worked for me. However, I had to do a trade-off. The filesystem inside these bundles are read-only. Updating anything from inside would not possible but I can read anything I want. That’s exactly what I did. I created a package in which I kept everything I need on my remote vRO, put it inside the bundle, and uploaded it to vRO. BINGO! After many trails, I managed to somehow fulfill my original requirement which was to upload a package to vRO from vRO.

Steps

  • Here is the Nodejs script I used in the bundle. Download the bundle “uploadPackageToVro.zip” from here.
/**
 * @description Uploads a .package file to vRO
 *
 * @param {string} vroFqdn 
 * @param {string} username 
 * @param {string} password 
 * @param {string} packageNameWithExtension packagename + ".package"
 *
 * @outputType void
 *
 */
exports.handler = (context, inputs, callback) => {
	const process = require('process');
	const request = require('request');
	const fs = require('fs');
	process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0; //throws a warning in vRO log, ignore it.
	console.log('Inputs were ' + JSON.stringify(inputs));
	var options = {
	  'method': 'POST',
	  'url': 'https://'+ inputs.vroFqdn +'/vco/api/packages',
	  'auth': {
			'user': inputs.username,
			'password': inputs.password
		},
	  'headers': {
                // Uncomment this and comment out the auth attribute above
                //'Authorization': 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', // for vRO Basic Authentication
                //'Authorization: 'Bearer AAAAAAAAAAAAAAAAAAAAAMLheAAAAAAA0%2BuSeid%2BULvsea4JtiGRiSDSJSI%3DEUifiRBkKG5E2XzMDjRfl76ZC9Ub0wnz4XsNiRVBChTYbJcE3F', // for vRA Access Token Authentication
		'Accept': 'application/xml'
	  },
	  formData: {
		'file': {
		  'value': fs.createReadStream(inputs.packageNameWithExtension),
		  'options': {
			'filename': inputs.packageNameWithExtension,
			'contentType': null
		  }
		}
	  }
	};
	request(options, function (error, response) {
	  if (error) throw new Error(error);
	  console.log(response.body);
	});
	callback(undefined, {status: "done"});
}
  • Extract the zip and replace the package “com.mayank.emptyPackage” with your custom package and rezip it either using 7-zip or Windows Compressor.
  • Create a new action in vRO, Select the Runtime, Type, Entry handler as shown below and Click Import to import the bundle.
  • Add Inputs as shown here as well as set the memory limits and timeout accordingly.
  • Run the action and provide input values. packageName should match the package that you have bundled in the zip.
  • Once the action is completed, you should see the package in other vRO.

The JavaScript Code that didn’t work

Below are my initial efforts where I spent a lot of time but didn’t work for me. I am still hopeful though that this code can be improved and should work.

Choice 1 Using Simple REST operations

var basicvROUsername = "username@domain";
var basicvROPassword = "pa$$w0rd!";
var vROUrl = "https://"+getvROName()+"/vco/api"; 
var packageName = "com.mayank.emptyPackage";

//Save package to a file in local filesystem
var vroHost = RESTHostManager.createHost("vROHost");
vroHost.url = vROUrl;
var host = RESTHostManager.createTransientHostFrom(vroHost);
host.authentication = RESTAuthenticationManager.createAuthentication('Basic', ['Shared Session', basicvROUsername, basicvROPassword]);
//System.log(host.authentication);
var url = "/content/packages/" + packageName + "?allowedOperations=vef&exportConfigSecureStringAttributeValues=false&exportConfigurationAttributeValues=true&exportExtensionData=false&exportGlobalTags=true&exportVersionHistory=true";
// vco/api/content/packages/com.mayank.emptyPackage
//System.debug(url);
var request = host.createRequest("GET", url, null);
request.setHeader("Accept-Encoding","gzip, deflate")
request.setHeader("Accept", "application/zip")
request.setHeader("Content-Type", "application/zip;charset=utf-8")
var response = request.execute();
//System.log(response);

if (response.statusCode != 200) { // 200 - HTTP status OK
    System.warn("Request failed with status code " + response.statusCode);
    if(response.statusCode == 401)
        System.warn("User is not authorized. Cannot Proceed!");
} else {
    System.log(response.contentAsString);
    System.log(" >>> Time to save the content as a file");
    var fileDir = System.getTempDirectory();
    var filePath  = fileDir + "/"+packageName +".package";
    System.log(filePath);
    var writer = new FileWriter(filePath);
    writer.open();
    writer.write(response.contentAsString);
    writer.close();

}

//Upload .package file to vRO REST Host
var vroHost2 = RESTHostManager.createHost("vROHost2");
vroHost2.url = vROUrl;
var host2 = RESTHostManager.createTransientHostFrom(vroHost2);
host2.authentication = RESTAuthenticationManager.createAuthentication('Basic', ['Shared Session', basicvROUsername, basicvROPassword]);
var url = "/content/packages";
var myFileReader = new FileReader(filePath);
myFileReader.open();
var fileContent = myFileReader.readAll(); 
var request = host2.createRequest("POST", url, fileContent);
request.setHeader("Accept", "application/json");
request.setHeader("Content-Type", "multipart/form-data");
var response = request.execute();

System.log(response.contentAsString);
if (response.statusCode != 200) { // 200 - HTTP status OK
    System.warn("Request failed with status code " + response.statusCode);
    if(response.statusCode == 401)
        System.warn("User is not authorized. Cannot Proceed!");
} else {
 System.log("Check if package uploaded");
}

function getvROName(){ //works on 8.x
    var com = new Command(["printenv", "|", "fgrep", "JVM_OPTS"]);
    var res = com.execute(true);
    var output = com.output;
    var nodeName = output.match(/-Dvco.app.hostname=([^\s]+)/)[1];
    return nodeName;
};

Choice 2 Generating Multi-Part Content line by line

I used Postman code generator to get WebKit strings.

var boundary = System.nextUUID();
var crlf = "\r\n";
var wf_zip_path = filePath;
var packageName = "com.mayank.emptyPackage";
System.log(filePath);
System.log(rest_Host);

//Generate multipart content
// var content = "";
// content += "--" + boundary + crlf;
// content += "Content-Disposition: form-data; name=\"file\"; filename=\"com.mayank.emptyPackage\"" + crlf;
// content += "Content-Type: application/zip" + crlf + crlf;
var fr = new FileReader(wf_zip_path);
fr.open();
//content += fr.readAll() + crlf;
// content += "--" + boundary + crlf;
// // content += "Content-Disposition: form-data; name=\"categoryId\"" + crlf + crlf;
// // content += System.getObjectId(wf_folder) + crlf;
// // content += "--" + boundary + crlf;
// content += "Content-Disposition: form-data; name=\"overwrite\"" + crlf + crlf;
// content += true + crlf;
// content += "--" + boundary + "--" + crlf;
content = "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"file\"; filename=\"com.mayank.emptyPackage.package\"\r\nContent-Type: \"application/octet-stream\"\r\n\r\n" + fr.readAll() + "\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--";

var req = rest_Host.createRequest("POST", "content/packages", content);
req.contentType = "multipart/form-data; boundary=" + "----WebKitFormBoundary7MA4YWxkTrZu0gW";
req.setHeader("Accept", "application/xml");

var reponse = req.execute();

System.log("URL: " + req.fullUrl + " -> " + reponse.statusCode);
if (reponse.statusCode != 202) {
    System.warn("Error during workflow import.");
    System.log("---- Request ----");
    System.log("URL: " + req.fullUrl);
    System.log("Content:\r\n" + content);
    System.log("");
    System.log("");
    System.log("---Response---");
    System.log("Status Code: " + reponse.statusCode);
    System.log("Content: " + reponse.contentAsString);
    var headers = reponse.getAllHeaders();
    // for each(var k in headers.keys) {
    //     System.log("Header [" + k + "]:" + headers.get(k));
    // }
    throw "Error during workflow import (HTTP Code: " + reponse.statusCode + ")";
}

That’s all in this post. Hope you like it. See you on other posts. Thanks a lot.

Advertisement

Build a Notification bot with Google Chat for vRealize Orchestrator


Important To use bots in Google Chat, you must use a work or school account.


Objective

How about building a Chatbot that brings your vRealize Orchestrator services and resources right into Google Chat, letting users get vital bits of information without leaving the conversation. That would be great, right? Why Chat you may ask if you already have Gmail? I would say because Chat is more intuitive, simple, integrated and fun-to-watch😉. And it doesn’t require any SMTP configuration to be done in vRO.

Potential Use-cases

  • Notifications of successful Workflow Executions on Google Chat Team Spaces
  • Error Notifications and auto-assignments to team member using @mention
  • Daily Reports and Validations on Team Spaces
  • and so many possibilities…

Using Incoming webhooks

Incoming webhooks let you send asynchronous messages into Google Chat from applications that aren’t bots themselves. For example, you can configure vRealize Orchestrator to notify personnel on Google Chat when a particular server goes down. Incoming webhooks serve as a quick and easy way to integrate with existing workflows such as these without the overhead of writing entire bot applications. This is the path for low-effort integrations that provide information feeds or alerts. Let’s dive into the whole process of building a chatbot using webhooks that triggers via vRealize Orchestrator.

Step 1: Register the incoming webhook

  • Open Google Chat in your browser.
  • Create a new space or Go to the space to which you want to add a bot.
  • From the menu at the top of the page, select Manage webhooks.
  • Under Incoming Webhooks, name the new webhook ‘vRO Notifications’ and provide an avatar URL, click SAVE.

Tip Get the 128x128px avatar for your bot here.

Avatar URL: https://user-images.githubusercontent.com/7029361/157434341-d1cd1706-4072-4f32-abe1-2f0459e93879.png

  • Copy the URL listed next to your new webhook and keep it handy. We will use it later on.
  • Click outside the dialog box to close.

Step 2: Import the Node.js script action in vRO

  • Download the vRO package from here.
  • Import this package in your vRO.
  • Check action named sendNotificationsToGoogleChat. This action will be used to send notifications to Google Chat.
  • Run the action and pass webhookURL without https://chat.googleapis.com and a test message. (If you need to find the URL again, go to the Google Chat space, select Manage webhooks, and then copy the URL associated with your incoming webhook.)

If everything is OK, you should see this message in your space.

Step 3: Use sendNotificationsToGoogleChat action in Workflows

To utilize this action, we have to attach it in workflows for automatic notifications. Let’s take a scenario where we want to notify the team when the workflow is failed.

  • Create a new workflow Demo Workflow – Chatbot and add a scriptable task that will fail.
  • Add a Default error handler item and an scriptable task and the action sendNotificationsToGoogleChat.
  • In this new scriptable task, copy-paste this JS script with webhookPath that you have copied from your space.
webhookPath = "/v1/spaces/........";
message = "Important message for <users/all>: The workflow *";
message += workflow.rootWorkflow.name;
message += "* has been failed";
  • Add a error variable and add it to Exception Handling in all the workflow items
  • Time to run, Click Run and wait for it to fail.
  • Check your space. There should receive a message.

Tip Find this workflow in the package for testing purposes.


Limits and considerations

As you configure webhooks, take note of these limits and considerations:

  • If only select organizational units (OUs) in your domain have bots enabled, incoming webhooks created in a space will fail with the following error:
{
  "error": {
    "code": 403,
    "message": "The caller does not have permission",
    "status": "PERMISSION_DENIED"
  }
}
  • Incoming webhooks do work in direct messages, but only when all users have bots enabled.
  • Because Manage webhooks is only available from a web browser, webhooks must be set up from the Chat web app. Webhooks are not configurable from the Chat mobile app.

Text Message Formatting Guide

This section describes how to create simple messages that appear inline as if typed by a user. The is basically the message that we are passing to our vRO action.

Basic unformatted messages

Any message in Google Chat is represented as a JSON object. A basic message that specifies just a simple plaintext body could look like the following:

{  'text': 'Your workflow "Create Egde Gateway" has successfully executed'}

If posted into Google Chat (e.g. via incoming webhooks), it would look like the following:


Using formatted text in messages

Google Chat lets you add basic formatting to the message text, including bold, italic, and strikethrough. To format text, wrap it with the following symbols:

FormatSymbolExampleResult
Bold**hello*hello
Italic_ (underscore)_hello_hello
Strikethrough~~hello~hello
Monospace` (backquote)`hello`hello
Monospace block“` (three backquotes)“`
Hello
World
“`
Hello
World

For example, consider the following JSON:

{'text' : 'Your workflow *has successfully executed*!\nCustomer created with id: _A21347-VPC_'}

This places a message like this into the Chat space:


If you include a plain link URL in your message text, such as http://example.com/foo, Google Chat uses this as the link text and automatically hyperlinks that text to the specified URL.

To provide alternate link text for your link, use the following syntax:

SyntaxRendering
<https://cloudblogger.co.in|my link text>my link text

The pipe and link text are optional, so that <https://example.com/foo> and https://example.com/foo are equivalent.


Messages that @mention specific users

A bot can @mention a user in a message, providing the user ID in the following syntax:

<users/USER_ID>

This string will be substituted with an @mention of the specified user. For example, consider the following JSON:

{"text": "Hello <users/113352777658254482749> from vRO!}

This payload would produce a result like the following:

To determine the USER_ID for a user, In Google Chrome, Click

and Click Inspect on the chat page, and point out to the user,

you will get the USER-ID as data-member-id as shown here.

Important Change user/human/113352777658254482749 to users/113352777658254482749 while pasting it into your code.


Messages that @mention all users

You can use the special user ID all to @mention all the human users in a space. For example:

{    "text": "Important message for <users/all>: Code freeze starts at midnight tonight!"}

Coming Soon -> Card messages

Google Chat supports more complex UIs in the form of cards that can give much more insights of our infrastructure.

A simple example card: