RuairĂ­'s Site

How to only run a job on a pull request in CircleCI

2021-07-27

I wanted a thing to only happen when a pull request is opened. I also wanted to do some cleanup when the pull request is closed. In my last place we used GitHub actions and this was super easy. Now I am using CircleCI and this wasn't so easy.

In this post we will look at how to only run a job on a pull request in CircleCI. There is one major caveat. We also need a way to trigger the job on a pull request. We will look at how to do this with the CircleCI web api.

Conditionally run a job

There are a few options you can use to only run a job on a pull request in CircleCI. There is the option to only ever build on a pull request but this is all or nothing i.e. you can never run a build on a branch without opening a pull request.

Another option is, within a job, you can inspect the environment variables to see if there is a pull request number like so:

if [ "${CIRCLE_PULL_REQUEST##*/}" != "" ];then echo "Is a pull request" fi

This is OK but it would be nice to conditionally run a whole job instead. It is not possible to read environment variables when the pipeline is loaded. It is only possible when a job is run. To work around this we can use the circleci/continuation orb. If you are trying this out, make sure to update your project settings in Advanced Settings -> Enable dynamic config using setup workflows.

CircleCI expects all your configuration in one file called .circleci/config.yml. The continuation orb takes over as the entry point giving you access to the environment variables and then runs the pipeline using whatever configuration you tell it to. It's a little bit weird but it works.

This is an example of using the continuation orb to conditionally run a job only on a pull request.

.circleci/config.yml

setup: true version: 2.1 orbs: continuation: circleci/continuation@0.2.0 workflows: setup: jobs: - continuation/continue: configuration_path: ".circleci/main.yml" parameters: /home/circleci/params.json pre-steps: - run: command: | if [ -z "${CIRCLE_PULL_REQUEST##*/}" ] then IS_PR=false else IS_PR=true fi echo '{ "is_pr": '$IS_PR' }' >> /home/circleci/params.json

Note, we mentioning PR here but you could do more or less anything to configure your pipeline there. /home/circleci/params.json is written to and specified with parameters: /home/circleci/params.json.

.circleci/main.yml

version: 2.1 parameters: is_pr: type: boolean default: false jobs: do_something: docker: - image: cimg/base:2021.04 steps: - run: name: something command: echo 'You get the picture' workflows: version: 2 whence-pr: when: << pipeline.parameters.is_pr >> jobs: - do_something: name: something

We called the file main.yml here but it could be any file. You just need to specify it in the parameter called configuration_path. This post also shows another way to generate the configuration on the fly.

Now we have passed the is_pr parameter to the pipeline. We can conditionally run things using when: << pipeline.parameters.is_pr >>.

There is one major issue with this approach. Our build may have run before a PR (pull request) was ever opened. Opening a PR will not trigger a build in CircleCI.

Triggering CircleCI pipeline when a pull request is opened

First thing you must do is grab a CircleCi API token. A personal API token will do for this example.

You can trigger a pipeline run like so:

SCM=github ORG=your-org-here PROJECT=your-project-here CIRCLE_BRANCH=a-derived-branch curl -X POST \ -H "Circle-Token: ${CIRCLE_TOKEN}" \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d "{\"branch\":\"${CIRCLE_BRANCH}\"}" \ https://circleci.com/api/v2/project/${SCM}/${ORG}/${PROJECT}/pipeline

Hopefully it's clear what values you need to change there. How you will run this bit depends on what tools you have available to you. I was using GitHub and even though we use CircleCI, there are enough free GitHub Action minutes for me to setup an action like this:

.github/workflows/pr.yml

name: Trigger Build on PR on: pull_request: types: [opened, reopened] jobs: trigger-build: runs-on: ubuntu-latest steps: - name: Trigger CircleCI env: CIRCLE_BRANCH: ${{ github.head_ref }} CIRCLE_TOKEN: ${{ secrets.CIRCLE_TOKEN }} ORG: your-org-here PROJECT: your-project-here run: | curl -X POST \ -H "Circle-Token: ${CIRCLE_TOKEN}" \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d "{\"branch\":\"${CIRCLE_BRANCH}\"}" \ https://circleci.com/api/v2/project/github/${ORG}/${PROJECT}/pipeline

This feels like an incredible hack but it works.

A side note on doing something when a PR is merged

This has nothing to do with CircleCI but if you happen to have access to GitHub actions this might be useful.

.github/workflows/pr-closed.yml

name: On PR Closed on: pull_request: types: [closed] jobs: on-pr-closed: runs-on: ubuntu-latest steps: - name: Print PR number env: PR_NUMBER: ${{ github.event.number }} run: | echo "${PR_NUMBER}"