Long story short: we are in the process of migrating our Travis/Jenkins CI infrastructure to GitLab CI while keeping our source code in GitHub and thus the Open Source workflow that we love.

So what is the challenge with it?

At the moment, the GitLab CI integration with GitHub has a limitation that conflicts with our Open Source workflow: it will not run a pipeline for a PR when its origin is from a fork instead of a branch of the repo.

And what have we done to work around it?

We have written a simple bot to deal with this. It is Open Source and written in Golang. Keep reading to know more about it and get inspiration for your project!

Github gitlab logo

The Open Source workflow

We use a wildly extended workflow, where we welcome community contributions to our software repositories. The GitHub workflow that we recommend is to fork our components, add our changes, and then submit a Pull Request to our repo. So far so good.

None of this workflow seemed to be problematic for our GitLab migration: if we just keep our code in GitHub and keep using GitHub’s PR mechanism there, we only need to change the checks reporting from Jenkins/Travis to GitLab, and GitLab had excellent support for GitHub integration

Until we stumbled upon this issue: GitLab will refuse to run the pipelines for PRs coming from repo forks (both for community and developers of our own organization). Seems like we are not the only ones to want this feature, see issue #5667 at GitLab tracker.

A bit about our architecture and CI

To ensure a high quality product, we maintain a good deal of CI infrastructure and automated procedures around the Mender project.

Mender is built following a microservices architecture. For the CI infrastructure, this means that each component (independent of Git repository) has its own QA checks (typically unit tests, code coverage, etc.) which have no sensible secrets. In our old setup, this would be the verification run by Travis.

In addition, we have an integration QA pipeline, with a big number of integration tests and other miscellaneous checks that verify the workings of all the components together. This pipeline, however, contains secrets because of its responsibility for publishing Docker images or uploading artifacts during our releases.

For our migration to GitLab to be complete, we would like to automatically run the single component’s pipelines of all submitted PRs but prevent the main integration QA to run for PRs coming from authors outside of the organization. These will need to be manually triggered once we verify that it doesn’t contain any potentially malicious code.

The workaround: our bot

Back to our challenge. Inspired by Terry Cojean’s suggestion in the issue above, we got our hands on in writing a bot to deal with this limitation. The code, of course, is also Open Source, and the repo is hosted in GitHub.

The idea is the following:

Test runner bot workflow diagram

We set an organization-level webhook in GitHub so that any Pull Request activity triggers a POST call to our robot. The robot then triggers the pipeline for that specific component unconditionally. After creating a branch with the changes from the PR, the GitLab pipeline can run normally. In addition, and only when the author is part of our organization, we will trigger the integration Pipeline.

From here, GitLab will take care of updating the test results in the GitHub PR.

The robot is pretty easy to set up in a server: all you need is to compile it from source (go build…), install the binary in some known path, and use few environment variables to pass him the APIs tokens and secrets.

You can set up a systemd service similar to:

[Unit] After=mysql.service

[Service] ExecStart=/usr/bin/env GIN_RELEASE=release INTEGRATION_DIRECTORY="/root/integration/" GITHUB_TOKEN= GITHUB_SECRET= GITLAB_TOKEN=<Your GitLab token> GITLAB_BASE_URL=https://gitlab.com/api/v4 /root/integration-test-runner-gitlab Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bi n:/usr/games:/usr/local/games:/snap/bin:/root/integration/extra

[Install] WantedBy=default.target

The implementation is pretty much fitted for our specific needs in Mender and it won’t work out of the box for your project. But we hope that the design can inspire you if you run into the same challenge. And of course, we are always open to receive Pull Requests and listen to your ideas! ;)

Lluís Campos