7 min read

Working with Pull Requests, Status Checks, ARM Templates and GitHub Actions

Working with Pull Requests, Status Checks, ARM Templates and GitHub Actions

Recently I've been using GitHub Actions more frequently and to follow up on my previous post, I thought I'd share my findings on deploying ARM templates.

In more production-like scenarios, we're not just pushing to master and have an action deploy the template to Azure. What if you're working with multiple branches, pull requests and collaborate with a team? There's some possibilities for Automation here. And with some YAML magic, it's something that can be set up quite easily. In the end, we're stitching together different steps that can all be used separately or added to another workflow.

My goal was the following:

  • Work with different branches
  • Run tests upon Pull Requests
  • After merging, deploy to Azure

We'll talk about the capabilities and the considerations when going for a similar setup.

TL;DR: All code can be found here: https://github.com/whaakman/arm-templates-github-actions

The Workflows

To achieve this we need two workflows. One to execute when a Pull Request to the master branch is performed and one that will run when something is succesfully pushed to master.

The complete flow will look similar to this:

  1. Push to Dev Branch Pull Request to Master
  2. Run Checks
  3. Merge
  4. Deploy ARM Template to Azure.

One could argue that putting everything in a single workflow will work as well. Add yes it would but for re-usability I've chosen to separate the testing stages and the deployment stage.

In this scenario I'm working with a master branch and a 2nd branch called "dev1". Whenever someone I'm collaborating with performs a Pull Request from "dev1" to "master", we need the GitHub Action magic to happen.

Within the workflow directory (.github\workflows\) we'll create the two files:

  • TestAndValidateTemplates.yml
  • DeployARMTemplates.yml

The naming kind of tells what they're doing. Let's look at these individual workflows.

 

Test and Validate Templates

As mentioned above, we want to do test whichever changes are introduced when someone performs a Pull Request to the master branch. We can configure this at the start of the Workflow file like so:

on: 
  pull_request:
    branches:
      - master
      
name: Test and Validate ARM Templates

env:
  LOCATION: westeurope
  RESOURCE_GROUP_TEST: rg-validation

We'll then set the environment variables that we later use for our Azure CLI Commands to test and deploy the ARM Template. I choose the capitalize my variables so they're easily recognizable later in the file.

Then comes the actual "work to be done".  For testing the templates I went with a single job that's leveraging multiple steps.

  • Login to Azure
  • Checkout the 'dev1' repository
  • Create a temporary Resource Group for testing
  • Validate the deployment using 'az group deployment validate'
  • Clean-Up Resource Group
  • Run the Azure Resource Manager Template Toolkit (ARM TTK)

Let's break this down, step by step.

 

Azure Login
For the Azure Login I'm using the public Azure CLI action you can find all you need on how to use this here. Most important part of this step is that you need to create a Service Principal and paste the necessary IDs and secrets as returned by Azure upon creation of the service principal into a secret called "AZURE_CREDENTIALS" (or another name if you prefer). You will create this secret within your repository and can be referenced as "${{ secrets.AZURE_CREDENTIALS }}"

jobs:
  test_arm_templates:
    runs-on: ubuntu-latest
    name: Test deployment and Run Azure Resource Manager Template Toolkit
    steps:
      
      - name: Azure Login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

 

Checkout
As a second step we will checkout the repository using the public well known Checkout action so we can grab the ARM Template(s) from the repository which then will be temporary stored on the GitHub Runner. The only addition to the standard configuration is that I'm only checking out the "dev1"branch as that is where the new version of the template will be stored. If you want to reference the specifics pull request you can do this by setting the "ref:" to "${{ github.event.pull_request.head.sha }}", for instance if you're allowing pull requests from different repositories.

The only addition to the standard configuration is that I'm checking out the contents of the 'dev1' branch.

      - name: Checkout
        uses: actions/checkout@v2
        with:
          ref: 'dev1'

 

Validate Deployment / Azure CLI
First we'll run the template validation using the "az group deployment validate" command which is available through the Azure CLI action. This action allows for custom inline scripting using the Azure CLI.

As we've stored the template in the root of the repository we can reference this using $GITHUB_WORKSPACE. If you're using different paths you can add additional paths. In this step we will also reference our own variables "LOCATION" and "RESOURCE_GROUP_TEST". Creating a testing Resource Group (and not point to the production one) allows us to expand to step in the feature and for instance do a complete deployment to instead of just validating without touching production.

