Using GitHub workflows to automate adding extra commits to Renovate Pull Requests

We've been using Renovate for quite a while now (switching from Dependabot), which scans our GitHub repositories and opens Pull Requests to update dependency versions. It works great, easy to activate, and means we don't have to manually check for updates for all of our dependencies.

There was however one issue we ran into, which is that once Renovate has updated the version of a dependency, there are times that we then need to run other commands to generate more changes to the code. One example is Terraform Docs - which is a tool that generates documentation for Terraform projects and modules.

At first, we dealt with this manually. Just pulled down the renovate branch, ran terraform-docs ., made an extra commit and pushed it back up. But it got to a point that we were doing it often enough, that I decided to look further into an automated solution. This took me down a road of extra issue ๐Ÿ˜ฌ

Renovate settings

I'd not really done much with Renovate settings, just enabling it and letting it do it's thing. So I had a quick search online, to find if there was a way to configure it to run a command once it had made an update.

The only setting that looked like it'd help, was the postUpgradeTasks ... But unfortunately this option was only available for self-hosted Renovate instances ๐Ÿ˜•

As a last resort, I asked ChatGPT just in case I missed anything - And it spewed out a load of configuration claiming it could do it, but nope ...

Adding a commit using GitHub workflows, but only for Renovate branches

So the Terraform Docs GitHub action does have an option to add a commit if it finds any changes that are needed, however we disabled this by default, and instead just fail the check if Terraform Docs hasn't been ran for the pull request. We decided to do this, because it adds a lot of 'terraform-docs' commits and messages, and can make the git history look a bit horid.

There was no way around having these extra Terraform Docs commits for Renovate PRs, because if you squash commits and push to a Renovate managed branch, it'll just re-run itself and erase the extra code changes. So I looked into a way to only run the Terraform Docs GitHub action with git-push: true parameter set. Say hello to another issue ...

To get the branch name of a Pull Request, we can use the github.head_ref property. We can then check to see if it begins with renovate/ and conditionally run a step. However, this is only available on Pull Requests. If it's used on Pushes (eg. after the PR has been merged into main), the workflow will fail. If we use the github.ref property, this would work for all branches, but on a Pull Request, it returns the format refs/pull/{pull_request_number}/merge - so we couldn't tell if it was a renovate branch.

So this is what I came up with:

on:
  push:
    branches: main
  pull_request:

env:
  GITHUB_PR_BRANCH: ""

jobs:
  terraform-docs-validation:
    name: Terraform Docs validation
    needs: terraform-validate
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.ref }}

      - name: Get PR base branch
        id: pr-base-branch-name
        if: github.event_name == 'pull_request'
        run: |
          echo "GITHUB_PR_BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV

      - name: Generate Terraform docs
        uses: terraform-docs/gh-actions@v1.0.0
        if: "!startsWith(env.GITHUB_PR_BRANCH, 'renovate/')"
        with:
          working-dir: .
          config-file: .terraform-docs.yml
          output-file: README.md
          output-method: inject
          fail-on-diff: true

      - name: Generate Terraform docs for Renovate
        uses: terraform-docs/gh-actions@v1.0.0
        if: "startsWith(env.GITHUB_PR_BRANCH, 'renovate/')"
        with:
          working-dir: .
          config-file: .terraform-docs.yml
          output-file: README.md
          output-method: inject
          git-push: true
          ref: ${{ github.event.pull_request.head.ref }}


What this does is:

  • Initiate the GITHUB_PR_BRANCH environment variable
  • Checks out the code
  • If the github.event_name is 'pull_request', set the GITHUB_PR_BRANCH environment variable to the github.head_ref value
  • If the branch name doesn't begin with renovate/, it will run terraform-docs without making a commit, but fail by using the fail-on-diff: true parameter
  • If the branch name does bein with renovate/, it will run terraform-docs with the git-push: true parameter, making an extra commit.

At first, it looked like it was working ... But then quickly realised that the GitHub workflows weren't being triggered once the new commit was added ๐Ÿ˜ž

Allowing commits added through GitHub workflows to trigger the GitHub checks

I had a feeling that the checks weren't running due to some permissions issue, as the extra commit is made from within the workflow triggered by Renovate, using the default GITHUB_TOKEN.

However, thanks to a blog post I stumbled across (which you can find here: https://joht.github.io/johtizen/build/2022/01/20/github-actions-push-into-repository.html ), I found out that it's a feature of GitHub workflows to not allow the GITHUB_TOKEN to run the checks when making commits - because it could end up causing an infinite loop of workflow runs.

I then found the GitHub documentation that confirms this: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow

When you use the repository's GITHUB_TOKEN to perform tasks, events triggered by the GITHUB_TOKEN, with the exception ofย  workflow_dispatch and repository_dispatch, will not create a new workflow run. This prevents you from accidentally creating recursive workflow runs.

Using a Personal Access Token (PAT) to create the commit

Personal Access Tokens in GitHub projects has always felt a bit funny - They were (until recently) tied to a specific GitHub account. If we had to use one, we would just hope that the person who created it, didn't accidently (or intentionally) delete it, as that could potentially break things. So I was hoping that I wouldn't need to use a PAT.

However, with the new 'Fine-grained' tokens, we can can create tokens, setting the 'Resource Owner' to a GitHub Organisation. These are currently in Beta, and require them to be activated within the Organisation Settings. You can find out more about Fine-grained tokens here: https://github.blog/2022-10-18-introducing-fine-grained-personal-access-tokens-for-github/

I went ahead and created a PAT with our GitHub Organisation as the resource owner (With repository 'Contents' permission set to read/write), and then set the token as an Organisation Secret, so that it can be used across multiple repositories.

Now all I needed to do, was add this to the Checkout github action within the `terraform-validate` job:

  steps:
    - name: Check out code
      uses: actions/checkout@v4
      with:
        ref: ${{ github.event.pull_request.head.ref }}
        token: ${{ secrets.TERRAFORM_DOCS_RENOVATE_WORKFLOW_GITHUB_TOKEN }}


Finally we now had Renovate PRs that would add the commit, and then run the checks again to make sure everything was okay ๐Ÿ™‚

An example PR for this change can be found here: https://github.com/dxw/terraform-template/pull/15