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