VCF Automation Blog

from Stefan Schnell

In this post is a way described how to get detailed information about all actions. For this an action is used that collects all the desired information and saves it in XML format.

Get Detailed Information about all Actions


First we take a look at what information are provided and where it can be found in the UI of VCF Automation. Here a look at the General and Script tab of an Action.

General Tab

In the General Tab we can find information about the module, name and the unique ID of the script. Also we have a description and a version here.

vcf automation orchestrator action general tab

Script Tab

In the Script tab we can find the script code, the return type of the script and the input parameters.

vcf automation orchestrator action script tab

XML Mapping

In XML format the same information can be mapped as follows. The chosen tag or attribute names correspond exactly to the descriptions in the UI, to avoid confusion.

<?xml version="1.0"?>
<actions>
  <action module="de.stschnell" name="testStefan">
    <id>aa347d8e-1a8d-4a9b-8737-cf5dbbba7b23</id>
    <description>This is a test</description>
    <version>0.1.0</version>
    <runtime/>
    <inputs>
      <input>
        <name>in_HelloWho</name>
        <type>string</type>
        <description>Say Hello</description>
      </input>
    </inputs>
    <returnType>string</returnType>
    <script>
System.log(&quot;Hello World from &quot; + in_HelloWho);
return &quot;Hello Stefan&quot;;
    </script>
  </action>
</actions>

Action

To get these information an action which detects all actions is used. Then the detailed information are detected in a loop, with the help of the Action object and its methods. This information are stored in the XML structure presented above and viewed in the log. The XML data can then be copied from the log to the clipboard for further use.

JavaScript

/**
 * This action creates an XML file with all details of all existing
 * actions and its script code in the log.
 *
 * @name getActions
 * @param {string} in_userName - Name of the user
 * @param {SecureString} in_password - Password of the user
 * @param {string} in_moduleFilter - Search for substring in module, optional
 * @param {string} in_nameFilter - Search for substring in name, optional
 *
 * @author Stefan Schnell <mail@stefan-schnell.de>
 * @license MIT
 * @version 1.4.1
 *
 * Checked with release 8.12.0, 8.14.1 and 8.16.2
 */

