Skip to main content
Version: Release 22.1

Create Custom Task Types

In Release you can add custom task types that appear in the user interface and integrate seamlessly with other tasks in the release flow. You can use custom tasks to integrate with third-party components. For example, Release includes JIRA integration tasks, which are a set of custom tasks.

Custom tasks are written in the Python language.

To create a custom task, you need:

  • The definition of the task and its properties
  • The implementation of the task in a Python script

Defining a custom task

Custom tasks are stored in the ext or plugins directory of Release server:

  • ext is used when you are developing a custom task. It contains custom task definitions in a file called synthetic.xml. Python scripts are placed in subdirectories of ext.
  • plugins/__local__ contains bundled custom tasks that are packaged in a single zip file with extension .jar

This is an example of the layout of the ext directory:

synthetic.xml
jira/
CreateIssue.py
UpdateIssue.py

You define the custom task in XML in synthetic.xml. As an example, this is the definition of the "Create Jira Issue" task:

<synthetic xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.xebialabs.com/deployit/synthetic"
xsi:schemaLocation="http://www.xebialabs.com/deployit/synthetic synthetic.xsd">

<type type="jira.CreateIssue" extends="xlrelease.PythonScript" label="Create issue">
<property name="jiraServer" category="input" label="Server" referenced-type="jira.Server" kind="ci"/>
<property name="username" category="input"/>
<property name="password" category="input" kind="string" password="true" />
<property name="project" category="input"/>
<property name="title" category="input"/>
<property name="description" category="input" size="large" />
<property name="issueType" category="input" default="Task"/>
<property name="issueId" category="output"/>
</type>

</synthetic>

Synthetic element

The <synthetic> element is the root node. It contains the XML grammar definitions and should not be changed. For Deploy users: this is the same definition language that is used to extend Deploy.

The <type> element in <synthetic> defines the custom task. The type attribute defines the name of the custom task and is always of the form prefix.TaskName:

  • The prefix is the category of the task (for example, 'jira'). It should be in lowercase.
  • The task name is a descriptive name of the task (for example, "CreateIssue"). It should be in camel case.
important

The extends="xlrelease.PythonScript" attribute defines the type as a custom task for Release. You must not change it.

The label attribute defines the name of the task that will appear when adding a new task in the release flow. By default, if is not defined, Release will create a label for you with the following format: Prefix: Task Name using the capital letters as new words (example: "Jira: Create Issue"). You can override the prefix by adding a colon in the label attribute. Example: label="Integration: Create Issue".

You can add the following properties to the <type> element to further customize your task:

  • scriptLocation: Specifies a custom script location that overrides the default rules.
  • iconLocation: Location of an icon file (PNG or GIF format) that is used in the UI for this task.
  • taskColor: The color to use for the task in the UI, specified in HTML hexadecimal RGB format.

Example:

<type type="myplugin.MyTask" extends="xlrelease.PythonScript">
<property name="scriptLocation" required="false" hidden="true" default="my/custom/dir/script.py" />
<property name="iconLocation" required="false" hidden="true" default="my/custom/dir/icon.png" />
<property name="taskColor" hidden="true" default="#9C00DB" />
...

Property element

The properties are defined as nested <property> elements. The following attributes can be set on each property:

PropertyDescription
nameName of the property. This is also the name of the variable by which it is referred in the Python script.
categoryRelease supports two categories.
input appear in the task in the Release UI and must be specified before the task starts. They are then passed to the Python script. If you add an input type and do not specify the value for the required field, it will be set to the default value true.
output can be set in the Python script. When the script completes, they can be copied into release variables in Release.
labelGroup and label used in the Release UI. If you do not specify a group and label, Release will attempt to make a readable version. For example, myCompany.myTask will appear as a My Task task type in the My Company group.

You can group task types in your preferred groups by adding the group before a colon in the label; for example, Other Items: My Task.
descriptionHelp text explaining the property in more detail. This will appear in the UI.
kindThe property type, which is string, integer, boolean, ci, list_of_string, set_of_string, map_string_string, or enum.

If omitted, this attribute defaults to string.
passwordSet this attribute to true to instruct Release to treat the property as a password. The content of password fields are obscured in the UI and encrypted in network traffic and storage.
sizeIndicates how much space the UI assigns to the property. Supported levels are default, small, medium, and large.
defaultThe default value of the property.
referenced-typeIndicates the type of CI this property can reference (only apply if kind is set to ci).

Output properties size limit

To prevent performance issues, output properties of type string are limited to 32 Kb. If your script adds content that exceeds the limit, Release will truncate the property. You can still print the property inside the script and Release will attach the content to the task.

You can change this limit for each task type in the XL_RELEASE_SERVER_HOME/conf/deployit-defaults.properties file. For example:

#webhook.JsonWebhook.maxOutputPropertySize=18000

