Terraform Drift Detection #47
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Terraform Drift Detection | |
on: | |
workflow_dispatch: | |
schedule: | |
- cron: '0 3 * * *' | |
permissions: | |
contents: read | |
issues: write | |
env: | |
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
AWS_REGION: ${{ secrets.AWS_BASE_REGION }} | |
TFSTATE_BUCKET: ${{ secrets.TFSTATE_BUCKET }} | |
TFSTATE_KEY: ${{ secrets.TFSTATE_KEY }} | |
GRAFANA_ADMIN_PASSWORD: ${{ secrets.GRAFANA_ADMIN_PASSWORD }} | |
ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }} | |
EKS_CLUSTER_NAME: ${{ secrets.EKS_CLUSTER_NAME }} | |
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }} | |
CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }} | |
CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }} | |
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} | |
TAILSCALE_CLIENT_ID: ${{ secrets.TAILSCALE_CLIENT_ID }} | |
TAILSCALE_CLIENT_SECRET: ${{ secrets.TAILSCALE_CLIENT_SECRET }} | |
CROWDSEC_ENROLL_KEY: ${{ secrets.CROWDSEC_ENROLL_KEY }} | |
jobs: | |
terraform-plan: | |
name: Terraform Plan | |
runs-on: ubuntu-latest | |
outputs: | |
TFPLAN_EXIT_CODE: ${{ steps.tfplan.outputs.exitcode }} | |
defaults: | |
run: | |
working-directory: ./terraform | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- name: Configure AWS Credentials | |
uses: aws-actions/configure-aws-credentials@v4 | |
with: | |
aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} | |
aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} | |
aws-region: ${{ env.AWS_REGION }} | |
- name: Setup Terraform | |
uses: hashicorp/setup-terraform@v3 | |
with: | |
terraform_wrapper: false | |
- name: Terraform Init | |
run: | | |
terraform init \ | |
-backend-config="bucket=${TFSTATE_BUCKET}" \ | |
-backend-config="key=${TFSTATE_KEY}" \ | |
-backend-config="region=${AWS_REGION}" \ | |
-backend-config="encrypt=true" | |
- name: Terraform Plan | |
id: tfplan | |
env: | |
TF_VAR_GRAFANA_ADMIN_PASSWORD: ${{ env.GRAFANA_ADMIN_PASSWORD }} | |
TF_VAR_CLOUDFLARE_ZONE_ID: ${{ env.CLOUDFLARE_ZONE_ID }} | |
TF_VAR_CLOUDFLARE_TOKEN: ${{ env.CLOUDFLARE_TOKEN }} | |
TF_VAR_CLOUDFLARE_EMAIL: ${{ env.CLOUDFLARE_EMAIL }} | |
TF_VAR_CLOUDFLARE_API_TOKEN: ${{ env.CLOUDFLARE_API_TOKEN }} | |
TF_VAR_SLACK_WEBHOOK: ${{ env.SLACK_WEBHOOK }} | |
TF_VAR_TAILSCALE_CLIENT_ID: ${{ env.TAILSCALE_CLIENT_ID }} | |
TF_VAR_TAILSCALE_CLIENT_SECRET: ${{ env.TAILSCALE_CLIENT_SECRET }} | |
TF_VAR_CROWDSEC_ENROLL_KEY: ${{ env.CROWDSEC_ENROLL_KEY }} | |
run: | | |
export exitcode=0 | |
terraform plan -var-file ./stages/prod.tfvars -detailed-exitcode -no-color -out tfplan || exitcode=$? | |
echo "exitcode=$exitcode" >> $GITHUB_ENV | |
if [ $exitcode -eq 1 ]; then | |
echo Terraform Plan Failed! | |
exit 1 | |
else | |
exit 0 | |
fi | |
- name: Upload Terraform Plan | |
uses: actions/upload-artifact@v4 | |
with: | |
name: tfplan | |
path: tfplan | |
- name: Create String Output | |
id: tf-plan-string | |
run: | | |
TERRAFORM_PLAN=$(terraform show -no-color tfplan) | |
delimiter="$(openssl rand -hex 8)" | |
echo "summary<<${delimiter}" >> $GITHUB_OUTPUT | |
echo "## Terraform Plan Output" >> $GITHUB_OUTPUT | |
echo "<details><summary>Click to expand</summary>" >> $GITHUB_OUTPUT | |
echo "" >> $GITHUB_OUTPUT | |
echo '```terraform' >> $GITHUB_OUTPUT | |
echo "$TERRAFORM_PLAN" >> $GITHUB_OUTPUT | |
echo '```' >> $GITHUB_OUTPUT | |
echo "</details>" >> $GITHUB_OUTPUT | |
echo "${delimiter}" >> $GITHUB_OUTPUT | |
- name: Publish Terraform Plan to Task Summary | |
env: | |
SUMMARY: ${{ steps.tf-plan-string.outputs.summary }} | |
run: | | |
echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY | |
- name: Publish Drift Report | |
if: steps.tf-plan.outputs.exitcode == 2 | |
uses: actions/github-script@v7 | |
env: | |
SUMMARY: "${{ steps.tf-plan-string.outputs.summary }}" | |
with: | |
github-token: ${{ github.token }} | |
script: | | |
const body = `${process.env.SUMMARY}`; | |
const title = 'Terraform Configuration Drift Detected'; | |
const creator = 'github-actions[bot]' | |
// Look to see if there is an existing drift issue | |
const issues = await github.rest.issues.listForRepo({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
state: 'open', | |
creator: creator, | |
title: title | |
}) | |
if( issues.data.length > 0 ) { | |
// We assume there shouldn't be more than 1 open issue, since we update any issue we find | |
const issue = issues.data[0] | |
if ( issue.body == body ) { | |
console.log('Drift Detected: Found matching issue with duplicate content') | |
} else { | |
console.log('Drift Detected: Found matching issue, updating body') | |
github.rest.issues.update({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: issue.number, | |
body: body | |
}) | |
} | |
} else { | |
console.log('Drift Detected: Creating new issue') | |
github.rest.issues.create({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
title: title, | |
body: body | |
}) | |
} | |
- name: Publish Drift Report | |
if: steps.tf-plan.outputs.exitcode == 0 | |
uses: actions/github-script@v7 | |
with: | |
github-token: ${{ github.token }} | |
script: | | |
const title = 'Terraform Configuration Drift Detected'; | |
const creator = 'github-actions[bot]' | |
// Look to see if there is an existing drift issue | |
const issues = await github.rest.issues.listForRepo({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
state: 'open', | |
creator: creator, | |
title: title | |
}) | |
if( issues.data.length > 0 ) { | |
const issue = issues.data[0] | |
github.rest.issues.update({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: issue.number, | |
state: 'closed' | |
}) | |
} | |
- name: Error on Failure | |
if: steps.tf-plan.outputs.exitcode == 2 | |
run: exit 1 |