Calamares CI Infrastructure
Calamares is a Linux system installer used by dozens of different Linux distributions; it’s an Open Source project and runs its processes and development entirely in the open. Like any good software project, Calamares has continuous builds (let’s call it continuous integration, CI for short anyway) running to check that things are OK. Let’s take a look at what powers this CI.
While Calamares is a project that uses a lot of KDE technologies, it is not a KDE project; KDE uses GitLab which is lovely, but not relevant to this description.
Up until recently, Calamares used Travis-CI, on the “.org” plan for Open Source projects. Not that the commit-rate for Calamares is huge, but I think we also tried reasonably well to be mindful of the resources used at Travis – they support Open Source development, best be polite. However, Travis-CI support for Open Source is changing – some would say shutting down – and it looks like it is going to turn into a hassle, with administrative futzing instead of “just running and being there”.
Calamares itself is hosted on GitHub, so if CI has got to move anyway, it may as well move to GitHub itself, right? There’s this fancy “actions” stuff that can do CI. Thankfully I could borrow some expertise from Manjaro to do initial setup (thank you Jonas!) to get things started.
Unlike my previous experience with Travis and GitLab, I constantly have a feeling of impedance mismatch: the way I expect to get things done is not How Things are Done in GitHub CI. The documentation is extensive, but doesn’t answer the kind of questions I have. There’s tons of examples, none of which fit my kind of use-case (build a C++ application and then send a notification that it happened). There’s a “marketplace” where you can browse actions – I guess calling something a store somehow makes it classy – and there’s a zillion npm-based actions and deploying to Azure is simple and saying hi on Slack can be done in a dozen ways.
Nowhere is there a “cmake ; make ; make install ; send-message-to-Freenode” kind of example; let alone an example that takes you by the hand to explore the information you have during the build.
Even though Manjaro got things working initially, it still took me an entire afternoon to wade through documentation and a dozen “try this on CI” commits; now, afterwards, some of the earlier examples make more sense. It’s still a pain in the butt.
I’m going to continue griping about stuff, but at the end there is a snippet that might be useful if you end up stuck with GitHub actions as well.
Everything is written in YAML. Calamares uses YAML for its configuration. What could go wrong? Well, I’ve learned a lot of new edge-cases in YAML today, that could go wrong.
- Even if steps don’t need an id, and few examples put an id in a step, you do need that id to refer to a particular step; if none is specified, you get some hash instead and the name is not used to inform the id.
- Use double-quotes
"around strings, except when the string is supposed to be a string in an expression; then, use single quotes
'. In particular, in an if expression, use single quotes around strings (e.g. around the names of issue-states, or repository names). Admittedly, the examples do this, but without a big fat “pay attention!” attached. Maybe I should go to the marketplace and buy better punctuation.
- You can pass information from one step to another, using plain
echo(1). That’s cool. Hidden away in the documentation about workflow commands, though.
- Figuring out what information you do have is non-trivial.
I’m sure the documentation is generated from the schema of the (JSON)
data there is, but it is a sore exercise to get from there back to
“this is the information I need”. Some useful variables I found:
github.event.head_commit.idthe full SHA of the head commit that you are building; in a CI context, that’s usually the last / latest commit in the sequence that was just pushed.
github.event.compareis a URL that will get you the diff (comparison) between the last run of this workflow, and the current one. In a CI context, that’s usually the full diff of the sequence that was just pushed.
- There is the full SHA, and the full commit message, but there is no convenience support for something shorter: for instance, the first line of the commit, or the short SHA. I suppose when sending notifications to platforms that support rich-text rendering that’s not such an issue. For notifications to IRC, where one-line-messages are preferred and there’s no markup, this is a real annoyance. I ended up with a step specifically crafted to produce shorter git output, so I could use it later in an IRC notification.
Given a full SHA for a commit, it is possible to get a “short summary” of the commit, with a short SHA and the first line of the commit message: what you’d commonly see in a git history browser. The command looks like this:
git log -1 --abbrev-commit --pretty=oneline --no-decorate SHA
--abbrev-commit produces the short-SHA,
--pretty=oneline is a format
for getting everything on one line with just the first bit of the
commit message, and
--no-decorate removes mention of the branch
the commit is on (when it is at the tip of a branch – in a CI context,
that is usually the case). Sample output looks like this:
9af44a3c8 CI: one more with shorter notifications
That’s a short-SHA, and then part of the commit message. In Calamares,
commit-messages conventionally mention a component first, so
is just my way of indicating that this commit applies to the CI parts
of the project and not the source code.
Now to move this information from one step to another. In my job, as one of the steps, I have this:
- name: "gather information" id: information run: | echo "::set-output name=message::"`git log -1 --abbrev-commit --pretty=oneline --no-decorate $`
This is slightly mangled for display here, but it is a shell command
echo(1) to output some magic for GitHub, followed by the
string that is interesting: the short-SHA and commit message of the
latest commit. Note the id of this step, and the name of the output (message)
that is being set. Those are needed later.
Later, in another step, we can refer to the output from before. I found an IRC-notifier in the marketplace; it is a tidy Python script, using pydle, which sends one message. I use it to send a specific message when the build fails, in the following step:
- name: "notify: fail" uses: rectalogic/notify-irc@v1 if: failure() with: server: chat.freenode.net nickname: my-ci-notifier channel: "#calamares" message: | $ FAIL $ $ .. DIFF $
The “magic” of actions translates this into a call to a shell script, where environment variables and munged and passed onwards to the Python script itself; the relevant bits are that this particular action documents that server, nickname and channel specify where to notify (and as whom) and message is the message to send. Here the message to send is a multi-line string, where:
github.workflownames the CI workflow (that name is set elsewhere in the YAML file),
github.repositorynames the repo in question (so I can re-use this workflow in multiple repositories, all of which notify to the same IRC channel, but telling me what the CI execution applies to),
steps.information.outputs.messageis the string set in an earlier step: this is why the id of that earlier step, and the output name, are both important.
github.event.compareis a URL that can be used to view the whole change that the CI run applies to.
This approach is extensible in that I can add more outputs to the “information” step, which runs inside the build container for Calamares, and then write a longer IRC message, or do notifications based on the output of the information step.
Overall, this feels fragile and cumbersome compared to what I had before. Presumably, once it’s up and running and “just works” and I no longer feel any need to conserve build resources, I’ll forget this frustration, at least until it comes time to migrate CI again.
Seriously, GitLab CI is much nicer to set up, and looks generally pleasant as well; here’s KDE Invent CI for the okular repository.