balaji958685's picture
Add CI/CD pipeline
ab04f8c verified
name: SmartClass CI/CD
on:
push:
branches: [main, master]
tags: ["v*"]
pull_request:
branches: ["*"]
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ${{ github.repository_owner }}/smart-attendance
jobs:
# ─── Python CI ─────────────────────────────────────────────────────
python-ci:
name: Python CI
runs-on: ubuntu-latest
services:
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: sc_user
POSTGRES_PASSWORD: test_password
POSTGRES_DB: smartclass_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Lint with ruff
run: |
pip install ruff
ruff check src/ services/ scripts/
ruff format --check src/ services/ scripts/
- name: Type check with mypy
run: |
pip install mypy
mypy src/ --ignore-missing-imports
- name: Verify imports
run: |
python -c "from src.edge_pipeline import EdgePipeline; print('Edge imports OK')"
python -c "from services.api.app.main import app; print('API imports OK')"
- name: Run tests
env:
DATABASE_URL: postgresql+asyncpg://sc_user:test_password@localhost:5432/smartclass_test
REDIS_URL: redis://localhost:6379
JWT_SECRET_KEY: test_secret_key_for_ci_only_not_production
ENVIRONMENT: test
run: |
pytest tests/ -v --tb=short --cov=src --cov=services --cov-report=xml
- name: Upload coverage
if: github.event_name == 'push'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.xml
# ─── Frontend CI ───────────────────────────────────────────────────
frontend-ci:
name: Frontend CI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js 20
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
working-directory: frontend
run: npm ci
- name: Lint
working-directory: frontend
run: npm run lint
- name: Type check
working-directory: frontend
run: npm run type-check || true
- name: Build
working-directory: frontend
env:
VITE_API_URL: http://localhost:8000
run: npm run build
- name: Run tests
working-directory: frontend
run: npm test -- --run || true
# ─── Docker Images ────────────────────────────────────────────────
docker-build:
name: Build & Push Docker Images
runs-on: ubuntu-latest
needs: [python-ci, frontend-ci]
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
permissions:
contents: read
packages: write
strategy:
matrix:
include:
- image: edge
context: .
dockerfile: Dockerfile
- image: api
context: ./services/api
dockerfile: Dockerfile
- image: frontend
context: ./frontend
dockerfile: Dockerfile
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub 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 }}/${{ env.IMAGE_PREFIX }}-${{ matrix.image }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: ${{ matrix.context }}
file: ${{ matrix.context }}/${{ matrix.dockerfile }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VITE_API_URL=${{ vars.VITE_API_URL || 'http://localhost:8000' }}
# ─── Deploy (on tag push) ─────────────────────────────────────────
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
needs: [docker-build]
if: startsWith(github.ref, 'refs/tags/v')
environment: production
steps:
- uses: actions/checkout@v4
- name: Deploy notification
run: |
echo "πŸš€ Deploying version ${{ github.ref_name }} to production"
echo "Images:"
echo " - ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-edge:${{ github.ref_name }}"
echo " - ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api:${{ github.ref_name }}"
echo " - ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-frontend:${{ github.ref_name }}"
- name: Deploy via SSH
if: vars.DEPLOY_HOST != ''
uses: appleboy/ssh-action@v1
with:
host: ${{ vars.DEPLOY_HOST }}
username: ${{ vars.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
cd /opt/smartclass
git pull origin main
docker compose pull
docker compose up -d --remove-orphans
docker compose exec api alembic upgrade head
echo "βœ… Deployment complete"