<div dir="ltr">Really nice to see this documented.<div><br></div><div>+1 ack</div><div><br></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Fri, Jul 10, 2015 at 5:36 PM, Adam Miller <span dir="ltr"><<a href="mailto:maxamillion@fedoraproject.org" target="_blank">maxamillion@fedoraproject.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div class="HOEnZb"><div class="h5">On Fri, Jul 10, 2015 at 5:33 PM, Mathieu Bridon <<a href="mailto:bochecha@daitauha.fr">bochecha@daitauha.fr</a>> wrote:<br>
> ---<br>
><br>
> Koji supports different types of plugins.<br>
><br>
> This commit documents the 3 types that I know of, but I have absolutely<br>
> no idea if there are others.<br>
><br>
> docs/Writing_a_plugin.md | 153 +++++++++++++++++++++++++++++++++++++++++++++++<br>
> 1 file changed, 153 insertions(+)<br>
> create mode 100644 docs/Writing_a_plugin.md<br>
><br>
> diff --git a/docs/Writing_a_plugin.md b/docs/Writing_a_plugin.md<br>
> new file mode 100644<br>
> index 0000000..36cddd9<br>
> --- /dev/null<br>
> +++ b/docs/Writing_a_plugin.md<br>
> @@ -0,0 +1,153 @@<br>
> +# Writing Koji plugins<br>
> +<br>
> +Depending on what you are trying to do, there are different ways to write a<br>
> +Koji plugin.<br>
> +<br>
> +Each is described in this file, by use case.<br>
> +<br>
> +## Adding new task types<br>
> +<br>
> +Koji can do several things, for example build RPMs, or live CDs. Those are<br>
> +types of tasks which Koji knows about.<br>
> +<br>
> +If you need to do something which Koji does not know yet how to do, you could<br>
> +create a Koji Builder plugin.<br>
> +<br>
> +Such a plugin would minimally look like this:<br>
> +<br>
> + from koji.tasks import BaseTaskHandler<br>
> +<br>
> + class MyTask(BaseTaskHandler):<br>
> + Methods = ['mytask']<br>
> + _taskWeight = 2.0<br>
> +<br>
> + def handler(self, arg1, arg2, kwarg1=None):<br>
> + self.logger.debug("Running my task...")<br>
> +<br>
> + # Here is where you actually do something<br>
> +<br>
> +A few explanations on what goes on here:<br>
> +<br>
> +* Your task needs to inherit from `koji.tasks.BaseTaskHandler`<br>
> +* Your task must have a `Methods` attribute, which is a list of the method<br>
> + names your task can handle.<br>
> +* You can specify the weight of your task with the `_taskWeight` attribute.<br>
> + The more intensive (CPU, IO, ...) your task is, the higher this number<br>
> + should be.<br>
> +* The task object has a `logger` attribute, which is a Python logger with the<br>
> + usual `debug`, `info`, `warning` and `error` methods. The messages you send<br>
> + with it will end up in the Koji Builder logs (`kojid.log`)<br>
> +* Your task must have a `handler()` method. That is the method Koji will call<br>
> + to run your task. It is the method that should actually do what you need. It<br>
> + can have as many positional and named arguments as you want.<br>
> +<br>
> +Save your plugin as e.g `mytask.py`, then install it in the Koji Builder<br>
> +plugins folder: `/usr/lib/koji-builder-plugins/`<br>
> +<br>
> +Finally, edit the Koji Builder config file, `/etc/kojid/kojid.conf`:<br>
> +<br>
> + # A space-separated list of plugins to enable<br>
> + plugins = mytask<br>
> +<br>
> +Restart the Koji Builder service, and your plugin will be enabled.<br>
> +<br>
> +You can try running a task from your new task type with the command-line:<br>
> +<br>
> + $ koji make-task mytask arg1 arg2 kwarg1<br>
> +<br>
> +## Exporting new API methods over XMLRPC<br>
> +<br>
> +Koji clients talk to the Koji Hub via an XMLRPC API.<br>
> +<br>
> +It is sometimes desirable to add to that API, so that clients can request<br>
> +things Koji does not expose right now.<br>
> +<br>
> +Such a plugin would minimally look like this:<br>
> +<br>
> + def mymethod(arg1, arg2, kwarg1=None):<br>
> + # Here is where you actually do something<br>
> +<br>
> + mymethod.exported = True<br>
> +<br>
> +A few explanations on what goes on here:<br>
> +<br>
> +* Your plugin is just a method, with whatever positional and/or named<br>
> + arguments you need.<br>
> +* You must export your method by setting its `exported` attribute to `True`<br>
> +* The `context.session.assertPerm()` is how you ensure that the<br>
> +<br>
> +Save your plugin as e.g `mymethod.py`, then install it in the Koji Hub plugins<br>
> +folder: `/usr/lib/koji-hub-plugins/`<br>
> +<br>
> +Finally, edit the Koji Hub config file, `/etc/koji-hub/hub.conf`:<br>
> +<br>
> + # A space-separated list of plugins to enable<br>
> + Plugins = mymethod<br>
> +<br>
> +Restart the Koji Hub service, and your plugin will be enabled.<br>
> +<br>
> +You can try calling the new XMLRPC API with the Python client library:<br>
> +<br>
> + >>> import koji<br>
> + >>> session = koji.ClientSession("<a href="http://koji/example.org/kojihub" rel="noreferrer" target="_blank">http://koji/example.org/kojihub</a>")<br>
> + >>> session.mymethod(arg1, arg2, kwarg1='some value')<br>
> +<br>
> +### Ensuring the user has the required permissions<br>
> +<br>
> +If you want your new XMLRPC API to require specific permissions from the user,<br>
> +all you need to do is add the following to your method:<br>
> +<br>
> + from koji.context import context<br>
> +<br>
> + def mymethod(arg1, arg2, kwarg1=None):<br>
> + context.session.assertPerm("admin")<br>
> +<br>
> + # Here is where you actually do something<br>
> +<br>
> + mymethod.exported = True<br>
> +<br>
> +In the example above, Koji will ensure that the user is an administrator. You<br>
> +could of course create your own permission, and check for that.<br>
> +<br>
> +## Running code automatically triggered on events<br>
> +<br>
> +You might want to run something automatically when something else happens in<br>
> +Koji.<br>
> +<br>
> +A typical example is to automatically sign a package right after a build<br>
> +finished. Another would be to send a notification to a message bus after any<br>
> +kind of event.<br>
> +<br>
> +This can be achieved with a plugin, which would look minimally as follows:<br>
> +<br>
> + from koji.plugin import callback<br>
> +<br>
> + @callback('preTag', 'postTag')<br>
> + def mycallback(cbtype, tag, build, user, force=False):<br>
> + # Here is where you actually do something<br>
> +<br>
> +A few explanations on what goes on here:<br>
> +<br>
> +* The `@callback` decorator allows you to declare which events should trigger<br>
> + your function. You can pass as many as you want. For a list of supported<br>
> + events, see `koji/plugins.py`.<br>
> +* The arguments of the function depend on the event you subscribed to. As a<br>
> + result, you need to know how it will be called by Koji. You probably should<br>
> + use `*kwargs` to be safe. You can see how callbacks are called in the<br>
> + `hub/kojihub.py` file, search for calls of the `run_callbacks` function.<br>
> +<br>
> +Save your plugin as e.g `mycallback.py`, then install it in the Koji Hub<br>
> +plugins folder: `/usr/lib/koji-hub-plugins`<br>
> +<br>
> +Finally, edit the Koji Hub config file, `/etc/koji-hub/hub.conf`:<br>
> +<br>
> + # A space-separated list of plugins to enable<br>
> + Plugins = mycallback<br>
> +<br>
> +Restart the Koji Hub service, and your plugin will be enabled.<br>
> +<br>
> +You can try triggering your callback plugin with the command-line. For<br>
> +example, if you registered a callback for the `postTag` event, try tagging a<br>
> +build:<br>
> +<br>
> + $ koji tag-build mytag mypkg-1.0-1<br>
> --<br>
> 2.4.3<br>
><br>
<br>
</div></div>Can I +1 more than once? ;)<br>
<br>
But no, seriously this is amazing. Thank you so much for writing this.<br>
<br>
For whatever it's worth: +1<br>
<br>
-AdamM<br>
<div class="HOEnZb"><div class="h5"><br>
> --<br>
> buildsys mailing list<br>
> <a href="mailto:buildsys@lists.fedoraproject.org">buildsys@lists.fedoraproject.org</a><br>
> <a href="https://admin.fedoraproject.org/mailman/listinfo/buildsys" rel="noreferrer" target="_blank">https://admin.fedoraproject.org/mailman/listinfo/buildsys</a><br>
--<br>
buildsys mailing list<br>
<a href="mailto:buildsys@lists.fedoraproject.org">buildsys@lists.fedoraproject.org</a><br>
<a href="https://admin.fedoraproject.org/mailman/listinfo/buildsys" rel="noreferrer" target="_blank">https://admin.fedoraproject.org/mailman/listinfo/buildsys</a></div></div></blockquote></div><br><br clear="all"><div><br></div>-- <br><div class="gmail_signature"><br>-Jon Disnard</div>
</div>