-
Notifications
You must be signed in to change notification settings - Fork 0
fix/cleanup ci #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix/cleanup ci #103
Changes from all commits
7833fed
3ba566d
f71817d
eca0e02
e1f8fac
fd8082a
7c00f34
90a721e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| name: CI - Quality Checks | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [main] | ||
| push: | ||
| branches: | ||
| - "feature/**" | ||
|
|
||
| jobs: | ||
| quality-checks: | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| python-version: ["3.11", "3.12"] | ||
|
|
||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Python | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: ${{ matrix.python-version }} | ||
|
|
||
| - name: Get pip cache dir | ||
| id: pip-cache | ||
| run: | | ||
| echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT | ||
|
|
||
| - name: Cache dependencies | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: ${{ steps.pip-cache.outputs.dir }} | ||
| key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/requirements.txt') }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-pip-${{ matrix.python-version }}- | ||
| ${{ runner.os }}-pip- | ||
|
|
||
| - name: Install dependencies and tools | ||
| run: | | ||
| pip install --upgrade pip | ||
| pip install -r requirements.txt | ||
| pip install black isort autoflake bandit safety pytest pytest-asyncio pytest-cov | ||
|
|
||
| - name: Run linters | ||
| run: | | ||
| echo "--- Running Black ---" | ||
| black --check app | ||
| echo "--- Running isort ---" | ||
| isort --check-only app | ||
|
|
||
| - name: Run security scans | ||
| run: | | ||
| echo "--- Running Bandit ---" | ||
| bandit -r app -ll | ||
| echo "--- Running Safety ---" | ||
| safety check -r requirements.txt || true | ||
|
|
||
| - name: Run unit tests | ||
| run: pytest -q --disable-warnings --maxfail=1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,96 @@ | ||
| name: CD – Deploy to Railway | ||
| name: CD - NeuroBank Deployment (Karpathy Edition) | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
|
|
||
| jobs: | ||
| deploy: | ||
| name: Build & Deploy | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| packages: write | ||
|
|
||
| env: | ||
| IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/neurobank:${{ github.sha }} | ||
| SERVICE_ID: "REPLACE_ME" # <- luego pones el tuyo | ||
| RAILWAY_API: https://backboard.railway.app/graphql | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Install Railway CLI | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
|
|
||
| # ============================================================ | ||
| # A — BUILD DOCKER IMAGE | ||
| # ============================================================ | ||
| - name: Log in to GHCR | ||
| run: | | ||
| echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io \ | ||
| -u "${{ github.actor }}" --password-stdin | ||
| - name: Build Docker image | ||
| run: | | ||
| echo "➜ Building Docker image: $IMAGE_NAME" | ||
| docker build -t $IMAGE_NAME . | ||
| - name: Push Docker image to GHCR | ||
| run: | | ||
| echo "➜ Pushing image to GHCR..." | ||
| docker push $IMAGE_NAME | ||
| # ============================================================ | ||
| # B — TRY RAILWAY CLI (NON-BLOCKING) | ||
| # ============================================================ | ||
| - name: Try installing Railway CLI | ||
| id: cli_install | ||
| continue-on-error: true | ||
| run: | | ||
| echo "➜ Attempting Railway CLI install…" | ||
| curl -fsSL https://railway.app/install.sh | sh | ||
| if command -v railway > /dev/null; then | ||
| echo "cli=true" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "cli=false" >> $GITHUB_OUTPUT | ||
| fi | ||
| - name: Deploy to Railway | ||
| - name: Deploy using Railway CLI | ||
| if: steps.cli_install.outputs.cli == 'true' | ||
| env: | ||
| RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} | ||
| run: railway up --ci | ||
| continue-on-error: true | ||
| run: | | ||
| echo "➜ Railway CLI OK → Trying deploy…" | ||
| railway up --ci || echo "⚠️ CLI deploy failed, continuing with API fallback" | ||
| # ============================================================ | ||
| # C — API FALLBACK DEPLOY (INFALIBLE) | ||
| # ============================================================ | ||
| - name: Trigger Railway deployment via API (fallback) | ||
| if: steps.cli_install.outputs.cli == 'false' | ||
| env: | ||
| RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} | ||
| run: | | ||
| echo "⚠️ CLI unavailable → Using API fallback mode." | ||
| echo "➜ Deploying image: $IMAGE_NAME" | ||
| curl -X POST "$RAILWAY_API" \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "Authorization: Bearer $RAILWAY_TOKEN" \ | ||
| -d "{ | ||
| \"query\": \"mutation { deployService(input: { serviceId: \\\"$SERVICE_ID\\\", image: \\\"$IMAGE_NAME\\\" }) { id } }\" | ||
| }" | ||
|
Comment on lines
+70
to
+83
|
||
| echo "✔ Deployment requested successfully via Railway API." | ||
| - name: Final status | ||
| run: | | ||
| echo "" | ||
| echo "-------------------------------------------" | ||
| echo " KARPATHY DEPLOY PIPELINE COMPLETED" | ||
| echo "-------------------------------------------" | ||
| echo "Image: $IMAGE_NAME" | ||
| echo "Service: $SERVICE_ID" | ||
| echo "If Railway falla → tú no fallas." | ||
| echo "-------------------------------------------" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| name: CI – Security Scan | ||
| name: CI - Security Scan | ||
|
|
||
| on: | ||
| pull_request: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| name: CI – Test Suite | ||
| name: CI - Test Suite | ||
|
|
||
| on: | ||
| pull_request: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| 3.11.8 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,50 +1,61 @@ | ||
| # NeuroBank FastAPI Toolkit - Production Dockerfile optimized for Railway | ||
| FROM python:3.14-slim | ||
| # ============================================ | ||
| # STAGE 1 — BUILDER | ||
| # Compilación limpia, reproducible, sin root | ||
| # ============================================ | ||
| FROM python:3.11-slim AS builder | ||
|
|
||
| ENV PYTHONDONTWRITEBYTECODE=1 \ | ||
| PYTHONUNBUFFERED=1 | ||
|
|
||
| # Establecer el directorio de trabajo | ||
| WORKDIR /app | ||
|
|
||
| # Instalar dependencias del sistema optimizado para Railway | ||
| RUN apt-get update && apt-get install -y \ | ||
| gcc \ | ||
| curl \ | ||
| && rm -rf /var/lib/apt/lists/* \ | ||
| && apt-get clean | ||
| # Dependencias del sistema mínimas y suficientes | ||
| RUN apt-get update && apt-get install -y --no-install-recommends \ | ||
| build-essential \ | ||
| libpq-dev \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| # Copiar archivos de dependencias primero para mejor cache de Docker | ||
| # Copiamos dependencias del proyecto | ||
| COPY requirements.txt . | ||
|
|
||
| # Instalar dependencias de Python con optimizaciones | ||
| RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \ | ||
| pip install --no-cache-dir -r requirements.txt | ||
| # Usamos wheels para maximizar reproducibilidad | ||
| RUN pip install --upgrade pip wheel && \ | ||
| pip wheel --no-cache-dir --no-deps -r requirements.txt -w /wheels | ||
|
|
||
| # Copiar el código de la aplicación | ||
| COPY ./app ./app | ||
| COPY lambda_handler.py . | ||
| COPY start.sh . | ||
|
|
||
| # Hacer ejecutable el script de inicio | ||
| RUN chmod +x start.sh | ||
| # ============================================ | ||
| # STAGE 2 — RUNTIME ULTRALIGHT | ||
| # Cero herramientas innecesarias, zero trust | ||
| # ============================================ | ||
| FROM python:3.11-slim AS runtime | ||
|
|
||
| # Crear usuario no-root para seguridad y configurar permisos | ||
| RUN groupadd -r appuser && useradd -r -g appuser appuser && \ | ||
| chown -R appuser:appuser /app | ||
| USER appuser | ||
| ENV PYTHONDONTWRITEBYTECODE=1 \ | ||
| PYTHONUNBUFFERED=1 \ | ||
| PATH="/home/appuser/.local/bin:${PATH}" | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| # Exponer el puerto dinámico de Railway | ||
| EXPOSE $PORT | ||
| # Crear usuario no-root y seguro | ||
| RUN useradd -m appuser | ||
|
|
||
| # Configurar variables de entorno optimizadas para Railway | ||
| ENV PYTHONPATH=/app | ||
| ENV PYTHONUNBUFFERED=1 | ||
| ENV PYTHONDONTWRITEBYTECODE=1 | ||
| ENV PORT=8000 | ||
| ENV ENVIRONMENT=production | ||
| ENV WORKERS=1 | ||
| # Copiar wheels + instalar sin red | ||
| COPY --from=builder /wheels /wheels | ||
| RUN pip install --no-cache-dir /wheels/* | ||
|
|
||
| # Copiamos solo el código (sin tests, sin dev) | ||
| COPY app ./app | ||
|
|
||
| # Ajustar permisos | ||
| RUN chown -R appuser:appuser /app | ||
| USER appuser | ||
|
|
||
| # Health check específico para Railway con puerto dinámico | ||
| HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ | ||
| CMD sh -c 'curl -f http://localhost:$PORT/health || exit 1' | ||
| # ============================================ | ||
| # EJECUCIÓN — UVICORN KARPATHIAN MODE | ||
| # ============================================ | ||
| EXPOSE 8000 | ||
|
|
||
| # Comando optimizado para Railway con puerto dinámico | ||
| CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port $PORT --workers 1 --loop uvloop --timeout-keep-alive 120 --access-log"] | ||
| # Workers definidos por CPU (Karpathy-approved) | ||
| CMD ["uvicorn", "app.main:app", \ | ||
| "--host", "0.0.0.0", \ | ||
| "--port", "8000", \ | ||
| "--workers", "2"] |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -3,10 +3,27 @@ | |||
| from functools import lru_cache | ||||
| from typing import List, Optional | ||||
|
|
||||
| from pydantic import Field | ||||
| from pydantic_settings import BaseSettings | ||||
|
|
||||
|
|
||||
| class Settings(BaseSettings): | ||||
| class BaseAppSettings(BaseSettings): | ||||
| model_config = {"extra": "ignore"} | ||||
|
|
||||
| cors_origins: list[str] = Field(default_factory=list) | ||||
|
|
||||
|
Comment on lines
+13
to
+14
|
||||
| cors_origins: list[str] = Field(default_factory=list) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| """ | ||
| Telemetry and Monitoring Module for NeuroBank FastAPI Toolkit | ||
|
|
||
| This module provides telemetry setup for tracking application metrics, | ||
| performance monitoring, and distributed tracing. | ||
| """ | ||
|
|
||
| import logging | ||
| from typing import Optional | ||
|
|
||
| from fastapi import FastAPI | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def setup_telemetry(app: FastAPI) -> None: | ||
| """ | ||
| Configure telemetry and monitoring for the FastAPI application. | ||
|
|
||
| This function sets up: | ||
| - Application metrics tracking | ||
| - Performance monitoring | ||
| - Request/response logging | ||
| - Health check endpoints integration | ||
|
|
||
| Args: | ||
| app: FastAPI application instance | ||
|
|
||
| Note: | ||
| In production, this can be extended with: | ||
| - OpenTelemetry integration | ||
| - CloudWatch custom metrics | ||
| - AWS X-Ray tracing | ||
| - Prometheus metrics export | ||
| """ | ||
| logger.info("🔧 Setting up telemetry...") | ||
|
|
||
| # Add startup event for telemetry initialization | ||
| @app.on_event("startup") | ||
| async def startup_telemetry(): | ||
| logger.info("📊 Telemetry initialized successfully") | ||
| logger.info(f"📍 Application: {app.title} v{app.version}") | ||
|
|
||
| # Add shutdown event for cleanup | ||
| @app.on_event("shutdown") | ||
| async def shutdown_telemetry(): | ||
| logger.info("📊 Telemetry shutdown complete") | ||
|
|
||
| logger.info("✅ Telemetry setup complete") | ||
|
|
||
|
|
||
| def log_request_metrics( | ||
| endpoint: str, | ||
| method: str, | ||
| status_code: int, | ||
| duration_ms: float, | ||
| request_id: Optional[str] = None, | ||
| ) -> None: | ||
| """ | ||
| Log request metrics for monitoring and analysis. | ||
|
|
||
| Args: | ||
| endpoint: API endpoint path | ||
| method: HTTP method (GET, POST, etc.) | ||
| status_code: Response status code | ||
| duration_ms: Request processing duration in milliseconds | ||
| request_id: Optional unique request identifier | ||
| """ | ||
| logger.info( | ||
| f"📊 Request: {method} {endpoint} | " | ||
| f"Status: {status_code} | " | ||
| f"Duration: {duration_ms:.2f}ms" | ||
| f"{f' | RequestID: {request_id}' if request_id else ''}" | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The placeholder SERVICE_ID value "REPLACE_ME" will cause Railway deployments to fail. This should either be replaced with the actual service ID or configured as a GitHub secret and referenced via the secrets context.