[Github Actions] Pipelines for Terraform

As I’ve stated in other posts I’ve done here, I am a big fan of Github Actions. I really like the simplicity of it and how easily you can build pipelines for your Terraform code.

Here, I want to show you what pipelines I am building for my terraform code and how they are working.

In my modules, I always have three pipelines:

  • AutoTag
  • Pre-commit check
  • Terraform deploy

Autotag

This is a rather simple pipeline that you can reuse for everything you want. I like to tag everything I am developing with this format: vMajor.Minor.Patch.

The autotag pipeline always runs when you merge your code and by default, will bump the Patch version.

In order to increase the major or the minor version of the tag, you simply need to add in your commit message #major or #minor and whenever a push is done to the main branch those will be bumped.

If you are using this pipeline in an organisation, make sure that you add a secret in the Github repository for a Personal Access Token, otherwise you won’t have enough permissions to push the tag back to the repository.

This is how it looks like:

name: Bump version
on:
  push:
    branches:
      - main
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: "0"
      - name: Bump version and push tag
        uses: anothrNick/github-tag-action@1.36.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          WITH_V: true
          DEFAULT_BUMP: patch

PreCommit

In this pipeline, I am usually checking if I ran pre-commit locally on all of my files and folders. This is checking whatever hooks I’ve added inside .pre-commit-config.yaml.

In my case the hooks are: tflint, tfdocs, fmt, validate, eof and trailing whitespaces. You can configure that file to your liking.

I’ve configured this to run on all pushes, because I want to make sure that I am not merging my feature branches without this check is passing.

Below there is an example of what I am using:

on:
  push:

jobs:
  pre-commit:
    name: "pre-commit"
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Setup python
        uses: actions/setup-python@v3

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v1

      - name: Install pre-commit
        run: pip install pre-commit

      - name: Install other dependencies
        run: |
          curl -sSLo ./terraform-docs.tar.gz https://terraform-docs.io/dl/v0.16.0/terraform-docs-v0.16.0-$(uname)-amd64.tar.gz
          tar -xzf terraform-docs.tar.gz
          chmod +x terraform-docs
          mv terraform-docs /usr/local/bin/terraform-docs
          curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
      - name: run pre-commit
        run: pre-commit run --all-files -v

Terraform Deploy

I also have a manual pipeline that runs examples inside of my module.

Whenever you want to deploy resources inside of an example, you can refer to this pipeline and you will have to select one of three options: plan / apply / destroy. By default that option is plan.

This pipeline can be easily copied from the module to the repository in which you are going to run the code you’ve built based on your Terraform modules.

Example pipeline:

on:
  workflow_dispatch:
    inputs:
      terraform_operation:
        description: "Terraform operation: plan, apply, destroy"
        required: true
        default: "plan"
        type: choice
        options:
          - plan
          - apply
          - destroy

jobs:
  terraform:
    name: "terraform"
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v1

      - name: Go to the example directory
        run: cd example

      - name: Terraform init
        run: terraform init

      - name: Terraform plan
        run: terraform plan
        if: "${{ github.event.inputs.terraform_operation == 'plan' }}"

      - name: Terraform apply
        run: terraform apply --auto-approve
        if: "${{ github.event.inputs.terraform_operation == 'apply' }}"

      - name: Terraform destroy
        run: terraform destroy --auto-approve
        if: "${{ github.event.inputs.terraform_operation == 'destroy' }}"

The sky is the limit when it comes to what you can embed in these pipelines. These can be easily replicated to other CI/CD tools like Jenkins, Gitlab CI, Bitbucket Pipelines, Circle CI and others, so don’t feel restricted to using Github Actions.