fix(aws/recommendations): case-insensitive EC2 tenancy so AWS RI recs… #703
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 GCP Cloud Run | |
| # | |
| # This workflow deploys the CUDly application to GCP Cloud Run. | |
| # Serverless container platform with automatic scaling. | |
| # | |
| # Required GitHub Secrets: | |
| # - ADMIN_EMAIL: Admin email for notifications | |
| # | |
| # Required GitHub Variables: | |
| # - GCP_REGION: GCP region (e.g., us-central1) | |
| # - GCP_PROJECT_ID: GCP project ID | |
| # - GCP_WORKLOAD_IDENTITY_PROVIDER: WIF provider resource name for OIDC keyless auth | |
| # - GCP_SERVICE_ACCOUNT: SA email for OIDC keyless auth | |
| # - ARTIFACT_REGISTRY_REPO: Artifact Registry repository name (default: cudly) | |
| # | |
| # Triggered by: | |
| # - Pushes to main or feat/multicloud-web-frontend branch | |
| # - Manual workflow dispatch | |
| # - Workflow call from deploy-all.yml | |
| name: Deploy to GCP Cloud Run | |
| permissions: | |
| id-token: write | |
| contents: read | |
| concurrency: | |
| group: deploy-gcp-${{ 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 | |
| secrets: | |
| ADMIN_EMAIL: | |
| required: true | |
| TF_BACKEND_GCP: | |
| required: true | |
| env: | |
| GCP_REGION: ${{ vars.GCP_REGION || 'us-central1' }} | |
| ARTIFACT_REGISTRY_REPO: ${{ vars.ARTIFACT_REGISTRY_REPO || 'cudly' }} | |
| 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 }} | |
| 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 | |
| # Build Docker image (via Terraform build module) and deploy | |
| build-and-deploy: | |
| name: Build & Deploy | |
| runs-on: ubuntu-latest | |
| needs: prepare | |
| outputs: | |
| service_url: ${{ steps.deploy.outputs.service_url }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 | |
| - name: Authenticate to Google Cloud | |
| uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 | |
| with: | |
| workload_identity_provider: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }} | |
| service_account: ${{ vars.GCP_SERVICE_ACCOUNT }} | |
| - name: Set up Cloud SDK | |
| uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1 | |
| - name: Setup Terraform | |
| uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 | |
| with: | |
| terraform_version: ${{ env.TF_VERSION }} | |
| - name: Terraform Init | |
| env: | |
| TF_BACKEND: ${{ secrets.TF_BACKEND_GCP }} | |
| ENVIRONMENT: ${{ needs.prepare.outputs.environment }} | |
| run: | | |
| printf '%s\nprefix = "github-%s"\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_FILE="gs://${BUCKET}/github-${ENVIRONMENT}/default.tflock" | |
| gsutil rm "${LOCK_FILE}" 2>/dev/null || true | |
| fi | |
| cd terraform/environments/gcp | |
| terraform init -backend-config=/tmp/backend.tfbackend | |
| - name: Terraform Plan | |
| env: | |
| TF_VAR_admin_email: ${{ secrets.ADMIN_EMAIL }} | |
| run: | | |
| cd terraform/environments/gcp | |
| terraform plan \ | |
| -var-file="github-${{ needs.prepare.outputs.environment }}.tfvars" \ | |
| -var="project_id=${{ vars.GCP_PROJECT_ID }}" \ | |
| -out=tfplan | |
| - name: Terraform Apply | |
| id: deploy | |
| env: | |
| TF_VAR_admin_email: ${{ secrets.ADMIN_EMAIL }} | |
| run: | | |
| cd terraform/environments/gcp | |
| terraform apply -auto-approve tfplan | |
| # Get service URL | |
| SERVICE_URL=$(terraform output -raw cloud_run_service_url 2>/dev/null | grep -v '::' || echo "") | |
| echo "service_url=$SERVICE_URL" >> $GITHUB_OUTPUT | |
| - 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_FILE="gs://${BUCKET}/github-${ENVIRONMENT}/default.tflock" | |
| echo "Releasing GCS state lock: ${LOCK_FILE}" | |
| gsutil rm "${LOCK_FILE}" 2>/dev/null || echo "No lock file found (already clean)" | |
| fi | |
| - name: Save deployment info | |
| run: | | |
| cat <<EOF > deployment-info.json | |
| { | |
| "environment": "${{ needs.prepare.outputs.environment }}", | |
| "service_url": "${{ steps.deploy.outputs.service_url }}", | |
| "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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: deployment-info-gcp-${{ needs.prepare.outputs.environment }} | |
| path: deployment-info.json | |
| retention-days: 90 | |
| # Test the deployment | |
| test-deployment: | |
| name: Test Deployment | |
| runs-on: ubuntu-latest | |
| needs: [prepare, build-and-deploy] | |
| if: always() && needs.build-and-deploy.result == 'success' | |
| steps: | |
| - name: Get Service URL | |
| id: get-url | |
| run: | | |
| SERVICE_URL="${{ needs.build-and-deploy.outputs.service_url }}" | |
| if [ -z "$SERVICE_URL" ]; then | |
| echo "Failed to get service URL from deploy outputs" | |
| exit 1 | |
| fi | |
| SERVICE_URL="${SERVICE_URL%/}" | |
| echo "url=$SERVICE_URL" >> $GITHUB_OUTPUT | |
| - name: Wait for Cloud Run to be ready | |
| run: | | |
| echo "Waiting 30 seconds for Cloud Run to be ready..." | |
| sleep 30 | |
| - name: Test health endpoint | |
| run: | | |
| URL="${{ steps.get-url.outputs.url }}" | |
| echo "Testing health endpoint: $URL/health" | |
| for i in {1..5}; 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 5 attempts" | |
| exit 1 | |
| - name: Run smoke tests | |
| run: | | |
| URL="${{ steps.get-url.outputs.url }}" | |
| echo "Running basic smoke tests..." | |
| # Test health endpoint multiple times | |
| 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!" | |
| # Post deployment summary | |
| summary: | |
| name: Deployment Summary | |
| runs-on: ubuntu-latest | |
| needs: [prepare, build-and-deploy, test-deployment] | |
| if: always() | |
| steps: | |
| - name: Download deployment info | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: deployment-info-gcp-${{ needs.prepare.outputs.environment }} | |
| continue-on-error: true | |
| - name: Post summary | |
| run: | | |
| echo "## GCP Cloud Run Deployment Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Environment:** ${{ needs.prepare.outputs.environment }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Region:** ${{ env.GCP_REGION }}" >> $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.build-and-deploy.result }}" >> $GITHUB_STEP_SUMMARY | |
| echo "- Test: ${{ needs.test-deployment.result }}" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ needs.build-and-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 |