Dynamic Command

Introduction

Dynamic Commands allow you to add supporting functionality to a task instance.  In this chapter, we will enhance the ue-task Extension developed in the previous chapters by adding a Dynamic Command.  The command we create will reset the task instance - to prepare it for a rerun scenario.

On this page, we will cover the following:

  1. Add Dynamic Command to the "UE Task" Universal Template.
  2. Add a backing implementation for Dynamic Command to the extension.py file in the ue-task Extension project.
  3. Rebuild and upload the modified Extension.
  4. Execute the Dynamic Command

Step 1 - Add a Dynamic Command to the "UE Task" Universal Template

Go back to the "UE Task" Universal Template form.  

On the Commands tab, click the "New" button and create the following “Reset Environment” command.

Note that the “Supported Status(es)” is set to: In Doubt, Cancelled, Failed, Finished, and Success.  These are the task instance statuses where the command will be available for execution.  For all other statuses the command will be greyed out.

Save the command.

Step 2 - Add Backing Implementation for Dynamic Command to extension.py

Open file ~/dev/extensions/sample-task/src/extension.py in your editor of choice.

Add the implementation of the reset_environment Dynamic Command:

reset_environment Dynamic Command
from __future__ import (print_function)
import time
from universal_extension import UniversalExtension
from universal_extension import ExtensionResult
from universal_extension import logger
from universal_extension import ui
from universal_extension.deco import dynamic_choice_command
from universal_extension.deco import dynamic_command


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__()

    @dynamic_choice_command("primary_choice_field")
    def primary_choice_command(self, fields):
        """Dynamic choice command implementation for
        primary_choice_field.

        Parameters
        ----------
        fields : dict
            populated with the values of the dependent fields
        """
        return ExtensionResult(
            rc=0,
            message="Values for choice field: 'primary_choice_field'",
            values=["Start", "Pause", "Stop", "Build", "Destroy"]
        )

    @dynamic_choice_command("secondary_choice_field")
    def secondary_choice_command(self, fields):
        """Dynamic choice command implementation for
        secondary_choice_field.

        Parameters
        ----------
        fields : dict
            populated with the values of the dependent fields
        """
        return ExtensionResult(
            rc=0,
            message="Values for choice field: 'secondary_choice_field'",
            values=["System", "Command", "Application", "Transfer", "Evidence"]
        )

    @dynamic_command("reset_environment")
    def reset_environment(self, fields):
        """Dynamic command implementation for reset_environment command.

        Parameters
        ----------
        fields : dict
            populated with the values of the dependent fields
        """

        # Reset the state of the Output Only 'step' fields.
        out_fields = {}
        out_fields["step_1"] = "Initial"
        out_fields["step_2"] = "Initial"
        ui.update_output_fields(out_fields)

        return ExtensionResult(
            message="Message: Hello from dynamic command 'reset_environment'!",
            output=True,
            output_data='The environment has been reset.',
            output_name='DYNAMIC_OUTPUT')

    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
        """

        sleep_value = 5

        # Update the 'task_action' Output Only field on the task instance form
        out_fields = {}
        task_action = "{0} {1}".format(
            fields['primary_choice_field'][0],
            fields['secondary_choice_field'][0])
        out_fields["task_action"] = task_action
        ui.update_output_fields(out_fields)

        # Sleep
        time.sleep(sleep_value)

        # Update the 'step_1' Output Only field on the task instance form
        out_fields = {}
        out_fields["step_1"] = "Step 1 - Success"
        ui.update_output_fields(out_fields)

        # Sleep
        time.sleep(sleep_value)

        # Update the 'step_2' Output Only field on the task instance form
        out_fields = {}
        out_fields["step_2"] = "Step 2 - Success"
        ui.update_output_fields(out_fields)

        # 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!'
        )

Line 8We added an import for the dynamic_command decorator which comes from the the UniversalExtension base package. (Also moved the time module import to line 2)
Lines 53 to 73The complete implementation of the Dynamic Command that backs the reset_environment command defined in the Universal Template.

Note

The value supplied to the dynamic_command decorator must match the command Name of the associated Dynamic Command defined in the Controller's Universal Template (for example, @dynamic_command("reset_environment")).

Step 3 - Build and Upload the Extension Zip Archive Using the UIP VS Code Extension

Save all changes to extension.py.

From the VS Code command pallet, execute the UIP: Push command as shown below:

Recall that the UIP: Push command builds and uploads the Extension zip archive


 Click here to expand uip-cli details...

Step 3 Supplemental - Build and Upload the Extension Zip Archive Using the CLI

Save all changes to extension.py.

From the command line, cd to the ~/dev/extensions/sample-task directory and execute the push command as shown below:

Recall that the push command builds and uploads the Extension zip archive

Step 4 - Execute Dynamic Command

In the Controller, open the "UE Task tasks" tab.  If the tab is already open, it must be closed and reopened before the the new Dynamic Command will be available on the task instances.

Switch to the Instances tab.  If there are no task instances in the list, launch a new one now using the VS Code Command (or manually from the Controller) and let it run to completion.

Dynamic Commands are available from the context menu generated by right-clicking on the task instance form and from the context menu generated by right-clicking on the task instance in a list of task instances (for example, in the Activity Monitor and/or the Instances tab of a task definition form).

For this example, double-click on a completed task to open the task instance form.  next, right-click on the form to open a context menu.  At the top of the context menu should be the "UE Task Task Instance Commands".



Select the Reset Environment command.

This displays an additional dialog, which can be used to pull in dependent fields from the task form if needed. This is optional functionality that is defined in the Universal template and made use of by the backing implementation in the extension archive.



Submit the Dynamic Command.

The immediate result of Dynamic Command is new window showing the output results of the command.



In this case, the resulting output is “The environment has been reset”.

Additionally, the Reset Environment command was designed to “reset” the “Step” Output Only fields on the task instance form.

To see this, close the “Output Details” window and scroll the open task instance form to view the “UE Task" Details section.



To reveal the change, refresh the task instance form.



The Step 1 and Step 2 Output Only fields have been set to Initial. This is a contrived example, but it demonstrates how a Dynamic Command could be used as a helper command for a task instance by performing some action on the target agent system (or beyond) and then updating the task instance in a meaningful way that may be required for a rerun scenario.

In this case, information flowed from the Dynamic Command execution back to the task instance fields. However, task specific information could also have been passed down to the Dynamic command.

Step 5 - Update the Local template.json

In step 1, we modified the Universal Template by adding a new Dynamic command. Recall that inside ~/dev/extensions/sample-task/src/templates, there is a template.json file. This file should correspond to the Universal Template in the Controller. 

Right now, they are not both the same. The Controller's version of template.json has the Dynamic Choice Fields changes. To grab those changes, use the pull command as shown below:

UIP VS Code Extension

 Click here to expand uip-cli details...

Step Supplemental - CLI


Now, both the local and Controller's version of the Universal Template are the same.

< Previous     Next >