Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions .github/workflows/test_terragrunt.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
---
name: Test Terragrunt

on:
pull_request: # Plan.
paths: [tests/terragrunt/**, .github/workflows/test_terragrunt.yaml]
types: [opened, synchronize, reopened, labeled]
merge_group: # Apply.
types: [checks_requested]
workflow_dispatch:
inputs:
command:
description: 'Infrastructure command to run'
required: true
default: 'plan'
type: choice
options:
- plan
- apply
- destroy

env:
TF_VERSION: "1.5.0"
TG_VERSION: "0.48.0"
AWS_REGION: "us-west-1"

jobs:
Target:
runs-on: ubuntu-24.04

permissions:
issues: write # Required to add PR label.
pull-requests: write # Required to add PR comment.

outputs:
targets: ${{ steps.changed.outputs.targets }}

steps:
- name: Changed files
id: changed
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.number }}
run: |
# Add link to PR during apply job summary.
if [[ "${{ github.event_name }}" == "merge_group" ]]; then
PR_NUMBER=$(echo "${{ github.ref_name }}" | sed -n 's/.*pr-\([0-9]*\)-.*/\1/p')
echo "View PR [#${PR_NUMBER}](https://github.com/${{ github.repository }}/pull/${PR_NUMBER}) to review planned proposal." >> $GITHUB_STEP_SUMMARY
fi
# Remove "tg-plan" PR label if it exists.
if [[ "${{ github.event.action }}" == "labeled" ]]; then gh api /repos/${{ github.repository }}/issues/${PR_NUMBER}/labels/tg-plan --method DELETE --silent; fi
# Output changed targets.
changed=$(gh api /repos/${{ github.repository }}/pulls/${PR_NUMBER}/files --paginate --jq '.[].filename')
echo "targets=$(echo "$changed" | jq -R 'select(test("^tests/terragrunt/live/")) | split("/")[4]' | jq -c -s 'unique | sort')" >> $GITHUB_OUTPUT

TG:
runs-on: ubuntu-24.04
needs: [Target]
if: ${{ needs.Target.outputs.targets != '[]' || github.event_name == 'workflow_dispatch' }}

permissions:
actions: read # Required to identify workflow run.
checks: write # Required to add status summary.
contents: read # Required to checkout repository.
id-token: write # Required to authenticate via OIDC.
issues: write # Required to add PR label.
pull-requests: write # Required to add PR comment.

strategy:
fail-fast: false
matrix:
target: ${{ fromJson(needs.Target.outputs.targets) }}

concurrency:
cancel-in-progress: false
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name}}-${{ matrix.target }}

environment: ${{ matrix.target }}

steps:
- name: Authenticate AWS
uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 # v5.0.0
with:
aws-region: ${{ vars.AWS_REGION }}
role-to-assume: ${{ vars.AWS_ROLE }}
role-session-name: tg-via-pr-${{ github.run_id }}-${{ github.run_attempt }}

- name: Authenticate GitHub
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.number }}
run: |
# Authenticate with GitHub token.
git config --global url."https://token:${GH_TOKEN}@github.com".insteadOf "https://github.com"
# Add the target name as a PR label if it does not exist.
if [[ "${{ github.event_name }}" == "pull_request" && "${{ !contains(github.event.pull_request.labels.*.name, matrix.target) }}" == "true" ]]; then
gh api /repos/${{ github.repository }}/issues/${PR_NUMBER}/labels --method POST --field "labels[]=${{ matrix.target }}" --silent
fi

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}

- name: Setup Terragrunt
run: |
wget https://github.com/gruntwork-io/terragrunt/releases/download/v${{ env.TG_VERSION }}/terragrunt_linux_amd64
chmod +x terragrunt_linux_amd64
sudo mv terragrunt_linux_amd64 /usr/local/bin/terragrunt

- name: Checkout PR
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false

- name: Provision TG
id: tg
uses: ./
with:
working-directory: tests/terragrunt/live/project/${{ matrix.target }}
tool: terragrunt
command: ${{ github.event_name == 'merge_group' && 'apply' || (github.event_name == 'workflow_dispatch' && github.event.inputs.command || 'plan') }}
arg-lock-timeout: 3m
plan-encrypt: secrets.TF_ENCRYPTION
plan-parity: true
retention-days: 1
expand-diff: true
tag-actor: never
arg-auto-approve: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.command == 'apply' || github.event.inputs.command == 'destroy') }}

- name: Troubleshoot TG
if: ${{ failure() && github.event_name == 'merge_group' }}
uses: op5dev/prompt-ai@4cacb93e4a1e101f3a89650b31a3582321f2461d # v2.0.0
with:
model: openai/gpt-4.1-mini
system-prompt: You are a helpful DevOps assistant and expert at troubleshooting Terragrunt errors.
user-prompt: Troubleshoot the following Terragrunt output; ${{ steps.tg.outputs.result }}

- name: Clear directory
if: ${{ failure() && github.event_name == 'merge_group' }}
run: find ${{ github.workspace }} -mindepth 1 -delete

- name: Checkout main
if: ${{ failure() && github.event_name == 'merge_group' }}
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: main
persist-credentials: false

- name: Rollback TG
if: ${{ failure() && github.event_name == 'merge_group' }}
uses: ./
with:
working-directory: tests/terragrunt/live/project/${{ matrix.target }}
tool: terragrunt
command: apply
arg-auto-approve: true
arg-lock-timeout: 3m
comment-pr: never

# Todo: Will uncomment when this workflow is fully implemented.
Notify:
runs-on: [ubuntu-24.04]
needs: [Target, TG]
if: ${{ !cancelled() }}

permissions:
actions: read # Required to identify workflow run.

steps:
- name: Notify Slack on failure
if: ${{ github.event_name == 'merge_group' && contains(needs.*.result, 'failure') }}
uses: gamesight/slack-workflow-status@68bf00d0dbdbcb206c278399aa1ef6c14f74347a # v1.3.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
slack_webhook_url: https://hooks.slack.com/services/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

- name: Exit status
run: exit ${{ contains(needs.*.result, 'failure') && 1 || 0 }}
Loading
Loading