This commit is contained in:
Waloshi6
2026-01-27 17:34:16 +01:00
parent 4c3bd195b7
commit c8204829e9
237 changed files with 3542 additions and 1737 deletions

66
src/app/Dockerfile Normal file
View File

@@ -0,0 +1,66 @@
# ==========================
# STAGE 1 : Construction
# ==========================
FROM python:3.11-slim AS builder
WORKDIR /app
# Installer les dépendances système
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
gcc \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# Créer un environnement virtuel
RUN python -m venv /venv
ENV PATH="/venv/bin:$PATH"
# Installer les dépendances
COPY src/app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# ==========================
# STAGE 2 : Production
# ==========================
FROM python:3.11-slim AS runtime
# Définir le répertoire de travail
WORKDIR /app
# Installer les bibliothèques système
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
&& rm -rf /var/lib/apt/lists/*
# 🔥 Installe curl ici
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Créer un utilisateur non-root
RUN useradd -m -u 1000 devopsuser
# Copier l'environnement virtuel
COPY --from=builder /venv /venv
ENV PATH="/venv/bin:$PATH"
# Copier le code
COPY src/app /app
# Changer les droits
RUN chown -R devopsuser:devopsuser /app
USER devopsuser
# Variables d'environnement
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV ENV=production
ENV HOSTNAME=devops-container
ENV PYTHONPATH="${PYTHONPATH}:/app"
EXPOSE 8000
# Lancer l'application
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--no-access-log"]

0
src/app/__init__.py Normal file
View File

11
src/app/database.py Normal file
View File

@@ -0,0 +1,11 @@
import psycopg2
from psycopg2.extras import RealDictCursor
def get_db_connection():
conn = psycopg2.connect(
host="db",
database="devopsdb",
user="devopsuser",
password="devopspass"
)
return conn

153
src/app/main.py Normal file
View File

@@ -0,0 +1,153 @@
# ==========================
# IMPORTS DES DÉPENDANCES
# ==========================
from fastapi import FastAPI, Request, HTTPException, Response
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
import time
import os
import logging
from typing import Dict
# Import du routeur pour /users
from routes import router as routes_app
# ==========================
# CONFIGURATION DE L'APPLICATION
# ==========================
# Configuration du logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Création de l'application FastAPI
app = FastAPI(
title="DevOps Windows API",
description="Application de démonstration DevOps sous Windows",
version="1.0.0"
)
# Inclusion du routeur (doit être après la création de 'app')
app.include_router(routes_app, prefix="/users", tags=["users"])
# ==========================
# MÉTRIQUES PROMETHEUS
# ==========================
REQUEST_COUNT = Counter(
'http_requests_total',
'Total des requêtes HTTP',
['method', 'endpoint', 'status']
)
REQUEST_LATENCY = Histogram(
'http_request_duration_seconds',
'Temps de réponse HTTP',
['method', 'endpoint']
)
# ==========================
# MIDDLEWARE DE MONITORING
# ==========================
@app.middleware("http")
async def monitor_requests(request: Request, call_next):
"""Middleware pour suivre les requêtes et mesurer la latence."""
start_time = time.time()
try:
response = await call_next(request)
status_code = str(response.status_code)
except Exception as e:
status_code = "500"
response = Response(
content=f"Erreur serveur : {str(e)}",
status_code=500
)
process_time = time.time() - start_time
# Enregistrement des métriques
REQUEST_COUNT.labels(
method=request.method,
endpoint=request.url.path,
status=status_code
).inc()
REQUEST_LATENCY.labels(
method=request.method,
endpoint=request.url.path
).observe(process_time)
# Ajout du temps de traitement dans les headers
response.headers["X-Process-Time"] = f"{process_time:.3f}s"
return response
# ==========================
# ENDPOINTS PRINCIPAUX
# ==========================
@app.get("/")
async def home():
"""Endpoint racine - Statut de l'application."""
return {
"message": "🚀 DevOps Stack Windows - Fonctionnel !",
"environment": os.getenv("ENV", "development"),
"status": "running",
"hostname": os.getenv("HOSTNAME", "windows-devops"),
"version": "1.0.0"
}
@app.get("/health")
async def health():
"""Health check pour Kubernetes."""
return {
"status": "healthy",
"timestamp": time.time(),
"service": "devops-app"
}
@app.get("/metrics")
async def metrics():
"""Endpoint pour Prometheus."""
try:
data = generate_latest()
return Response(
content=data,
media_type=CONTENT_TYPE_LATEST,
headers={"Cache-Control": "no-cache"}
)
except Exception as e:
logger.error(f"Erreur lors de la génération des métriques : {e}")
raise HTTPException(status_code=500, detail="Échec de la génération des métriques")
@app.get("/info")
async def info():
"""Informations système."""
return {
"python_version": "3.11",
"platform": "windows",
"service": "FastAPI DevOps",
"features": ["docker", "kubernetes", "monitoring", "ci-cd"]
}
@app.get("/env")
async def show_env():
"""Affiche les variables d'environnement (sécurisées)."""
safe_env = {
"ENV": os.getenv("ENV", "not-set"),
"HOSTNAME": os.getenv("HOSTNAME", "not-set"),
"PYTHON_VERSION": os.getenv("PYTHON_VERSION", "not-set")
}
return safe_env
# ==========================
# LANCEMENT EN DÉVELOPPEMENT
# ==========================
if __name__ == "__main__":
import uvicorn
logger.info("Démarrage du serveur FastAPI...")
uvicorn.run(
app,
host="0.0.0.0",
port=8000,
log_level="info",
reload=True # Auto-reload en dev
)

8
src/app/requirements.txt Normal file
View File

@@ -0,0 +1,8 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
prometheus-client==0.19.0
python-dotenv==1.0.0
pytest==7.4.3
httpx==0.25.1
psycopg2==2.9.9

23
src/app/routes.py Normal file
View File

@@ -0,0 +1,23 @@
from fastapi import APIRouter
from psycopg2.extras import RealDictCursor
from database import get_db_connection
router = APIRouter()
@router.get("/users")
async def get_users():
conn = get_db_connection()
cursor = conn.cursor(cursor_factory=RealDictCursor)
cursor.execute("SELECT * FROM users")
rows = cursor.fetchall()
conn.close()
return rows
@router.post("/users")
async def add_user(name: str, email: str):
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("INSERT INTO users (name, email) VALUES (%s, %s) ON CONFLICT (email) DO NOTHING", (name, email))
conn.commit()
conn.close()
return {"status": "success"}

44
src/app/test_main.py Normal file
View File

@@ -0,0 +1,44 @@
import pytest
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_home_endpoint():
"""Test de l'endpoint principal"""
response = client.get("/")
assert response.status_code == 200
data = response.json()
assert "message" in data
assert "status" in data
assert data["status"] == "running"
def test_health_endpoint():
"""Test du health check"""
response = client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
assert "service" in data
def test_metrics_endpoint():
"""Test des métriques Prometheus"""
response = client.get("/metrics")
assert response.status_code == 200
assert "text/plain" in response.headers["content-type"]
assert "http_requests_total" in response.text
def test_info_endpoint():
"""Test des informations"""
response = client.get("/info")
assert response.status_code == 200
data = response.json()
assert "python_version" in data
assert "platform" in data
def test_env_endpoint():
"""Test des variables d'environnement"""
response = client.get("/env")
assert response.status_code == 200
data = response.json()
assert "ENV" in data