Skip to content

Staging

Staging #7

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