Using Forgejo CI
Published on 2024-03-09
It took me quite a while to understand why it was so difficult to get the CI runner for Forgejo "Actions", forgejo-runner working to my satisfaction.
Earlier I wrote
documentation
on how to setup forgejo-runner
to work under a standard user account using
systemd
user services, and podman
which turns out to work super smooth!
But then, how do you actually write these "workflow" files in a way that makes sense to how our current CI works on SourceHut?
Of course, it is a given that I will have strong opinions on how I want my CI to work, which I'll detail below. It actually matches exactly with how SourceHut builds work. Perhaps I should also quickly explain what I mean with CI in this context: On git push I want the unit tests to run in the CI system on the exact platform the code will be deployed on in production. We cheat a little bit here, as our OS package builders (Debian/Ubuntu, Fedora/EL) take care of the exact same thing during package build, but we do not create packages on every commit. It would be nice to run at least on one of the supported platforms, let's say Debian 12 so we make sure most is at least somewhat in order.
Searching for documentation regarding Forgejo, Gitea or GitHub "Actions" is not
really fruitful as all seem to want to use either ubuntu-latest
, whatever
that is. It turns out to be a 60 GB, no really, image used by GitHub. Why?!
So, it turns out our best bet is to use "plain" Docker images, for example
debian:latest
, prepare it and run our unit tests on it. It took quite a bit
of trial and error, it doesn't seem that what I want is what others want, so it
requires some fiddling with users accounts in the Docker image, but after
powering through, finally I got something that works more or less the way I
want. Without further ado, here the .forgejo/workflows/tests.yml
of one of my
PHP projects:
# ---
env:
BUILD_DEPENDENCIES: git composer unzip php-curl php-date php-dom php-intl
# ---
on: [push]
jobs:
tests:
runs-on: docker
container:
image: debian:latest
steps:
- name: Install OS Dependencies
run: |
apt-get update && apt-get install --yes $BUILD_DEPENDENCIES
- name: Add CI User
run: |
useradd -m ci-user
- name: Clone Repository
run: |
mkdir app
chown ci-user:ci-user app
su -c "git clone --depth 1 -b ${{ github.ref_name }} ${{ github.server_url }}/${{ github.repository }} app" ci-user
- name: Install Dependencies
run: |
cd app
su -c "composer update" ci-user
- name: Run Unit Tests
run: |
cd app
su -c "unshare -c -n vendor/bin/phpunit" ci-user
It should be rather self explanatory, but some small notes:
- You can select your Docker image by setting
image
to whatever Docker Hub. In the example we usedebian:latest
, but you can also use e.g.debian:12
,fedora:latest
, or if you are a software archaeologist:centos:7
; - We use the
BUILD_DEPENDENCIES
environment variable to list the Debian packages that need to be installed; - We create a user account, because by default the
root
user is used for everything, which may very well work, but not always. For examplecomposer
, PHP's dependency manager complains loudly that you should not do that and disables certain functionality. So, why not run all tasks that do not needroot
under ourci-user
account? - You can actually use variables in the YAML file so you do not need to explicitly set the repository URL or the branch/tag you want to run your tests on, so you do not need to modify the file if you push to other branches for example;
- The
unshare -c -n
command runs the program in a new (empty) namespace, which means there will be no network access at all. Here we make sure the unit tests do not use the network.
So, it would be much nicer if there was a mechanism to only install OS dependencies as root, and run the rest as user without the boilerplate. These are things I hope to find out at some point how to do!