Bug 1389248 - Move actions spec to taskcluster docs r=dustin

MozReview-Commit-ID: BsYRmivUZZ7

--HG--
extra : rebase_source : 05d3c67f06585f43b224fce9785dbf8f97edd3e5
This commit is contained in:
Brian Stack 2017-08-10 13:59:05 -07:00
Родитель 795d1f97fc
Коммит d1e7547331
5 изменённых файлов: 241 добавлений и 776 удалений

Просмотреть файл

@ -1,244 +0,0 @@
Action Task Implementation
==========================
This document shows how to define an action in-tree such that it shows up in
supported user interfaces like Treeherder. For details on interface between
in-tree logic and external user interfaces, see
:doc:`the specification for actions.json <action-spec>`.
There are two options for defining actions: creating a callback action, or
creating a custom action task. A callback action automatically defines an
action task that will invoke a Python function of your devising.
A custom action task is an arbitrary task definition that will be created
directly. In cases where the callback would simply call ``queue.createTask``,
a custom action task can be more efficient.
Creating a Callback Action
--------------------------
A *callback action* is an action that calls back into in-tree logic. That is,
you register the action with name, title, description, context, input schema and a
python callback. When the action is triggered in a user interface,
input matching the schema is collected, passed to a new task which then calls
your python callback, enabling it to do pretty much anything it wants to.
To create a new action you must create a file
``taskcluster/taskgraph/actions/my-action.py``, that at minimum contains::
from registry import register_callback_action
@register_callback_action(
name='hello',
title='Say Hello',
symbol='hw', # Show the callback task in treeherder as 'hw'
description="Simple **proof-of-concept** callback action",
order=10000, # Order in which it should appear relative to other actions
)
def hello_world_action(parameters, input, task_group_id, task_id, task):
# parameters is an instance of taskgraph.parameters.Parameters
# it carries decision task parameters from the original decision task.
# input, task_id, and task should all be None
print "Hello was triggered from taskGroupId: " + taskGroupId
The example above defines an action that is available in the context-menu for
the entire task-group (result-set or push in Treeherder terminology). To create
an action that shows up in the context menu for a task we would specify the
``context`` parameter.
Setting the Action Context
..........................
The context parameter should be a list of tag-sets, such as
``context=[{"platform": "linux"}]``, which will make the task show up in the
context-menu for any task with ``task.tags.platform = 'linux'``. Below is
some examples of context parameters and the resulting conditions on
``task.tags`` (tags used below are just illustrative).
``context=[{"platform": "linux"}]``:
Requires ``task.tags.platform = 'linux'``.
``context=[{"kind": "test", "platform": "linux"}]``:
Requires ``task.tags.platform = 'linux'`` **and** ``task.tags.kind = 'test'``.
``context=[{"kind": "test"}, {"platform": "linux"}]``:
Requires ``task.tags.platform = 'linux'`` **or** ``task.tags.kind = 'test'``.
``context=[{}]``:
Requires nothing and the action will show up in the context menu for all tasks.
``context=[]``:
Is the same as not setting the context parameter, which will make the action
show up in the context menu for the task-group.
(i.e., the action is not specific to some task)
The example action below will be shown in the context-menu for tasks with
``task.tags.platform = 'linux'``::
from registry import register_callback_action
@register_callback_action(
name='retrigger',
title='Retrigger',
symbol='re-c', # Show the callback task in treeherder as 're-c'
description="Create a clone of the task",
order=1,
context=[{'platform': 'linux'}]
)
def retrigger_action(parameters, input, task_group_id, task_id, task):
# input will be None
print "Retriggering: {}".format(task_id)
print "task definition: {}".format(task)
When the ``context`` parameter is set, the ``task_id`` and ``task`` parameters
will provided to the callback. In this case the ``task_id`` and ``task``
parameters will be the ``taskId`` and *task definition* of the task from whose
context-menu the action was triggered.
Typically, the ``context`` parameter is used for actions that operate on
tasks, such as retriggering, running a specific test case, creating a loaner,
bisection, etc. You can think of the context as a place the action should
appear, but it's also very much a form of input the action can use.
Specifying an Input Schema
..........................
In call examples so far the ``input`` parameter for the callbacks has been
``None``. To make an action that takes input you must specify an input schema.
This is done by passing a JSON schema as the ``schema`` parameter.
When designing a schema for the input it is important to exploit as many of the
JSON schema validation features as reasonably possible. Furthermore, it is
*strongly* encouraged that the ``title`` and ``description`` properties in
JSON schemas is used to provide a detailed explanation of what the input
value will do. Authors can reasonably expect JSON schema ``description``
properties to be rendered as markdown before being presented.
The example below illustrates how to specify an input schema. Notice that while
this example doesn't specify a ``context`` it is perfectly legal to specify
both ``input`` and ``context``::
from registry import register_callback_action
@register_callback_action(
name='run-all',
title='Run All Tasks',
symbol='ra-c', # Show the callback task in treeherder as 'ra-c'
description="**Run all tasks** that have been _optimized_ away.",
order=1,
input={
'title': 'Action Options',
'description': 'Options for how you wish to run all tasks',
'properties': {
'priority': {
'title': 'priority'
'description': 'Priority that should be given to the tasks',
'type': 'string',
'enum': ['low', 'normal', 'high'],
'default': 'low',
},
'runTalos': {
'title': 'Run Talos'
'description': 'Do you wish to also include talos tasks?',
'type': 'boolean',
'default': 'false',
}
},
'required': ['priority', 'runTalos'],
'additionalProperties': False,
},
)
def retrigger_action(parameters, input, task_group_id, task_id, task):
print "Create all pruned tasks with priority: {}".format(input['priority'])
if input['runTalos']:
print "Also running talos jobs..."
When the ``schema`` parameter is given the callback will always be called with
an ``input`` parameter that satisfies the previously given JSON schema.
It is encouraged to set ``additionalProperties: false``, as well as specifying
all properties as ``required`` in the JSON schema. Furthermore, it's good
practice to provide ``default`` values for properties, as user interface generators
will often take advantage of such properties.
Once you have specified input and context as applicable for your action you can
do pretty much anything you want from within your callback. Whether you want
to create one or more tasks or run a specific piece of code like a test.
Conditional Availability
........................
The decision parameters ``taskgraph.parameters.Parameters`` passed to
the callback are also available when the decision task generates the list of
actions to be displayed in the user interface. When registering an action
callback the ``availability`` option can be used to specify a callable
which, given the decision parameters, determines if the action should be available.
The feature is illustrated below::
from registry import register_callback_action
@register_callback_action(
name='hello',
title='Say Hello',
symbol='hw', # Show the callback task in treeherder as 'hw'
description="Simple **proof-of-concept** callback action",
order=2,
# Define an action that is only included if this is a push to try
available=lambda parameters: parameters.get('project', None) == 'try',
)
def try_only_action(parameters, input, task_group_id, task_id, task):
print "My try-only action"
Properties of ``parameters`` are documented in the
:doc:`parameters section <parameters>`. You can also examine the
``parameters.yml`` artifact created by decisions tasks.
Creating a Custom Action Task
------------------------------
It is possible to define an action that doesn't take a callback. Instead, you'll
then have to provide a task template. For details on how the task template
language works refer to :doc:`the specification for actions.json <action-spec>`,
the example below illustrates how to create such an action::
from registry import register_task_action
@register_task_action(
name='retrigger',
title='Retrigger',
description="Create a clone of the task",
order=1,
context=[{'platform': 'linux'}],
input={
'title': 'priority'
'description': 'Priority that should be given to the tasks',
'type': 'string',
'enum': ['low', 'normal', 'high'],
'default': 'low',
},
)
def task_template_builder(parameters):
# The task template builder may return None to signal that the action
# isn't available.
if parameters.get('project', None) != 'try':
return None
return {
'created': {'$fromNow': ''},
'deadline': {'$fromNow': '1 hour'},
'expires': {'$fromNow': '14 days'},
'provisionerId': '...',
'workerType': '...',
'priority': '${input}',
'payload': {
'command': '...',
'env': {
'TASK_DEFINITION': {'$json': {'eval': 'task'}}
},
...
},
# It's now your responsibility to include treeherder routes, as well
# additional metadata for treeherder in task.extra.treeherder.
...
},
This kind of action is useful for creating simple derivative tasks, but is
limited by the expressiveness of the template language. On the other hand, it
is more efficient than an action callback as it does not involve an
intermediate action task before creating the task the user requested.
For further details on the template language, see :doc:`the specification for
actions.json <action-spec>`.

