Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Current »

Introduction

To demonstrate the process of Extension development, a sample Extension will be created and deployed using Visual Studio Code with the UIP Visual Studio Code Extension.  The UIP Visual Studio Code Extension relies heavily on functionality provided by the uip-cli tool to enhance the UIP development experience.  Therefore, a similar experience can be achieved with other code editors using uip-cli manually.  This alternative use scenario will be documented as well.

The functionality of this sample Extension is contrived and serves no real purpose other than to illustrate the process of developing an extension that supports and utilizes all features available. The sample Extension, however, can be a good starting point for creating more complex Extensions.

On this page, we will cover the following:

  1. Introduce the UIP Visual Studio Code Extension and uip-cli that will be used to create and configure the sample Extension.
  2. Deploy the initial Extension without any changes and review the output.
  3. Modify the sample Extension.
  4. Deploy the modified Extension to the Controller and review the output.

Note that it is assumed the UIP Visual Studio Code Extension (or uip-cliis already installed. See the previous document for installation instructions.

Step 1 - Create a New Extension Project using the UIP VS Code Extension

As mentioned in (1.2.0) Development Environment Set-Up, this tutorial will be using Visual Studio Code running in Windows and connected to a WSL (Windows Subsystem for Linux) project environment.

Initializing a new UIP project is a multi-step process.

  1. Select a folder for VS Code to open for the new UIP project.

  2. Select a starter template for the UIP project.

  3. Iterate over the template parameters.

To begin, create a project directory (for example, ~/dev/extensions/sample-task) in the WSL file system where the Universal Extension will be created.

Select project folder

Next, Use Visual Studio Code to open the sample-task folder in WSL:

  1. Open the Remote Window command pallet.
  2. Select "Open Folder in WSL...".

In the resulting dialog, navigate to the sample-task folder and click on Select Folder:

Now that VS Code is in the WSL environment, open the command pallet (ctrl+shift+p) and type "UIP".  This will show all the UIP Extension commands:

The command we are interested in now is UIP: Initialize New Project.  Note that the commands can show up in varying orders depending on use.  If the UIP: Initialize New Project is not visible, type "uip init" to further filter the displayed commands.

Select the UIP: Initialize New Project command.

If you have not yet installed uip-cli on the WSL system, you will likely be presented with two notifications after selecting UIP: Initialize New Project:

  1. Indicates that uip-cli has not been installed to the selected Python environment and gives options for installing it.  This notification comes from the UIP extension.
  2. Indicates that a Python interpreter has not been selected. This notification comes from the Python extension

The first notification (1), indicates that uip-cli is not installed in this Python environment and gives some options for installation.  Note that there is currently no Python interpreter for the project so, the system python ("python") is used as the default.  If the indicated Python environment is agreeable, choose one of the install options.  

The installation of uip-cli is performed by calling pip install. Selecting “Yes (user)” from the notification will cause pip to perform a user scheme install where uip-cli is installed to the Python user install directory for the platform. Typically ~/.local/, or %APPDATA%\Python on Windows. (See the Python documentation for site.USER_BASE for full details.).

If the default Python is not acceptable or desirable for uip-cli installation, select "No".  This will cancel the UIP: Initialize New Project operation and allow you to select a specific Python environment (from notification 2).  Once an acceptable Python environment has been selected, open the command pallet again and select UIP: Initialize New Project.
Once uip-cli has been installed to a Python environment, the notifications will no longer pop-up for UIP operations using that Python environment.

After an install option is selected, uip-cli will be installed to the target Python environment.  The results of the installation procedure can be seen in a UIP terminal window that opens within VS Code.  Additionally, the "Select UIP Project Folder" dialog will be displayed at the top of the VS Code window:

  1. UIP terminal with output from uip-cli install.
  2. The "Select UIP Project Folder" dialog.

At this point, the sample-task folder should be selected in the dialog so, go ahead and click "OK". 

Note that, this dialog can be used to navigate to another folder.  This is useful if you start the UIP: Initialize New Project process when VS Code is opened on a folder other than the target of initialization.

Selecting a starter template

After selecting "OK", the UIP project initialization process starts on the selected folder and you are presented with a list of available "starter templates" for the UIP project.  Starter templates contain boiler plate code to kick-start a project.  At the time of this writing, two starter templates are available: ue-task and ue-publisher.  For now, we will be working with ue-task (ue-publisher will be demoed later in the tutorial series), so go ahead and click on the ue-task template.

Setting template parameter values for selected starter template

Starter templates contain parameters that allow you to supply project specific values into boilerplate template code at creation time.

Once the starter template is selected, the available parameters are presented one by one with default values pre-selected. For this tutorial, all parameters are suitable so, just pressing 'Enter' to select the default for each parameter would be sufficient.  However, in the series of images below, the 'Extension Owner' parameter was modified to use a value of "SampleOwner" (step 6/8).  Notice that the dialog title will update to indicate which step you are on and how many steps are left to complete the dialog - (2/8), (3/8), (4/8), etc..  

Note that pressing “Alt + left arrow” or clicking the ← icon in the top left of the image will return to the previous step (preserving the value of the current step).

After hitting enter on the last parameter:

  1. A notification pops up to indicate the new project has been created.

  2. The new project is created

  3. The uip.yml project configuration file is opened in an editor.

  4. The generated extension.py source file is opened in an editor.

When the “UIP: Initialize New Project” command completes, a project will be initialized in the selected folder with the following structure:

|-- sample-task                # Sample project directory
    |--setup.py                # Setup file for packaging extension
    |--setup.cfg               # Configuration file for configuring setup
	|--__init__.py             
	|
	|--.uip                    # Folder used by CLI to validate directory
	|	|--config              # Configuration File Folder
	|		|--uip.yml         # Local Configuration File
	|
    |--src                     # Source directory for Extension implementation
        |--extension.py        # Extension implementation
        |--extension.yml       # Extension metadata
		|--__init__.py            
		|
		|--templates
			|--template.json   # Universal Template JSON Definition File

At this point, the project is fully initialized. 

 Click here to expand uip-cli details...

Step 1 supplemental - Create a New Extension Project using the CLI

Create a project directory (for example, ~/dev/extensions/sample-task) where the sample-task Extension will be created, and cd into the directory.

As of now, the CLI offers two starter Extension templates called ue-task and ue-publisher. To see all the available starter Extension templates, type the template-list command:

Both the starter Extensions above can be configured before they are initialized. 

To see the list of variables that can be used to configure one of the starter Extension templates, type the template name after the previous command.

Shown below are the list of variables for the ue-task starter template:

As shown in the image above, there are quite a few ways to configure the Extension. As for the sample Extension developed for this tutorial, we will work with ue-task, and only the owner_name option will be configured. Everything else will be set to the default value.

The Extension can be configured and initialized using the init command. There are three ways to configure the ue-task Extension template using the command line:

  • Using the -e option multiple times:

  • Using a JSON string 

  • Using a JSON/YAML file
    • Create a YAML file called vars.yml and define the variables as follows:

    • Use the YAML file to initialize the project:

Pick one of the three ways shown above; it does not matter which one. Note that an optional, positional argument can be provided at the end of each of the three commands that specifies the directory in which the Extension will be initialized to. If the directory does not exist, the CLI will create it. 

To verify that the Extension was configured as intended, open the extension.yml file (see directory structure below), and ensure the owner_name is SampleOwner.

This is the directory structure of the sample-1 Extension:

|-- sample-task                # Sample project directory
    |--setup.py                # Setup file for packaging extension
    |--setup.cfg               # Configuration file for configuring setup
	|--__init__.py             
	|
	|--.uip                    # Folder used by CLI to validate directory
	|	|--config              # Configuration File Folder
	|		|--uip.yml         # Local Configuration File
	|
    |--src                     # Source directory for Extension implementation
        |--extension.py        # Extension implementation
        |--extension.yml       # Extension metadata
		|--__init__.py            
		|
		|--templates
			|--template.json   # Universal Template JSON Definition File


Step 2 - Deploy the Initial Extension using the UIP VS Code Extension

Before deploying the Extension, let's take a look at the code to get a sense of the expected output. Open the ~/dev/extensions/sample-task/src/extension.py in VS Code (It should already be open in the editor following the project initialization):

extension.py
from __future__ import (print_function)
from universal_extension import UniversalExtension
from universal_extension import ExtensionResult
from universal_extension import logger


class Extension(UniversalExtension):
    """Required class that serves as the entry point for the extension
    """

    def __init__(self):
        """Initializes an instance of the 'Extension' class
        """
        # Call the base class initializer
        super(Extension, self).__init__()

    def extension_start(self, fields):
        """Required method that serves as the starting point for work performed
        for a task instance.

        Parameters
        ----------
        fields : dict
            populated with field values from the associated task instance
            launched in the Controller

        Returns
        -------
        ExtensionResult
            once the work is done, an instance of ExtensionResult must be
            returned. See the documentation for a full list of parameters that
            can be passed to the ExtensionResult class constructor
        """

        # Get the value of the 'action' field
        action = fields.get('action', [""])[0]

        if action.lower() == 'print':
            # Print to standard output...
            print("Hello STDOUT!")
        else:
            # Log to standard error...
            logger.info('Hello STDERR!')

        # Return the result with a payload containing a Hello message...
        return ExtensionResult(
            unv_output='Hello Extension!'
        )

The code presented above is ready to run without any modifications.

Note the following points of interest from the code above:

Line 17Starts the implementation of the extension_start override method. This is the entry point for normal task execution and must be implemented by all Universal tasks.
Line 36Extracts the value of the 'action' field passed down from the Controller (See the Info box below).
Line 38-40Uses the standard Python print function to print the 'Hello STDOUT!' message to standard output stream if the selected action is 'print'
Line 42-43Uses the ExtensionLogger class (exposed as part of the universal_extension file) to log the 'Hello STDERR!' message to the standard error stream if action is anything other than 'print'
Line 46Uses the unv_output parameter of the ExtensionResult class to send the 'Hello Extension!' string to the EXTENSION output payload associated with the task instance in the Controller.

What is the 'action' field?

You may have noticed the template.json file in the sample-task directory structure above. That is the Universal Template, in JSON format, that the Controller uses to render the template UI. 

One of the things template.json defines is the 'action' field of type Choice with two possible values: 'print' and 'log'. Soon, the template will be pushed out to the Controller where we will be able to see it visually. 

For now, keep in mind that the Extension can use fields defined in the Universal Template. 


Now, let's deploy the Extension. Note that Extensions are stored in the Controller as Universal Templates of type Extension.

To connect to the Controller, the CLI needs the Controller's URL along with userid and password. To avoid typing the same information multiple times, the URL and userid can be stored in the local configuration file (~/dev/extensions/sample-task/.uip/config/uip.yml), and the password can be stored as an environment variable.  Alternatively, all values can be set as environment variables and, using the UIP VS Code Extension, those values can be persisted with the project and reloaded as needed. 

Set Project specific Environment Variables

Open the VS Code command pallet (ctrl+shift+p) and type "uip set".  This will show a list of UIP commands for setting environment variables:

Select each command (one at a time) and enter appropriate values to connect to your Controller:

 

Once these values are set, they will be persisted to project specific storage and be reloaded each time the VS Code project is opened.


To clear the values from the project, open the VS Code command pallet (ctrl+shift+p) and type "uip clear" and select the appropriate command:

UIP: Clear Environment clears user ID, Password, and URL with a single command.


Using the UIP VS Code Extension, there are two primary ways to deploy the Extension: using the UIP: Push command, or the UIP: Build All command followed by the UIP: Upload All command. The latter method will be shown once below, but in subsequent deployments throughout the rest of the tutorial, the UIP: Push command will be used.

  • Open the command pallet (ctrl+shift+p) and type "uip build" and select UIP: Build All:

    The UIP: Build All command will build the full package (Extension + Universal Template). Recall that the Universal Template is called UE Task, and it does not exist in the Controller. Therefore, the entire package must be built and uploaded.
    When the command is selected, a UIP terminal window will be opened in VS Code and the uip-cli command 'uip build -a' will be executed.  The results of the command can be reviewed in the terminal:


  • Verify the full package was built by going to the ./sample-task/dist/package_build folder, and making sure UE_Task_universal_template.zip file exists:


  • Upload the full package using the UIP: Upload All command:

    Once again, the UIP: Upload All option indicates the full package needs to be uploaded.

    When the command is selected, a UIP terminal window will be opened (if not already open) and the uip-cli command 'uip upload -a' will be executed.  The results of the command can be reviewed in the terminal:


  • Verify the UE Task Universal Template and ue-task Extension are created on the Controller side:





    1) Click the "Refresh" icon, if necessary
    2) Verify the UE Task template is listed
    3) Verify the template name is UE Task
    4) Verify the ue-task Extension is attached to the template

  • Verify the UE Task template contains the "action" field:
    • If you are curious, double-click the "action" field and explore the field details. In the "Choices" tab, you will see the 'print' and 'log' choices      


 Click here to expand uip-cli specific instructions...

