Automate Deployments with Renovate and GitHub Actions

Automate Deployments with Renovate and GitHub Actions

For quite some time I've used Renovate on GitHub to create pull requests if there is a new version of the software I use in my projects. For a while, I always merged the pull requests created by Renovate and then deployed them via Ansible.

For a certain number of PRs, this may work just fine. But if, for example, you always want to roll out the latest Docker image tag for a variety of software in different repositories, this can consume quite a bit of time. I wanted to use this time for more meaningful things and so I looked for a solution to this problem.

In the end, it was Renovate that created pull requests in my repositories and then GitHub Actions that executed the various Ansible deployment playbooks through certain triggers. Let me give you a more detailed overview of what I did.

Note: If you need a TL;DR, scroll down to the end of the blog post.

Configure Renovate to label pull requests

To make this work we need to configure Renovate to add labels to pull requests. Using the example of an updated Docker image tag, I've added 2 labels in case the pull request contains changes to a Dockerfile or a docker-compose file.

  "packageRules": [
    {
      "matchManagers": [
        "docker-compose",
        "dockerfile"
      ],
      "labels": [
        "renovate_docker"
      ]
    },
    {
      "matchPaths": [
        "roles/grafana_stack/**"
      ],
      "addLabels": [
        "auto_deploy_grafana"
      ]
    }
  ]

So for "dockerfile" and "docker-compose" as matchManagers, I've defined the label "renovate_docker". From now on all docker-related pull requests created by Renovate will have this label attached. Furthermore, I've added the label "auto_deploy_grafana" for every pull request that will change a file under the path "roles/grafana_stack". In this case, this is the location where my docker-compose files for my Grafana setup are.

Now a pull request made by Renovate will look something like this:

Note: If you already have some open pull requests that you want to label you need to close them and let Renovate reopen them because Renovate only adds labels to newly created pull requests.

Setup the GitHub Action

For further automation, we need a GitHub action, where we execute the code as soon as certain criteria are met. First, we need to define a trigger for our action to run the workflow. I chose the pull request trigger for this.

on:
  pull_request:
    types:
      - closed

If we don't configure something else our action will run on every closed pull request now which is not what we want. To check if the action should run in general we need to inspect if the pull request contains the "renovate_docker" label. To achieve this we add an if statement under the name of our job to check if the pr is merged and if the pull request contains any labels.

jobs:
  update_services:
    if: >-
      github.event.pull_request.merged == true &&
      contains(github.event.pull_request.labels.*.name, 'renovate_docker')

If both return true we continue starting the job.

This is also where we need the second label. Because now we can decide on the step level which Ansible playbook should be executed. To do this we have to add another if statement to our GitHub Action.

steps:
      - name: Deploy Grafana
        if: contains(github.event.pull_request.labels.*.name, 'auto_deploy_grafana')
        shell: bash
        env:
          ANSIBLE_VAULT: ${{ secrets.ANSIBLE_VAULT }}
        run: |
          echo "$ANSIBLE_VAULT" | ansible-playbook playbooks/grafana_stack.yml --vault-password-file /bin/cat

The important line is line 3. Because there we look for the other label which Renovate created for us, based on which file got changed. If the label does not match the label from our if statement, this step is skipped.

So to wrap things up the action could look something like this (simplified):

name: Deploy our intern services
on:
  pull_request:
    types:
      - closed
jobs:
  update_services:
    if: >-
      github.event.pull_request.merged == true &&
      contains(github.event.pull_request.labels.*.name, 'renovate_docker')
    runs-on: ubuntu-latest
    steps:
      - name: Deploy Grafana
        if: contains(github.event.pull_request.labels.*.name, 'auto_deploy_grafana')
        shell: bash
        env:
          ANSIBLE_VAULT: ${{ secrets.ANSIBLE_VAULT }}
        run: |
          echo "$ANSIBLE_VAULT" | ansible-playbook playbooks/grafana_stack.yml --vault-password-file /bin/cat

What happens now depends on what you have defined in your Ansible code, but this should not be part of this post.

TL;DR

I've used Renovate to create pull requests for certain docker images and other software I use in my repositories. I then configured Renovate to add an appropriate label to the pull request. This means that a pull request e.g. the Grafana Docker image then had a label "auto_deploy_grafana" attached to it. After I've merged this specific pull request a GitHub Action will check if I have configured a step inside of my deployment job which then checks if the pull request contains a special label. If this is the case, an Ansible playbook is executed, which then takes over the further deployment.

Sources

Renovate documentation - https://docs.renovatebot.com/

GitHub Actions documentation - https://docs.github.com/en/actions