CodeBuild Buildspec for posting Terraform output to GitHub PRs

Wang Poh Peng
4 min readJun 17, 2024

GitHub PRs’ comments can be modified using GitHub’s REST API, hence we can make use of PAT tokens and write back Terraform commands’ outputs back as a comment for better infra changes review.

By storing the PAT token into AWS SecretsManager we can reference it to make comments to PR as a bot user.

Terraform Plan:

version: 0.2
env:
secrets-manager:
PAT_TOKEN: "AWS_ARN_OF_SECRETS_MANAGER_SECRET"
variables:
TF_DIR: "envs/test"

phases:
install:
runtime-versions:
nodejs: 18
commands:
- curl -s -qL -o terraform_install.zip https://releases.hashicorp.com/terraform/1.8.0/terraform_1.8.0_linux_amd64.zip
- unzip terraform_install.zip -d /usr/bin/
- chmod +x /usr/bin/terraform

finally:
- terraform --version
- aws sts get-caller-identity --o text
build:
commands: |
cd $TF_DIR
terraform init -no-color > /tmp/plan.txt 2>&1
terraform plan -var-file="dev.tfvars" -no-color >> /tmp/plan.txt 2>&1
# Save TF Plan Command output
cat /tmp/plan.txt
finally: |
export TF_PLAN_OUTPUT="$(cat '/tmp/plan.txt')"
export JSON_ENCODED_TF=$(echo "$TF_PLAN_OUTPUT" | jq -Rs .)
export CODE_BLOCK_START='Terraform Plan Output: \n```\n'
export CODE_BLOCK_END='\n```'
# Insert code block wrapper before and after
export FORMATTED_TF="${JSON_ENCODED_TF:0:1}$TF_DIR \n$CODE_BLOCK_START${JSON_ENCODED_TF:1}"
export FORMATTED_TF="${FORMATTED_TF::-1}$CODE_BLOCK_END${FORMATTED_TF: -1}"
export PR_NUMBER="${CODEBUILD_SOURCE_VERSION##*/}"

