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:
GitHub account (free or paid). Instructions here
GitHub Repository. Instructions here
Terraform configuration files in the repository
Visual Studio Code
Clone of the repository for local use. Instructions here
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:
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.