Introduction
In the earlier blog post, I showcased the steps to achieve continuous deployment using GitHub Actions and AWS CodeBuild specifically for code deployment. Expanding on those principles, let’s delve into the next phase of our workflow — incorporating infrastructure deployment through Terraform.
Table of contents
Open Table of contents
Setup Terraform Cloud Workspace
The GitHub Action you’re about to set up will seamlessly integrate with Terraform Cloud, allowing you to efficiently plan and apply your configurations. Before configuring the Actions workflow, ensure you’ve taken the necessary steps: creating a workspace, incorporating your AWS credentials into your Terraform Cloud workspace, and generating a Terraform Cloud user API token.
Step 1:
To kickstart the process, initiate a new Terraform Cloud workspace. Navigate to the Projects & Workspaces page, click on New Workspace, and opt for the API-Driven Workflow option.
Step 2:
Provide a name for the workspace and proceed by clicking on the Create button.
Step 3:
Now, it’s time to include your AWS IAM credentials as environmental variables (AWS_ACCESS_KEY_ID
, AWS_SECRET_ACCESS_KEY
, and AWS_DEFAULT_REGION
) within the workspace. Navigate to the Variables section in the workspace, click on Add Variable, and select Environment variable as the variable category. Terraform Cloud will use these credentials to authenticate to AWS.
Step 4:
Now, let’s generate an API token to enable authentication for the Actions workflow in Terraform Cloud. Head to the Tokens page within your Terraform Cloud User Settings. Click on Create an API token, provide a description, and then click Generate token.
Set up a GitHub repository
Establish a GitHub repository dedicated to housing your infrastructure configuration code and the associated GitHub Actions workflow.
Step 1:
Once you’ve created the repository, proceed to the Settings page. Access the Secrets and Variables menu, specifically selecting Actions. Click New Repository Secret and input the Terraform API token generated in the previous step.
Step 2:
Now, it’s time to incorporate the following GitHub Actions workflow.
Plan
name: "Terraform"
on:
pull_request:
branches:
- main
paths:
- terraform/**
- modules/**
env:
TF_API_TOKEN: ${{ secrets.TF_API_TOKEN }}
TF_WORKING_DIRECTORY: terraform/
permissions:
contents: read
pull-requests: write
jobs:
terraform:
name: "Plan"
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ env.TF_WORKING_DIRECTORY }}
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
cli_config_credentials_token: ${{ env.TF_API_TOKEN }}
- name: Terraform Init
id: init
run: |
git config --global url."https://oauth2:${{ secrets.GITHUB_TOKEN }}@github.com".insteadOf ssh://git@github.com
terraform init
- name: Terraform Validate
id: validate
run: terraform validate -no-color
- name: Terraform Plan
id: plan
run: terraform plan -no-color
continue-on-error: true
- name: Update PR
uses: actions/github-script@v7
env:
PLAN_OUTPUT: ${{ steps.plan.outputs.stdout }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// 1. Retrieve existing bot comments for the PR
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
})
const botComment = comments.find(comment => {
return comment.user.type === 'Bot' && comment.body.includes('Terraform Plan Results')
})
// 2. Prepare format of the comment
const output = `### Terraform Plan Results 🚀
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
<details><summary>Validation Output</summary>
\`\`\`\n
${{ steps.validate.outputs.stdout }}
\`\`\`
</details>
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`\n
${process.env.PLAN_OUTPUT}
\`\`\`
</details>
*Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.TF_WORKING_DIRECTORY }}\`, Workflow: \`${{ github.workflow }}\`*`;
// 3. If we have a comment, update it, otherwise create a new one
if (botComment) {
github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: output
})
} else {
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
}
Now, let’s delve into the deployment job’s steps. We’ve included four key actions:
-
Check out code: I’m utilizing a GitHub action to retrieve the code from the repository.
-
Terraform Init: This step utilizes the
hashicorp/setup-terraform@v3
action to initialize Terraform. I included the Git configuration in case the code involves private GitHub modules. -
Terraform Validate: This step is dedicated to validating the Terraform code.
-
Terraform Plan: This step is employed to execute the Terraform plan.
-
Update PR: This step leverages the
actions/github-script@v7
action to update the Pull Request with the results from the preceding actions.
Apply
name: "Terraform"
on:
push:
branches:
- main
paths:
- terraform/**
- modules/**
env:
TF_API_TOKEN: ${{ secrets.TF_API_TOKEN }}
TF_WORKING_DIRECTORY: terraform/
permissions:
contents: read
jobs:
terraform:
name: "Apply"
runs-on: ubuntu-latest
environment: prd
defaults:
run:
working-directory: ${{ env.TF_WORKING_DIRECTORY }}
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
cli_config_credentials_token: ${{ env.TF_API_TOKEN }}
- name: Terraform Init
id: init
run: |
git config --global url."https://oauth2:${{ secrets.GITHUB_TOKEN }}@github.com".insteadOf ssh://git@github.com
terraform init
- name: Terraform Apply
id: apply
run: terraform apply -auto-approve -no-color
Let’s delve into the steps outlined in this workflow.
-
Check out code: I’m utilizing a GitHub action to retrieve the code from the repository.
-
Terraform Init: This step utilizes the
hashicorp/setup-terraform@v3
action to initialize Terraform. Additionally, I’ve included Git configuration in case the code incorporates private GitHub modules. Executing this action is a prerequisite before proceeding with the apply step. -
Terraform Apply: This step applies the Terraform infrastructure configuration to AWS.
Demo
Now, let’s put our setup into action. For demonstration purposes, I’ve included Terraform configuration files to create an S3 bucket.
Pull Request
After incorporating the Terraform configuration files into a feature
branch, let’s create a pull request to the main
branch. This action will initiate GitHub Actions to execute Validation and Plan, with the results seamlessly added back to the pull request for convenient review.
Merge
After a thorough review of the pull request and ensuring the Terraform plan results meet expectations, proceed to merge the pull request. This will trigger the Terraform apply process.
Wrapping it up
In conclusion, we’ve explored the powerful capabilities of GitHub Actions in automating Terraform infrastructure deployments. If you’ve already followed my previous blog on code deployment using GitHub Actions, you can seamlessly combine these processes to achieve sequential infrastructure deployment and code deployment, streamlining your development workflow.