vRealize Orchestrator provides a nice way to manage and interact with web services. I have always liked that you can add HTTP-REST API endpoints, configured with basic authentication, which doesn’t require a token to be requested each time they are used. It’s also quite useful having visibility of these endpoints in the inventory, which makes them easy to discover.
It’s also possible to create operations on these endpoints (i.e. all the get/post operations), but I am not a big fan of using these. The problem is that using them takes you that little bit further away from managing everything in code. They are also more difficult to port/migrate to other vRO instances, which is especially true if you have multiple environments for development, test, pre-prod, prod, etc. My much-preferred method is to use Actions which perform these operations instead.
I have also seen many other developers use Actions for their HTTP-REST operations, which is good. The problem, however, is that I have seen these written in so many different ways. I decided that I needed to create a consistent experience when interacting with these web services and created the HTTP Rest Client. This client is a class with associated methods, that are used to make requests against HTTP-REST web services.
You can download a package containing the HTTP-REST Client Action here.
Page Contents
HTTP REST Client
The client is an action that returns the HttpClient object. This object has several methods that can be used to execute requests against a web service. The following methods are supported:
- GET, POST, PUT, DELETE and PATCH
The Action itself requires no inputs as these are provided when instantiating the object or supplied to the methods when called.
Below is the code for the HttpClient class.
/** * Creates and returns an instance of the HttpClient object. * @author Gavin Stephens <gavin.stephens@simplygeek.co.uk> * @version 1.0.0 * @function httpClient * @returns {*} An instance of the HttpClient object. */ var logType = "Action"; var logName = "httpClient"; // This must be set to the name of the action var Logger = System.getModule("com.simplygeek.library.util").logger(logType, logName); var log = new Logger(logType, logName); var reqResponse = ""; //REST API request response /** * Defines the HttpClient object. * @class * @param {REST:RESTHost} restHost - The HTTP REST host. * @param {string} [acceptType] - The encoding format to accept. * @returns {*} An instance of the HttpClient object. */ function HttpClient(restHost, acceptType) { this.restHost = restHost; if (acceptType) { this.acceptType = acceptType; } else { this.acceptType = "application/json"; } /** * Defines the GET method. * @method * @param {string} restUri - The request uri. * @param {number[]} [expectedResponseCodes] - A list of expected response codes. * @param {Properties} [headers] - A key/value set of headers to include in the request. * @returns {*} The request response object. */ this.get = function (restUri, expectedResponseCodes, headers) { reqResponse = this._executeRequest("GET", restUri, null, null, expectedResponseCodes, headers); return reqResponse; }; /** * Defines the POST method. * @method * @param {string} restUri - The request uri. * @param {string} [content] - The request content. * @param {string} [contentType] - The encoding for content. * @param {number[]} [expectedResponseCodes] - A list of expected response codes. * @param {Properties} [headers] - A key/value set of headers to include in the request. * @returns {*} The request response object. */ this.post = function (restUri, content, contentType, expectedResponseCodes, headers) { if (!content) { content = "{}"; } reqResponse = this._executeRequest("POST", restUri, content, contentType, expectedResponseCodes, headers); return reqResponse; }; /** * Defines the PUT method. * @method * @param {string} restUri - The request uri. * @param {string} content - The request content. * @param {string} [contentType] - The encoding for content. * @param {number[]} [expectedResponseCodes] - A list of expected response codes. * @param {Properties} [headers] - A key/value set of headers to include in the request. * @returns {*} The request response object. */ this.put = function (restUri, content, contentType, expectedResponseCodes, headers) { reqResponse = this._executeRequest("PUT", restUri, content, contentType, expectedResponseCodes, headers); return reqResponse; }; /** * Defines the DELETE method. * @method * @param {string} restUri - The request uri. * @param {string} content - The request content. * @param {string} [contentType] - The encoding for content. * @param {number[]} [expectedResponseCodes] - A list of expected response codes. * @param {Properties} [headers] - A key/value set of headers to include in the request. * @returns {*} The request response object. */ this.delete = function (restUri, content, contentType, expectedResponseCodes, headers) { reqResponse = this._executeRequest("DELETE", restUri, content, contentType, expectedResponseCodes, headers); return reqResponse; }; /** * Defines the PATCH method. * @method * @param {string} restUri - The request uri. * @param {string} content - The request content. * @param {string} [contentType] - The encoding for content. * @param {number[]} [expectedResponseCodes] - A list of expected response codes. * @param {Properties} [headers] - A key/value set of headers to include in the request. * @returns {*} The request response object. */ this.patch = function (restUri, content, contentType, expectedResponseCodes, headers) { reqResponse = this._executeRequest("PATCH", restUri, content, contentType, expectedResponseCodes, headers); return reqResponse; }; /** * A private method that executes the request. * @method * @private * @param {string} restMethod - The request method. * @param {string} restUri - The request uri. * @param {string} content - The request content. * @param {string} [contentType] - The encoding for content. * @param {number[]} [expectedResponseCodes] - A list of expected response codes. * @param {Properties} [headers] - A key/value set of headers to include in the request. * @returns {*} The request response object. */ this._executeRequest = function (restMethod, restUri, content, contentType, expectedResponseCodes, headers) { var response; var maxAttempts = 5; var timeout = 10; var success = false; var statusCode; // Default to status code '200' if no expected codes have been defined. if (!expectedResponseCodes || (Array.isArray(expectedResponseCodes) && expectedResponseCodes.length < 1)) { expectedResponseCodes = [200]; } this._createRequest(restMethod, restUri, content, contentType, headers); log.debug("Executing request..."); log.debug("URL: " + this.request.fullUrl); log.debug("Method: " + restMethod); for (var i = 0; i < maxAttempts; i++) { try { response = this.request.execute(); success = true; break; } catch (e) { System.sleep(timeout * 1000); log.warn("Request failed: " + e + " retrying..."); continue; } } if (!success) { log.error("Request failed after " + maxAttempts.toString + " attempts. Aborting."); } statusCode = response.statusCode; if (expectedResponseCodes.indexOf(statusCode) > -1) { log.debug("Request executed successfully with status: " + statusCode); } else { log.error("Request failed, incorrect response code received: '" + statusCode + "' expected one of: '" + expectedResponseCodes.join(",") + "'\n" + response.contentAsString); } return response; }; /** * A private method that creates the request. * @method * @private * @param {string} restMethod - The request method. * @param {string} restUri - The request uri. * @param {string} content - The request content. * @param {string} [contentType] - The encoding for content. * @param {Properties} [headers] - A key/value set of headers to include in the request. * @returns {*} The request response object. */ this._createRequest = function (restMethod, restUri, content, contentType, headers) { var uri = encodeURI(restUri); log.debug("Creating REST request..."); if (restMethod === "GET") { this.request = this.restHost.createRequest(restMethod, uri); } else { if (!content) { this.request = this.restHost.createRequest(restMethod, uri); } else { if (!contentType) { contentType = this.acceptType; } this.request = this.restHost.createRequest(restMethod, uri, content); this.request.contentType = contentType; } } log.debug("Setting headers..."); this._setHeaders(headers); }; /** * A private method that sets the request headers. * @method * @private * @param {Properties} [headers] - A key/value set of headers to include in the request. */ this._setHeaders = function (headers) { log.debug("Adding Header: Accept: " + this.acceptType); this.request.setHeader("Accept", this.acceptType); if (headers && (headers instanceof Properties)) { for (var headerKey in headers.keys) { var headerValue = headers.get(headerKey); // eslint-disable-next-line padding-line-between-statements log.debug("Adding Header: " + headerKey + ": " + headerValue); this.request.setHeader(headerKey, headerValue); } } }; } return HttpClient;
Usage
A new instance of the HttpClient class can be created using the new keyword. The class allows two parameters to be provided:
new HttpClient(restHost, acceptType)
Parameters:
Name | Type | Description |
---|---|---|
restHost |
REST:RESTHost | The HTTP REST host. |
acceptType |
string | (optional) The encoding format to accept. |
To use the HTTP-REST Client, simply add the following lines of code into an Action:
var HttpClient = System.getModule("com.simplygeek.library.rest").httpClient(); var http = new HttpClient(restHost, acceptType);
The following methods can be used:
GET
get(restUri, expectedResponseCodes, headers) → {*}
Parameters:
Name | Type | Description |
---|---|---|
restUri |
string | The request uri. |
expectedResponseCodes |
Array/number | (optional) A list of expected response codes. |
headers |
Properties | (optional) A key/value set of headers to include in the request. |
Returns the request response object.
POST
post(restUri, content, contentType, expectedResponseCodes, headers) → {*}
Parameters:
Name | Type | Description |
---|---|---|
restUri |
string | The request uri. |
content |
string | (optional) The request content. |
contentType |
string | (optional) The encoding for content. |
expectedResponseCodes |
Array/number | (optional) A list of expected response codes. |
headers |
Properties | (optional) A key/value set of headers to include in the request. |
Returns the request response object.
PUT
put(restUri, content, contentType, expectedResponseCodes, headers) → {*}
Parameters:
Name | Type | Description |
---|---|---|
restUri |
string | The request uri. |
content |
string | The request content. |
contentType |
string | (optional) The encoding for content. |
expectedResponseCodes |
Array/number | (optional) A list of expected response codes. |
headers |
Properties | (optional) A key/value set of headers to include in the request. |
Returns the request response object.
DELETE
delete(restUri, content, contentType, expectedResponseCodes, headers) → {*}
Parameters:
Name | Type | Description |
---|---|---|
restUri |
string | The request uri. |
content |
string | The request content. |
contentType |
string | (optional) The encoding for content. |
expectedResponseCodes |
Array/number | (optional) A list of expected response codes. |
headers |
Properties | (optional) A key/value set of headers to include in the request. |
Returns the request response object.
PATCH
patch(restUri, content, contentTypeopt, expectedResponseCodesopt, headersopt) → {*}
Parameters:
Name | Type | Description |
---|---|---|
restUri |
string | The request uri. |
content |
string | The request content. |
contentType |
string | (optional) The encoding for content. |
expectedResponseCodes |
Array/number | (optional) A list of expected response codes. |
headers |
Properties | (optional) A key/value set of headers to include in the request. |
Returns the request response object.
Responses
Each method will return the RESTResponse object. I felt it would be easier to leave it to the developer to decide how to handle the response. This way, you can decide if you want the content in string format, or retrieve headers, or both. Look at my examples to see how responses are handled.
Examples
I have provided some examples which were used with some NSX API interactions to demonstrate the use of the HTTP-REST Client.
GET Example
/*global nsxRestHost*/ /** * Gets all security tags that exist on the NSX manager. * @author Gavin Stephens <gavin.stephens@simplygeek.co.uk> * @version 1.0.0 * @function getSecurityTags * @param {REST:RESTHost} nsxRestHost - The NSX Rest Host. * @returns {string} Returns the security tags in JSON format. */ function checkParams(nsxRestHost) { var inputErrors = []; var errorMessage; if (!nsxRestHost || System.getObjectType(nsxRestHost) !== "REST:RESTHost") { inputErrors.push(" - nsxRestHost missing or not of type 'REST:RESTHost'"); } if (inputErrors.length > 0) { errorMessage = "Mandatory parameters not satisfied:\n" + inputErrors.join("\n"); log.error(errorMessage); } } var logType = "Action"; var logName = "getSecurityTags"; // This must be set to the name of the action var Logger = System.getModule("com.simplygeek.library.util").logger(logType, logName); var log = new Logger(logType, logName); var tagList = []; var numTagsFound = 0; var tags = ""; // API variables var HttpClient = System.getModule("com.simplygeek.library.rest").httpClient(); var acceptType = "application/json"; var restUrl = "/2.0/services/securitytags/tag"; var response; try { checkParams(nsxRestHost); log.debug("Getting NSX security tags."); var http = new HttpClient(nsxRestHost, acceptType); response = http.get(restUrl).contentAsString; tagList = JSON.parse(response).securityTagDtoList; numTagsFound = tagList.length; tags = JSON.stringify(tagList); log.debug("Found " + numTagsFound.toString() + " NSX security tags."); } catch (e) { log.error("Action failed to get NSX security tags.",e); } return tags;
POST Example
/*global nsxRestHost, tagName, tagDescription*/ /** * Creates an NSX security tag with the specified name and description. * @author Gavin Stephens <gavin.stephens@simplygeek.co.uk> * @version 1.0.0 * @function createNsxSecurityTag * @param {REST:RESTHost} nsxRestHost - The NSX Rest Host. * @param {string} tagName - The NSX security tag name. * @param {string} tagDescription - The NSX security tag description. */ function checkParams(nsxRestHost, tagName, tagDescription) { var inputErrors = []; var errorMessage; if (!nsxRestHost || System.getObjectType(nsxRestHost) !== "REST:RESTHost") { inputErrors.push(" - nsxRestHost missing or not of type 'REST:RESTHost'"); } if (!tagName || typeof tagName !== "string") { inputErrors.push(" - tagName missing or not of type 'string'"); } if (!tagDescription || typeof tagDescription !== "string") { inputErrors.push(" - tagDescription missing or not of type 'string'"); } if (inputErrors.length > 0) { errorMessage = "Mandatory parameters not satisfied:\n" + inputErrors.join("\n"); log.error(errorMessage); } } var logType = "Action"; var logName = "createNsxSecurityTag"; // This must be set to the name of the action var Logger = System.getModule("com.simplygeek.library.util").logger(); var log = new Logger(logType, logName); var tagConfigObj = {}; var tagConfig; var allSecurityTags; var tagList = []; var xTagName = ""; var tagId; var tagFound = false; // API variables var HttpClient = System.getModule("com.simplygeek.library.rest").httpClient(); var contentType = "application/json"; var acceptType = "application/json"; var restUrl = "/2.0/services/securitytags/tag"; var expectedResponseCodes = [201]; var response; try { checkParams(nsxRestHost, tagName, tagDescription); log.log("Creating NSX security tag '" + tagName + "'"); var http = new HttpClient(nsxRestHost, acceptType); allSecurityTags = System.getModule("com.simplygeek.library.nsx.rest.securitytags").getSecurityTags(nsxRestHost); tagList = JSON.parse(allSecurityTags); for (var i = 0; i < tagList.length; i++) { xTagName = tagList[i].name; if (tagName === xTagName) { tagId = tagList[i].objectId; log.log("An existing NSX security tag '" + tagName + "' with Id '" + tagId + "' was found."); tagFound = true; break; } } if (!tagFound) { tagConfigObj.name = tagName; tagConfigObj.description = tagDescription; tagConfig = JSON.stringify(tagConfigObj); response = http.post(restUrl,tagConfig,contentType,expectedResponseCodes); if (response) { log.log("Successfully created NSX security tag '" + tagName + "'"); } } } catch (e) { log.error("Action failed to create NSX security tag.",e); }
PUT Example
/*global nsxRestHost, vcVm, tagName*/ /** * Attaches the specified nsx security tag to a vCenter virtual machine. * @author Gavin Stephens <gavin.stephens@simplygeek.co.uk> * @version 1.0.0 * @function attachSecurityTagToVcVm * @param {REST:RESTHost} nsxRestHost - The NSX Rest Host. * @param {VC:VirtualMachine} vcVm - The vCenter Virtual Machine. * @param {string} tagName - The NSX security tag name. */ function checkParams(nsxRestHost, vcVm, tagName) { var inputErrors = []; var errorMessage; if (!nsxRestHost || System.getObjectType(nsxRestHost) !== "REST:RESTHost") { inputErrors.push(" - nsxRestHost missing or not of type 'REST:RESTHost'"); } if (!vcVm || System.getObjectType(vcVm) !== "VC:VirtualMachine") { inputErrors.push(" - vcVm missing or not of type 'VC:VirtualMachine'"); } if (!tagName || typeof tagName !== "string") { inputErrors.push(" - tagName missing or not of type 'string'"); } if (inputErrors.length > 0) { errorMessage = "Mandatory parameters not satisfied:\n" + inputErrors.join("\n"); log.error(errorMessage); } } var logType = "Action"; var logName = "attachSecurityTagToVcVm"; // This must be set to the name of the action var Logger = System.getModule("com.simplygeek.library.util").logger(); var log = new Logger(logType, logName); var vmName = ""; var vmUuid = ""; var tagId = ""; // API variables var HttpClient = System.getModule("com.simplygeek.library.rest").httpClient(); var acceptType = "application/json"; var restUrl = "/2.0/services/securitytags/tag/"; var response; try { checkParams(nsxRestHost, vcVm, tagName); var http = new HttpClient(nsxRestHost, acceptType); vmName = vcVm.name; vmUuid = vcVm.config.instanceUuid; log.log("Attaching NSX security tag '" + tagName + "' to VM: '" + vmName + "'"); tagId = System.getModule("com.simplygeek.library.nsx.rest.securitytags").getSecurityTagIdByName(nsxRestHost, tagName); restUrl += tagId + "/vm/" + vmUuid; response = http.put(restUrl); if (response) { log.log("Successfully attached NSX security tag '" + tagName + "' to VM: '" + vmName + "'"); } } catch (e) { log.error("Action failed to attach NSX security tag to vm.",e); }
DELETE Example
/*global nsxRestHost, tagName*/ /** * Deletes an NSX security tag by its name. * @author Gavin Stephens <gavin.stephens@simplygeek.co.uk> * @version 1.0.0 * @function deleteSecurityTag * @param {REST:RESTHost} nsxRestHost - The NSX Rest Host. * @param {string} tagName - The NSX security tag name. */ function checkParams(nsxRestHost, tagName) { var inputErrors = []; var errorMessage; if (!nsxRestHost || System.getObjectType(nsxRestHost) !== "REST:RESTHost") { inputErrors.push(" - nsxRestHost missing or not of type 'REST:RESTHost'"); } if (!tagName || typeof tagName !== "string") { inputErrors.push(" - tagName missing or not of type 'string'"); } if (inputErrors.length > 0) { errorMessage = "Mandatory parameters not satisfied:\n" + inputErrors.join("\n"); log.error(errorMessage); } } var logType = "Action"; var logName = "deleteSecurityTag"; // This must be set to the name of the action var Logger = System.getModule("com.simplygeek.library.util").logger(); var log = new Logger(logType, logName); var tagId = ""; // API variables var HttpClient = System.getModule("com.simplygeek.library.rest").httpClient(); var acceptType = "application/json"; var restUrl = "/2.0/services/securitytags/tag/"; var response; try { checkParams(nsxRestHost, tagName); log.log("Deleting NSX security tag '" + tagName + "'"); var http = new HttpClient(nsxRestHost, acceptType); tagId = System.getModule("com.simplygeek.library.nsx.rest.securitytags").getSecurityTagIdByName(nsxRestHost, tagName); restUrl += tagId; response = http.delete(restUrl); if (response) { log.log("Successfully deleted NSX security tag '" + tagName + "'"); } } catch (e) { log.error("Action failed to delete NSX security tag.",e); }
I hope this has been useful. If you discover bugs with any of my code, require some help or simply need an ad-hoc solution, then please drop me a message via the Drift app.