Triggers
The package provides ability to write custom extensions which will be run on (the most) actions, e.g. after updates. By default ahriman provides several types of extensions - reporting, files uploading, PKGBUILD synchronization, repository archiving, housekeeping and distributed builds support. Each extension must derive from the ahriman.core.triggers.Trigger class and should implement at least one of the abstract methods:
on_result- trigger action which will be called after build process, the build result and the list of repository packages will be supplied as arguments.on_start- trigger action which will be called right before the start of the application process.on_stop- action which will be called right before the exit.
Note, it isn’t required to implement all of those methods (or even one of them), however, it is highly recommended to avoid trigger actions in __init__ method as it will be run on any application start (e.g. even if you are just searching in AUR).
Built-in triggers
For the configuration details and settings explanation kindly refer to the documentation.
ahriman.core.archive.ArchiveTrigger
This trigger provides date-based snapshots of the repository. It organizes packages into a daily directory tree (repos/YYYY/MM/DD) with its own pacman database. On each run it creates symlinks from the daily snapshot to the actual package archives and maintains the database accordingly. It also takes care of cleaning up broken symlinks and empty directories for packages which have been removed.
ahriman.core.distributed.WorkerLoaderTrigger
Special trigger to be used to load workers from database on the start of the application rather than configuration. If the option is already set, it will skip processing.
ahriman.core.distributed.WorkerTrigger
Another trigger for the distributed system, which registers itself as remote worker, calling remote service periodically.
ahriman.core.gitremote.RemotePullTrigger
This trigger will be called before any action (on_start) and pulls remote PKGBUILD repository locally; after that it copies found PKGBUILDs from the cloned repository to the local cache. It is useful in case if you have patched PGKBUILDs (or even missing in AUR) which you would like to use for package building and, technically, just simplifies the local package building.
In order to update those packages you would need to clone your repository separately, make changes in PKGBUILD (e.g. bump version and update checksums), commit them and push back. On the next ahriman’s repository update, it will pull changes you committed and will perform package update.
ahriman.core.gitremote.RemotePushTrigger
This trigger will be called right after build process (on_result). It will pick PKGBUILDs for the updated packages, pull them (together with any other files) and commit and push changes to remote repository. No real use cases, but the most of user repositories do it.
ahriman.core.housekeeping.ArchiveRotationTrigger
This trigger removes old package versions from the archive directory. It implements on_result and, after each build, compares available versions for updated packages and removes the older ones, keeping only the last N versions as configured by keep_built_packages option.
ahriman.core.housekeeping.LogsRotationTrigger
Simple trigger to rotate build logs. It implements on_result and removes old log records after each build process, keeping only the last N records as configured by keep_last_logs option.
ahriman.core.report.ReportTrigger
Trigger which can be used for reporting. It implements on_result method and thus being called on each build update and generates report (e.g. html, telegram etc) according to the current settings.
ahriman.core.support.KeyringTrigger
Generator for keyring package. This trigger will extract keys from local keychain and pack them into keyring specific format. This trigger will generate sources including PKGBUILD, which can be used later for package building.
ahriman.core.support.MirrorlistTrigger
Simple generator for mirrorlist package, based on the URLs which were set by configuration. This trigger will generate sources including PKGBUILD, which can be used later for package building.
ahriman.core.upload.UploadTrigger
This trigger takes build result (on_result) and performs syncing of the local packages to the remote mirror (e.g. S3 or just by rsync).
Context variables
By default, only configuration and architecture are passed to triggers. However, some triggers might want to have access to other high-level wrappers. In order to provide such ability and avoid (double) initialization, the service provides a global context variables, which can be accessed from ahriman.core package:
from ahriman.core import context
ctx = context.get()
Just because context is wrapped inside contexvars.ContextVar, you need to explicitly extract variable by get() method. Later you can extract any variable if it is set, e.g.:
from ahriman.core.database import SQLite
from ahriman.models.context_key import ContextKey
database = ctx.get(ContextKey("database", SQLite))
In order to provide typed API, all variables are stored together with their type. The get(ContextKey) method will throw KeyError in case if key is missing. Alternatively you can set your own variable inside context:
ctx.set(ContextKey("answer", int), 42)
context.set(ctx)
Note, however, that there are several limitations:
Context variables are immutable, thus you cannot override value if the key already presented.
The
return_typeofContextKeyshould match the value type, otherwise exception will be thrown.
The context also implements collection methods such as __iter__ and __len__.
Trigger example
Lets consider example of reporting trigger (e.g. slack, which provides easy HTTP API for integration triggers).
In order to post message to slack we will need a specific trigger URL (something like https://hooks.slack.com/services/company_id/trigger_id), channel (e.g. #archrepo) and username (repo-bot).
As it has been mentioned, our trigger must derive from specific class:
from ahriman.core.triggers import Trigger
class SlackReporter(Trigger):
def __init__(self, repository_id, configuration):
Trigger.__init__(self, repository_id, configuration)
self.slack_url = configuration.get("slack", "url")
self.channel = configuration.get("slack", "channel")
self.username = configuration.get("slack", "username")
By now we have class with all required variables. Lets implement run method. Slack API requires positing data with specific payload by HTTP, thus:
import json
import requests
def notify(result, slack_url, channel, username):
text = f"""Build has been completed with packages: {", ".join([package.name for package in result.success])}"""
payload = {"channel": channel, "username": username, "text": text}
response = requests.post(slack_url, data={"payload": json.dumps(payload)})
response.raise_for_status()
Obviously you can implement the specified method in class, but for guide purpose it has been done as separated method. Now we can merge this method into the class:
class SlackReporter(Trigger):
def __init__(self, repository_id, configuration):
Trigger.__init__(self, repository_id, configuration)
self.slack_url = configuration.get("slack", "url")
self.channel = configuration.get("slack", "channel")
self.username = configuration.get("slack", "username")
def on_result(self, result, packages):
notify(result, self.slack_url, self.channel, self.username)
Setup the trigger
First, put the trigger in any path it can be exported, e.g. by packing the resource into python package (which will lead to import path as package.slack_reporter.SlackReporter) or just put file somewhere it can be accessed by application (e.g. /usr/local/lib/slack_reporter.SlackReporter).
After that run application as usual and receive notification in your slack channel.
Trigger configuration schema
Triggers can expose their configuration schema. It can be achieved by implementing CONFIGURATION_SCHEMA class variable according to cerberus documentation. For more details and examples, please refer to built-in triggers implementations.