name: π CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
env:
REGISTRY: ghcr.io
FRONTEND_IMAGE: ghcr.io/sandraschi/robotics-webapp/frontend
BACKEND_IMAGE: ghcr.io/sandraschi/robotics-webapp/backend
jobs:
# π Code Quality & Security
quality-check:
name: π Quality Check
runs-on: ubuntu-latest
steps:
- name: π₯ Checkout code
uses: actions/checkout@v4
- name: π§ Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: π§ Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: π¦ Install frontend dependencies
run: |
cd frontend
npm ci
- name: π¦ Install backend dependencies
run: |
cd backend
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: π§Ή Run ESLint
run: |
cd frontend
npm run lint
- name: π Run TypeScript check
run: |
cd frontend
npm run type-check
- name: π Run Python linting
run: |
cd backend
pip install flake8 black isort mypy
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
black --check .
isort --check-only .
- name: π Security scan (frontend)
run: |
cd frontend
npm audit --audit-level moderate
- name: π Security scan (backend)
run: |
cd backend
pip install safety
safety check
# π§ͺ Testing Suite
test:
name: π§ͺ Testing
runs-on: ubuntu-latest
needs: quality-check
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: robotics_test
POSTGRES_USER: robotics
POSTGRES_PASSWORD: test_password
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: π₯ Checkout code
uses: actions/checkout@v4
- name: π§ Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: π§ Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: π¦ Install frontend dependencies
run: |
cd frontend
npm ci
- name: π¦ Install backend dependencies
run: |
cd backend
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov pytest-asyncio httpx
- name: π§ͺ Run frontend unit tests
run: |
cd frontend
npm run test:unit -- --coverage --watchAll=false
- name: π§ͺ Run backend unit tests
run: |
cd backend
python -m pytest tests/unit/ -v --cov=app --cov-report=xml --cov-report=term-missing
- name: π§ͺ Run integration tests
run: |
cd backend
python -m pytest tests/integration/ -v
- name: π Upload coverage reports
uses: codecov/codecov-action@v3
with:
file: ./backend/coverage.xml
flags: backend
name: Backend Coverage
- name: π Upload frontend coverage
uses: codecov/codecov-action@v3
with:
directory: ./frontend/coverage/
flags: frontend
name: Frontend Coverage
# π³ Docker Build & Push
docker:
name: π³ Docker Build
runs-on: ubuntu-latest
needs: [quality-check, test]
permissions:
contents: read
packages: write
strategy:
matrix:
service: [frontend, backend]
steps:
- name: π₯ Checkout code
uses: actions/checkout@v4
- name: π Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: π Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ github.repository }}/${{ matrix.service }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: ποΈ Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./${{ matrix.service }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# π Deploy to Staging
deploy-staging:
name: π Deploy to Staging
runs-on: ubuntu-latest
needs: docker
if: github.ref == 'refs/heads/develop' || github.event_name == 'workflow_dispatch'
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: us-east-1
- name: π Deploy to ECS
run: |
# Update ECS service
aws ecs update-service \
--cluster robotics-staging \
--service robotics-webapp-staging \
--force-new-deployment \
--region us-east-1
- name: β³ Wait for deployment
run: |
aws ecs wait services-stable \
--cluster robotics-staging \
--services robotics-webapp-staging \
--region us-east-1
- name: π§ͺ Run smoke tests
run: |
# Wait for service to be healthy
sleep 60
# Test health endpoints
curl -f https://staging.robotics-webapp.com/health || exit 1
curl -f https://api-staging.robotics-webapp.com/health || exit 1
- name: π’ Notify deployment
uses: 8398a7/action-slack@v3
if: always()
with:
status: ${{ job.status }}
text: "Staging deployment ${{ job.status == 'success' && 'successful β
' || 'failed β' }}"
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
# π― Deploy to Production
deploy-production:
name: π― Deploy to Production
runs-on: ubuntu-latest
needs: docker
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
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: us-east-1
- name: π Deploy to ECS
run: |
# Update ECS service
aws ecs update-service \
--cluster robotics-production \
--service robotics-webapp-production \
--force-new-deployment \
--region us-east-1
- name: β³ Wait for deployment
run: |
aws ecs wait services-stable \
--cluster robotics-production \
--services robotics-webapp-production \
--region us-east-1
- name: π§ͺ Run production smoke tests
run: |
# Wait for service to be healthy
sleep 120
# Test health endpoints
curl -f https://robotics-webapp.com/health || exit 1
curl -f https://api.robotics-webapp.com/health || exit 1
# Test WebSocket connection
timeout 10s websocat ws://api.robotics-webapp.com/ws/robotics || exit 1
- name: π·οΈ Create Git tag
run: |
VERSION=$(date +%Y.%m.%d.%H%M%S)
git tag "v${VERSION}"
git push origin "v${VERSION}"
- name: π’ Notify deployment
uses: 8398a7/action-slack@v3
if: always()
with:
status: ${{ job.status }}
text: "Production deployment ${{ job.status == 'success' && 'successful β
' || 'failed β' }}"
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
# π Performance Testing
performance:
name: β‘ Performance Test
runs-on: ubuntu-latest
needs: deploy-staging
if: github.ref == 'refs/heads/develop'
steps:
- name: π₯ Checkout code
uses: actions/checkout@v4
- name: π§ Setup k6
run: |
curl https://github.com/grafana/k6/releases/download/v0.45.0/k6-v0.45.0-linux-amd64.tar.gz -L | tar xvz
sudo mv k6-v0.45.0-linux-amd64/k6 /usr/local/bin/k6
- name: π§ͺ Run performance tests
run: |
k6 run --out json=results.json \
-e BASE_URL=https://staging.robotics-webapp.com \
performance/k6-script.js
- name: π Upload performance results
uses: actions/upload-artifact@v4
with:
name: performance-results
path: results.json
# π Security Scanning
security:
name: π Security Scan
runs-on: ubuntu-latest
needs: docker
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'
- name: π€ Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-results.sarif'
- name: π‘οΈ Run Snyk security scan
uses: snyk/actions/node@master
continue-on-error: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --file=frontend/package.json
# π Release
release:
name: π Create Release
runs-on: ubuntu-latest
needs: [deploy-production, performance, security]
if: github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, 'feat:')
steps:
- name: π₯ Checkout code
uses: actions/checkout@v4
- name: π Generate changelog
run: |
# Extract version from package.json
VERSION=$(node -p "require('./frontend/package.json').version")
# Generate changelog for this version
echo "# Release v${VERSION}" > release_notes.md
echo "" >> release_notes.md
git log --oneline --pretty=format:"- %s" $(git describe --tags --abbrev=0)..HEAD >> release_notes.md
- 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_path: release_notes.md
draft: false
prerelease: false
# π Notifications
notifications:
name: π Send Notifications
runs-on: ubuntu-latest
needs: [quality-check, test, docker, deploy-staging, deploy-production]
if: always()
steps:
- name: π Generate summary
run: |
echo "## π CI/CD Pipeline Summary" > summary.md
echo "" >> summary.md
echo "| Job | Status |" >> summary.md
echo "|-----|--------|" >> summary.md
echo "| Quality Check | ${{ needs.quality-check.result }} |" >> summary.md
echo "| Testing | ${{ needs.test.result }} |" >> summary.md
echo "| Docker Build | ${{ needs.docker.result }} |" >> summary.md
echo "| Staging Deploy | ${{ needs.deploy-staging.result }} |" >> summary.md
echo "| Production Deploy | ${{ needs.deploy-production.result }} |" >> summary.md
echo "" >> summary.md
echo "**Commit:** ${{ github.sha }}" >> summary.md
echo "**Branch:** ${{ github.ref }}" >> summary.md
echo "**Triggered by:** ${{ github.actor }}" >> summary.md
- name: π’ Slack notification
uses: 8398a7/action-slack@v3
if: always()
with:
status: ${{ job.status }}
fields: repo,message,commit,author,action,eventName,ref,workflow
title: 'Robotics MCP WebApp CI/CD'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
- name: π§ Email notification
uses: dawidd6/action-send-mail@v3
if: failure()
with:
server_address: smtp.gmail.com
server_port: 465
username: ${{ secrets.EMAIL_USERNAME }}
password: ${{ secrets.EMAIL_PASSWORD }}
subject: 'π¨ Robotics WebApp CI/CD Failed'
body: |
CI/CD pipeline failed for Robotics MCP WebApp
Branch: ${{ github.ref }}
Commit: ${{ github.sha }}
Actor: ${{ github.actor }}
Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
Please check the logs for more details.
to: robotics@sandraschi.dev
from: CI/CD Bot <robotics@sandraschi.dev>