Step 2 Supplemental - Deploy the Initial Extension using the CLI

To connect to the Controller, the CLI needs the Controller's URL along with userid and password. To avoid typing the same information multiple times, the URL and userid will be stored in the local configuration file, and the password will be stored as an environment variable.

  • Recall that the local configuration file is located in ~/dev/extensions/sample-task/.uip/config/uip.yml. Open the file, and enter the URL and userid information near the end of the file. Shown below is a sample snippet: 
  • Go back to the command line, and create an environment variable called UIP_PASSWORD with the password as the value.
    • If using Windows, then in CMD:  set UIP_PASSWORD=<your password>
    • If using Unix/Linux, then in the terminal: export UIP_PASSWORD=<your password>


Using the CLI, there are two primary ways to deploy the Extension: using the push command, or the build command followed by the upload command. The latter method will be shown once below, but in subsequent deployments throughout the rest of the tutorial, the push command will be used.

  • cd into the sample-task directory, if not already in there. The CLI can only execute the deployment commands if it is called from the directory containing the .uip folder. In this case, it is sample-task.
  • Issue the build command as follows: 

    The -a command will build the full package (Extension + Universal Template). Recall that the Universal Template is called UE Task, and it does not exist in the Controller. Therefore, the entire package must be built and uploaded.
  • Verify the full package was built by going to the ~/dev/extensions/sample-task/dist/package_build folder, and making sure UE_Task_universal_template.zip file exists.
  • Upload the full package as follows:

    Once again, the -a option indicates the full package needs to be uploaded.
  • Verify the UE Task Universal Template and Extension are created on the Controller side using the procedure shown above the supplemental


