Skip to content

fix(cleanup): use AWS API directly for ECR/RDS cleanup, avoid terrafo… #19

fix(cleanup): use AWS API directly for ECR/RDS cleanup, avoid terrafo…

fix(cleanup): use AWS API directly for ECR/RDS cleanup, avoid terrafo… #19

# Deploy to AWS ECS Fargate
#
# This workflow deploys the CUDly application to AWS ECS Fargate with ALB.
# Alternative to Lambda for always-on containerized workloads.
#
# Required GitHub Secrets:
# - ADMIN_EMAIL: Admin email for notifications
#
# Required GitHub Variables:
# - AWS_REGION: AWS region
# - AWS_ROLE_TO_ASSUME: IAM role ARN for OIDC keyless auth
# - ECR_REPOSITORY: ECR repository name
#
# Triggered by:
# - Pushes to main or feat/multicloud-web-frontend branch
# - Manual workflow dispatch
# - Workflow call from deploy-all.yml
name: Deploy to AWS Fargate
permissions:
id-token: write
contents: read
concurrency:
group: deploy-fargate-${{ github.ref }}
cancel-in-progress: false
on:
push:
branches: [main, feat/multicloud-web-frontend]
workflow_dispatch:
inputs:
environment:
description: 'Environment'
required: true
type: choice
options: [dev, staging, prod]
workflow_call:
inputs:
environment:
required: true
type: string
image_uri:
required: false
type: string
env:
AWS_REGION: ${{ vars.AWS_REGION || 'us-east-1' }}
TF_VERSION: '1.10.0'
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
# Determine deployment environment
prepare:
name: Prepare Deployment
runs-on: ubuntu-latest
outputs:
environment: ${{ steps.set-env.outputs.environment }}
image_tag: ${{ steps.set-tag.outputs.tag }}
steps:
- name: Determine environment
id: set-env
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "environment=${{ inputs.environment }}" >> $GITHUB_OUTPUT
elif [[ "${{ github.event_name }}" == "workflow_call" ]]; then
echo "environment=${{ inputs.environment }}" >> $GITHUB_OUTPUT
else
echo "environment=dev" >> $GITHUB_OUTPUT
fi
- name: Set image tag
id: set-tag
run: |
echo "tag=${{ github.sha }}" >> $GITHUB_OUTPUT
# Deploy with Terraform
deploy:
name: Deploy to Fargate
runs-on: ubuntu-24.04-arm
needs: prepare
outputs:
alb_url: ${{ steps.outputs.outputs.alb_url }}
service_name: ${{ steps.outputs.outputs.service_name }}
environment:
name: aws-fargate-${{ needs.prepare.outputs.environment }}
url: ${{ steps.outputs.outputs.alb_url }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ env.AWS_REGION }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Terraform Init
env:
TF_BACKEND: ${{ secrets.TF_BACKEND_AWS }}
ENVIRONMENT: ${{ needs.prepare.outputs.environment }}
run: |
printf '%s\nkey = "github-fargate-%s/terraform.tfstate"\n' "$TF_BACKEND" "$ENVIRONMENT" > /tmp/backend.tfbackend
# Break any stale state lock from a previous failed run
BUCKET=$(grep -E '^\s*bucket\s*=' /tmp/backend.tfbackend 2>/dev/null | tr -d ' "' | cut -d= -f2)
if [ -n "$BUCKET" ]; then
LOCK_KEY="github-fargate-${ENVIRONMENT}/terraform.tfstate.tflock"
aws s3 rm "s3://${BUCKET}/${LOCK_KEY}" 2>/dev/null || true
fi
cd terraform/environments/aws
terraform init -backend-config=/tmp/backend.tfbackend
- name: Terraform Plan
env:
TF_VAR_admin_email: ${{ secrets.ADMIN_EMAIL }}
ENVIRONMENT: ${{ needs.prepare.outputs.environment }}
run: |
cd terraform/environments/aws
terraform plan \
-var-file="github-${ENVIRONMENT}.tfvars" \
-var="compute_platform=fargate" \
-out=tfplan
- name: Terraform Apply
env:
TF_VAR_admin_email: ${{ secrets.ADMIN_EMAIL }}
run: |
cd terraform/environments/aws
terraform apply -auto-approve tfplan
- name: Release state lock on failure
if: failure() || cancelled()
env:
ENVIRONMENT: ${{ needs.prepare.outputs.environment }}
run: |
BUCKET=$(grep -E '^\s*bucket\s*=' /tmp/backend.tfbackend 2>/dev/null | tr -d ' "' | cut -d= -f2)
if [ -n "$BUCKET" ]; then
LOCK_KEY="github-fargate-${ENVIRONMENT}/terraform.tfstate.tflock"
echo "Releasing S3 state lock: s3://${BUCKET}/${LOCK_KEY}"
aws s3 rm "s3://${BUCKET}/${LOCK_KEY}" 2>/dev/null || echo "No lock file found (already clean)"
fi
- name: Get outputs
id: outputs
run: |
cd terraform/environments/aws
echo "alb_url=$(terraform output -raw alb_dns_name 2>/dev/null || echo "")" >> $GITHUB_OUTPUT
echo "service_name=$(terraform output -raw ecs_service_name 2>/dev/null || echo "")" >> $GITHUB_OUTPUT
- name: Save deployment info
run: |
cat <<EOF > deployment-info.json
{
"environment": "${{ needs.prepare.outputs.environment }}",
"image_tag": "${{ needs.prepare.outputs.image_tag }}",
"alb_url": "${{ steps.outputs.outputs.alb_url }}",
"service_name": "${{ steps.outputs.outputs.service_name }}",
"deployed_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"deployed_by": "${{ github.actor }}",
"commit": "${{ github.sha }}"
}
EOF
cat deployment-info.json
- name: Upload deployment info
uses: actions/upload-artifact@v4
with:
name: deployment-info-fargate-${{ needs.prepare.outputs.environment }}
path: deployment-info.json
retention-days: 90
# Test deployment
test-deployment:
name: Test Deployment
runs-on: ubuntu-latest
needs: [prepare, deploy]
if: always() && needs.deploy.result == 'success'
steps:
- name: Get ALB URL
id: get-url
run: |
ALB_URL="${{ needs.deploy.outputs.alb_url }}"
if [ -z "$ALB_URL" ]; then
echo "Failed to get ALB URL from deploy outputs"
exit 1
fi
# Ensure URL has scheme and no trailing slash
case "$ALB_URL" in
http://*|https://*) ;; # already has scheme
*) ALB_URL="http://$ALB_URL" ;;
esac
ALB_URL="${ALB_URL%/}"
echo "url=$ALB_URL" >> $GITHUB_OUTPUT
- name: Wait for deployment
run: |
echo "Waiting 60 seconds for ECS service to stabilize..."
sleep 60
- name: Test health endpoint
run: |
URL="${{ steps.get-url.outputs.url }}"
echo "Testing health endpoint: $URL/health"
for i in {1..10}; do
if curl -f -s "$URL/health" > /dev/null; then
RESPONSE=$(curl -s "$URL/health")
echo "Response: $RESPONSE"
if echo "$RESPONSE" | grep -q '"status"'; then
echo "Health check passed!"
exit 0
fi
fi
echo "Attempt $i failed, retrying in 10 seconds..."
sleep 10
done
echo "Health check failed after 10 attempts"
exit 1
- name: Run smoke tests
run: |
URL="${{ steps.get-url.outputs.url }}"
echo "Running basic smoke tests..."
for i in {1..3}; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$URL/health")
if [ "$STATUS" -eq 200 ]; then
echo "Health check $i: passed (HTTP $STATUS)"
else
echo "Health check $i: failed (HTTP $STATUS)"
exit 1
fi
sleep 2
done
echo "All smoke tests passed!"
# Summary
summary:
name: Deployment Summary
runs-on: ubuntu-latest
needs: [prepare, deploy, test-deployment]
if: always()
steps:
- name: Download deployment info
uses: actions/download-artifact@v4
with:
name: deployment-info-fargate-${{ needs.prepare.outputs.environment }}
continue-on-error: true
- name: Post summary
run: |
echo "## AWS Fargate Deployment Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Environment:** ${{ needs.prepare.outputs.environment }}" >> $GITHUB_STEP_SUMMARY
echo "**Deploy Status:** ${{ needs.deploy.result }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f deployment-info.json ]; then
echo "### Deployment Details" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`json" >> $GITHUB_STEP_SUMMARY
cat deployment-info.json >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Job Results" >> $GITHUB_STEP_SUMMARY
echo "- Deploy: ${{ needs.deploy.result }}" >> $GITHUB_STEP_SUMMARY
echo "- Test: ${{ needs.test-deployment.result }}" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.deploy.result }}" == "success" ] && [ "${{ needs.test-deployment.result }}" == "success" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Deployment successful!**" >> $GITHUB_STEP_SUMMARY
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Deployment failed. Check logs for details.**" >> $GITHUB_STEP_SUMMARY
fi