The last part cleans up the Resource Group so we can move on.

  - name: Azure CLI Validate Deployment
        uses: azure/CLI@v1
        with:
          azcliversion: 2.0.72
          inlineScript: |
            az group create --location $LOCATION --name $RESOURCE_GROUP_TEST
            az group deployment validate --resource-group $RESOURCE_GROUP_TEST --template-file $GITHUB_WORKSPACE/azuredeploy.json
            az group delete --resource-group $RESOURCE_GROUP_TEST --yes --no-wait 

 

ARM Template Toolkit
The ARM Template Toolkit allows for different and custom testing as opposed to the previous step that just validates (and even that does not 100% guarantee a successful deployment). It's optional whether you include this step but if you want to go all the way it might be interesting to get into configuring your own custom tests. If you want to know more about using the ARM-TTK with a GitHub Docker Container Action check my previous post here.

      - name: Run Azure Resource Manager Template Toolkit
        uses: ./.github/actions
        id: action

That leaves us with a very pretty YAML file:

DeployARMTemplates.yml

on: 
  pull_request:
    branches:
      - master

name: Test and Validate ARM Templates

env:
  LOCATION: westeurope
  RESOURCE_GROUP_TEST: rg-validation

jobs:
  test_arm_templates:
    runs-on: ubuntu-latest
    name: Test deployment and Run Azure Resource Manager Template Toolkit
    steps:
      
      - name: Azure Login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Checkout
        uses: actions/checkout@v2
        with:
          ref: 'dev1'

      - name: Azure CLI Validate Deployment
        uses: azure/CLI@v1
        with:
          azcliversion: 2.0.72
          inlineScript: |
            az group create --location $LOCATION --name $RESOURCE_GROUP_TEST
            az group deployment validate --resource-group $RESOURCE_GROUP_TEST --template-file $GITHUB_WORKSPACE/azuredeploy.json
            az group delete --resource-group $RESOURCE_GROUP_TEST --yes --no-wait 

      - name: Run Azure Resource Manager Template Toolkit
        uses: ./.github/actions
        id: action

 

Deploying to Azure

The actual deployment workflow is somewhat similar but contains just 3 steps. First we want this to run whenever something is pushed to master. Like the Pull Request we can configure that at the start of the workflow.

on: 
  push:
    branches:
      - master
name: Deploy ARM Template

env:
  LOCATION: westeurope
  RESOURCE_GROUP: rg-production-action

Note that we're also defining the environment variables again, this time for our production deployment.

To login we use the identical step as in the previous workflow file and we're using the same credentials. If you're separating deployments between subscriptions for dev/test and production you can create a new service principal and create an additional secret in your repository to be referenced in your workflow file.

We're also running the checkout step again. As this is a different workflow and thus a different job, this will run on a clean instance of a GitHub runner and therefor doesn't have access to the previous checked out files.

Last but not least we'll deploy to Azure using the Azure CLI action once more.

      uses: azure/CLI@v1
      with:
        azcliversion: 2.0.72
        inlineScript: |
          az group deployment create --resource-group $RESOURCE_GROUP --template-file $GITHUB_WORKSPACE/azuredeploy.json

 

Configuring the GitHub side of things

First things first, commit and push all the actions to your repository so we can see them appearing in GitHub.

Before we can test the workflow we need to make sure that Pull Requests can only be merged if the tests have passed. Right now all we did was configure the "triggers" (on push or on pull request). We can achieve this by configuring branch protection.

Go to the settings of your Repository and click on "Branches". Once there click on "add rule".

We can now add a branch protection rule. We'll configure it to protect "master" and require the status checks to pass before merging and as a status check we'll add the "Test deployment and Run Azure Resource Manager Template Toolkit" as a required step. Ignore the other random names as they are a result of my testing.

Click create and we're good to go!

 

Does this thing work?

Let's check. As a test I'm using an ARM Template that deploys a storage account, nothing more. To see if the checks work, I've removed some parts of the template that will surely cause the test to fail. Once pushed to my dev1 branch I can do a pull request.

Once created we can see the checks are running. Note that I'm the administrator so I could merge even if the checks fail but obviously you don't want to do that in practice unless you have a very good reason :)

After a couple of seconds the check will fail.

And by clicking on the details of the action we can see that we've made a boo boo in the template.

We'll disregard the Pull Request, fix the template and try again. And we can see that the checks have passed:

All we have to do is merge the Pull Request to kick off our second workflow that will deploy the storage account. As expected the second workflow will start:

And success!

 

 

Wrap-up

Of course there are many ways to work with deployments, pull requests and GitHub actions. If you're running this in production the workflow will likely become more complicated once you start including more of your deployment logic. However, this does illustrate the basics and I hope it helps you getting started :)