name: Deploy to Production on: push: branches: - main tags: - 'v*' workflow_dispatch: inputs: environment: description: 'Environment to deploy' required: true default: 'production' type: choice options: - staging - production version: description: 'Version to deploy (e.g., v1.0.0)' required: true type: string concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: AWS_REGION: us-east-1 ECR_REPOSITORY: mockupaws ECS_CLUSTER: mockupaws-production ECS_SERVICE_BACKEND: backend jobs: #------------------------------------------------------------------------------ # Build & Test #------------------------------------------------------------------------------ build-and-test: name: Build & Test runs-on: ubuntu-latest 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 Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install uv run: | curl -LsSf https://astral.sh/uv/install.sh | sh echo "$HOME/.cargo/bin" >> $GITHUB_PATH - name: Install Python dependencies run: uv sync - name: Run Python linting run: uv run ruff check src/ - name: Run Python tests run: uv run pytest --cov=src --cov-report=xml -v - name: Install frontend dependencies working-directory: frontend run: npm ci - name: Run frontend linting working-directory: frontend run: npm run lint - name: Build frontend working-directory: frontend run: npm run build - name: Upload coverage uses: codecov/codecov-action@v3 with: files: ./coverage.xml fail_ci_if_error: false #------------------------------------------------------------------------------ # Security Scan #------------------------------------------------------------------------------ security-scan: name: Security Scan runs-on: ubuntu-latest needs: build-and-test steps: - name: Checkout code uses: actions/checkout@v4 - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: scan-type: 'fs' scan-ref: '.' format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' - name: Upload Trivy scan results uses: github/codeql-action/upload-sarif@v2 if: always() with: sarif_file: 'trivy-results.sarif' - name: Scan Python dependencies run: | pip install safety safety check -r requirements.txt --json || true - name: Scan frontend dependencies working-directory: frontend run: | npm audit --audit-level=high || true #------------------------------------------------------------------------------ # Build & Push Docker Images #------------------------------------------------------------------------------ build-docker: name: Build Docker Images runs-on: ubuntu-latest needs: [build-and-test, security-scan] outputs: backend_image: ${{ steps.build-backend.outputs.image }} frontend_image: ${{ steps.build-frontend.outputs.image }} 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: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Extract version id: version run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT else echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT fi - name: Build and push backend image id: build-backend env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ steps.version.outputs.VERSION }} run: | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY-backend:$IMAGE_TAG -f Dockerfile.backend . docker push $ECR_REGISTRY/$ECR_REPOSITORY-backend:$IMAGE_TAG docker tag $ECR_REGISTRY/$ECR_REPOSITORY-backend:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY-backend:latest docker push $ECR_REGISTRY/$ECR_REPOSITORY-backend:latest echo "image=$ECR_REGISTRY/$ECR_REPOSITORY-backend:$IMAGE_TAG" >> $GITHUB_OUTPUT - name: Build and push frontend image id: build-frontend env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ steps.version.outputs.VERSION }} run: | cd frontend docker build -t $ECR_REGISTRY/$ECR_REPOSITORY-frontend:$IMAGE_TAG . docker push $ECR_REGISTRY/$ECR_REPOSITORY-frontend:$IMAGE_TAG docker tag $ECR_REGISTRY/$ECR_REPOSITORY-frontend:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY-frontend:latest docker push $ECR_REGISTRY/$ECR_REPOSITORY-frontend:latest echo "image=$ECR_REGISTRY/$ECR_REPOSITORY-frontend:$IMAGE_TAG" >> $GITHUB_OUTPUT #------------------------------------------------------------------------------ # Deploy to Staging #------------------------------------------------------------------------------ deploy-staging: name: Deploy to Staging runs-on: ubuntu-latest needs: build-docker if: github.ref == 'refs/heads/main' || github.event.inputs.environment == 'staging' environment: name: staging url: https://staging.mockupaws.com 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: Deploy to ECS Staging run: | aws ecs update-service \ --cluster mockupaws-staging \ --service backend \ --force-new-deployment - name: Wait for stabilization run: | aws ecs wait services-stable \ --cluster mockupaws-staging \ --services backend - name: Health check run: | sleep 30 curl -f https://staging.mockupaws.com/api/v1/health || exit 1 #------------------------------------------------------------------------------ # E2E Tests on Staging #------------------------------------------------------------------------------ e2e-tests: name: E2E Tests runs-on: ubuntu-latest needs: deploy-staging steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies working-directory: frontend run: npm ci - name: Install Playwright working-directory: frontend run: npx playwright install --with-deps - name: Run E2E tests working-directory: frontend env: BASE_URL: https://staging.mockupaws.com run: npx playwright test - name: Upload test results uses: actions/upload-artifact@v4 if: always() with: name: playwright-report path: frontend/playwright-report/ #------------------------------------------------------------------------------ # Deploy to Production #------------------------------------------------------------------------------ deploy-production: name: Deploy to Production runs-on: ubuntu-latest needs: [build-docker, e2e-tests] if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.environment == 'production' environment: name: production url: https://mockupaws.com 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: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Update ECS task definition id: task-def uses: aws-actions/amazon-ecs-render-task-definition@v1 with: task-definition: infrastructure/ecs/task-definition.json container-name: backend image: ${{ needs.build-docker.outputs.backend_image }} - name: Deploy to ECS Production uses: aws-actions/amazon-ecs-deploy-task-definition@v1 with: task-definition: ${{ steps.task-def.outputs.task-definition }} service: ${{ env.ECS_SERVICE_BACKEND }} cluster: ${{ env.ECS_CLUSTER }} wait-for-service-stability: true - name: Run database migrations run: | aws ecs run-task \ --cluster ${{ env.ECS_CLUSTER }} \ --task-definition mockupaws-migrate \ --launch-type FARGATE \ --network-configuration "awsvpcConfiguration={subnets=[${{ secrets.PRIVATE_SUBNET_ID }}],securityGroups=[${{ secrets.ECS_SECURITY_GROUP }}],assignPublicIp=DISABLED}" - name: Health check run: | sleep 60 curl -f https://mockupaws.com/api/v1/health || exit 1 - name: Notify deployment success uses: slackapi/slack-github-action@v1 if: success() with: payload: | { "text": "āœ… Deployment to production successful!", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "*mockupAWS Production Deployment*\nāœ… Successfully deployed ${{ needs.build-docker.outputs.backend_image }}" } }, { "type": "section", "fields": [ { "type": "mrkdwn", "text": "*Version:*\n${{ github.ref_name }}" }, { "type": "mrkdwn", "text": "*Commit:*\n<${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}>" } ] } ] } env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK