Deploy Terraform using GitHub Actions to Azure

Using Terraform to deploy your Azure resources is becoming more and more popular; in some instances overtaking the use of ARM to deploy into Azure. I will show you in this blog how you can deploy your Azure Resources created in Terraform using GitHub Actions.

You may have saw my post previously Deploy Terraform using Azure DevOps , it will be a similar context to it

What is GitHub Actions?

They allow you to create workflows with your GitHub repository – similar to Azure DevOps Pipelines; they allow you create an automated workflow(s). They are pretty awesome!

Recommended reading

What is GitHub Actions for Azure

Azure/actions GitHub Repository

Initial requirements before you can begin deploying using GitHub Actions

There are some prior requirements you need to complete before we can get deploying Terraform using GitHub Actions.

  • Storing the Terraform state file remotely
  • Azure Service Principal
  • Saving Service Principal credentials within GitHub Repository as secrets

Storing the Terraform state file locally

When deploying Terraform there is a requirement that it must store a state file; this file is used by Terraform to map Azure Resources to your configuration that you want to deploy, keeps track of meta data and can also assist with improving performance for larger Azure Resource deployments.

In this deployment, I want to store the state file remotely in Azure; I will be storing my state file in a Storage Account container called:- tfstatedevops

Lets deploy the required storage container called tfstatedevops in Storage Account tamopstf inside Resource Group tamopstfstates

Terraform must store state about your managed infrastructure and configuration. This state is used by Terraform to map real world resources to your configuration, keep track of metadata, and to improve performance for large infrastructures.

# Create Resource Group
az group create -n tamopstfstates -l eastus2

# Create Storage Account
az storage account create -n tamopstf -g tamopstfstates -l eastus2 --sku Standard_LRS

# Create Storage Account Container
az storage container create -n tfstatedevops 

Azure Service Principal

Next we create a service principal that will be used by Terraform to authenticate to Azure (Note down password)

# Create Service Principal 
az ad sp create-for-rbac --name tamopstf2

Assign role assignment to this newly created service principal (RBAC) to the required subscription. Further details on RBAC roles is documented here

Saving Service Principal credentials within GitHub Repository as secrets

Within the GitHub repository to where you are going to be running the terraform from, select settings -> secrets

Add 4 secrets

  • AZURE_AD_CLIENT_ID – Will be the service principal ID from above
  • AZURE_AD_CLIENT_SECRET – The secret that was created as part of the Azure Service Principal
  • AZURE_AD_TENANT_ID – The Azure AD tenant ID to where the service principal was created
  • AZURE_SUBSCRIPTION_ID – Subscription ID of where you want to deploy the Terraform

Sample Terraform Code

terraform {
  required_version = ">= 1.5.7"
  backend "azurerm" {
    resource_group_name  = "thomasthorntoncloud"
    storage_account_name = "thomasthorntontfstate"
    container_name       = "github-thomasthorntoncloud-terraform-example"
    key                  = "github-thomasthorntoncloud-terraform-example.tfstate"
  }
}

provider "azurerm" {
  features {}
}

data "azurerm_client_config" "current" {}

#Create Resource Group
resource "azurerm_resource_group" "tamops" {
  name     = "github-thomasthorntoncloud-terraform-example"
  location = "uksouth"
}

#Create Virtual Network
resource "azurerm_virtual_network" "vnet" {
  name                = "tamops-vnet"
  address_space       = ["192.168.0.0/16"]
  location            = "uksouth"
  resource_group_name = azurerm_resource_group.tamops.name
}

# Create Subnet
resource "azurerm_subnet" "subnet" {
  name                 = "subnet"
  resource_group_name  = azurerm_resource_group.tamops.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["192.168.0.0/24"]
}

Notice the reference of the backend? This is where the .tfstate file will be stored

  backend "azurerm" {
    resource_group_name  = "thomasthorntoncloud"
    storage_account_name = "thomasthorntontfstate"
    container_name       = "github-thomasthorntoncloud-terraform-example"
    key                  = "github-thomasthorntoncloud-terraform-example.tfstate"
  }

Folder structure below, the terraform will be stored in main.tf

thomasthorntoncloud-terraform-example
    └──.github/workflows
       └──terraform.yml
    └──terraform
       └──main.tf
       
  

Example GitHub Action to deploy Terraform into Azure

To add this GitHub Action to your repository, within your GitHub Repo – select Actions -> Workflows -> New workflow

name: 'Terraform'

on:
  push:
    branches:
    - main
  pull_request:

jobs:
  terraform:
    name: 'Terraform'
    env:
      ARM_CLIENT_ID: ${{ secrets.AZURE_AD_CLIENT_ID }}
      ARM_CLIENT_SECRET: ${{ secrets.AZURE_AD_CLIENT_SECRET }}
      ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      ARM_TENANT_ID: ${{ secrets.AZURE_AD_TENANT_ID }}
      TF_VERSION: 1.5.7
    runs-on: ubuntu-latest
    environment: production

    # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest
    defaults:
      run:
        shell: bash

    steps:
    # Checkout the repository to the GitHub Actions runner
    - name: Checkout
      uses: actions/checkout@v2

    - name: 'Terraform Format'
      uses: hashicorp/terraform-github-actions@master
      with:
        tf_actions_version: ${{ env.TF_VERSION }}
        tf_actions_subcommand: 'fmt'
        tf_actions_working_dir: "./terraform"
        
    - name: 'Terraform Init'
      uses: hashicorp/terraform-github-actions@master
      with:
        tf_actions_version: ${{ env.TF_VERSION }}
        tf_actions_subcommand: 'init'
        tf_actions_working_dir: "./terraform"

    - name: 'Terraform Validate'
      uses: hashicorp/terraform-github-actions@master
      with:
        tf_actions_version: ${{ env.TF_VERSION }}
        tf_actions_subcommand: 'validate'
        tf_actions_working_dir: "./terraform"
        
    - name: 'Terraform Plan'
      uses: hashicorp/terraform-github-actions@master
      with:
        tf_actions_version: ${{ env.TF_VERSION }}
        tf_actions_subcommand: 'plan'
        tf_actions_working_dir: "./terraform"

    - name: Terraform Apply
      if: github.ref == 'refs/heads/main'
      uses: hashicorp/terraform-github-actions@master
      with:
        tf_actions_version: ${{ env.TF_VERSION }}
        tf_actions_subcommand: 'apply'
        tf_actions_working_dir: "./terraform"

Once the Action has been ran, we can review each step

Select any of these workflow runs

You can drill-down into each step and review its output

GitHub Repository example here

Hopefully you find this blog useful and give you a look into deploying Terraform using GitHub Actions

7 comments

  1. Good article.

    A litte typo :
    tf_actions_subcommand: ‘applya’ should be tf_actions_subcommand: ‘apply’ in the Terraform Apply action.
    I did run terraform fmt in my local shell before the push to get the workflow right

    Thanks again

    1. Thank you for your message, now updated

      Sorry missed this message initially

      Thanks

      Thomas

Leave a Reply