Просмотреть файл

@ -1,242 +0,0 @@
Action Specification
====================
This document specifies how actions exposed by the *decision task* are to be
presented and triggered from Treeherder, or similar user interfaces.
The *decision task* creates an artifact ``public/actions.json`` to be consumed
by a user interface such as Treeherder. The ``public/actions.json`` file
specifies actions that can be triggered such as:
* Retrigger a task,
* Retry specific test cases many times,
* Obtain a loaner machine,
* Back-fill missing tasks,
* ...
Through the ``public/actions.json`` file it is possible expose actions defined
in-tree such that the actions can be conveniently triggered in Treeherder.
This has two purposes:
1. Facilitate development of utility actions/tools in-tree, and,
2. Strongly decouple build/test configuration from Treeherder.
For details on how define custom actions in-tree, refer to
:doc:`the in-tree actions section <action-details>`. This document merely
specifies how ``actions.json`` shall be interpreted.
Actions
-------
The content of ``actions.json`` is a list of actions (and variables, to be
described later). Each action has a ``kind`` describing how a user interface
should trigger it. There is currently only one kind defined: ``task``.
An action with ``kind: 'task'`` specifies a task that the user interface should
create. That is, when the action is triggered, the user interface calls the
Taskcluster API to create a new task, with the content of that task determined
from ``actions.json``.
The task created by the action may be useful in its own right (for example,
running a test with additional debugging), or it may simplify trigger in-tree
scripts that create new tasks. The latter form is called an *action task*, and
is similar to a decision task. This allows in-tree scripts to execute
complicated actions such as backfilling.
Actions of the ``'task'`` *kind* **must** have a ``task`` property. This
property specifies the task template to be parameterized and created in order
to trigger the action.
The template is parameterized using `JSON-e
<https://github.com/taskcluster/json-e>`_, with the following context entries
available:
``taskGroupId``
the ``taskGroupId`` of task-group this is triggerd from,
``taskId``
the ``taskId`` of the selected task, ``null`` if no task is
selected (this is the case if the action has ``context: []``),
``task``
the task definition of the selected task, ``null`` if no task is
selected (this is the case if the action has ``context: []``), and,
``input``
the input matching the ``schema`` property, ``null`` if the action
doesn't have a ``schema`` property. See "Action Input" below.
``<key>``
Any ``<key>`` defined in the ``variables`` property may also be referenced.
See "Variables" below.
The following **example** demonstrates how a task template can specify
timestamps and dump input JSON into environment variables::
{
"version": 1,
"actions": [
{
"kind": "task",
"name": "thing",
"title: "Do A Thing",
"description": "Do something",
"task": {
"workerType": "my-worker",
"payload": {
"created": {"$fromNow": ""},
"deadline": {"$fromNow": "1 hour 15 minutes"},
"expiration": {"$fromNow": "14 days"},
"image": "my-docker-image",
"env": {
"TASKID_TRIGGERED_FOR": "${taskId}",
"INPUT_JSON": {"$json": {"$eval": "input"}}
},
...
},
...
}
}
],
"variables: {},
}
MetaData
........
Each action entry must define a ``name``, ``title`` and ``description``.
furthermore, the list of actions should be sorted by the order in which actions
should appear in a menu.
The ``name`` is used by user interfaces to identify the action. For example, a
retrigger button might look for an action with `name = "retrigger"`.
Action names must be unique for a given task, or for a taskgroup, but the same
name may be used for actions applying to disjoint sets of tasks. For example,
it may be helpful to define different "retrigger" actions for build tasks
`[{jobKind: 'build'}]` and test tasks `[{jobKind: 'test'}]`, and in this case
only one such action would apply to any given task.
The ``title`` is a human readable string intended to be used as label on the
button, link or menu entry that triggers the action. This should be short and
concise. Ideally, you'll want to avoid duplicates.
The ``description`` property contains a human readable string describing the
action, such as what it does, how it does it, what it is useful for. This string
is to be render as **markdown**, allowing for bullet points, links and other
simple formatting to explain what the action does.
Action Context
..............
Few actions are relevant in all contexts. For this reason each action specifies
a ``context`` property. This property specifies when an action is relevant.
Actions *relevant* for a task should be displayed in a context menu for the
given task. Similarly actions *not relevant* for a given task should not be
displayed in the context menu for the given task.
As a special case we say that actions for which *no relevant* contexts can
exist, are *relevant* for the task-group. This could for example be an action
to create tasks that was optimized away.
The ``context`` property is specified as a list of *tag-sets*. A *tag-set* is a
set of key-value pairs. A task is said to *match* a *tag-set* if ``task.tags``
is a super-set of the *tag-set*. An action is said to be *relevant* for a given
task, if ``task.tags`` *match* one of the *tag-sets* given in the ``context``
property for the action.
Naturally, it follows that an action with an empty list of *tag-sets* in its
``context`` property cannot possibly be *relevant* for any task. Hence, by
previously declared special case such an action is *relevant* for the
task-group.
**Examples**::
// Example task definitions (everything but tags eclipsed)
TaskA = {..., tags: {kind: 'test', platform: 'linux'}}
TaskB = {..., tags: {kind: 'test', platform: 'windows'}}
TaskC = {..., tags: {kind: 'build', platform: 'linux'}}
Action1 = {..., context: [{kind: 'test'}]}
// Action1 is relevant to: TaskA, TaskB
Action2 = {..., context: [{kind: 'test', platform: 'linux'}]}
// Action2 is relevant to: TaskA
Action3 = {..., context: [{platform: 'linux'}]}
// Action3 is relevant to: TaskA, TaskC
Action4 = {..., context: [{kind: 'test'}, {kind: 'build'}]}
// Action4 is relevant to: TaskA, TaskB, TaskC
Action5 = {..., context: [{}]}
// Action5 is relevant to: TaskA, TaskB, TaskC (all tasks in fact)
Action6 = {..., context: []}
// Action6 is relevant to the task-group
Action Input
............
An action can take JSON input, the input format accepted by an action is
specified using a `JSON schema <http://json-schema.org/>`_. This schema is
specified with by the action's ``schema`` property. For example::
{
"version": 1,
"actions": [
{
"kind": "task",
"name": "thing",
"title: "Do A Thing",
"description": "Do something",
"schema": {
"description": "The thing to do",
"title": "Thing",
"default": "something",
"type": "string"
"maxLength": 255
},
"task": {
"payload": {
"env": {
"INPUT_JSON": {"$json": {"$eval": "input"}}
},
...
},
...
}
}
],
"variables: {},
}
User interfaces for triggering actions, like Treeherder, are expected to provide
JSON input that satisfies this schema. These interfaces are also expected to
validate the input against the schema before attempting to trigger the action.
It is perfectly legal to reference external schemas using
constructs like ``{"$ref": "https://example.com/my-schema.json"}``, in this case
it however strongly recommended that the external resource is available over
HTTPS and allows CORS requests from any source.
When writing schemas it is strongly encouraged that the JSON schema
``description`` properties are used to provide detailed descriptions. It is
assumed that consumers will render these ``description`` properties as markdown.
Variables
---------
The ``public/actions.json`` artifact has a ``variables`` property that is a
mapping from variable names to JSON values to be used as constants.
These variables can be referenced from task templates, but beware that they
may overshadow builtin variables. This is mainly useful to deduplicate commonly
used values, in order to reduce template size. This feature does not
introduce further expressiveness.
Formal Specification
--------------------
The following is the JSON schema for ``actions.json``.
.. literalinclude:: actions-schema.yml
:language: YAML

