1Actions 2======= 3 4This document shows how to define an action in-tree such that it shows up in 5supported user interfaces like Treeherder. For details on interface between 6in-tree logic and external user interfaces, see `the actions.json spec`_. 7 8At a very high level, the process looks like this: 9 10 * The decision task produces an artifact, ``public/actions.json``, indicating 11 what actions are available. 12 13 * A user interface (for example, Treeherder or the Taskcluster tools) consults 14 ``actions.json`` and presents appropriate choices to the user, if necessary 15 gathering additional data from the user, such as the number of times to 16 re-trigger a test case. 17 18 * The user interface follows the action description to carry out the action. 19 In most cases (``action.kind == 'task'``), that entails creating an "action 20 task", including the provided information. That action task is responsible 21 for carrying out the named action, and may create new sub-tasks if necessary 22 (for example, to re-trigger a task). 23 24Defining Action Tasks 25--------------------- 26 27There is one options for defining actions: creating a callback action. 28A callback action automatically defines an action task that will invoke a 29Python function of your devising. 30 31Creating a Callback Action 32-------------------------- 33 34.. note:: 35 36 You can generate ``actions.json`` on the command line with ``./mach taskgraph actions``. 37 38A *callback action* is an action that calls back into in-tree logic. That is, 39you register the action with name, title, description, context, input schema and a 40python callback. When the action is triggered in a user interface, 41input matching the schema is collected, passed to a new task which then calls 42your python callback, enabling it to do pretty much anything it wants to. 43 44To create a new callback action you must create a file 45``taskcluster/gecko_taskgraph/actions/my-action.py``, that at minimum contains:: 46 47 from __future__ import absolute_import, print_function, unicode_literals 48 49 from .registry import register_callback_action 50 51 @register_callback_action( 52 name='hello', 53 title='Say Hello', 54 symbol='hw', # Show the callback task in treeherder as 'hw' 55 description="Simple **proof-of-concept** callback action", 56 order=10000, # Order in which it should appear relative to other actions 57 ) 58 def hello_world_action(parameters, graph_config, input, task_group_id, task_id, task): 59 print("Hello was triggered from taskGroupId: {}".format(task_group_id)) 60 61The arguments are: 62 63``parameters`` 64 an instance of ``taskgraph.parameters.Parameters``, carrying decision task parameters from the original decision task. 65 66``graph_config`` 67 an instance of ``taskgraph.config.GraphConfig``, carrying configuration for this tree 68 69``input`` 70 the input from the user triggering the action (if any) 71 72``task_group_id`` 73 the target task group on which this action should operate 74 75``task_id`` 76 the target task on which this action should operate (or None if it is operating on the whole group) 77 78``task`` 79 the definition of the target task (or None, as for ``task_id``) 80 81The example above defines an action that is available in the context-menu for 82the entire task-group (result-set or push in Treeherder terminology). To create 83an action that shows up in the context menu for a task we would specify the 84``context`` parameter. 85 86The ``order`` value is the sort key defining the order of actions in the 87resulting ``actions.json`` file. If multiple actions have the same name and 88match the same task, the action with the smallest ``order`` will be used. 89 90Setting the Action Context 91.......................... 92The context parameter should be a list of tag-sets, such as 93``context=[{"platform": "linux"}]``, which will make the task show up in the 94context-menu for any task with ``task.tags.platform = 'linux'``. Below is 95some examples of context parameters and the resulting conditions on 96``task.tags`` (tags used below are just illustrative). 97 98``context=[{"platform": "linux"}]``: 99 Requires ``task.tags.platform = 'linux'``. 100``context=[{"kind": "test", "platform": "linux"}]``: 101 Requires ``task.tags.platform = 'linux'`` **and** ``task.tags.kind = 'test'``. 102``context=[{"kind": "test"}, {"platform": "linux"}]``: 103 Requires ``task.tags.platform = 'linux'`` **or** ``task.tags.kind = 'test'``. 104``context=[{}]``: 105 Requires nothing and the action will show up in the context menu for all tasks. 106``context=[]``: 107 Is the same as not setting the context parameter, which will make the action 108 show up in the context menu for the task-group. 109 (i.e., the action is not specific to some task) 110 111The example action below will be shown in the context-menu for tasks with 112``task.tags.platform = 'linux'``:: 113 114 from registry import register_callback_action 115 116 @register_callback_action( 117 name='retrigger', 118 title='Retrigger', 119 symbol='re-c', # Show the callback task in treeherder as 're-c' 120 description="Create a clone of the task", 121 order=1, 122 context=[{'platform': 'linux'}] 123 ) 124 def retrigger_action(parameters, graph_config, input, task_group_id, task_id, task): 125 # input will be None 126 print "Retriggering: {}".format(task_id) 127 print "task definition: {}".format(task) 128 129When the ``context`` parameter is set, the ``task_id`` and ``task`` parameters 130will provided to the callback. In this case the ``task_id`` and ``task`` 131parameters will be the ``taskId`` and *task definition* of the task from whose 132context-menu the action was triggered. 133 134Typically, the ``context`` parameter is used for actions that operate on 135tasks, such as retriggering, running a specific test case, creating a loaner, 136bisection, etc. You can think of the context as a place the action should 137appear, but it's also very much a form of input the action can use. 138 139 140Specifying an Input Schema 141.......................... 142In call examples so far the ``input`` parameter for the callbacks has been 143``None``. To make an action that takes input you must specify an input schema. 144This is done by passing a JSON schema as the ``schema`` parameter. 145 146When designing a schema for the input it is important to exploit as many of the 147JSON schema validation features as reasonably possible. Furthermore, it is 148*strongly* encouraged that the ``title`` and ``description`` properties in 149JSON schemas is used to provide a detailed explanation of what the input 150value will do. Authors can reasonably expect JSON schema ``description`` 151properties to be rendered as markdown before being presented. 152 153The example below illustrates how to specify an input schema. Notice that while 154this example doesn't specify a ``context`` it is perfectly legal to specify 155both ``input`` and ``context``:: 156 157 from registry import register_callback_action 158 159 @register_callback_action( 160 name='run-all', 161 title='Run All Tasks', 162 symbol='ra-c', # Show the callback task in treeherder as 'ra-c' 163 description="**Run all tasks** that have been _optimized_ away.", 164 order=1, 165 input={ 166 'title': 'Action Options', 167 'description': 'Options for how you wish to run all tasks', 168 'properties': { 169 'priority': { 170 'title': 'priority' 171 'description': 'Priority that should be given to the tasks', 172 'type': 'string', 173 'enum': ['low', 'normal', 'high'], 174 'default': 'low', 175 }, 176 'runTalos': { 177 'title': 'Run Talos' 178 'description': 'Do you wish to also include talos tasks?', 179 'type': 'boolean', 180 'default': 'false', 181 } 182 }, 183 'required': ['priority', 'runTalos'], 184 'additionalProperties': False, 185 }, 186 ) 187 def retrigger_action(parameters, graph_config, input, task_group_id, task_id, task): 188 print "Create all pruned tasks with priority: {}".format(input['priority']) 189 if input['runTalos']: 190 print "Also running talos jobs..." 191 192When the ``schema`` parameter is given the callback will always be called with 193an ``input`` parameter that satisfies the previously given JSON schema. 194It is encouraged to set ``additionalProperties: false``, as well as specifying 195all properties as ``required`` in the JSON schema. Furthermore, it's good 196practice to provide ``default`` values for properties, as user interface generators 197will often take advantage of such properties. 198 199It is possible to specify the ``schema`` parameter as a callable that returns 200the JSON schema. It will be called with a keyword parameter ``graph_config`` 201with the `graph configuration <taskgraph-graph-config>` of the current 202taskgraph. 203 204Once you have specified input and context as applicable for your action you can 205do pretty much anything you want from within your callback. Whether you want 206to create one or more tasks or run a specific piece of code like a test. 207 208Conditional Availability 209........................ 210 211The decision parameters ``taskgraph.parameters.Parameters`` passed to 212the callback are also available when the decision task generates the list of 213actions to be displayed in the user interface. When registering an action 214callback the ``availability`` option can be used to specify a callable 215which, given the decision parameters, determines if the action should be available. 216The feature is illustrated below:: 217 218 from registry import register_callback_action 219 220 @register_callback_action( 221 name='hello', 222 title='Say Hello', 223 symbol='hw', # Show the callback task in treeherder as 'hw' 224 description="Simple **proof-of-concept** callback action", 225 order=2, 226 # Define an action that is only included if this is a push to try 227 available=lambda parameters: parameters.get('project', None) == 'try', 228 ) 229 def try_only_action(parameters, graph_config, input, task_group_id, task_id, task): 230 print "My try-only action" 231 232Properties of ``parameters`` are documented in the 233:doc:`parameters section <parameters>`. You can also examine the 234``parameters.yml`` artifact created by decisions tasks. 235 236Context can be similarly conditionalized by passing a function which returns 237the appropriate context:: 238 239 context=lambda params: 240 [{}] if int(params['level']) < 3 else [{'worker-implementation': 'docker-worker'}], 241 242Creating Tasks 243-------------- 244 245The ``create_tasks`` utility function provides a full-featured way to create 246new tasks. Its features include creating prerequisite tasks, operating in a 247"testing" mode with ``./mach taskgraph test-action-callback``, and generating 248artifacts that can be used by later action tasks to figure out what happened. 249See the source for more detailed docmentation. 250 251The artifacts are: 252 253``task-graph.json`` (or ``task-graph-<suffix>.json``: 254 The graph of all tasks created by the action task. Includes tasks 255 created to satisfy requirements. 256``to-run.json`` (or ``to-run-<suffix>.json``: 257 The set of tasks that the action task requested to build. This does not 258 include the requirements. 259``label-to-taskid.json`` (or ``label-to-taskid-<suffix>.json``: 260 This is the mapping from label to ``taskid`` for all tasks involved in 261 the task-graph. This includes dependencies. 262 263More Information 264---------------- 265 266For further details on actions in general, see `the actions.json spec`_. 267The hooks used for in-tree actions are set up by `ci-admin`_ based on configuration in `ci-configuration`_. 268 269.. _the actions.json spec: https://firefox-ci-tc.services.mozilla.com/docs/manual/tasks/actions/spec 270.. _ci-admin: http://hg.mozilla.org/ci/ci-admin/ 271.. _ci-configuration: http://hg.mozilla.org/ci/ci-configuration/ 272