Automation Blog

from Stefan Schnell

Actions are standard or individual functions which can be used in workflows. VCF Automation provides libraries of predefined actions. Actions are important elements for structuring and reusing code. That is why their use is very important, also in other runtime environments e.g. like Python. This post presents a class that simplifies the handling and calling of actions in Python.

Action Class for Python Runtime Environment

This class contains two external methods to call actions, getActionId and invoke. The invoke method executes an action, by specifying the module of the action, its name and parameters, as well as the orchestrator URL and token. This class also contains four methods that allows the creation, reading, updating and deletion (CRUD) of actions.

"""
@author Stefan Schnell <mail@stefan-schnell.de>
@license MIT
@version 1.1
"""

import json
import time

from util.http import Http

class Action:
    """ Handles VCF Automation actions
    """

    def __init__(self):
        self.__http = Http()

    def create(
        self,
        vcoUrl: str,
        bearerToken: str,
        actionModule: str,
        actionName: str,
        description: str = "",
        version: str = "1.0.0",
        outputType: str = "Properties",
        inputParameters: list = [],
        code: str = "",
        runtime: str | None = None
    ) -> dict | None:
        """ Creates an action

        @param {string} vcoUrl - URL of Aria orchestrator
        @param {string} bearerToken
        @param {string} actionModule - Module of the action
        @param {string} actionName - Name of the action
        @param {string} description - Description of the action
        @param {string} version - Version of the action
        @param {string} outputType - Output type of the action
        @param {list} inputParameters - Input parameters of the action
        @param {string} code - Script code ot the action
        @param {string or None} runtime - Runtime environment
        @returns {dictionary or None}
        """

        returnValue: str | None = None

        try:

            url: str = f"{vcoUrl}/api/actions?uniqueName=false"

            body: dict = {
                "module": actionModule,
                "name": actionName,
                "description": description,
                "version": version,
                "output-type": outputType,
                "input-parameters": inputParameters,
                "script": code,
                "runtime": runtime
            }

            returnValue = self.__http.request(
                url = url,
                bearerToken = bearerToken,
                method = "POST",
                body = body
            )

        except Exception as err:
            raise ValueError(
                f"An error occurred at create action - {err}"
            ) from err

        return returnValue

    def read(
        self,
        vcoUrl: str,
        bearerToken: str,
        actionModule: str,
        actionName: str
    ) -> dict | None:
        """ Reads an action

        @param {string} vcoUrl - URL of Aria orchestrator
        @param {string} bearerToken
        @param {string} actionModule - Module of the action
        @param {string} actionName - Name of the action
        @returns {dictionary or None}
        """

        returnValue: str | None = None

        try:

            actionID: str = self.getActionId(
                vcoUrl,
                bearerToken,
                actionModule,
                actionName
            )

            if actionID:

                url: str = f"{vcoUrl}/api/actions/{actionID}"

                returnValue = self.__http.request(
                    url = url,
                    bearerToken = bearerToken
                )

        except Exception as err:
            raise ValueError(
                f"An error occurred at read action - {err}"
            ) from err

        return returnValue

    def update(
        self,
        vcoUrl: str,
        bearerToken: str,
        actionModule: str,
        actionName: str,
        itemsToChange: list | None = None,
        updateReferences: bool = True
    ) -> None:
        """ Upadates an action

        @param {string} vcoUrl - URL of Aria orchestrator
        @param {string} bearerToken
        @param {string} actionModule - Module of the action
        @param {string} actionName - Name of the action
        @param {list.<dictionary> or None} itemsToChange - Entries to modify
        @param {bool} updateReferences - Flag to update action references
        @returns {None}
        """

        try:

            actionID: str = self.getActionId(
                vcoUrl,
                bearerToken,
                actionModule,
                actionName
            )

            if not actionID:
                raise ValueError("Can not find action")

            url: str = f"{vcoUrl}/api/actions/{actionID}"

            if updateReferences:
                url += "?updateReferences=true"
            else:
                url += "?updateReferences=false"

            body: dict = self.read(
                vcoUrl = vcoUrl,
                bearerToken = bearerToken,
                actionModule = actionModule,
                actionName = actionName
            )

            if itemsToChange is None:
                itemsToChange = []

            for item in itemsToChange:
                for key, value in item.items():
                    body[key] = value

            self.__http.request(
                url = url,
                bearerToken = bearerToken,
                method = "PUT",
                body = body
            )

        except Exception as err:
            raise ValueError(
                f"An error occurred at update action - {err}"
            ) from err

    def delete(
        self,
        vcoUrl: str,
        bearerToken: str,
        actionModule: str,
        actionName: str,
        force: bool = False
    ) -> None:
        """ Deletes an action

        @param {string} vcoUrl - URL of Aria orchestrator
        @param {string} bearerToken
        @param {string} actionModule - Module of the action
        @param {string} actionName - Name of the action
        @param {bool} force - Flag to delete action in any case
        @returns {None}
        """

        try:

            actionID: str = self.getActionId(
                vcoUrl,
                bearerToken,
                actionModule,
                actionName
            )

            if not actionID:
                raise ValueError("Can not find action")

            url: str = f"{vcoUrl}/api/actions/{actionID}"

            if force:
                url += "?force=true"
            else:
                url += "?force=false"

            self.__http.request(
                url = url,
                bearerToken = bearerToken,
                method = "DELETE"
            )

        except Exception as err:
            raise ValueError(
                f"An error occurred at delete action - {err}"
            ) from err

    def getActionId(
        self,
        vcoUrl: str,
        bearerToken: str,
        actionModule: str,
        actionName: str
    ) -> str:
        """ Gets the ID of an action

        @param {string} vcoUrl - URL of Aria orchestrator
        @param {string} bearerToken
        @param {string} actionModule - Module of the action
        @param {string} actionName - Name of the action
        @returns {string}
        """

        returnValue: str = ""

        try:

            actions = self.__http.request(
                url = f"{vcoUrl}/api/actions",
                bearerToken = bearerToken
            )

            if not actions or "link" not in actions:
                return returnValue

            found: bool = False
            for action in actions["link"]:
                for attribute in action["attributes"]:
                    if attribute["name"] == "fqn" and \
                    attribute["value"] == actionModule + "/" + actionName:
                        for attribute in action["attributes"]:
                            if attribute["name"] == "id":
                                returnValue = attribute["value"]
                                found = True
                if found:
                    break

        except Exception as err:
            raise ValueError(
                f"An error occurred at get Action ID - {err}"
            ) from err

        return returnValue

    def __executeAction(
        self,
        vcoUrl: str,
        bearerToken: str,
        actionId: str,
        parameters: dict = {}
    ) -> dict:
        """ Executes an action

        @param {string} vcoUrl - URL of Aria orchestrator
        @param {string} bearerToken
        @param {string} actionId - ID of the action
        @param {dictionary} parameters - Parameters of the action
        @returns {dictionary}
        """

        returnValue: dict = {}

        try:

            returnValue = self.__http.request(
                url = f"{vcoUrl}/api/actions/{actionId}/executions",
                bearerToken = bearerToken,
                method = "POST",
                body = parameters
            )

        except Exception as err:
            raise ValueError(
                f"An error occurred at action executing - {err}"
            ) from err

        return returnValue

    def __getActionLog(
        self,
        vcoUrl: str,
        bearerToken: str,
        executionId: str
    ) -> dict:
        """ Delivers the action log

        @param {string} vcoUrl - URL of Aria orchestrator
        @param {string} bearerToken
        @param {string} executionId - ID of the execution, from executeAction
        @returns {dictionary}
        """
        returnValue: dict = {}

        try:

            returnValue = self.__http.request(
                url = (
                    f"{vcoUrl}/api/actions/{executionId}/"
                    f"logs?maxResult=2147483647"
                ),
                bearerToken = bearerToken
            )

        except Exception as err:
            raise ValueError(
                f"An error occurred at get action log - {err}"
            ) from err

        return returnValue

    def invoke(
        self,
        vcoUrl: str,
        bearerToken: str,
        actionModule: str,
        actionName: str,
        parameters: list
    ) -> dict:
        """ Calls an action

        @param {string} vcoUrl - URL of Aria orchestrator
        @param {string} bearerToken
        @param {string} actionModule - Module of the action
        @param {string} actionName - Name of the action
        @param {list} parameters - Parameters of the action
        @returns {dictionary}
        """

        returnValue: dict = {}

        try:

            actionID: str = self.getActionId(
                vcoUrl,
                bearerToken,
                actionModule,
                actionName
            )

            if not actionID:
                raise ValueError("Can not find action")
            _parameters: dict = {
                "async-execution": False,
                "parameters": parameters
            }

            executionResult: dict = self.__executeAction(
                vcoUrl,
                bearerToken,
                actionID,
                _parameters
            )

            returnValue["executionResult"] = executionResult

            if not executionResult or "execution-id" not in executionResult:
                raise ValueError(
                    f"Execution failed or returned invalid response: "
                    f"{executionResult}"
                )

            executionId: str = executionResult["execution-id"]

            time.sleep(2.5)

            actionLog: dict = self.__getActionLog(
                vcoUrl,
                bearerToken,
                executionId
            )

            returnValue["actionLog"] = actionLog

        except Exception as err:
            raise ValueError(
                f"An error occurred at call action - {err}"
            ) from err

        return returnValue

    def getUsages(
        self,
        vcoUrl: str,
        bearerToken: str,
        actionModule: str,
        actionName: str
    ) -> dict:
        """ Detects the usages of the action

        @param {string} vcoUrl - URL of Aria orchestrator
        @param {string} bearerToken
        @param {string} actionModule - Module of the action
        @param {string} actionName - Name of the action
        @returns {dictionary}
        """

        returnValue = None

        try:

            actionID = self.getActionId(
                vcoUrl,
                bearerToken,
                actionModule,
                actionName
            )

            if not actionID:
                raise ValueError("Can not find action")

            returnValue = self.__http.request(
                url = f"{vcoUrl}/api/actions/{actionID}/usages",
                bearerToken = bearerToken
            )

        except Exception as err:
            raise ValueError(
                f"An error occurred at usages - {err}"
            ) from err

        return returnValue

    def getDependencies(
        self,
        vcoUrl: str,
        bearerToken: str,
        actionModule: str,
        actionName: str
    ) -> dict:
        """ Detects the dependencies of the action

        @param {string} vcoUrl - URL of Aria orchestrator
        @param {string} bearerToken
        @param {string} actionModule - Module of the action
        @param {string} actionName - Name of the action
        @returns {dictionary}
        """

        returnValue = None

        try:

            actionID = self.getActionId(
                vcoUrl,
                bearerToken,
                actionModule,
                actionName
            )

            if not actionID:
                raise ValueError("Can not find action")

            returnValue = self.__http.request(
                url = f"{vcoUrl}/api/actions/{actionID}/dependencies",
                bearerToken = bearerToken
            )

        except Exception as err:
            raise ValueError(
                f"An error occurred at dependencies - {err}"
            ) from err

        return returnValue