var _getActionsNS = {

  /**
   * _escapeXML
   *
   * Escapes characters in a string, which could be misinterpreted as
   * markup in XML.
   *
   * @name escapeXML
   * @param {string} strXML - XML which characters to convert
   * @returns {string} Converted XML
   */
  _escapeXML : function(strXML) {
    if (strXML) {
      return strXML.replace(/&/g, "&amp;")
                   .replace(/</g, "&lt;")
                   .replace(/>/g, "&gt;")
                   .replace(/"/g, "&quot;")
                   .replace(/'/g, "&apos;");
/*
                   .replace(/\u00e4/g, &quot;ae&quot;)
                   .replace(/\u00c4/g, &quot;Ae&quot;)
                   .replace(/\u00f6/g, &quot;oe&quot;)
                   .replace(/\u00d6/g, &quot;Oe&quot;)
                   .replace(/\u00fc/g, &quot;ue&quot;)
                   .replace(/\u00dc/g, &quot;Ue&quot;)
                   .replace(/\u00df/g, &quot;ss&quot;);
*/
    }
  },

  Main : function(userName, password, moduleFilter, nameFilter) {

    var output = "<?xml version=\"1.0\"?>";
    output += "<actions>";

    try {

      var actions = 
        System.getModule("com.vmware.library.action").getAllActions();

      // Search for substring in module
      if (moduleFilter) {
        // Reverse search in actions array to ...
        for (var i = actions.length - 1; i >= 0; i--) {
          if (actions[i].module.name.indexOf(moduleFilter) === -1) {
            // delete element if moduleFilter not found in module name
            actions.splice(i, 1);
          }
        }
      }

      // Search for substring in script name
      if (nameFilter) {
        // Reverse search in actions array to ...
        for (var i = actions.length - 1; i >= 0; i--) {
          if (actions[i].name.indexOf(nameFilter) === -1) {
            // delete element if nameFilter not found in script name
            actions.splice(i, 1);
          }
        }
      }

      actions.forEach( function(action) {

        output += "<action module=\"" + action.module.name +
          "\" name=\"" + _getActionsNS._escapeXML(action.name) + "\">";
        // General
        output += "<id>" + action.id + "</id>";
        if (action.description) {
          var actionDescription = action.description.replace(/[\r\n]/gm, '');
          output += "<description>" +
            _getActionsNS._escapeXML(actionDescription) + "</description>";
        } else {
          output += "<description/>";
        }
        output += "<version>" + action.version + "</version>";

        // Runtime if available
        var runtime = System.getModule("de.stschnell").getActionInformation(
          userName,
          password,
          action.module.name,
          action.name
        ).runtime;
        if (runtime) {
          output += "<runtime>" + runtime + "</runtime>";
        }

        // Script > Properties > Inputs
        var parameters = action.parameters;
        output += "<inputs>";
        parameters.forEach( function(parameter) {
          output += "<input>";
          output += "<name>" +
            _getActionsNS._escapeXML(parameter.name) + "</name>";
          output += "<type>" + parameter.type + "</type>";
          if (parameter.description) {
            output += "<description>" +
              _getActionsNS._escapeXML(parameter.description) + 
              "</description>";
          } else {
            output += "<description/>";
          }
          output += "</input>";
        });
        output += "</inputs>";

        // Script > Properties > Return type
        output += "<returnType>" + action.returnType +
          "</returnType>";

        // Script > Code
        output += "<script>";
        if (action.script) {
          output += _getActionsNS._escapeXML(action.script);
        }
        output += "</script>";

        output += "</action>";

      });

      output += "</actions>";

    } catch(e) {
      System.log(e);
      System.log(e.stack);
    } finally {
      System.log(output);
    }

  }

}

// Main
if (
  String(in_userName).trim() !== "" &&
  String(in_password).trim() !== ""
) {
  _getActionsNS.Main(
    in_userName,
    in_password,
    in_moduleFilter,
    in_nameFilter
  );
} else {
  throw new Error(
    "in_userName or in_password argument can not be null"
  );
}

Python

"""
This action creates an XML file with all details of all existing actions
and its script code in the log.

@name getActions
@param {string} in_moduleFilter - Search for substring in module, optional
@param {string} in_nameFilter - Search for substring in name, optional
@returns {Properties}

@author Stefan Schnell <mail@stefan-schnell.de>
@license MIT
@version 2.0.0
@runtime python:3.10
@memoryLimit 256000000

Checked with release 8.17.0
"""

import json
import ssl
import urllib.request

def escapeXML(strXML):
    """
    Escapes characters in a string, which could be misinterpreted as
    markup in XML.

    @name escapeXML
    @param {string} strXML - XML which characters to convert
    @returns {string} Converted XML
    """
    if strXML:
        returnValue = strXML.replace("&", "&amp;")
        returnValue = returnValue.replace("<", "&lt;")
        returnValue = returnValue.replace(">", "&gt;")
        returnValue = returnValue.replace("\"", "&quot;")
        returnValue = returnValue.replace("'", "&apos;")
        return returnValue
    else:
        return ""

def getAllActions(vcoUrl, bearerToken):
    """
    Get all actions.

    @name getAllActions
    @param {string} vcoUrl
    @param {string} bearerToken
    @returns {dictionary}
    """

    returnValue = {}

    try:

        requestActions = urllib.request.Request(
            url = vcoUrl + "/api/actions"
        )
        requestActions.add_header(
            "Authorization", "Bearer " + bearerToken
        )
        requestActions.add_header(
            "Content-Type", "application/json"
        )

        responseActions = urllib.request.urlopen(
            requestActions,
            context = ssl._create_unverified_context()
        )

        if responseActions.getcode() == 200:
            returnValue = json.loads(responseActions.read())

    except Exception as err:
        raise Exception("An error occurred at detecting all actions") \
            from err

    return returnValue

def getActionDetails(actionHref, bearerToken):
    """
    Gets the details of the given action.

    @name getActionDetails
    @param {string} actionHref
    @param {string} bearerToken
    @returns {dictionary}
    """

    returnValue = {}

    try:

        requestAction = urllib.request.Request(
            url = actionHref
        )
        requestAction.add_header(
            "Authorization", "Bearer " + bearerToken
        )
        requestAction.add_header(
            "Content-Type", "application/json"
        )

        responseAction = urllib.request.urlopen(
            requestAction,
            context = ssl._create_unverified_context()
        )

        if responseAction.getcode() == 200:
            returnValue = json.loads(responseAction.read())

    except Exception as err:
        raise Exception("An error occurred at detecting action details") \
            from err

    return returnValue

def handler(context, inputs):
    """
    Standard VCF Automation handler function.
    """

    output = "<?xml version=\"1.0\"?>"
    output += "<actions>"

    try:

        vcoUrl = context["vcoUrl"]
        bearerToken = context['getToken']()

        actions = getAllActions(
            vcoUrl,
            bearerToken
        )
        if not actions:
            raise ValueError("No actions were detected")

        # Sort actions by module and name
        actionList = []

        for action in actions["link"]:

            fqn = ""

            for attribute in actions["attributes"]:
                if attribute["name"] == "fqn":
                    fqn = attribute["value"]

            if fqn != "":
                actionList.append(
                    {"href": action["href"], "fqn": fqn}
                )

        actionList.sort(key = lambda x: x["fqn"])

        # Filter for substring in module
        if inputs["in_moduleFilter"]:
            actionList = [
                x for x in actionList \
                if inputs["in_moduleFilter"].lower() in x["fqn"].lower()
            ]

        # Filter for substring in script name
        if inputs["in_nameFilter"]:
            actionList = [
                x for x in actionList \
                if inputs["in_nameFilter"].lower() in x["fqn"].lower()
            ]

        # Write action data
        for action in actionList:

            actionDetails = getActionDetails(
                action["href"],
                bearerToken
            )
            if not actionDetails:
                raise Exception("No action details were detected")

            output += "<action module=\""
            output += actionDetails["module"]
            output += "\" name=\""
            output += escapeXML(actionDetails["name"])
            output += "\">"

            # General
            output += "<id>"
            output += actionDetails["id"]
            output += "</id>"

            if "description" in actionDetails:
                actionDescription = actionDetails["description"]
                actionDescription = actionDescription.replace("\r", "")
                actionDescription = actionDescription.replace("\n", "")
                output += "<description>"
                output += escapeXML(actionDescription)
                output += "</description>"
            else:
                output += "<description/>"

            output += "<version>"
            output += actionDetails["version"]
            output += "</version>"

            if "runtime" in actionDetails:
                output += "<runtime>"
                output += actionDetails["runtime"]
                output += "</runtime>"
            else:
                output += "<runtime/>"

            # Script > Properties > Inputs
            if "input-parameters" in actionDetails:
                output += "<inputs>"
                for inputParameter in actionDetails["input-parameters"]:
                    output += "<input>"
                    output += "<name>"
                    output += inputParameter["name"]
                    output += "</name>"
                    output += "<type>"
                    output += inputParameter["type"]
                    output += "</type>"
                    output += "<description>"
                    output += escapeXML(inputParameter["description"])
                    output += "</description>"
                    output += "</input>"
                output += "</inputs>"
            else:
                output += "<inputs/>"

            # Script > Properties > Return type
            output += "<returnType>"
            output += actionDetails["output-type"]
            output += "</returnType>"

            # Script > Code
            if "script" in actionDetails:
                output += "<script>"
                output += escapeXML(actionDetails["script"])
                output += "</script>"
            else:
                output += "<script/>"

            output += "</action>"

        output += "</actions>"

        outputs = {
            "status": "done",
            "result": output
        }

    except Exception as err:

        outputs = {
            "status": "incomplete",
            "error": repr(err),
            "result": output
        }

    return outputs

Both codes are really easy to understand. First all actions are detected and possible selections are made over the inputs, by module or script names. Now, action by action, the details are detected, with certain characters having to be converted to conform to XML conventions. If this action is now executed, we get an XML output in the log with the desired information.

Conclusion

The generated XML information can be used for analysis purposes, e.g. via XPath. Here, for example, would be the finding of code duplicates or deviations from specifications. The advantage of fully local provision of these XML data brings independence and performance improvements.