Step 3 - Create and launch a new 'ue-task-test' task

We are now ready to create a new task to test the initial ue-task Extension. In order to make the new UE Task task type available in the Integrations section of the Controller’s Services menu, we must first refresh the Navigation Tree.

To do this, right click anywhere in the Navigation Tree and select Refresh



Click the "Services" menu and click the star/favorites icon next to "UE Task Tasks"


Clicking the star/favorite icon will make the "Integrations" dropdown visible in the Navigation tree. Click the "Integrations" dropdown and select "UE Test Tasks". This should open the "UE Test Tasks" tab


Create the task as show below

  1. Give the task a name (for example, ue-task-test as shown)
  2. Select an active agent of version 7.0.0.0 or higher for the task to run on.
  3. Select the "Log to STDERR" action

Save the task.


Now, we are ready to launch the task. This can either be done manually through the Controller or using the VS Code Extension as shown below.

Open the command palette (ctrl + shift + p), search for "UIP: Set", and select "UIP: Set Target Task":

Type the task name and press enter:

Launch the task by opening the command palette and selecting "UIP: Task Launch"



The terminal should be opened, and the uip-cli will be invoked to launch the task. You should see the task status transition from Queue to Running to Success as shown below:

Upon task completion, the task output will be retrieved and printed to the screen.