Conclusion

This Python class greatly simplifies the handling and invoking of actions and it can be used as an integration basis in VCF Automation. It can also be used in other environments that support Python.

References


Addendum

The following source code shows example calls to all methods of the Action class.

import json

from util.action import Action

def handler(context: dict, inputs: dict) -> dict:

    action = Action()

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

    print(
        action.getUsages(
            vcoUrl = vcoUrl,
            bearerToken = bearerToken,
            actionModule = "com.vmware.library.action",
            actionName = "getAllActions"
        )
    )

    print(
        action.getDependencies(
            vcoUrl = vcoUrl,
            bearerToken = bearerToken,
            actionModule = "com.vmware.library.authorization",
            actionName = "addObjectToAuthorizationForGroup"
        )
    )

    action.create(
        vcoUrl = vcoUrl,
        bearerToken = bearerToken,
        actionModule = "de.stschnell",
        actionName = "automaticTest",
        description = "This is a test",
        version = "1.0.0",
        outputType = "string",
        inputParameters = [
            {
                "name": "inputParam",
                "type": "string",
                "description": "Name",
                "value": {
                    "string": { "value": "Stefan" }
                }
            }
        ],
        code = "return 'Hallo ' + inputParam;"
    )

    time.sleep(2.5)

    print(
        action.read(
            vcoUrl = vcoUrl,
            bearerToken = bearerToken,
            actionModule = "de.stschnell",
            actionName = "automaticTest"
        )
    )

    parameters: list = [
        {
            "name": "inputParam",
            "type": "string",
            "value": {
                "string": { "value": "Stefan" }
            }
        }
    ]

    print(
        action.invoke(
            vcoUrl = vcoUrl,
            bearerToken = bearerToken,
            actionModule = "de.stschnell",
            actionName = "automaticTest",
            parameters = parameters
        )
    )

    itemsToChange: list = [ 
        { "description": "This is a second test" },
        { "version": "2.0.0"}
    ]

    action.update(
        vcoUrl = vcoUrl,
        bearerToken = bearerToken,
        actionModule = "de.stschnell",
        actionName = "automaticTest",
        itemsToChange = itemsToChange
    )

    print(
        action.read(
            vcoUrl = vcoUrl,
            bearerToken = bearerToken,
            actionModule = "de.stschnell",
            actionName = "automaticTest"
        )
    )

    action.delete(
        vcoUrl = vcoUrl,
        bearerToken = bearerToken,
        actionModule = "de.stschnell",
        actionName = "automaticTest"
    )

    return {"status": "success"}