Skip to content

Merge pull request #104 from coder/jatcod3r/karpenter-migrate #15

Merge pull request #104 from coder/jatcod3r/karpenter-migrate

Merge pull request #104 from coder/jatcod3r/karpenter-migrate #15

Workflow file for this run

name: "Terraform Deploy"
on:
push:
branches:
- main
paths:
- 'infra/**'
- '.github/workflows/terraform-*.yml'
workflow_dispatch:
permissions:
contents: read
id-token: write # Required for AWS OIDC
concurrency:
group: terraform-deploy-${{ github.ref }}
cancel-in-progress: false # Don't cancel terraform operations
env:
TERRAFORM_VERSION: "1.15.8"
TERRAGRUNT_VERSION: "0.99.1"
jobs:
discover-changes:
name: Discover Changed Directories
runs-on: ubuntu-latest
outputs:
changed_dirs: ${{ steps.find-changes.outputs.changed_dirs }}
filter_args: ${{ steps.build-filters.outputs.filter_args }}
has_changes: ${{ steps.find-changes.outputs.has_changes }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Find changed directories
id: find-changes
shell: bash
run: |
# For push events, compare with previous commit
if [ "${{ github.event_name }}" = "push" ]; then
BASE_REF="${{ github.event.before }}"
HEAD_REF="${{ github.sha }}"
else
# For workflow_dispatch, we'll scan all directories
echo "Workflow dispatch - will process all directories"
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "changed_dirs=[]" >> $GITHUB_OUTPUT
exit 0
fi
# Find all changed files in infra/
changed_files=$(git diff --name-only "$BASE_REF" "$HEAD_REF" -- infra/ || echo "")
if [ -z "$changed_files" ]; then
echo "No changes detected in infra/"
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "changed_dirs=[]" >> $GITHUB_OUTPUT
exit 0
fi
echo "Changed files:"
echo "$changed_files"
echo ""
# Find directories containing terragrunt.hcl files
terragrunt_dirs=$(find infra -type f -name "terragrunt.hcl" -not -path "*/.terraform/*" | sed 's|/terragrunt.hcl||' | sort -u)
# Identify which Terragrunt directories were affected
changed_dirs=""
while IFS= read -r file; do
file_dir=$(dirname "$file")
# Find the closest parent directory that contains a terragrunt.hcl
while [ "$file_dir" != "." ] && [ "$file_dir" != "infra" ]; do
if echo "$terragrunt_dirs" | grep -q "^$file_dir$"; then
changed_dirs="${changed_dirs}${file_dir}"$'\n'
break
fi
file_dir=$(dirname "$file_dir")
done
done <<< "$changed_files"
# Remove duplicates and sort
changed_dirs=$(echo "$changed_dirs" | sort -u | grep -v '^$' || echo "")
if [ -z "$changed_dirs" ]; then
# Changes were in infra but not in any terragrunt module directory
echo "Changes detected but not in terragrunt module directories"
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "changed_dirs=[]" >> $GITHUB_OUTPUT
else
echo "Changed Terragrunt directories:"
echo "$changed_dirs"
# Strip "infra/" prefix since terragrunt commands run from within infra/
changed_dirs_relative=$(echo "$changed_dirs" | sed 's|^infra/||')
echo "Relative paths (for filters):"
echo "$changed_dirs_relative"
# Convert to JSON array
changed_dirs_json=$(echo "$changed_dirs_relative" | jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "changed_dirs=$changed_dirs_json" >> $GITHUB_OUTPUT
fi
- name: Build filter arguments
id: build-filters
shell: bash
run: |
changed_dirs='${{ steps.find-changes.outputs.changed_dirs }}'
if [ "$changed_dirs" = "[]" ] || [ -z "$changed_dirs" ]; then
echo "filter_args=" >> $GITHUB_OUTPUT
exit 0
fi
# Build --filter arguments for each changed directory
filter_args=""
for dir in $(echo "$changed_dirs" | jq -r '.[]'); do
filter_args="${filter_args} --filter=${dir}"
done
# Trim leading space
filter_args=$(echo "$filter_args" | sed 's/^ //')
echo "Filter arguments: $filter_args"
echo "filter_args=$filter_args" >> $GITHUB_OUTPUT
apply:
name: Apply Infrastructure
needs: discover-changes
runs-on: ubuntu-latest
environment:
name: production
url: https://ai.coder.com
env:
# Repository Variables
TERRAGRUNT_ARGS: ${{ vars.TERRAGRUNT_ARGS }}
TERRAFORM_INIT_ARGS: ${{ vars.TERRAFORM_INIT_ARGS }}
TERRAFORM_APPLY_ARGS: ${{ vars.TERRAFORM_APPLY_ARGS }}
CODER_TF_BACKEND_AWS_BUCKET_NAME: ${{ vars.CODER_TF_BACKEND_AWS_BUCKET_NAME }}
CODER_TF_BACKEND_AWS_REGION: ${{ vars.CODER_TF_BACKEND_AWS_REGION }}
CODER_TF_BACKEND_AWS_PROFILE: ${{ vars.CODER_TF_BACKEND_AWS_PROFILE }}
CODER_TF_BACKEND_ENCRYPT: ${{ vars.CODER_TF_BACKEND_ENCRYPT }}
CODER_AWS_PROFILE: ${{ vars.CODER_AWS_PROFILE }}
CODER_AWS_REGION: ${{ vars.CODER_AWS_REGION }}
CODER_AWS_AZS: ${{ vars.CODER_AWS_AZS }}
CODER_CLUSTER_NAME: ${{ vars.CODER_CLUSTER_NAME }}
CODER_CLUSTER_VERSION: ${{ vars.CODER_CLUSTER_VERSION }}
CODER_CLUSTER_INSTANCE_TYPE: ${{ vars.CODER_CLUSTER_INSTANCE_TYPE }}
CODER_VPC_NAME: ${{ vars.CODER_VPC_NAME }}
CODER_VPC_CIDR: ${{ vars.CODER_VPC_CIDR }}
CODER_VPC_AZS: ${{ vars.CODER_VPC_AZS }}
CODER_VPC_NAT_NAME: ${{ vars.CODER_VPC_NAT_NAME }}
CODER_DB_SUBNET_GROUP_NAME: ${{ vars.CODER_DB_SUBNET_GROUP_NAME }}
CODER_PUBLIC_SUBNET_SUFFIX: ${{ vars.CODER_PUBLIC_SUBNET_SUFFIX }}
CODER_PRIVATE_SUBNET_SUFFIX: ${{ vars.CODER_PRIVATE_SUBNET_SUFFIX }}
CODER_DB_RDS_ID: ${{ vars.CODER_DB_RDS_ID }}
CODER_DB_USERNAME: ${{ vars.CODER_DB_USERNAME }}
CODER_DB_NAME: ${{ vars.CODER_DB_NAME }}
CODER_IMAGE_REPO: ${{ vars.CODER_IMAGE_REPO }}
CODER_IMAGE_TAG: ${{ vars.CODER_IMAGE_TAG }}
LOKI_S3_BUCKET_NAME: ${{ vars.LOKI_S3_BUCKET_NAME }}
LOKI_S3_BUCKET_REGION: ${{ vars.LOKI_S3_BUCKET_REGION }}
GRAFANA_DOMAIN_NAME: ${{ vars.GRAFANA_DOMAIN_NAME }}
GRAFANA_DB_USERNAME: ${{ vars.GRAFANA_DB_USERNAME }}
CODER_DOMAIN_NAME: ${{ vars.CODER_DOMAIN_NAME }}
CODER_WILDCARD_URL: ${{ vars.CODER_WILDCARD_URL }}
CODER_LICENSE: ${{ vars.CODER_LICENSE }}
CODER_EXPERIMENTS: ${{ vars.CODER_EXPERIMENTS }}
CODER_BUILT_IN_PROVISIONER_COUNT: ${{ vars.CODER_BUILT_IN_PROVISIONER_COUNT }}
CODER_OIDC_SIGN_IN_TEXT: ${{ vars.CODER_OIDC_SIGN_IN_TEXT }}
CODER_OIDC_ICON_URL: ${{ vars.CODER_OIDC_ICON_URL }}
CODER_OIDC_SCOPES: ${{ vars.CODER_OIDC_SCOPES }}
CODER_OIDC_EMAIL_DOMAIN: ${{ vars.CODER_OIDC_EMAIL_DOMAIN }}
CODER_OIDC_ISSUER_URL: ${{ vars.CODER_OIDC_ISSUER_URL }}
# Secrets
CF_EMAIL: ${{ secrets.CF_EMAIL }}
CF_TOKEN: ${{ secrets.CF_TOKEN }}
CODER_GITHUB_EXTERN_AUTH_CLIENT_ID: ${{ secrets.CODER_GITHUB_EXTERN_AUTH_CLIENT_ID }}
CODER_GITHUB_EXTERN_AUTH_CLIENT_SECRET: ${{ secrets.CODER_GITHUB_EXTERN_AUTH_CLIENT_SECRET }}
CODER_GITHUB_OAUTH_CLIENT_ID: ${{ secrets.CODER_GITHUB_OAUTH_CLIENT_ID }}
CODER_GITHUB_OAUTH_CLIENT_SECRET: ${{ secrets.CODER_GITHUB_OAUTH_CLIENT_SECRET }}
CODER_OIDC_CLIENT_ID: ${{ secrets.CODER_OIDC_CLIENT_ID }}
CODER_OIDC_CLIENT_SECRET: ${{ secrets.CODER_OIDC_CLIENT_SECRET }}
CODER_RUNNER_TOKEN: ${{ secrets.CODER_RUNNER_TOKEN }}
GRAFANA_USER_PASSWORD: ${{ secrets.GRAFANA_USER_PASSWORD }}
VANTAGE_API_TOKEN: ${{ secrets.VANTAGE_API_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TERRAFORM_VERSION }}
terraform_wrapper: false
- name: Setup Terragrunt
uses: gruntwork-io/terragrunt-action@v3
with:
tf_path: "terraform"
tg_version: ${{ env.TERRAGRUNT_VERSION }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6.2.3
with:
role-to-assume: ${{ vars.AWS_RUNNER_ROLE_ARN }}
aws-region: ${{ vars.AWS_RUNNER_REGION }}
aws-profile: ${{ vars.CODER_AWS_PROFILE }}
- name: Terragrunt init
shell: bash
working-directory: infra
run: |
echo "::group::Initializing all modules"
FILTER_ARGS="${{ needs.discover-changes.outputs.filter_args }}"
# Build the full command with filters
CMD="terragrunt run --all --queue-exclude-dir='./' --provider-cache"
if [ -n "$FILTER_ARGS" ]; then
CMD="$CMD $FILTER_ARGS"
echo "Applying filters: $FILTER_ARGS"
else
echo "No filters - processing all modules"
fi
if [ -n "${TERRAGRUNT_ARGS}" ]; then
CMD="$CMD ${TERRAGRUNT_ARGS}"
fi
CMD="$CMD -- init -input=false -reconfigure -upgrade"
if [ -n "${TERRAFORM_INIT_ARGS}" ]; then
CMD="$CMD ${TERRAFORM_INIT_ARGS}"
fi
echo "Running: $CMD"
eval "$CMD"
echo "✓ Initialization successful"
echo "::endgroup::"
- name: Terragrunt run-all apply
shell: bash
working-directory: infra
run: |
echo "::group::Running Terragrunt run-all apply"
FILTER_ARGS="${{ needs.discover-changes.outputs.filter_args }}"
# Build the full command with filters
CMD="terragrunt run --all --queue-exclude-dir='./' --provider-cache"
if [ -n "$FILTER_ARGS" ]; then
CMD="$CMD $FILTER_ARGS"
echo "Applying filters: $FILTER_ARGS"
else
echo "No filters - processing all modules"
fi
if [ -n "${TERRAGRUNT_ARGS}" ]; then
CMD="$CMD ${TERRAGRUNT_ARGS}"
fi
CMD="$CMD -- apply -input=false -auto-approve"
if [ -n "${TERRAFORM_APPLY_ARGS}" ]; then
CMD="$CMD ${TERRAFORM_APPLY_ARGS}"
fi
echo "Running: $CMD"
eval "$CMD" 2>&1 | tee apply.log
EXIT_CODE=${PIPESTATUS[0]}
echo "::endgroup::"
if [[ $EXIT_CODE -eq 0 ]]; then
echo "✅ Apply succeeded"
else
echo "❌ Apply failed"
exit $EXIT_CODE
fi
- name: Upload apply log
if: always()
uses: actions/upload-artifact@v4
with:
name: apply-log
path: infra/apply.log
retention-days: 90
summary:
name: Deployment Summary
needs: [discover-changes, apply]
if: always()
runs-on: ubuntu-latest
steps:
- name: Check deployment results
shell: bash
run: |
echo "## Terraform Deployment Summary"
echo ""
changed_dirs='${{ needs.discover-changes.outputs.changed_dirs }}'
has_changes="${{ needs.discover-changes.outputs.has_changes }}"
apply_result="${{ needs.apply.result }}"
# Display changed directories
if [ "$has_changes" = "true" ] && [ "$changed_dirs" != "[]" ] && [ -n "$changed_dirs" ]; then
echo "Deployed Terragrunt directories:"
echo "$changed_dirs" | jq -r '.[]' | sed 's/^/ - /'
echo ""
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "Manual deployment - all modules processed"
echo ""
elif [ "$has_changes" = "true" ]; then
echo "Changes detected in infra/ but not in terragrunt module directories"
echo ""
else
echo "No changes detected in infra/"
echo ""
fi
echo "Deployment result: $apply_result"
echo ""
if [[ "$apply_result" == "success" ]]; then
echo "✅ Deployment succeeded!"
echo ""
echo "Infrastructure changes have been applied to production."
exit 0
else
echo "❌ Deployment failed"
echo ""
echo "Please check the logs and resolve any issues."
exit 1
fi