Notice that the STDOUT output section is empty since we are not printing anything using the print() function.

The EXTENSION output section contains "Hello Extension!" which is exactly what we coded in line 46 of extension.py

The STDERR output contains "Hello STDERR!" because we selected the "Log to STDERR" action in the task form. In the next step, we will change the action to "print to STDOUT" instead and observe the behavior.

 Click here to expand uip-cli specific instructions...

Step 3 Supplemental - Create and launch a new 'ue-task-test' task

The task creation process is exactly the same as above. As for launching the task, run the command shown in the GIF animation above.

Step 4 - Modify the Extension and Deploy using the UIP VS Code Extension 

So far, we have pushed the initial Extension, created a task for it, and tested the Extension code. Now, the extension.py file will be slightly modified to demonstrate the process of deploying a modified Extension using the UIP VS Code Extension.

Edit the ~/dev/extensions/sample-task/src/extension.py file to contain the following code:

extension.py
from __future__ import (print_function)
from universal_extension import UniversalExtension
from universal_extension import ExtensionResult
from universal_extension import logger


class Extension(UniversalExtension):
    """Required class that serves as the entry point for the extension
    """

    def __init__(self):
        """Initializes an instance of the 'Extension' class
        """
        # Call the base class initializer
        super(Extension, self).__init__()

    def extension_start(self, fields):
        """Required method that serves as the starting point for work performed
        for a task instance.

        Parameters
        ----------
        fields : dict
            populated with field values from the associated task instance
            launched in the Controller

        Returns
        -------
        ExtensionResult
            once the work is done, an instance of ExtensionResult must be
            returned. See the documentation for a full list of parameters that
            can be passed to the ExtensionResult class constructor
        """

        # Get the value of the 'action' field
        action = fields.get('action', [""])[0]

		# Print/Log the message 3 times
        for _ in range(3):
            if action.lower() == 'print':
                # Print to standard output...
                print("Hello STDOUT!")
            else:
                # Log to standard error...
                logger.info('Hello STDERR!')

        # Return the result with a payload containing a Hello message...
        return ExtensionResult(
            unv_output='Hello Extension!'
        )