Просмотреть файл

@ -1,87 +0,0 @@
User Interface Considerations
=============================
The actions system decouples in-tree changes from user interface changes by
taking advantage of graceful degradation. User interfaces, when presented with
an unfamiliar action, fall back to a usable default behavior, and can later be
upgraded to handle that action with a more refined approach.
Default Behavior
----------------
Every user interface should support the following:
* Displaying a list of actions relevant to each task, and displaying
task-group tasks for the associated task-group.
* Providing an opportuntity for the user to enter input for an action. This
might be in JSON or YAML, or using a form auto-generated from the action's
JSON-schema. If the action has no schema, this step should be skipped.
The user's input should be validated against the schema.
* For ``action.kind = 'task'``, rendering the template using the JSON-e
library, using the variables described in :doc:`action-spec`.
* Calling ``Queue.createTask`` with the resulting task, using the user's
Taskcluster credentials. See the next section for some important
security-related concerns.
Creating Tasks
--------------
When executing an action, a UI must ensure that the user is authorized to
perform the action, and that the user is not being "tricked" into executing
an unexpected action.
To accomplish the first, the UI should create tasks with the user's Taskcluster
credentials. Do not use credentials configured as part of the service itself!
To accomplish the second, use the decision tasks's ``scopes`` property as the
`authorizedScopes
<https://docs.taskcluster.net/manual/design/apis/hawk/authorized-scopes>`_ for
the ``Queue.createTask`` call. This prevents action tasks from doing anything
the original decision task couldn't do.
Specialized Behavior
--------------------
The default behavior is too clumsy for day-to-day use for common actions. User
interfaces may want to provide a more natural interface that still takes advantage
of the actions system.
Specialized Input
.................
A user interface may provide specialized input forms for specific schemas. The
input generated from the form must conform to the schema.
To ensure that the schema has not changed, implementers should do a deep
comparison between a schema for which a hand-written form exists, and the
schema required by the action. If the two differ, the default behavior should
be used instead.
Specialized Triggering
......................
A user interface may want to trigger a specific action using a dedicated UI
element. For example, an "start interactive session" button might be placed
next to each failing test in a list of tests.
User interfaces should look for the desired action by name. The UI should check
that there is exactly one matching action available for the given task or
task-graph. If multiple actions match, the UI should treat that as an error
(helping to avoid actions being surreptitiously replaced by similarly-named,
malicious actions).
Having discovered the task, the user interface has a choice in how to provide
its input. It can use the "specialized input" approach outlined above, providing
a customized form if the action's schema is recognized and gracefully degrading
if not.
But if the user interface is generating the input internally, it may instead
validate that generated input against the action's schema as given, proceeding
if validation succeeds. In this alternative, there is no need to do a deep
comparison of the schema. This approach allows in-tree changes that introduce
backward-compatible changes to the schema, without breaking support in user
interfaces. Of course, if the changes are not backward-compatibile, breakage
will ensue.

