Writing Koji plugins¶
Depending on what you are trying to do, there are different ways to write a Koji plugin.
Each is described in this file, by use case.
Adding new task types¶
Koji can do several things, for example build RPMs, or live CDs. Those are types of tasks which Koji knows about.
If you need to do something which Koji does not know yet how to do, you could create a Koji Builder plugin.
Such a plugin would minimally look like this:
from koji.tasks import BaseTaskHandler
class MyTask(BaseTaskHandler):
Methods = ['mytask']
_taskWeight = 2.0
def handler(self, arg1, arg2, kwarg1=None):
self.logger.debug("Running my task...")
# Here is where you actually do something
A few explanations on what goes on here:
Your task needs to inherit from
koji.tasks.BaseTaskHandler
Your task must have a
Methods
attribute, which is a list of the method names your task can handle.You can specify the weight of your task with the
_taskWeight
attribute. The more intensive (CPU, IO, …) your task is, the higher this number should be.The task object has a
logger
attribute, which is a Python logger with the usualdebug
,info
,warning
anderror
methods. The messages you send with it will end up in the Koji Builder logs (kojid.log
)Your task must have a
handler()
method. That is the method Koji will call to run your task. It is the method that should actually do what you need. It can have as many positional and named arguments as you want.
Save your plugin as e.g mytask.py
, then install it in the Koji
Builder plugins folder: /usr/lib/koji-builder-plugins/
Finally, edit the Koji Builder config file, /etc/kojid/kojid.conf
:
# A space-separated list of plugins to enable
plugins = mytask
Restart the Koji Builder service, and your plugin will be enabled.
You can try running a task from your new task type with the command-line:
$ koji make-task mytask arg1 arg2 kwarg1
Exporting new API methods over XMLRPC¶
Koji clients talk to the Koji Hub via an XMLRPC API.
It is sometimes desirable to add to that API, so that clients can request things Koji does not expose right now.
Such a plugin would minimally look like this:
def mymethod(arg1, arg2, kwarg1=None):
context.session.assertPerm('admin')
# Here is where you actually do something
mymethod.exported = True
A few explanations on what goes on here:
Your plugin is just a method, with whatever positional and/or named arguments you need.
You must export your method by setting its
exported
attribute toTrue
The
context.session.assertPerm('admin')
is how you ensure that only the user with administrator privileges can use this call. Read-only methods can be (in most cases) public, so such line is not needed.
Save your plugin as e.g mymethod.py
, then install it in the Koji Hub
plugins folder: /usr/lib/koji-hub-plugins/
Finally, edit the Koji Hub config file, /etc/koji-hub/hub.conf
:
# A space-separated list of plugins to enable
Plugins = mymethod
Restart the Koji Hub service, and your plugin will be enabled.
You can try calling the new XMLRPC API with the Python client library:
>>> import koji
>>> session = koji.ClientSession("http://koji/example.org/kojihub")
>>> session.mymethod(arg1, arg2, kwarg1='some value')
Ensuring the user has the required permissions¶
If you want your new XMLRPC API to require specific permissions from the user, all you need to do is add the following to your method:
from koji.context import context
def mymethod(arg1, arg2, kwarg1=None):
context.session.assertPerm("admin")
# Here is where you actually do something
mymethod.exported = True
In the example above, Koji will ensure that the user is an administrator. You could of course create your own permission, and check for that.
Running code automatically triggered on events¶
You might want to run something automatically when something else happens in Koji.
A typical example is to automatically sign a package right after a build finished. Another would be to send a notification to a message bus after any kind of event.
This can be achieved with a plugin, which would look minimally as follows:
from koji.plugin import callback
@callback('preTag', 'postTag')
def mycallback(cbtype, tag, build, user, force=False):
# Here is where you actually do something
A few explanations on what goes on here:
The
@callback
decorator allows you to declare which events should trigger your function. You can pass as many as you want. For a list of supported events, seekoji/plugins.py
.The arguments of the function depend on the event you subscribed to. As a result, you need to know how it will be called by Koji. You probably should use
*kwargs
to be safe. You can see how callbacks are called in thehub/kojihub.py
file, search for calls of therun_callbacks
function.
Save your plugin as e.g mycallback.py
, then install it in the Koji
Hub plugins folder: /usr/lib/koji-hub-plugins
Finally, edit the Koji Hub config file, /etc/koji-hub/hub.conf
:
# A space-separated list of plugins to enable
Plugins = mycallback
Restart the Koji Hub service, and your plugin will be enabled.
You can try triggering your callback plugin with the command-line. For
example, if you registered a callback for the postTag
event, try
tagging a build:
$ koji tag-build mytag mypkg-1.0-1
List of callbacks¶
hub:
preBuildStateChange
preImport
prePackageListChange
preRPMSign
preRepoDone
preRepoInit
preTag
preTaskStateChange
preUntag
postBuildStateChange
postImport
postPackageListChange
postRPMSign
postRepoDone
postRepoInit
postTag
postTaskStateChange
postUntag
builder:
preSCMCheckout
postSCMCheckout
postCreateDistRepo
postCreateRepo
New command for CLI¶
When you add new XMLRPC call or just wanted to do some more complicated things with API, you can benefit from writing a new command for CLI.
Most simple command would look like this:
from koji.plugin import export_cli
@export_cli
def anon_handle_echo(options, session, args):
"[info] Print arguments"
usage = "usage: %prog echo <message>"
parser = OptionParser(usage=usage)
(opts, args) = parser.parse_args(args)
print(args[0])
@export_cli
is a decorator which registers a new command. The command
name is derived from name of the function. The function name must start with
either anon_handle_
or handle_
. The rest of the name becomes the name of
the command.
In the first case, the command will not automatically
authenticate with the hub (though the user can still override
this behavior with --force-auth
option). In the second case, the command
will perform authentication by default (this too can be overridden by the
user with the --noauth
option).
The example above is very simplistic. We recommend that developers also
examine the actual calls included in Koji. The built in commands live in
koji_cli.commands
and our standard cli plugins live in plugins/cli
.
Koji provides some important functions via in the client cli library
(koji_cli.lib
) for use by cli commands. Some notable examples are:
activate_session(session, options)
- It is needed to authenticate against hub. Both parameters are same as those passed to handler.
watch_tasks(session, tasklist, quiet=False, poll_interval=60)
- It is the same function used e.g. inbuild
command for waiting for spawned tasks.
list_task_output_all_volumes(session, task_id)
- wrapper function forlistTaskOutput
with different versions of hub.
Final command has to be saved in python system-wide library path - e.g. in
/usr/lib/python3.4/site-packages/koji_cli_plugins
. Filename doesn’t matter
as all files in this directory are searched for @export_cli
macros. Note,
that python 3 variant of CLI is looking to different directory than python 2
one.
CLI plugins structure will be extended (made configurable and allowing more than just adding commands - e.g. own authentication methods, etc.) in future.
Pull requests¶
These plugins have to be written in python 2.6+/3.x compatible way. We are using six library to support this, so we will also prefer pull requests written this way. CLI (and client library) is meant to be fully compatible with python 3 from koji 1.13.
Tests are also recommended for PR. For example one see
tests/test_plugins/test_runroot_cli.py
.