The changes made above are:

Line 38For-loop that runs three times. In each iteration, either the STDOUT or STDERR message will be printed


Now, we need to deploy the modified Extension to the Controller. Unlike the first time where the entire package needed to be deployed, only the Extension needs to be deployed this time since that is all we changed. 

  • Open the command pallete and run the UIP: Push command:


    When the command is selected, a UIP terminal window will be opened (if not already open) and the uip-cli command 'uip push' will be executed.  The results of the command can be reviewed in the terminal:



    Instead of UIP: Build All followed by UIP: Upload All, this time UIP: Push was used as it is essentially just a combination of UIP: Build and UIP: Upload. Notice that the UIP: Push All command was not used given that we are only pushing the Extension and not the entire package. If the template.json file was modified locally, then the UIP: Push All command should be used to update the entire package on the Controller side. 
    If you are wondering how the UIP: Push command knows what Universal Template to push the ue-task extension to, open the ~/dev/extensions/sample-task/src/templates/template.json file, and there will be a field called "name" with the value "UE Task". The "UE Task" is the name of the Universal Template. Internally, the CLI reads the template.json file and extracts the name from it. 


To verify the ue-task Extension was updated successfully, run the ue-task-test task again as shown in the previous step. The output should look similar to the one shown below:


Notice that the STDOUT in the output above is still empty as expected. Now, we will change the value of the "Action" field in ue-task-test. Navigate to the task and change the "Action" to "Print to STDOUT":

Make sure to save the task as shown previously.

Now, we will run the task using the "UIP: Task Launch No Wait" option shown below:

The "No Wait" signifies the task will be launched, but the CLI will not wait for the task to finish. Consequently, the final status and output of the task will NOT be retrieved.

Go ahead and launch the task using the "No Wait" option. You should see something similar as shown below:


We can retrieve the task status and output manually from the Controller or use the VS Code Extension as shown below.

Open the command palette, search for "UIP: Task Status", and select "UIP: Task Status":

The "UIP: Task Status" command lists the status of the most recent task instance whereas the "UIP: Task Status List" gets the statuses of the 20 (this number can be configured using "UIP: Set Task Instance Count") most recent task instances.

Upon running the command, you should see something similar to the output shown below (the "Instance Number", "Instance Id", and "Launch Time" will most likely be different):


As expected, the task succeeded. To get the output, a similar command called "UIP: Task Output" is available:

The "UIP: Task Output" command retrieves the output of the latest task instance whereas the "UIP: Task Output for Instance Number" gets the output of a task instance with a specific instance number.

Since we want to get the output of the recent task instance, "UIP: Task Output" is sufficient. Run the command, and the output should be similar to one shown below:

Notice that this time, the STDERR output is empty and STDOUT output is not. This is expected since we changed the value of the "Action" field in the task form.

Step 5 - Reset the Extension Changes

The For-Loop changes that prints out the 'Hello' message multiple times were added to demonstrate the process of modifying and deploying the Extension. The next few tutorials assume that the For-Loop changes aren't in extension.py, so either undo the For-Loop changes or copy the code shown below to extension.py before moving on to the next guide:

extension.py
from __future__ import (print_function)
from universal_extension import UniversalExtension
from universal_extension import ExtensionResult
from universal_extension import logger


class Extension(UniversalExtension):
    """Required class that serves as the entry point for the extension
    """

    def __init__(self):
        """Initializes an instance of the 'Extension' class
        """
        # Call the base class initializer
        super(Extension, self).__init__()

    def extension_start(self, fields):
        """Required method that serves as the starting point for work performed
        for a task instance.

        Parameters
        ----------
        fields : dict
            populated with field values from the associated task instance
            launched in the Controller

        Returns
        -------
        ExtensionResult
            once the work is done, an instance of ExtensionResult must be
            returned. See the documentation for a full list of parameters that
            can be passed to the ExtensionResult class constructor
        """

        # Get the value of the 'action' field
        action = fields.get('action', [""])[0]

        if action.lower() == 'print':
            # Print to standard output...
            print("Hello STDOUT!")
        else:
            # Log to standard error...
            logger.info('Hello STDERR!')

        # Return the result with a payload containing a Hello message...
        return ExtensionResult(
            unv_output='Hello Extension!'
        )

< Previous     Next >




  • No labels