Просмотреть файл

@ -1,197 +0,0 @@
$schema: http://json-schema.org/draft-04/schema#
id: https://hg.mozilla.org/mozilla-central/raw-file/tip/taskcluster/docs/actions-schema.yml
title: Schema for Exposing Actions
description: |
This document specifies the schema for the `public/actions.json` used by
_decision tasks_ to expose actions that can be triggered by end-users.
For the purpose of this document the _consumer_ is the user-interface that
displays task results to the end-user and allows end-users to trigger actions
defined by `public/actions.json`. A _consumer_ might be Treeherder.
The _end-user_ is a developer who is inspecting the results, and wish to
trigger actions.
type: object
properties:
version:
enum: [1]
type: integer
variables:
type: object
description: |
Mapping from variable name to constants that can be referenced using
`{$eval: '<variable>'}` within the task templates defined for each action.
This is useful for commonly used constants that are used in many task
templates. Whether it's to reduce the size of the `public/actions.json`
artifact by reuseing large constants, or simply to make it easier to
write task templates by exposing additional variables.
These will overwrite any builtin variables, such as `taskGroupId`,
`input`, `taskId`, `task`, and any further variables that future
backwards compatible iterations of this specifcation adds. Hence, you
should avoid declaring variables such as `input`, as it will shadow the
builtin `input` variable.
additionalProperties: true
actions:
type: array
description: |
List of actions that can be triggered.
items:
type: object
properties:
name:
type: string
maxLength: 255
description: |
The name of this action. This is used by user interfaces to
identify the action. For example, a retrigger button might look for
an action with `name = "retrigger"`.
Action names must be unique for a given task, or for a taskgroup,
but the same name may be used for actions applying to disjoint sets
of tasks. For example, it may be helpful to define different
"retrigger" actions for build tasks `[{jobKind: 'build'}]` and test
tasks `[{jobKind: 'test'}]`, and in this case only one such action
would apply to any given task.
title:
type: string
maxLength: 255
description: |
Title text to be displayed on the button or link triggering the action.
description:
type: string
maxLength: 4096
description: |
Human readable description of the action in markdown.
Can be displayed in tooltip, popup and/or dialog when triggering
the action.
kind:
enum:
- task
description: |
Specifies the kind of action this is.
The `task` _action kind_ is triggered by creating a task, following
a task template.
Other kinds might be added in the future. Consumers should ignore
all entries featuring a `kind` property they don't recognize.
context:
type: array
default: []
items:
type: object
additionalProperties:
type: string
maxLength: 4096
title: tag-set
description: |
A set of key-value pairs specifying a _tag-set_.
description: |
The `context` property determines in what context the action is
relevant. Thus, what context the action should be presented to the
end-user.
The `context` property contains a set of tag-sets. A _tag-set_ is a
set of key-value pairs. A task is said satisfy a tag-set if
`task.tags` is a super-set of the given tag-set. An action is
relevant for a task if the task satisfies at-least one of
the tag-sets.
Hence, an action with `context: [{a: '1'}, {b: '2'}]` is relevant
for any task with `task.tags.a = '1'` or `task.tags.b = '2'`.
An action with `context: [{a: '1', b: '2'}]` is only relevant for
tasks with `task.tags.a = '1'` and `task.tags.b = '2'`.
This allows restrictions of what tasks an action is relevant for.
For example some tasks might not support running under a debugger.
The keen reader observes that actions with `context: [{}]` are
relevant for all tasks. Conversely, we have that tasks with
`context: []` are irrelevant for all tasks. We abuse this property
and define actions with `context: []` to be relevant for the
_task-group_ only.
That is an action with `context: []` should not be display in the
context-sensitive menu for a task, rather it should be display when
selecting the entire task-group. Presentation details are left for
consumer to decide.
Notice that the `context` property is optional, but defined to have
a default value `context: []`. Hence, if the `context` is not
specified consumer should take this to mean `context: []` implying
that the action is relevant to the task-group, rather than any
subset of tasks.
schema:
$ref: http://json-schema.org/schema
description: |
JSON schema for input parameters to the `task` template property.
Consumers shall offer a user-interface where end-users can enter
values that satisfy this schema. Furthermore, consumers **must**
validate enter values against the given schema before parameterizing
the `task` template property and triggering the action.
In practice it's encourage that consumers employ a facility that
can generate HTML forms from JSON schemas. However, if certain
schemas are particularly complicated or common, consumers may also
hand-write a user-interface for collecting the input. In this case
the consumer **must** do a deep comparison between the schema given
in the action, and the schema for which a custom user-interface have
been written, and fall-back to an auto-generated form if the schema
doesn't match.
It is assumed that the JSON schema `description` property will be
rendered as markdown when displayed as documentation for end-users.
Producers of `public/actions.json` is encouraged to provide a
detailed explanation of the input parameters using these
`description` properties. And consumers are *strongly* encouraged
to render `description` values as markdown.
The `schema` property is optional, and if not given the input for
`task` template parameterization shall be `null`.
task:
type: object
title: task template
description: |
Task template for triggering the action.
When an action have been selected in the appropriate context and
input satisfying the `schema` (if any) has been collected. The
action is triggered by parameterizing the task template given in
this property, and creating the resulting task.
The template is an object that is parameterized using
[JSON-e](https://github.com/taskcluster/json-e), with the above
variables supplied as context.
This allows for dumping `input` and `taskId` into environment
variables for the task to be created. The following task template
injects `input` and `taskId` as environment variables:
```json
{
"workerType": "my-worker",
"payload": {
"created": {"$fromNow": ""},
"deadline": {"$fromNow": "1 hour 15 minutes"},
"expiration": {"$fromNow": "14 days"},
"image": "my-docker-image",
"env": {
"TASKID_TRIGGERED_FOR": "${taskId}",
"INPUT_JSON": {"$json": {"$eval": "input"}}
},
...
},
...
}
```
additionalProperties: false
required:
- title
- description
- kind
- task
additionalProperties: false
required:
- version
- actions
- variables

Просмотреть файл

@ -1,8 +1,9 @@
Actions
=======
This section shows how to define an action in-tree such that it shows up in
supported user interfaces like Treeherder.
This document shows how to define an action in-tree such that it shows up in
supported user interfaces like Treeherder. For details on interface between
in-tree logic and external user interfaces, see `the actions.json spec`_.
At a very high level, the process looks like this:
@ -20,9 +21,243 @@ At a very high level, the process looks like this:
for carrying out the named action, and may create new sub-tasks if necessary
(for example, to re-trigger a task).
Action Task Implementation
==========================
.. toctree::
There are two options for defining actions: creating a callback action, or
creating a custom action task. A callback action automatically defines an
action task that will invoke a Python function of your devising.
action-spec
action-uis
action-implementation
A custom action task is an arbitrary task definition that will be created
directly. In cases where the callback would simply call ``queue.createTask``,
a custom action task can be more efficient.
Creating a Callback Action
--------------------------
A *callback action* is an action that calls back into in-tree logic. That is,
you register the action with name, title, description, context, input schema and a
python callback. When the action is triggered in a user interface,
input matching the schema is collected, passed to a new task which then calls
your python callback, enabling it to do pretty much anything it wants to.
To create a new action you must create a file
``taskcluster/taskgraph/actions/my-action.py``, that at minimum contains::
from registry import register_callback_action
@register_callback_action(
name='hello',
title='Say Hello',
symbol='hw', # Show the callback task in treeherder as 'hw'
description="Simple **proof-of-concept** callback action",
order=10000, # Order in which it should appear relative to other actions
)
def hello_world_action(parameters, input, task_group_id, task_id, task):
# parameters is an instance of taskgraph.parameters.Parameters
# it carries decision task parameters from the original decision task.
# input, task_id, and task should all be None
print "Hello was triggered from taskGroupId: " + taskGroupId
The example above defines an action that is available in the context-menu for
the entire task-group (result-set or push in Treeherder terminology). To create
an action that shows up in the context menu for a task we would specify the
``context`` parameter.
Setting the Action Context
..........................
The context parameter should be a list of tag-sets, such as
``context=[{"platform": "linux"}]``, which will make the task show up in the
context-menu for any task with ``task.tags.platform = 'linux'``. Below is
some examples of context parameters and the resulting conditions on
``task.tags`` (tags used below are just illustrative).
``context=[{"platform": "linux"}]``:
Requires ``task.tags.platform = 'linux'``.
``context=[{"kind": "test", "platform": "linux"}]``:
Requires ``task.tags.platform = 'linux'`` **and** ``task.tags.kind = 'test'``.
``context=[{"kind": "test"}, {"platform": "linux"}]``:
Requires ``task.tags.platform = 'linux'`` **or** ``task.tags.kind = 'test'``.
``context=[{}]``:
Requires nothing and the action will show up in the context menu for all tasks.
``context=[]``:
Is the same as not setting the context parameter, which will make the action
show up in the context menu for the task-group.
(i.e., the action is not specific to some task)
The example action below will be shown in the context-menu for tasks with
``task.tags.platform = 'linux'``::
from registry import register_callback_action
@register_callback_action(
name='retrigger',
title='Retrigger',
symbol='re-c', # Show the callback task in treeherder as 're-c'
description="Create a clone of the task",
order=1,
context=[{'platform': 'linux'}]
)
def retrigger_action(parameters, input, task_group_id, task_id, task):
# input will be None
print "Retriggering: {}".format(task_id)
print "task definition: {}".format(task)
When the ``context`` parameter is set, the ``task_id`` and ``task`` parameters
will provided to the callback. In this case the ``task_id`` and ``task``
parameters will be the ``taskId`` and *task definition* of the task from whose
context-menu the action was triggered.
Typically, the ``context`` parameter is used for actions that operate on
tasks, such as retriggering, running a specific test case, creating a loaner,
bisection, etc. You can think of the context as a place the action should
appear, but it's also very much a form of input the action can use.
Specifying an Input Schema
..........................
In call examples so far the ``input`` parameter for the callbacks has been
``None``. To make an action that takes input you must specify an input schema.
This is done by passing a JSON schema as the ``schema`` parameter.
When designing a schema for the input it is important to exploit as many of the
JSON schema validation features as reasonably possible. Furthermore, it is
*strongly* encouraged that the ``title`` and ``description`` properties in
JSON schemas is used to provide a detailed explanation of what the input
value will do. Authors can reasonably expect JSON schema ``description``
properties to be rendered as markdown before being presented.
The example below illustrates how to specify an input schema. Notice that while
this example doesn't specify a ``context`` it is perfectly legal to specify
both ``input`` and ``context``::
from registry import register_callback_action
@register_callback_action(
name='run-all',
title='Run All Tasks',
symbol='ra-c', # Show the callback task in treeherder as 'ra-c'
description="**Run all tasks** that have been _optimized_ away.",
order=1,
input={
'title': 'Action Options',
'description': 'Options for how you wish to run all tasks',
'properties': {
'priority': {
'title': 'priority'
'description': 'Priority that should be given to the tasks',
'type': 'string',
'enum': ['low', 'normal', 'high'],
'default': 'low',
},
'runTalos': {
'title': 'Run Talos'
'description': 'Do you wish to also include talos tasks?',
'type': 'boolean',
'default': 'false',
}
},
'required': ['priority', 'runTalos'],
'additionalProperties': False,
},
)
def retrigger_action(parameters, input, task_group_id, task_id, task):
print "Create all pruned tasks with priority: {}".format(input['priority'])
if input['runTalos']:
print "Also running talos jobs..."
When the ``schema`` parameter is given the callback will always be called with
an ``input`` parameter that satisfies the previously given JSON schema.
It is encouraged to set ``additionalProperties: false``, as well as specifying
all properties as ``required`` in the JSON schema. Furthermore, it's good
practice to provide ``default`` values for properties, as user interface generators
will often take advantage of such properties.
Once you have specified input and context as applicable for your action you can
do pretty much anything you want from within your callback. Whether you want
to create one or more tasks or run a specific piece of code like a test.
Conditional Availability
........................
The decision parameters ``taskgraph.parameters.Parameters`` passed to
the callback are also available when the decision task generates the list of
actions to be displayed in the user interface. When registering an action
callback the ``availability`` option can be used to specify a callable
which, given the decision parameters, determines if the action should be available.
The feature is illustrated below::
from registry import register_callback_action
@register_callback_action(
name='hello',
title='Say Hello',
symbol='hw', # Show the callback task in treeherder as 'hw'
description="Simple **proof-of-concept** callback action",
order=2,
# Define an action that is only included if this is a push to try
available=lambda parameters: parameters.get('project', None) == 'try',
)
def try_only_action(parameters, input, task_group_id, task_id, task):
print "My try-only action"
Properties of ``parameters`` are documented in the
:doc:`parameters section <parameters>`. You can also examine the
``parameters.yml`` artifact created by decisions tasks.
Creating a Custom Action Task
------------------------------
It is possible to define an action that doesn't take a callback. Instead, you'll
then have to provide a task template. For details on how the task template
language works refer to `the actions.json spec`_, the example below illustrates
how to create such an action::
from registry import register_task_action
@register_task_action(
name='retrigger',
title='Retrigger',
description="Create a clone of the task",
order=1,
context=[{'platform': 'linux'}],
input={
'title': 'priority'
'description': 'Priority that should be given to the tasks',
'type': 'string',
'enum': ['low', 'normal', 'high'],
'default': 'low',
},
)
def task_template_builder(parameters):
# The task template builder may return None to signal that the action
# isn't available.
if parameters.get('project', None) != 'try':
return None
return {
'created': {'$fromNow': ''},
'deadline': {'$fromNow': '1 hour'},
'expires': {'$fromNow': '14 days'},
'provisionerId': '...',
'workerType': '...',
'priority': '${input}',
'payload': {
'command': '...',
'env': {
'TASK_DEFINITION': {'$json': {'eval': 'task'}}
},
...
},
# It's now your responsibility to include treeherder routes, as well
# additional metadata for treeherder in task.extra.treeherder.
...
},
This kind of action is useful for creating simple derivative tasks, but is
limited by the expressiveness of the template language. On the other hand, it
is more efficient than an action callback as it does not involve an
intermediate action task before creating the task the user requested.
For further details on the template language, see `the actions.json spec`_.
.. _the actions.json spec: https://docs.taskcluster.net/manual/tasks/actions/spec