Staging #7
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
| name: Deploy Production | |
| on: | |
| pull_request: | |
| branches: [master] | |
| types: [closed] | |
| workflow_dispatch: | |
| inputs: | |
| skip_tests: | |
| description: 'Skip tests before deployment (NOT RECOMMENDED)' | |
| required: false | |
| default: 'false' | |
| type: choice | |
| options: | |
| - 'true' | |
| - 'false' | |
| concurrency: | |
| group: deploy-production | |
| cancel-in-progress: false | |
| jobs: | |
| validate-promotion: | |
| name: Validate Promotion Event | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_deploy: ${{ steps.promotion-check.outputs.should_deploy }} | |
| trigger_reason: ${{ steps.promotion-check.outputs.trigger_reason }} | |
| steps: | |
| - name: Evaluate deployment trigger | |
| id: promotion-check | |
| shell: bash | |
| run: | | |
| SHOULD_DEPLOY="false" | |
| TRIGGER_REASON="not-eligible" | |
| if [ "${{ github.event_name }}" == "pull_request" ] && \ | |
| [ "${{ github.event.pull_request.merged }}" == "true" ] && \ | |
| [ "${{ github.event.pull_request.base.ref }}" == "master" ] && \ | |
| [ "${{ github.event.pull_request.head.ref }}" == "staging" ]; then | |
| SHOULD_DEPLOY="true" | |
| TRIGGER_REASON="merged-staging-to-master-pr" | |
| elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then | |
| SHOULD_DEPLOY="true" | |
| TRIGGER_REASON="manual-dispatch" | |
| fi | |
| echo "should_deploy=$SHOULD_DEPLOY" >> "$GITHUB_OUTPUT" | |
| echo "trigger_reason=$TRIGGER_REASON" >> "$GITHUB_OUTPUT" | |
| echo "Promotion check result: $SHOULD_DEPLOY" | |
| echo "Reason: $TRIGGER_REASON" | |
| - name: Skip notice | |
| if: steps.promotion-check.outputs.should_deploy != 'true' | |
| run: | | |
| echo "No production deployment triggered." | |
| echo "This workflow only deploys on merged PRs from staging -> master, or manual dispatch." | |
| pre-deployment-checks: | |
| name: Pre-Deployment Validation | |
| runs-on: ubuntu-latest | |
| needs: [validate-promotion] | |
| if: ${{ needs.validate-promotion.outputs.should_deploy == 'true' && (github.event_name != 'workflow_dispatch' || github.event.inputs.skip_tests != 'true') }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v5 | |
| with: | |
| version: "latest" | |
| enable-cache: true | |
| - name: Create virtual environment | |
| run: uv venv .venv | |
| - name: Install dependencies | |
| run: | | |
| source .venv/bin/activate | |
| uv pip install -r coaching/requirements.txt | |
| uv pip install -r coaching/requirements-dev.txt | |
| shell: bash | |
| - name: Run Ruff Linting | |
| run: | | |
| source .venv/bin/activate | |
| python -m ruff check . --exclude=".venv,venv,__pycache__,.pytest_cache" | |
| shell: bash | |
| - name: Run MyPy Type Checking | |
| run: | | |
| source .venv/bin/activate | |
| python -m mypy coaching/src/ shared/ --config-file=pyproject.toml | |
| shell: bash | |
| - name: Run Unit Tests | |
| run: | | |
| source .venv/bin/activate | |
| python -m pytest coaching/tests/unit/ -v --cov=coaching/src --cov-fail-under=70 | |
| shell: bash | |
| env: | |
| PYTHONPATH: coaching:shared:. | |
| deploy-infrastructure: | |
| name: Deploy Infrastructure | |
| runs-on: ubuntu-latest | |
| needs: [validate-promotion, pre-deployment-checks] | |
| if: ${{ needs.validate-promotion.outputs.should_deploy == 'true' && (needs.pre-deployment-checks.result == 'success' || (github.event_name == 'workflow_dispatch' && github.event.inputs.skip_tests == 'true')) }} | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Install Pulumi Python dependencies | |
| working-directory: infrastructure/pulumi | |
| run: pip install -r requirements.txt | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| aws-region: us-east-1 | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| - name: Deploy Infrastructure | |
| uses: pulumi/actions@v5 | |
| with: | |
| command: up | |
| stack-name: prod | |
| work-dir: infrastructure/pulumi | |
| env: | |
| PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} | |
| AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| AWS_REGION: us-east-1 | |
| deploy-coaching: | |
| name: Deploy to Production | |
| runs-on: ubuntu-latest | |
| needs: [validate-promotion, deploy-infrastructure] | |
| if: ${{ needs.validate-promotion.outputs.should_deploy == 'true' && needs.deploy-infrastructure.result == 'success' }} | |
| permissions: | |
| id-token: write | |
| contents: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Install Pulumi Python dependencies | |
| working-directory: coaching/pulumi | |
| run: pip install -r requirements.txt | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| aws-region: us-east-1 | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| - name: Deploy Coaching Service | |
| uses: pulumi/actions@v5 | |
| with: | |
| command: up | |
| stack-name: prod | |
| work-dir: coaching/pulumi | |
| env: | |
| PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} | |
| AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| AWS_REGION: us-east-1 | |
| - name: Get API Gateway URL | |
| id: api-url | |
| working-directory: coaching/pulumi | |
| run: | | |
| URL=$(pulumi stack output customDomainUrl --stack prod) | |
| echo "url=$URL" >> $GITHUB_OUTPUT | |
| env: | |
| PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} | |
| - name: Create GitHub Release | |
| uses: actions/create-release@v1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| tag_name: v${{ github.run_number }} | |
| release_name: Release v${{ github.run_number }} | |
| body: | | |
| Production deployment of PurposePath Coaching API | |
| **Deployment Details:** | |
| - Environment: Production | |
| - Stack: prod | |
| - API URL: ${{ steps.api-url.outputs.url }} | |
| - Deployed at: ${{ github.event.pull_request.merged_at || github.event.repository.updated_at }} | |
| - Commit: ${{ github.event.pull_request.merge_commit_sha || github.sha }} | |
| draft: false | |
| prerelease: false | |
| continue-on-error: true | |
| - name: Deployment Summary | |
| run: | | |
| echo "## 🚀 Production Deployment Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "✅ Deployment successful" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Environment:** Production" >> $GITHUB_STEP_SUMMARY | |
| echo "**Stack:** prod" >> $GITHUB_STEP_SUMMARY | |
| echo "**API URL:** ${{ steps.api-url.outputs.url }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Region:** us-east-1" >> $GITHUB_STEP_SUMMARY | |
| echo "**Release:** v${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Deployed at:** $(date -u)" >> $GITHUB_STEP_SUMMARY | |
| smoke-tests: | |
| name: Post-Deployment Smoke Tests | |
| runs-on: ubuntu-latest | |
| needs: [validate-promotion, deploy-coaching] | |
| if: ${{ needs.validate-promotion.outputs.should_deploy == 'true' && needs.deploy-coaching.result == 'success' }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Pulumi CLI | |
| uses: pulumi/actions@v5 | |
| with: | |
| pulumi-version: 'latest' | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| aws-region: us-east-1 | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| - name: Get API Gateway URL | |
| id: api-url | |
| working-directory: coaching/pulumi | |
| run: | | |
| URL=$(pulumi stack output customDomainUrl --stack prod) | |
| echo "url=$URL" >> $GITHUB_OUTPUT | |
| env: | |
| PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} | |
| - name: Health Check | |
| run: | | |
| echo "Testing API health endpoint..." | |
| HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" ${{ steps.api-url.outputs.url }}/health || echo "000") | |
| if [ "$HTTP_CODE" == "200" ] || [ "$HTTP_CODE" == "404" ]; then | |
| echo "✅ API is responding (HTTP $HTTP_CODE)" | |
| else | |
| echo "⚠️ API returned HTTP $HTTP_CODE - Investigation needed" | |
| exit 1 | |
| fi | |
| - name: Smoke Test Summary | |
| run: | | |
| echo "## Smoke Tests - Production" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "✅ Health check passed" >> $GITHUB_STEP_SUMMARY | |
| echo "✅ API is responsive" >> $GITHUB_STEP_SUMMARY | |
| echo "✅ Production deployment verified" >> $GITHUB_STEP_SUMMARY | |
| notify-deployment: | |
| name: Notify Team | |
| runs-on: ubuntu-latest | |
| needs: [validate-promotion, deploy-coaching, smoke-tests] | |
| if: ${{ always() && needs.validate-promotion.outputs.should_deploy == 'true' }} | |
| steps: | |
| - name: Deployment Status | |
| run: | | |
| if [ "${{ needs.deploy-coaching.result }}" == "success" ] && [ "${{ needs.smoke-tests.result }}" == "success" ]; then | |
| echo "✅ Production deployment completed successfully" | |
| else | |
| echo "❌ Production deployment encountered issues" | |
| exit 1 | |
| fi |