Bug 1389248 - Move actions spec to taskcluster docs r=dustin
authorBrian Stack <bstack@mozilla.com>
Thu, 10 Aug 2017 13:59:05 -0700
changeset 374317 f245b7faed786451401f421d70c5688b880d64d8
parent 374316 64e7486cf038872939913a21c2e1e68f09cb1000
child 374318 5c6868214a60a66a9e54abef547007eace5c7436
push id32320
push userarchaeopteryx@coole-files.de
push dateSat, 12 Aug 2017 21:35:10 +0000
treeherdermozilla-central@6062341662fc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdustin
bugs1389248
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1389248 - Move actions spec to taskcluster docs r=dustin MozReview-Commit-ID: BsYRmivUZZ7
taskcluster/docs/action-implementation.rst
taskcluster/docs/action-spec.rst
taskcluster/docs/action-uis.rst
taskcluster/docs/actions-schema.yml
taskcluster/docs/actions.rst
deleted file mode 100644
--- a/taskcluster/docs/action-implementation.rst
+++ /dev/null
@@ -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>`.
deleted file mode 100644
--- a/taskcluster/docs/action-spec.rst
+++ /dev/null
@@ -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
deleted file mode 100644
--- a/taskcluster/docs/action-uis.rst
+++ /dev/null
@@ -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.
deleted file mode 100644
--- a/taskcluster/docs/actions-schema.yml
+++ /dev/null
@@ -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
--- a/taskcluster/docs/actions.rst
+++ b/taskcluster/docs/actions.rst
@@ -1,13 +1,14 @@
 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:
 
  * The decision task produces an artifact, ``public/actions.json``, indicating
    what actions are available.
 
  * A user interface (for example, Treeherder or the Taskcluster tools) consults
    ``actions.json`` and presents appropriate choices to the user, if necessary
@@ -15,14 +16,248 @@ At a very high level, the process looks 
    re-trigger a test case.
 
  * The user interface follows the action description to carry out the action.
    In most cases (``action.kind == 'task'``), that entails creating an "action
    task", including the provided information. That action task is responsible
    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.
+
+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
 
-    action-spec
-    action-uis
-    action-implementation
+  @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