Chloe McAree (McAteer)
Published on

Deploying Terraform using GitHub actions

Authors

When working with AWS services I have been using Terraform as my Infra As Code. In the project I am currently working on I have a CI/CD pipeline for deploying project code using GitHub actions. However, I would now like to extend my pipeline to deploy any infrastructure changes when there are any.

The Goals

  • I want the terraform action to only run when there has been changes to a file within my terraform directory, it doesn’t need to run on all code deployments
  • I want to be able to view a plan of my terraform changes, when any infra changes are pushed up in a commit to a PR. This will allow any reviewers to review the changes and plan as well.
  • I only want to proceed with a terraform deployment when the code has been merged into the main branch

TL;DR: If you are under time pressure, my working config is at the end of the blog with an explanation on how it works. I hope it helps!

Let’s get started!

To control when a GitHub action runs, we can use the on syntax to define the criteria that must be met before the job will run.

The following code snippet, defines when this action should run. Firstly you can see I have listed workflow_dispatch, this allows me to manually trigger this job through the Github interface if needed. Then I also have a configuration for push to the main branch or to a pull_request to the main branch. In either of these scenarios we do a further paths check, this means that when there is a push to the main branch or a pull request to the main branch the action will only run if there is a change to a file listed in this paths array e.g. only run if there is a change to a terraform file.

on:
  workflow_dispatch:
  push:
    branches: [main]
    paths: ["terraform/**"]

  pull_request:
    branches: [main]
    paths: ["terraform/**"]

Now we have when we want to run the action in place, this start to define what we want our jobs to do!

The first job I would like to do is run a terraform plan, however, before I can do this we need to checkout the code and authenticate with our AWS credentials!

Within the job, I am starting with the GitHub Checkout action to checkout the code from our GitHub repository. Then, I use the “Configure AWS credentials Github action” which allows the action to use my AWS access and secret keys.

jobs:
  plan:
    name: Terraform plan
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js 14.x
        uses: actions/setup-node@v1
        with:
          node-version: 14.x
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID}}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: eu-west-2

With the code being checked out and our credentials being authenticated, it time to try and run the terraform plan.

Terraform Plan

In order to run a terraform plan we need to navigate into the right directory and initialise terraform.

- name: Initialise project and view terraform plan
  run: |
    cd terraform
    cd qa
    terraform fmt
    terraform init

With the AWS credentials being read it and changing into the right directory, terraform is successfully initialising!

Then in order to run a plan, I can add in the plan command and pass in all the env vars that are required:

- name: Initialise project and view terraform plan
  run: |
    cd terraform
    cd qa
    terraform fmt
    terraform init
    terraform plan -var='example_api_key=${{ secrets.EXAMPLE_API_KEY }}'

With plan set up, it is now time to get the deployment job set up as well!

Terraform Deploy

We can set up another job for doing the deployment and within this job we can specify that it needs plan to complete successfully before it can be ran, using the needs syntax. For the plan we do want it to run on every commit to a PR, to test our changes — but we do not want to do a deployment unless changes have been approved and it has been merged into the main branch. To specify this we use the runs-on syntax so it only runs when its on the main branch!

deploy:
    name: Terraform Deploy
    needs: plan
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js 14.x
        uses: actions/setup-node@v1
        with:
          node-version: 14.x
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: eu-west-2
      - name: Initialise project and deploy terraform
        run: |
          cd terraform
          cd qa
          terraform fmt
          terraform init
          terraform apply -var='example_api_key=${{ secrets.EXAMPLE_API_KEY }}' --auto-approve=true

As you can see there isn’t that many steps involved, here is this complete workflow file with all steps:

name: Example Service Release

on:
  workflow_dispatch:
  push:
    branches: [main]
    paths: ["terraform/**"]

  pull_request:
    branches: [main]
    paths: ["terraform/**"]

jobs:
  plan:
    name: Terraform plan
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js 14.x
        uses: actions/setup-node@v1
        with:
          node-version: 14.x
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID}}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: eu-west-2
      - name: Initialise project and view terraform plan
        run: |
          cd terraform
          cd qa
          terraform fmt
          terraform init
          terraform plan -var='example_api_key=${{ secrets.EXAMPLE_API_KEY }}'

  deploy:
    name: Terraform Deploy
    needs: plan
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js 14.x
        uses: actions/setup-node@v1
        with:
          node-version: 14.x
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: eu-west-2
      - name: Initialise project and deploy terraform
        run: |
          cd terraform
          cd qa
          terraform fmt
          terraform init
          terraform apply -var='example_api_key=${{ secrets.EXAMPLE_API_KEY }}' --auto-approve=true