To change the limit, delete the number sign (#) at the beginning of the relevant line, change the limit as desired, save the file, and restart the Release server.

Add custom tasks

After you save synthetic.xml and restart the Release server, the custom task appears in the UI and you can add it to the release flow editor like any other task.

Select custom task

This is how the above task definition looks like in the task details window:

Jira task

Python scripts

When the custom task becomes active, it triggers the Python script that is associated with it. For information about the script, see API and scripting overview.

Store scripts in a directory that has the same name as the prefix of the task type definition. The script file name has the same name as the name of the task, followed by the .py extension. For example, the Python script for the jira.CreateIssue task must be stored in jira/CreateIssue.py.

Input properties are available as variables in the Python script. You can set output values by assigning values to their corresponding variables in the script. After execution, the script variables are copied to the release variables that were specified on the task in the UI.

tip

To concatenate multiple Python scripts and have Release schedule them, see Using scheduling in scripts to connect to long running jobs.

For example, this is a possible implementation of the jira.CreateIssue task in Python:

import sys, string
import com.xhaus.jyson.JysonCodec as json

ISSUE_CREATED_STATUS = 201

content = """
{
"fields": {
"project":
{
"key": "%s"
},
"summary": "%s",
"description": "%s",
"issuetype": {
"name": "%s"
}
}
}
""" % (project, title, description, string.capwords(issueType))

if jiraServer is None:
print "No server provided."
sys.exit(1)

jiraURL = jiraServer['url']
if jiraURL.endswith('/'):
jiraURL = jiraURL[:len(jiraURL)-1]

# You can pass custom headers to the request
headers = {'myCustomHeaders': 'myValue'}

# jiraServer object may contains proxy information (field proxyHost and proxyPort)
request = HttpRequest(jiraServer, username, password)
response = request.post('/rest/api/2/issue', content, contentType = 'application/json', headers = headers)

if response.status == ISSUE_CREATED_STATUS:
# In order to debug your script, you can print the status, the body and the headers
print response.status
print response.response
print response.headers

# you can access the http headers of the response
if response.headers['Content-Type'].startswith('application/json'):
# Parsing the response body as JSON
data = json.loads(response.response)
issueId = data.get('key')
print "Created %s in JIRA at %s." % (issueId, jiraURL)
else:
print "Failed to create issue in JIRA at %s." % jiraURL
response.errorDump()
sys.exit(1)

HttpRequest

HttpRequest is a class provided by Release that is used to perform HTTP calls. For more information, see the Jython API.

Examples

Posting JSON

request = HttpRequest({'url': 'http://site'})
response = request.post(
'/api/addObject',
'{"json": "content"}',
contentType = 'application/json')
content = response.getResponse()
status = response.getStatus()

Using connection credentials

request = HttpRequest({'url': 'http://site'}, "username", "password")
response = request.get('/api/tasks')
content = response.getResponse()
status = response.getStatus()

Using a proxy

request = HttpRequest({
'url': 'http://site',
'proxyHost': 'proxy.company',
'proxyPort': '8080'})
response = request.get('/api/tasks')
content = response.getResponse()
status = response.getStatus()

Getting results using a configuration object

If your task definition has a configuration object defined using:

<property name="jiraServer" category="input" label="Server" referenced-type="jira.Server" kind="ci"/>

You can use the jiraServer instance like that:

request = HttpRequest(jiraServer)
response = request.get('/api/tasks', contentType = 'application/json')
content = response.getResponse()
status = response.getStatus()

HttpRequest will then use the settings of the HttpConnection.

Release API

The Release API is available for scripts. To find a complete description of the methods you can call, see the Jython API reference.

Changing or removing customizations

Before changing or removing a custom task type or one of the properties of a custom task type, ensure that the type is not used in any releases or templates. Changing or removing a type that is in use may result in errors.

Packaging

You can compress the contents of the ext folder into a single file with a .jar extension and place this file in the plugins directory. Release reads custom tasks from this location.

When compressing, do not include the ext folder as part of the path. The synthetic.xml file should be in the root of the .jar file. On Unix systems, use these commands:

cd ext
zip -r ../myplugin.jar .

On Microsoft Windows systems, use Windows Explorer to compress the individual files in the ext folder.

note

Do not use .zip as the extension of the compressed file.

You can have multiple plugins and define contents in the ext folder at the same time. The ext directory takes precedence over the plugins directory.

If you change ext/synthetic.xml or the contents of the plugins folder, you must restart the Release server. You do not need to restart the server if you change the contents of a Python script.

Prevent cleaning output properties when retrying custom script task

You can keep previously executed output results by using property keepOutputPropertiesOnRetry. If that property is set to true, the output will not be updated.

If, on retry, you do not want to execute the part of the script that already populated a specific output property, then you must write your own custom script section.

Here is an example of a python script that does not execute a part of the original script, if the output is already populated:

prev_result = task.getPythonScript().getProperty("result")

if not task.keepOutputPropertiesOnRetry or not prev_result:
result = calculate_new_value()

You can also use it on the UI in the custom HTML:

<div class="form-group">
<label class="col-xs-3 control-label" for="keepPreviousOutputPropertiesOnRetry">
Retain Properties
</label>
<div class="col-xs-9">
<input type="checkbox" id="keepPreviousOutputPropertiesOnRetry"
data-ng-model="task.keepPreviousOutputPropertiesOnRetry"
data-ng-change="saveTask(task)"
data-ng-disabled="ctrl.areTaskPropertiesReadonlyOrLocked" title="Keep Previous Output Properties On Retry">
<div class="description">Keep the previous output properties on retry</div>
</div>
</div>