HashiCorp Terraform using GitHub Actions

HashiCorp Terraform using GitHub Actions

I've been using GitHub for a while now, familiarizing myself with all its features. GitHub was a repository-only platform that tapped into third-party services to power up its capabilities. It has moved a long way since then and now offers users the ability to create workflows and run actions against their repos.

In this post, I will cover how to create a workflow for a repository containing HashiCorp Terraform configuration files, using only GitHub Actions to run Terraform.

What do you need

To follow this post, you will need the following:

Creating a CI workflow

Launch Visual Studio Code and open your cloned repository. Within the root of the repository, you will need to create the following folder structure: .github/workflows. It should look something like this:

githubworkflowsfolder.png

Create a file within the workflows folder and call it terraform-ci.yml.

Open the file so we can start configuring the workflow. The below you can copy and replace values where you see fit:

name: Terraform CI

on:
  workflow_dispatch:

jobs:
  Terraform-CI:
    name: Terraform Plan
    runs-on: ubuntu-latest

    env:
      ARM_CLIENT_ID: ${{ secrets.TF_CLIENT_ID }}
      ARM_CLIENT_SECRET: ${{ secrets.TF_CLIENT_SECRET }}
      ARM_SUBSCRIPTION_ID: ${{ secrets.TF_SUB_ID }}
      ARM_TENANT_ID: ${{ secrets.TF_TENANT_ID }}
      ARM_ACCESS_KEY: ${{ secrets.TF_ACCESS_KEY }}

name - this will be the display name of the workflow within the Actions tab.

workflow_dispatch - on workflow dispatch allows us to trigger the workflow manually. You can change this later to another trigger like on push.

Terraform-Validate-Plan - here we are configuring the job (stage) name (not display name).

name: - we are configuring the display name of the job.

runs-on: - here we specify the container operating system which is Ubuntu.

env: - for this post I am using Azure resources within my Terraform configurations. So within env I am specifying the credentials to connect to my environment to access resources, as well as access my state file hosted in Azure. I am using GitHub secrets to store the details and passing them through using ${{ secrets.NAME}}.

Configure initial Terraform steps

We have a workflow defined, now its time to configure each step of the Terraform deployment.

We will start with checking out the repository so there is a local copy on the runner. To do this we will use the Checkout action:

    steps:

      - name: Checkout Code
        uses: actions/checkout@main

We will then install Terraform using the Setup Terraform action where we will specify to use the latest version of Terraform:

      - name: Install Terraform
        uses: hashicorp/setup-terraform@main
        with:
          terraform_version: latest

After installation, we want to run Terraform Format to make sure the code is correctly formatted:

      - name: Check Terraform Format
        id: fmt
        run: terraform fmt -check

After checking the format is correct, we will now initialise the code so providers and modules are downloaded:

      - name: Initialise Terraform
        id: init
        run: terraform init

Once initialised, we want to validate the configuration is correct against what the providers/modules that were downloaded, so we will run Terraform Validate:

      - name: Validate Terraform
        id: validate
        run: terraform validate -no-color

And finally we can then run Terraform Plan to see what the configuration will deploy:

      - name: Run Terraform Plan
        id: plan
        run: terraform plan -no-color

Your workflow should looks something like this:

name: Terraform CI

on:
  workflow_dispatch:

jobs:
  Terraform-CI:
    name: Terraform Plan
    runs-on: ubuntu-latest

    env:
      ARM_CLIENT_ID: ${{ secrets.TF_CLIENT_ID }}
      ARM_CLIENT_SECRET: ${{ secrets.TF_CLIENT_SECRET }}
      ARM_SUBSCRIPTION_ID: ${{ secrets.TF_SUB_ID }}
      ARM_TENANT_ID: ${{ secrets.TF_TENANT_ID }}
      ARM_ACCESS_KEY: ${{ secrets.TF_ACCESS_KEY }}

    steps:

      - name: Checkout Code
        uses: actions/checkout@master

      - name: Install Terraform
        uses: hashicorp/setup-terraform@main
        with:
          terraform_version: latest

      - name: Check Terraform Format
        id: fmt
        run: terraform fmt -check

      - name: Initialise Terraform
        id: init
        run: terraform init

      - name: Validate Terraform
        id: validate
        run: terraform validate -no-color

      - name: Run Terraform Plan
        id: plan
        run: terraform plan -no-color

All we have configured so far is checks to make sure the configuration is correct and output a plan against the state file and the live environment. We haven't configured Terraform to deploy yet and that's because we want a workflow which will run seperately.

Configure Terraform Deployment

Create a new file in the workflows directory and call it terraform-cd.yml

Terraform-CD:
    name: Terraform CD
    runs-on: ubuntu-latest

on:
  workflow_dispatch:

    env:
      ARM_CLIENT_ID: ${{ secrets.TF_CLIENT_ID }}
      ARM_CLIENT_SECRET: ${{ secrets.TF_CLIENT_SECRET }}
      ARM_SUBSCRIPTION_ID: ${{ secrets.TF_SUB_ID }}
      ARM_TENANT_ID: ${{ secrets.TF_TENANT_ID }}
      ARM_ACCESS_KEY: ${{ secrets.TF_ACCESS_KEY }}

This new workflow is similar to the previous one we created, but we will not include steps to validate and plan the deployment. Instead, we will just run Terraform apply using the below step:

steps:

      - name: Checkout Code
        uses: actions/checkout@master

      - name: Install Terraform
        uses: hashicorp/setup-terraform@main
        with:
          terraform_version: latest

      - name: Initialise Terraform
        id: init
        run: terraform init

      - name: Run Terraform Deploy
        id: deploy
        run: terraform deploy -no-color --auto-approve

Your final file should look like this:

Terraform-CD:
    name: Terraform CD
    runs-on: ubuntu-latest

on:
  workflow_dispatch:

    env:
      ARM_CLIENT_ID: ${{ secrets.TF_CLIENT_ID }}
      ARM_CLIENT_SECRET: ${{ secrets.TF_CLIENT_SECRET }}
      ARM_SUBSCRIPTION_ID: ${{ secrets.TF_SUB_ID }}
      ARM_TENANT_ID: ${{ secrets.TF_TENANT_ID }}
      ARM_ACCESS_KEY: ${{ secrets.TF_ACCESS_KEY }}

steps:

      - name: Checkout Code
        uses: actions/checkout@master

      - name: Install Terraform
        uses: hashicorp/setup-terraform@main
        with:
          terraform_version: latest

      - name: Initialise Terraform
        id: init
        run: terraform init

      - name: Run Terraform Deploy
        id: deploy
        run: terraform deploy -no-color --auto-approve

You can now commit the files to your branch and trigger them by selecting Actions, one of the workflows, the Run workflow, and finally, the branch to which you committed these files. Before triggering, please make sure you update your environments secrets into the repository for the authentication to take place.

Did you find this article valuable?

Support James Cook by becoming a sponsor. Any amount is appreciated!