# Check if there is existing PR comment
comment_response=$(curl -s -H "X-GitHub-Api-Version: 2022-11-28" -H "Authorization: token ${PAT_TOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/lloydslistintelligence/${CODEBUILD_SRC_DIR##*/}/issues/${PR_NUMBER}/comments?q=user:svc-seasearcher-sas)

# echo "$comment_response" | jq -c '.[]'
match_found=0

while IFS= read -r comment; do
# Extract the id and body attributes from the current JSON object
id=$(jq -r '.id' <<< "$comment")
body=$(jq -r '.body' <<< "$comment")

echo $id

# Extract the first line of the body attribute
first_line=$(echo "$body" | head -n 1)

echo "$first_line"

# Check if the first line contains the specified substring
if [[ "$first_line" == *"$TF_DIR"* ]]; then
echo "Match found with id: $id - Modify existing comment for $TF_DIR"
curl -X PATCH -H "Authorization: Bearer ${PAT_TOKEN}" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "https://api.github.com/repos/lloydslistintelligence/${CODEBUILD_SRC_DIR##*/}/issues/comments/$id" -d "{\"body\": $FORMATTED_TF}"
match_found=1
fi
done < <(echo "$comment_response" | jq -c '.[]')

# Check if any matches were found based on the flag
if [[ $match_found -eq 0 ]]; then
echo "No matches found. Creating new comment for $TF_DIR"
curl -s -H "X-GitHub-Api-Version: 2022-11-28" -H "Authorization: token ${PAT_TOKEN}" -X POST -d "{\"body\": ${FORMATTED_TF}}" "https://api.github.com/repos/lloydslistintelligence/${CODEBUILD_SRC_DIR##*/}/issues/${PR_NUMBER}/comments"
fi

With the above logic, we can make sure the bot does not create new comment but update the existing comment for repeated workflows.

As the trigger to the CodeBuild Workflow provides the PR number as the key for the API to update, each new PR can be properly updated by its related webhook.

For further setup you may refer to the following for the CodeBuild infrastructure setup. Make sure the buildspec file reference to set to the sample above!

For an improved version of the logic to allow multiple CodeBuilds to run within the same repo, you may refer to the one below:

version: 0.2
env:
secrets-manager:
PAT_TOKEN: "ARN_OF_SECRETS_MANAGER_SC"
variables:
TF_DIR: "PATH_TO_TF_FILES"
ACCOUNT_ID: <AWS_ACCOUNT_ID>

phases:
install:
runtime-versions:
nodejs: 18
commands:
- curl -s -qL -o terraform_install.zip https://releases.hashicorp.com/terraform/1.7.5/terraform_1.7.5_linux_amd64.zip
- unzip terraform_install.zip -d /usr/bin/
- chmod +x /usr/bin/terraform
- ASSUME_ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/security_groups_manager_role"
- TF_ROLE=$(aws sts assume-role --role-arn $ASSUME_ROLE_ARN --role-session-name tfplan)
- export TF_ROLE
- export AWS_ACCESS_KEY_ID=$(echo "${TF_ROLE}" | jq -r '.Credentials.AccessKeyId')
- export AWS_SECRET_ACCESS_KEY=$(echo "${TF_ROLE}" | jq -r '.Credentials.SecretAccessKey')
- export AWS_SESSION_TOKEN=$(echo "${TF_ROLE}" | jq -r '.Credentials.SessionToken')

finally:
- terraform --version
- aws sts get-caller-identity --o text
build:
commands: |
cd $TF_DIR
terraform init
terraform plan -no-color -var-file=uat.tfvars &> /tmp/plan.txt
# Save TF Plan Command output
cat /tmp/plan.txt
finally: |
export TF_PLAN_OUTPUT="$(cat '/tmp/plan.txt')"
export JSON_ENCODED_TF=$(echo "$TF_PLAN_OUTPUT" | jq -Rs .)
export CODE_BLOCK_START='Terraform Plan Output: \n```\n'
export CODE_BLOCK_END='\n```'
# Insert code block wrapper before and after
export FORMATTED_TF="${JSON_ENCODED_TF:0:1}$TF_DIR \n$CODE_BLOCK_START${JSON_ENCODED_TF:1}"
export FORMATTED_TF="${FORMATTED_TF::-1}$CODE_BLOCK_END${FORMATTED_TF: -1}"
export PR_NUMBER="${CODEBUILD_SOURCE_VERSION##*/}"

# Check if there is existing PR comment
comment_response=$(curl -s -H "X-GitHub-Api-Version: 2022-11-28" -H "Authorization: token ${PAT_TOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/lloydslistintelligence/${CODEBUILD_SRC_DIR##*/}/issues/${PR_NUMBER}/comments?q=user:svc-seasearcher-sas)

# echo "$comment_response" | jq -c '.[]'
match_found=0

while IFS= read -r comment; do
# Extract the id and body attributes from the current JSON object
id=$(jq -r '.id' <<< "$comment")
body=$(jq -r '.body' <<< "$comment")

echo $id

# Extract the first line of the body attribute
first_line=$(echo "$body" | head -n 1)

echo "$first_line"

# Check if the first line contains the specified substring
if [[ "$first_line" == *"$TF_DIR"* ]]; then
echo "Match found with id: $id - Modify existing comment for $TF_DIR"
curl -X PATCH -H "Authorization: Bearer ${PAT_TOKEN}" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "https://api.github.com/repos/lloydslistintelligence/${CODEBUILD_SRC_DIR##*/}/issues/comments/$id" -d "{\"body\": $FORMATTED_TF}"
match_found=1
fi
done < <(echo "$comment_response" | jq -c '.[]')

# Check if any matches were found based on the flag
if [[ $match_found -eq 0 ]]; then
echo "No matches found. Creating new comment for $TF_DIR"
curl -s -H "X-GitHub-Api-Version: 2022-11-28" -H "Authorization: token ${PAT_TOKEN}" -X POST -d "{\"body\": ${FORMATTED_TF}}" "https://api.github.com/repos/lloydslistintelligence/${CODEBUILD_SRC_DIR##*/}/issues/${PR_NUMBER}/comments"
fi

The above checks the comment’s first line as the comment key, it makes sure that only when the key matches before updating existing comment or create a new comment when the comment key does not exist! In this case, the PATH where the terraform files are located serves as its key.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Wang Poh Peng
Wang Poh Peng

Written by Wang Poh Peng

Technology Enthusiast & Culture Explorer

No responses yet

Write a response