fix(cleanup): use AWS API directly for ECR/RDS cleanup, avoid terrafo… #19
This file contains hidden or 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
| # 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 |