From 4a2048c37807146beb0723fad6490391416a30bc Mon Sep 17 00:00:00 2001 From: Thomas Peetz Date: Thu, 6 Nov 2025 16:37:41 +0100 Subject: [PATCH] add healthcheck to kontor-api and docker-compose.yml --- docker-compose.yml | 8 +++++-- kontor-api/Dockerfile | 4 +++- kontor-api/src/apis/version1/admin.py | 1 - kontor-api/src/apis/version1/healthcheck.py | 25 ++++++++++++++++++++ kontor-api/src/core/log_conf.py | 5 ++-- kontor-api/src/core/security.py | 6 ++--- kontor-api/src/db/utils.py | 6 +++-- kontor-api/src/main.py | 6 ++--- kontor-api/src/schema/admin.py | 6 +++++ kontor-api/src/webapps/media/route_videos.py | 6 ++--- 10 files changed, 55 insertions(+), 18 deletions(-) create mode 100644 kontor-api/src/apis/version1/healthcheck.py diff --git a/docker-compose.yml b/docker-compose.yml index 6a7a792..5c26c5c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,6 +41,11 @@ services: - kontor-api:0.2.0-SNAPSHOT image: kontor-api:0.2.0-SNAPSHOT restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://kontor-api:8800/health"] + interval: 10s + timeout: 5s + retries: 3 networks: - database - integration @@ -67,7 +72,7 @@ services: ports: - 8200:80 depends_on: - postgres: + kontor-api: condition: service_healthy kontor-vue: build: @@ -95,4 +100,3 @@ networks: volumes: activemq-data: images-data: - diff --git a/kontor-api/Dockerfile b/kontor-api/Dockerfile index 35d909a..15a0b5a 100644 --- a/kontor-api/Dockerfile +++ b/kontor-api/Dockerfile @@ -1,7 +1,7 @@ ## ------------------------------- Builder Stage ------------------------------ ## FROM python:3.13-bookworm AS builder -RUN apt-get update && apt-get install --no-install-recommends -y build-essential && \ +RUN apt-get update && apt-get install --no-install-recommends -y build-essential && \ apt-get clean && rm -rf /var/lib/apt/lists/* # Download the latest installer, install it and then remove it @@ -34,6 +34,8 @@ FROM python:3.13-slim-bookworm AS production #RUN --mount=type=secret,id=secret-key,target=secrets.json +RUN apt-get update && apt-get install --no-install-recommends -y curl + RUN useradd --create-home appuser USER appuser diff --git a/kontor-api/src/apis/version1/admin.py b/kontor-api/src/apis/version1/admin.py index 215d565..ccd596c 100644 --- a/kontor-api/src/apis/version1/admin.py +++ b/kontor-api/src/apis/version1/admin.py @@ -7,7 +7,6 @@ from fastapi.security import OAuth2PasswordRequestForm from src.core.config import settings from src.core.security import create_access_token, authenticate_user, get_current_active_user from src.db.models.admin import Profile -from src.db.session import SessionDep from src.schema.admin import Token, ProfileModel from src.webapps.auth.forms import LoginForm diff --git a/kontor-api/src/apis/version1/healthcheck.py b/kontor-api/src/apis/version1/healthcheck.py new file mode 100644 index 0000000..c531634 --- /dev/null +++ b/kontor-api/src/apis/version1/healthcheck.py @@ -0,0 +1,25 @@ +from fastapi import APIRouter, status + +from src.schema.admin import HealthCheck + +health_router = APIRouter() + + +@health_router.get( + "/health", + tags=["healthcheck"], + summary="Perform a health check", + response_description="Return HTTP status code 200 (OK)", + status_code=status.HTTP_200_OK +) +def health_check() -> HealthCheck: + """ + ## Perform a health check + Endpoint to perform a healthcheck on. This endpoint can primarily be used Docker + to ensure a robust container orchestration and management is in place. Other + services which rely on proper functioning of the API service will not deploy if this + endpoint returns any other HTTP status code except 200 (OK). + :return: + HealthCheck: Returns a JSON response with the health status + """ + return HealthCheck(status="ok") diff --git a/kontor-api/src/core/log_conf.py b/kontor-api/src/core/log_conf.py index 9a2335e..0ca6fe2 100644 --- a/kontor-api/src/core/log_conf.py +++ b/kontor-api/src/core/log_conf.py @@ -34,11 +34,12 @@ LOGGING_CONFIG: dict[str, Any] = { }, "loggers": { "root": {"handlers": ["default"], "level": "INFO", "propagate": False}, + "kontor": {"handlers": ["default"], "level": "INFO", "propagate": True}, "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False}, "uvicorn.error": {"level": "INFO"}, - "uvicorn.access": {"handlers": ["default"], "level": "INFO", "propagate": False}, + "uvicorn.access": {"handlers": ["default"], "level": "WARNING", "propagate": False}, }, } logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger(__name__) +logger = logging.getLogger('kontor') diff --git a/kontor-api/src/core/security.py b/kontor-api/src/core/security.py index 09f4741..3a45720 100644 --- a/kontor-api/src/core/security.py +++ b/kontor-api/src/core/security.py @@ -65,7 +65,7 @@ class OAuth2PasswordBearerWithCookie(OAuth2): def authenticate_user(username: str, password: str) -> Optional[Profile]: with SessionLocal() as db: user = get_profile(username=username, db=db) - print(user) + logger.info(user) if not user: return None if bcrypt.checkpw(password.encode(), user.password.encode()): @@ -103,7 +103,7 @@ async def get_current_user(security_scopes: SecurityScopes, token: Annotated[str try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) username: str = payload.get("sub") - print("username/email extracted is ", username) + logger.info("username/email extracted is ", username) if username is None: raise credentials_exception scope: str = payload.get("scope", "") @@ -147,7 +147,7 @@ def get_current_user_from_token(token: str = Depends(oauth2_scheme)): token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM] ) username: str = payload.get("sub") - print("username/email extracted is ", username) + logger.info("username/email extracted is ", username) if username is None: raise credentials_exception except JWTError: diff --git a/kontor-api/src/db/utils.py b/kontor-api/src/db/utils.py index 8914088..a404754 100644 --- a/kontor-api/src/db/utils.py +++ b/kontor-api/src/db/utils.py @@ -1,4 +1,6 @@ import databases + +from src.core.log_conf import logger from src.db.session import SQLALCHEMY_DATABASE_URL @@ -9,7 +11,7 @@ async def check_db_connected(): if not database.is_connected: await database.connect() await database.execute("SELECT 1") - print("Database is connected (^_^)") + logger.info("Database is connected (^_^)") except Exception as e: print( "Looks like db is missing or is there is some problem in connection,see below traceback" @@ -23,6 +25,6 @@ async def check_db_disconnected(): database = databases.Database(SQLALCHEMY_DATABASE_URL) if database.is_connected: await database.disconnect() - print("Database is Disconnected (-_-) zZZ") + logger.info("Database is Disconnected (-_-) zZZ") except Exception as e: raise e diff --git a/kontor-api/src/main.py b/kontor-api/src/main.py index 738b0a4..154363a 100644 --- a/kontor-api/src/main.py +++ b/kontor-api/src/main.py @@ -1,5 +1,3 @@ -import logging -import logging.config from contextlib import asynccontextmanager from fastapi import FastAPI @@ -7,7 +5,8 @@ from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware from src.apis.base import api_router -from src.core.log_conf import LOGGING_CONFIG, logger +from src.apis.version1.healthcheck import health_router +from src.core.log_conf import logger from src.db.session import engine from src.db.utils import check_db_connected, check_db_disconnected from src.webapps.base import api_router as web_app_router @@ -24,6 +23,7 @@ async def lifespan(app: FastAPI): def include_router(app: FastAPI): app.include_router(api_router) app.include_router(web_app_router) + app.include_router(health_router) def configure_static(app: FastAPI): app.mount("/static", StaticFiles(directory="src/static"), name="static") diff --git a/kontor-api/src/schema/admin.py b/kontor-api/src/schema/admin.py index f08eca6..5f9c3b4 100644 --- a/kontor-api/src/schema/admin.py +++ b/kontor-api/src/schema/admin.py @@ -19,3 +19,9 @@ class ProfileModel(BaseModel): first_name: str last_name: str active: bool + +class HealthCheck(BaseModel): + """ + Health check model + """ + status: str = "ok" diff --git a/kontor-api/src/webapps/media/route_videos.py b/kontor-api/src/webapps/media/route_videos.py index 9c5c951..5e70d39 100644 --- a/kontor-api/src/webapps/media/route_videos.py +++ b/kontor-api/src/webapps/media/route_videos.py @@ -1,12 +1,10 @@ -from typing import AnyStr - from fastapi import APIRouter, Request, status, responses from fastapi.security.utils import get_authorization_scheme_param from fastapi.templating import Jinja2Templates +from src.core.security import get_current_user_from_token from src.db.models.media import MediaVideo from src.db.repository.media import create_new_video -#from src.apis.version1.admin import get_current_user_from_token from src.db.models.admin import Profile from src.db.session import SessionDep from src.schema.media.video import AddLink @@ -28,7 +26,7 @@ def get_mediavideos(db: SessionDep, request: Request, msg: str = None): return templates.TemplateResponse("media/videos.html", {"request": request, "msg": msg, "user": None, "mediavideos": mediavideos}) @router.get("/videos/{video_id}") -def video_details(video_id: AnyStr, request: Request, db: SessionDep): +def video_details(video_id: str, request: Request, db: SessionDep): mediavideo = db.get(MediaVideo, video_id) return templates.TemplateResponse("media/video_detail.html", {"request": request, "mediavideo":mediavideo})