Skip to content

Merge pull request #22 from voidreamer/staging #110

Merge pull request #22 from voidreamer/staging

Merge pull request #22 from voidreamer/staging #110

Workflow file for this run

# SimpleNotes CI/CD Pipeline
# Runs tests, then deploys to AWS
# - staging branch -> staging environment
# - main branch -> production environment
name: CI/CD Pipeline
on:
push:
branches: [main, staging]
pull_request:
branches: [main, staging]
env:
AWS_REGION: ca-central-1
PYTHON_VERSION: "3.11"
NODE_VERSION: "20"
TF_VERSION: "1.6.0"
jobs:
# ============================================
# Backend Tests
# ============================================
test-backend:
name: Backend Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: backend/requirements.txt
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov pytest-asyncio
- name: Run tests
env:
ENVIRONMENT: test
AWS_REGION: ca-central-1
USERS_TABLE: test-users
HOUSEHOLDS_TABLE: test-households
LISTS_TABLE: test-lists
INVITES_TABLE: test-invites
SUPABASE_URL: https://test.supabase.co
SUPABASE_JWT_SECRET: test-jwt-secret
run: |
pytest tests/ -v --cov=app --cov-report=xml --cov-report=term-missing
- name: Upload coverage report
uses: codecov/codecov-action@v3
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
with:
file: backend/coverage.xml
flags: backend
fail_ci_if_error: false
# ============================================
# Frontend Tests & Build
# ============================================
test-frontend:
name: Frontend Tests & Build
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint || true
- name: Run tests
run: npm test -- --passWithNoTests || true
- name: Build
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: frontend-build
path: frontend/dist
retention-days: 1
# ============================================
# Deploy to Staging (on staging branch)
# ============================================
deploy-staging:
name: Deploy to Staging
needs: [test-backend, test-frontend]
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/staging'
environment: staging
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Set up Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install backend dependencies
working-directory: backend
run: |
pip install -r requirements.txt -t .
- name: Terraform Init
working-directory: terraform
run: |
terraform init
terraform workspace select staging || terraform workspace new staging
- name: Terraform Validate
working-directory: terraform
run: terraform validate
- name: Terraform Plan
working-directory: terraform
env:
TF_VAR_ses_email: ${{ secrets.SES_EMAIL }}
TF_VAR_supabase_url: ${{ secrets.SUPABASE_URL }}
TF_VAR_supabase_jwt_secret: ${{ secrets.SUPABASE_JWT_SECRET }}
TF_VAR_domain_name: ${{ secrets.DOMAIN_NAME }}
TF_VAR_environment: staging
run: terraform plan -out=tfplan
- name: Terraform Apply
working-directory: terraform
env:
TF_VAR_ses_email: ${{ secrets.SES_EMAIL }}
TF_VAR_supabase_url: ${{ secrets.SUPABASE_URL }}
TF_VAR_supabase_jwt_secret: ${{ secrets.SUPABASE_JWT_SECRET }}
TF_VAR_domain_name: ${{ secrets.DOMAIN_NAME }}
TF_VAR_environment: staging
run: terraform apply -auto-approve tfplan
- name: Download frontend build
uses: actions/download-artifact@v4
with:
name: frontend-build
path: frontend/dist
- name: Get Terraform outputs
id: tf-outputs
working-directory: terraform
run: |
echo "frontend_bucket=$(terraform output -raw frontend_bucket)" >> $GITHUB_OUTPUT
echo "frontend_url=$(terraform output -raw frontend_url)" >> $GITHUB_OUTPUT
echo "cloudfront_id=$(terraform output -raw cloudfront_distribution_id)" >> $GITHUB_OUTPUT
echo "api_url=$(terraform output -raw api_gateway_url)" >> $GITHUB_OUTPUT
- name: Update frontend config
run: |
cat > frontend/dist/config.js << EOF
window.APP_CONFIG = {
API_URL: "${{ steps.tf-outputs.outputs.api_url }}",
SUPABASE_URL: "${{ secrets.SUPABASE_URL }}",
SUPABASE_ANON_KEY: "${{ secrets.SUPABASE_ANON_KEY }}"
};
EOF
- name: Deploy frontend to S3
run: |
aws s3 sync frontend/dist s3://${{ steps.tf-outputs.outputs.frontend_bucket }} \
--delete \
--cache-control "max-age=31536000,public" \
--exclude "index.html" \
--exclude "config.js"
aws s3 cp frontend/dist/index.html s3://${{ steps.tf-outputs.outputs.frontend_bucket }}/index.html \
--cache-control "no-cache,no-store,must-revalidate"
aws s3 cp frontend/dist/config.js s3://${{ steps.tf-outputs.outputs.frontend_bucket }}/config.js \
--cache-control "no-cache,no-store,must-revalidate"
- name: Invalidate CloudFront cache
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ steps.tf-outputs.outputs.cloudfront_id }} \
--paths "/*"
- name: Output deployment URLs
run: |
echo "## Staging Deployment Complete!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Environment:** Staging" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Frontend URL:** ${{ steps.tf-outputs.outputs.frontend_url }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**API URL:** ${{ steps.tf-outputs.outputs.api_url }}" >> $GITHUB_STEP_SUMMARY
# ============================================
# Deploy to Production (on main branch)
# ============================================
deploy-production:
name: Deploy to Production
needs: [test-backend, test-frontend]
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
environment: production
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Set up Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install backend dependencies
working-directory: backend
run: |
pip install -r requirements.txt -t .
- name: Terraform Init
working-directory: terraform
run: |
terraform init
terraform workspace select prod || terraform workspace new prod
- name: Terraform Validate
working-directory: terraform
run: terraform validate
- name: Terraform Plan
working-directory: terraform
env:
TF_VAR_ses_email: ${{ secrets.SES_EMAIL }}
TF_VAR_supabase_url: ${{ secrets.SUPABASE_URL }}
TF_VAR_supabase_jwt_secret: ${{ secrets.SUPABASE_JWT_SECRET }}
TF_VAR_domain_name: ${{ secrets.DOMAIN_NAME }}
TF_VAR_environment: prod
run: terraform plan -out=tfplan
- name: Terraform Apply
working-directory: terraform
env:
TF_VAR_ses_email: ${{ secrets.SES_EMAIL }}
TF_VAR_supabase_url: ${{ secrets.SUPABASE_URL }}
TF_VAR_supabase_jwt_secret: ${{ secrets.SUPABASE_JWT_SECRET }}
TF_VAR_domain_name: ${{ secrets.DOMAIN_NAME }}
TF_VAR_environment: prod
run: terraform apply -auto-approve tfplan
- name: Download frontend build
uses: actions/download-artifact@v4
with:
name: frontend-build
path: frontend/dist
- name: Get Terraform outputs
id: tf-outputs
working-directory: terraform
run: |
echo "frontend_bucket=$(terraform output -raw frontend_bucket)" >> $GITHUB_OUTPUT
echo "frontend_url=$(terraform output -raw frontend_url)" >> $GITHUB_OUTPUT
echo "cloudfront_id=$(terraform output -raw cloudfront_distribution_id)" >> $GITHUB_OUTPUT
echo "api_url=$(terraform output -raw api_gateway_url)" >> $GITHUB_OUTPUT
- name: Update frontend config
run: |
cat > frontend/dist/config.js << EOF
window.APP_CONFIG = {
API_URL: "${{ steps.tf-outputs.outputs.api_url }}",
SUPABASE_URL: "${{ secrets.SUPABASE_URL }}",
SUPABASE_ANON_KEY: "${{ secrets.SUPABASE_ANON_KEY }}"
};
EOF
- name: Deploy frontend to S3
run: |
aws s3 sync frontend/dist s3://${{ steps.tf-outputs.outputs.frontend_bucket }} \
--delete \
--cache-control "max-age=31536000,public" \
--exclude "index.html" \
--exclude "config.js"
aws s3 cp frontend/dist/index.html s3://${{ steps.tf-outputs.outputs.frontend_bucket }}/index.html \
--cache-control "no-cache,no-store,must-revalidate"
aws s3 cp frontend/dist/config.js s3://${{ steps.tf-outputs.outputs.frontend_bucket }}/config.js \
--cache-control "no-cache,no-store,must-revalidate"
- name: Invalidate CloudFront cache
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ steps.tf-outputs.outputs.cloudfront_id }} \
--paths "/*"
- name: Output deployment URLs
run: |
echo "## Production Deployment Complete!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Environment:** Production" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Frontend URL:** ${{ steps.tf-outputs.outputs.frontend_url }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**API URL:** ${{ steps.tf-outputs.outputs.api_url }}" >> $GITHUB_STEP_SUMMARY