Plug-in tasks

Plug-in steps can interact with the plug-in tasks that are used in deployment plans.

Plug-in steps can respond to notification-type events in external tools and provide data from those tools to IBM® UrbanCode™ Release plug-in tasks. For example, you might create plug-ins that integrate with ticketing systems and email servers, and then have the external tools provide data to plug-in tasks in deployment plans. To enable a plug-in to work with plug-in tasks, you modify the steps in the plugin.xml file. To work with plug-in tasks, a plug-in step extends the PLUGIN_TASK class.

The plug-in step is responsible to set the task status after execution. The step can wait for the process execution to complete or provide a call-back URL in order for the external tool to update the task status later. The plug-in step can add information about the task execution to the task's Comments tab.

Example of a plug-in that can be used with plug-in tasks

The following example of a plugin.xml file illustrates how to create plug-in steps that can be used by plug-in tasks. The step extends PLUGIN_TASK and defines several properties. Users can set the property values in the plug-in task. The step sets the task's status in the post-processing element.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<plugin xmlns="http://www.urbancode.com/PluginXMLSchema_v1" xmlns:server="http://www.urbancode.com/PluginServerXMLSchema_v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <header>
    <identifier id="com.urbancode.plugin.demo" name="Demo Plugin" version="1"/>

    <plugin-type>Connector</plugin-type>
    <description>
      Demo Plugin
    </description>
    <tag>tools/demo</tag>
  </header>
  <!-- extends="PLUGIN_TASK" will register that step as a task when creating an integration provider with that plugin-->
  <step-type name="ExecuteTask" displayed="true" extends="PLUGIN_TASK">
    <description>Execute Task</description>
    <properties>
      <property name="property1" required="true">
        <property-ui type="textBox" description="Property 1" label="Plugin Property 1" />
      </property>
      <property name="property2" required="true">
        <property-ui type="textBox" description="Property 2" label="Plugin Property 2" />
      </property>
      <property name="property3" required="true">
        <property-ui type="textAreaBox" description="Property 3" label="Plugin Property 3"/>
      </property>
    </properties>
    <post-processing>
      <![CDATA[
        if (properties.get("exitCode") != 0) {
            properties.put(new java.lang.String("Status"), new java.lang.String("Failure"));
        }
        else {
            properties.put("Status", "Success");
        }
     ]]>
    </post-processing>

    <command program="${GROOVY_HOME}/bin/groovy">
      <arg value="-cp"/>
      <!-- ucr-plugin-util.jar is the plugin client for consuming UCR rest api-->
      <arg path="classes:lib/commons-codec.jar:lib/CommonsUtil.jar:lib/ucr-plugin-util.jar:lib/commons-lang3.jar:lib/commons-lang.jar:lib/jettison-1.1.jar"/>
      <arg file="ExecuteTask.groovy"/>
      <arg file="${PLUGIN_INPUT_PROPS}"/>
      <arg file="${PLUGIN_OUTPUT_PROPS}"/>
    </command>
  </step-type>
 
</plugin>

Groovy file example

The plug-in framework passes the entire context as a JsonObject, extraProperties, that can be parsed into an object. After the plug-in step parses the JsonObject, it has access to every element of the plug-in task, such as callBackUrl, taskUrl, and UserId.

The following code example, ExecuteTask.groovy, illustrates how to parse the extraProperties object, and add comments to a plug-in task.

#!/usr/bin/env groovy
/*
 * Licensed Materials - Property of IBM Corp.
 * IBM UrbanCode Release
 * (c) Copyright IBM Corporation 2014. All Rights Reserved.
 *
 * U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
 * GSA ADP Schedule Contract with IBM Corp.
 */

import static com.urbancode.release.rest.framework.Clients.version;
import static com.urbancode.release.rest.models.internal.InternalClients.status;
import static com.urbancode.release.rest.models.internal.InternalClients.versionStatus;
import static com.urbancode.release.rest.models.internal.InternalClients.version;
import com.urbancode.release.rest.models.internal.TaskExecution;
import com.urbancode.air.*
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;

import com.urbancode.release.rest.models.Version;
import com.urbancode.release.rest.models.internal.ApprovalSet; 
import com.urbancode.release.rest.models.internal.ScheduledDeployment;
import com.urbancode.release.rest.models.internal.PluginIntegrationProvider;
import com.urbancode.release.rest.models.internal.Status;
import com.urbancode.release.rest.models.internal.Comment;
import com.urbancode.release.rest.models.internal.VersionStatus;
import com.urbancode.release.rest.framework.Clients;

final def workDir = new File('.').canonicalFile

def apTool = new AirPluginTool(this.args[0], this.args[1]);
def props = apTool.getStepProperties();

//We launch the integration here
def executor = new TaskExecutor (props)
//We authenticate first
executor.releaseAuthentication();
//We then execute the task
executor.run ()

public class TaskExecutor {
    //Used to parse JsonObjects
    def slurper;
    def integrationProviderId

    //Used for authentication
    def releaseToken;
    def serverUrl;
    
    //JsonObject that will contain context properties
    def extraProperties;
    
    //Properties defined in the UI for the integration provider
    def uiProperty1 = "";
    def uiProperty2 = "";
    def uiProperty3 = "";

    //The task object
    def task;

