.. _writing-tasks-for-taskotron: =========================== Writing Tasks for Taskotron =========================== While the eventual target for most tasks will be to run in a production system, one of libtaskotron's features is that it can be run without the overhead of a full production-like system. To learn more about what can be done with taskotron, see :ref:`what-can-i-do-with-taskotron`. .. warning:: Taskotron is still **very** young. At the moment, this document is both the direction that we want to take in the near future and features that currently exist in libtaskotron. If you follow this tutorial, be aware that things will be changing. Do not expect libtaskotron to have a stable API yet and be aware that the interfaces may change drastically with little notice. Once the interfaces stabilize a bit more, this warning will be edited or removed. In order to write tasks, you must :ref:`install-libtaskotron` on your local machine if you aren't running from git. Examples ======== examplebodhi ------------- `examplebodhi task git repository `_ The examplebodhi task is a trivial example which takes an update and determines a PASS/FAIL results in a pseudo-random fashion. This task wasn't designed to be used in production but is useful as a starting point and for development purposes. rpmlint ------- `rpmlint task git repository `_ rpmlint is the simplest of the production tasks. It takes a koji build and runs rpmlint on it, reporting results as TAP13. Creating a New Task =================== While running rpmlint is an easy and trivial example, that doesn't help with creating something new. To write a new task, you'll need some information: * What type of object will the task be run against? * What will be the name of the task? * What are the dependencies? The taskotron runner works on specially formatted YAML files (see :ref:`taskotron-yaml-format` for details) which describe the task that is to be executed. In addition to the task YAML file, any other code or resources needed for task execution should be accessible by the runner. A directory layout could look like:: mytask/ mytask.yml mytask.py someresource.txt .. note:: Taskotron was designed to work with tasks stored in git repostitories and while this is not strictly required for the local execution case, it is highly recommended that you limit tasks to one per directory. Non-Executed Task Information ----------------------------- For this example, let's write a task that runs on bodhi ids. Looking at the non-executed task data needed in :file:`mytask.yml`: .. code-block:: yaml --- name: mytask desc: do something stupendous maintainer: somefasuser input: args: - arch - bodhi_id environment: rpm: - libtaskotron Executed Task Information ------------------------- Let's have this new task do the following: * download the rpms contained in the indicated bodhi update * run the function ``run_mytask`` in :file:`mytask.py` using a list of the downloaded rpms as input * report the results to resultsdb .. code-block:: python # this is a trivial example that doesn't do everything we want it to do, more # details will be added later in the example def run_mytask(rpmfiles, bodhi_id): print "Running mytask on %s" % bodhi_id .. code-block:: yaml task: # we can use bodhi_id and arch as variables here because they're defined # for us by the runner which also verifies that all required input listed # above is present before executing the task - name: download bodhi update bodhi: action: download bodhi_id: ${bodhi_id} arch: ${arch} export: bodhi_downloads # notice how the third item under python is 'rpmfiles' and this matches # the arg name in mytask.py. Anything outside of 'file' and 'callable' # are passed in as kwargs to the specified python callable. - name: run mytask python: file: mytask.py callable: run_mytask rpmfiles: ${bodhi_downloads} bodhi_id: ${bodhi_id} export: mytask_output # this assumes that the output from mytask is valid TAP - name: report mytask results to resultsdb resultsdb: results: ${mytask_output} checkname: 'mytask' .. this needs to be fixed, this output is doctored since it doesn't actually work. We can execute this new task using:: $ python runtask.py -i foo-1.2-3.fc99 -t bodhi_id -a x86_64 ../task-mytask/mytask.yml The output from this task should end with:: Running mytask on foo-1.2-3.fc99.x86_64.rpm [libtaskotron:logger.py:34] 2014-05-14 21:30:30 CRITICAL Traceback (most recent call last): File "runtask.py", line 4, in runner.main() File "/home/tflink/code/taskotron/libtaskotron/libtaskotron/runner.py", line 192, in main task_runner.run() File "/home/tflink/code/taskotron/libtaskotron/libtaskotron/runner.py", line 31, in run self.do_actions() File "/home/tflink/code/taskotron/libtaskotron/libtaskotron/runner.py", line 105, in do_actions self.do_single_action(action) File "/home/tflink/code/taskotron/libtaskotron/libtaskotron/runner.py", line 94, in do_single_action self.envdata) File "/home/tflink/code/taskotron/libtaskotron/libtaskotron/directives/resultsdb_directive.py", line 136, in process raise TaskotronDirectiveError("Failed to load 'results': %s" % e.message) TaskotronDirectiveError: Failed to load 'results': Failed to parse TAP contents: Missing plan in the TAP source We're not actually passing TAP output from our task, so let's get that fixed. Actually Doing Something ------------------------ The Python code for ``mytask.py`` above doesn't really do anything at all, so let's make it do something: * Change the task to create a result from the input bodhi update id and list all of the rpms contained in that update as details. There is a bit of complexity to the `TAP13 specification `_ but generating TAP from python tasks in Taskotron isn't very difficult. Our original task looked like: .. code-block:: python # this is a trivial example that doesn't do everything we want it to do, more # details will be added later in the example def run_mytask(rpmfiles): for rpmfile in rpmfiles: print "Running mytask on %s" % rpmfile To create valid TAP, we want to populate a :py:class:`libtaskotron.check.CheckDetail` object and use :py:meth:`libtaskotron.check.export_TAP` to generate valid TAP13 with the data required for reporting. .. code-block:: python from libtaskotron import check def run_mytask(rpmfiles, bodhi_id): """run through all passed in rpmfiles and emit TAP13 saying they all passed""" print "Running mytask on %s" % bodhi_id details = [] result = 'PASSED' summary = 'mycheck %s for %s' % (result, bodhi_id) detail = check.CheckDetail(bodhi_id, check.ReportType.BODHI_UPDATE, result, summary) for rpmfile in rpmfiles: detail.store(rpmfile, False) return check.export_TAP(detail) Now if we run the task we get output that ends with:: [libtaskotron:resultsdb_directive.py:123] 2014-05-15 09:17:44 INFO Reporting to ResultsDB is disabled. INFO:libtaskotron:Reporting to ResultsDB is disabled. [libtaskotron:runner.py:198] 2014-05-15 09:17:44 INFO TAP version 13 1..1 ok - $CHECKNAME for Bodhi update openvswitch-2.1.2-1.fc19 --- details: output: "foo-1.2-3.fc99.x86_64.rpm" item: foo-1.2-3.fc99 outcome: PASSED summary: mycheck PASSED for foo-1.2-3.fc99 type: bodhi_update ... .. note:: Reporting is disabled in the default configuration for Taskotron. While it is possible to set up a local resultsdb instance to check reporting, we want to make it easier than that and will update these docs when that easier method is available. For now, if the resultsdb directive doesn't throw exceptions, it's reasonable to assume that the TAP output was constructed correctly and is reasonably valid.