fix: simplify heredoc output to fix terraform syntax error #118
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
| # 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_acm_certificate_arn: ${{ secrets.ACM_CERTIFICATE_ARN }} | |
| 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_acm_certificate_arn: ${{ secrets.ACM_CERTIFICATE_ARN }} | |
| 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_acm_certificate_arn: ${{ secrets.ACM_CERTIFICATE_ARN }} | |
| 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_acm_certificate_arn: ${{ secrets.ACM_CERTIFICATE_ARN }} | |
| 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 |