    //Main constructor
    TaskExecutor (props) {
        this.integrationProviderId = props['releaseIntegrationProvider'];
        this.releaseToken = props['releaseToken'];
        this.serverUrl = props['releaseServerUrl'];
        //Context properties
        this.extraProperties = props['extraProperties'];
        
        //UI Integration properties
        this.uiProperty1 = props['property1'];
        this.uiProperty2 = props['property2'];
        this.uiProperty3 = props['property3'];
                
        this.extraProperties = props['extraProperties'];
        
        //We initialize the Json parser
        this.slurper = new groovy.json.JsonSlurper()
    }
    
    //--------------------------------------------------------------
    //Authentication with Release
    def releaseAuthentication () {
        Clients.loginWithToken(serverUrl, releaseToken);
    } 
        
    //--------------------------------------------------------------
    def run () {
    
         //Lets display the UI input properties
         addLogSection ("Global Properties set on the integration provider");
         
         log ("Property 1: "+this.uiProperty1);
         log ("Property 2: "+this.uiProperty2);
         log ("Property 3: "+this.uiProperty3);
         
         //We load the context properties
         //If ran from the integration provider page and not a scheduled deployment the extraProperties object my be null
         if (extraProperties) {
            log ("Task ran from a scheduled deployment");
            
            def contextProperties = slurper.parseText(extraProperties)
            //The context properties will provide the task object as well as the plugin  properties set on it in the plan
            
            addLogSection ("Task information");
            
            //callBackUrl
            log ("Call Back URL: "+contextProperties.callBackUrl);
            //Direct link in UCR for that task
            log ("Direct link to that task: "+contextProperties.taskUrl);
            //User who executed the task
            log ("User Id (Admin for automated tasks): "+contextProperties.userId);
            
            //Task properties
            if (contextProperties.task != null) {
                //We need the ucr task id so we can handle that UCR object later
                def urcTaskId = contextProperties.task.id
                
                task = new TaskExecution()
                task.id(urcTaskId)
                                
                log ("Task Id: "+urcTaskId);
                log ("Task Name: "+contextProperties.task.name);
                log ("Task Description: "+contextProperties.task.description);
                log ("Scheduled Deployment Id: "+contextProperties.task.scheduledDeploymentId);
                log ("Segment Id: "+contextProperties.task.segmentExecution.id);
                log ("Segment Name: "+contextProperties.task.segmentExecution.name);
                log ("Only Changed Versions: "+contextProperties.task.onlyChangedVersions);
                
                addLogSection ("Application Info");
                
                if (contextProperties.task.application != null) {
                    log ("Application UCR Id: "+contextProperties.task.application.id);
                    log ("Application External Id: "+contextProperties.task.application.externalId);
                    log ("Application Name: "+contextProperties.task.application.name);
                    
                    addLogSection ("Version Info");
                    
                    if (contextProperties.task.version != null) {
                        log ("Version UCR Id: "+contextProperties.task.version.id);
                        log ("Version External Id: "+contextProperties.task.version.externalId);
                        log ("Version Name: "+contextProperties.task.version.name);
                        //Version Statuses
                        if (contextProperties.task.version.versionStatuses != null) {
                            
                            contextProperties.task.version.versionStatuses.each {
                                versionStatus -> log ("Status: "+versionStatus.status.name+" added by "+versionStatus.createdByUser.actualName+" on "+new Date(versionStatus.dateCreated));
                            }
                        }
                    }
                }
                
                addLogSection ("Target Info");
                
                if (contextProperties.task.targets != null) {
                    contextProperties.task.targets.each {
                       target ->  log ("Target Name: "+target.name);
                       log ("Target UCR Id: "+target.id);
                       log ("Target External Id: "+target.externalId);
                    }
                }
                
                addLogSection ("Approval information");
                
                //To get more about the deployment we need to load the object
                def sdClient = new ScheduledDeployment();
                sdClient.format("detail");
                def sd = sdClient.id(contextProperties.task.scheduledDeploymentId).get();
                
                def approvalSet = sd.approval;
                
                approvalSet.tasks.each {
                     approvalItem ->  log ("Approval: "+approvalItem.name+" by "+approvalItem.executorRole.name+" User: "+ approvalItem.userName);
                     log ("Approval ID: "+approvalItem.id);
                }
                                    
            }
            
            addLogSection ("Task plugin properties");
            if (contextProperties.task.pluginProperties != null) {
               log ("Plugin properties input from the task UI "+contextProperties.task.pluginProperties);
            }
            
            
            ////////////////////////////////////////////////////////////////////////////////////////
            // WHATEVER THE PLUGIN TASK SHOULD BE DOING
            
            //Comments can be added to the task
            new Comment().userId(contextProperties.userId).task(task).comment("Here is a comment posted during the execution of the task").post();

            //Once done the status of the tasks should be updated
            
            //task.complete() or task.fail()
            
            task.complete();
            ////////////////////////////////////////////////////////////////////////////////////////
            
        }
        else {
            log ("Task ran from the integration provider page");
        }
    }
    
    //--------------------------------------------------------------
    def log (logText) {
        println (logText)
    }
    
    //--------------------------------------------------------------
    def addLogSection (sectionTitle) {
        println ("")
        println ("------------------------------------------------------------------")
        println ("<b><u>"+sectionTitle+"</u></b>")
        println ("")
    }
}