From 92dc1f3ce7fd6f3ba882b1739d6efdb037469785 Mon Sep 17 00:00:00 2001 From: Florian Date: Sun, 18 Oct 2026 12:17:41 +0200 Subject: [PATCH] Upgraded modules --- pyproject.toml | 2 +- requirements.txt | 46 ++++++++-------- src/dockerhub_api.py | 9 ++-- src/github_api.py | 6 ++- src/main.py | 113 +++++++++++++++++++++------------------ src/send_notification.py | 65 ---------------------- 6 files changed, 95 insertions(+), 146 deletions(-) delete mode 100644 src/send_notification.py diff --git a/pyproject.toml b/pyproject.toml index 9f8f13d..3cb97ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ ignore = ["E501"] [tool.ruff.lint.isort] known-first-party = ["db_module", "metrics_server", "rabbitmq_handler", "secret_manager", - "simple_logger_handler", "uvicorn_logger_config", "validator"] + "simple_logger_handler", "send_notifications"] [tool.ruff.format] indent-style = "tab" diff --git a/requirements.txt b/requirements.txt index 7dd74ba..07da7b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,25 +1,27 @@ --extra-index-url https://git.gansejunge.com/api/packages/notifier/pypi/simple/ -annotated-types==0.7.0 -anyio==4.11.0 -certifi==2025.10.5 -charset-normalizer==3.4.3 -click==8.3.0 -fastapi==0.118.2 +annotated-doc==0.0.5 +annotated-types==0.8.0 +anyio==4.15.1 +certifi==2026.7.22 +charset-normalizer==3.5.2 +click==8.5.0 +fastapi==0.143.5 h11==0.16.0 -idna==3.10 -lib-db-module==0.2.0 -lib-secret-manager==0.2.0 -lib-uvicorn-config==0.2.0 +idna==3.20 +lib-db-module==0.3.0 +lib-secret-manager==0.3.1 +lib-send-notifications==0.1.0 mysql-connector-python==9.4.0 -prometheus_client==0.23.1 -pydantic==2.12.0 -pydantic_core==2.41.1 -python-json-logger==4.0.0 -simple-logger-handler==0.1.0 -requests==2.32.5 -sniffio==1.3.1 -starlette==0.48.0 -typing-inspection==0.4.2 -typing_extensions==4.15.0 -urllib3==2.5.0 -uvicorn==0.37.0 +opentelemetry-api==1.45.1 +prometheus_client==0.26.0 +pydantic==2.14.1 +pydantic_core==2.50.1 +python-json-logger==4.2.0 +requests==2.34.2 +simple-logger-handler==0.2.1 +starlette==1.8.0 +typing-inspection==0.4.4 +typing_extensions==4.16.0 +urllib3==2.8.0 +uvicorn==0.54.0 + diff --git a/src/dockerhub_api.py b/src/dockerhub_api.py index bebba64..2b197ad 100644 --- a/src/dockerhub_api.py +++ b/src/dockerhub_api.py @@ -1,10 +1,13 @@ +import os + import requests -from secret_manager import return_credentials +from secret_manager import get_secret from simple_logger_handler import setup_logger -dockerhub_token = return_credentials("/etc/secrets/dockerhub_token") -dockerhub_username = return_credentials("/etc/secrets/dockerhub_username") +dockerhub_token = get_secret(os.getenv("DOCKERHUB_TOKEN",""),"/etc/secrets/dockerhub_token") +dockerhub_username = get_secret(os.getenv("DOCKERHUB_USERNAME",""),"/etc/secrets/dockerhub_username") + logger = setup_logger(__name__) def login_and_get_token() -> str: diff --git a/src/github_api.py b/src/github_api.py index 13deafe..e1980d9 100644 --- a/src/github_api.py +++ b/src/github_api.py @@ -1,9 +1,11 @@ +import os + import requests -from secret_manager import return_credentials +from secret_manager import get_secret from simple_logger_handler import setup_logger -github_token = return_credentials("/etc/secrets/github_token") +github_token = get_secret(os.getenv("GITHUB_TOKEN",""),"/etc/secrets/github_token") logger = setup_logger(__name__) def find_package_version_with_tag(org: str, package: str, target_tag: str) -> str: diff --git a/src/main.py b/src/main.py index 62048b0..5724df9 100644 --- a/src/main.py +++ b/src/main.py @@ -9,22 +9,21 @@ from dockerhub_api import find_package_version_with_tag as find_package_version_ from github_api import find_package_version_with_tag as find_package_version_with_tag_github from metrics_server import REQUEST_COUNTER from secret_manager import cleanup_secret_files -from send_notification import send_notification +from send_notifications import NotificationClient from simple_logger_handler import LOG_LEVEL, setup_logger -from uvicorn_logger_config import LOGGING_CONFIG logger = setup_logger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): - logger.info("[App] Starting application...") - - logger.info("[DB] Creating MySQL connection pool...") + logger.info("[FastAPI] Starting application...") + app.state.notifier = NotificationClient() + logger.info("[FastAPI] Creating MySQL connection pool...") create_connection_pool("repositoryQueryPool") start_healthcheck_thread() - logger.info("[DB] MySQL healthcheck thread started.") + logger.info("[FastAPI] MySQL healthcheck thread started.") SECRET_PATHS = frozenset({ "/etc/secrets/api_key", @@ -37,9 +36,9 @@ async def lifespan(app: FastAPI): cleanup_secret_files(SECRET_PATHS) yield - logger.info("[App] Closing MySQL connection pool...") + logger.info("[FastAPI] Closing MySQL connection pool...") close_connection_pool() - logger.info("[App] Shutdown complete.") + logger.info("[FastAPI] Shutdown complete.") api = FastAPI( @@ -51,18 +50,18 @@ api = FastAPI( @api.middleware("http") async def prometheus_middleware(request: Request, call_next): - logger.debug(f"[Metrics] Incoming request: {request.method} {request.url.path}") + logger.debug(f"[FastAPI] Incoming request: {request.method} {request.url.path}") status = 500 try: response = await call_next(request) status = response.status_code - logger.debug(f"[Metrics] Request processed with status {status}") + logger.debug(f"[FastAPI] Request processed with status {status}") except Exception as e: - logger.error(f"[Metrics] Exception occurred: {e}", exc_info=True) + logger.error(f"[FastAPI] Exception occurred: {e}", exc_info=True) raise finally: REQUEST_COUNTER.labels(request.method, request.url.path, status).inc() - logger.debug(f"[Metrics] Counter incremented for {request.method} {request.url.path} [{status}]") + logger.debug(f"[FastAPI] Counter incremented for {request.method} {request.url.path} [{status}]") return response @@ -79,31 +78,35 @@ def handle_suwayomi( ): try: - logger.info("[App] Suwayomi handler invoked") + logger.info("[FastAPI] Suwayomi handler invoked") online_version = find_package_version_with_tag_github("Suwayomi", "tachidesk", "stable") - logger.debug(f"[App] Fetched latest Suwayomi version from GitHub: {online_version}") + logger.debug(f"[FastAPI] Fetched latest Suwayomi version from GitHub: {online_version}") - cursor = db.cursor() - cursor.execute("SELECT latest_version FROM docker_repositories WHERE app='suwayomi'") - local_state = cursor.fetchone() - local_version = str(local_state[0]) if local_state and local_state[0] is not None else None - logger.debug(f"[App] Comparing versions: local='{local_version}' vs online='{online_version}'") + with db.cursor() as cursor: + cursor.execute("SELECT latest_version FROM docker_repositories WHERE app='suwayomi'") + local_state = cursor.fetchone() + local_version = str(local_state[0]) if local_state and local_state[0] is not None else None + logger.debug(f"[FastAPI] Comparing versions: local='{local_version}' vs online='{online_version}'") - if local_version != online_version: - logger.debug("[App] Version mismatch detected. Updating database.") - cursor.execute ("UPDATE docker_repositories SET latest_version=%s WHERE app='suwayomi'", - (online_version,)) - db.commit() - logger.info("[App] New Suwayomi version recorded in database") - send_notification("New Suwayomi version has been found") - logger.debug("[App] Notification sent for Suwayomi update") - return Response(status_code=200) + if local_version != online_version: + logger.debug("[FastAPI] Version mismatch detected. Updating database.") + cursor.execute ("UPDATE docker_repositories SET latest_version=%s WHERE app='suwayomi'", + (online_version,)) + db.commit() + logger.info("[FastAPI] New Suwayomi version recorded in database") + request.app.state.notifier.send( + title="New Suwayomi version has been found", + body=f"Version {online_version}", + category="utility" + ) + logger.debug("[FastAPI] Notification sent for Suwayomi update") + return Response(status_code=200) - logger.debug("[App] No new Suwayomi version found") + logger.debug("[FastAPI] No new Suwayomi version found") return Response(status_code=204) except Exception as e: - logger.error(f"[App] Error in Suwayomi handler: {e}", exc_info=True) + logger.error(f"[FastAPI] Error in Suwayomi handler: {e}", exc_info=True) raise HTTPException(status_code=500, detail=str(e)) from e @@ -114,44 +117,48 @@ def handle_pihole( db = Depends(get_db) ): try: - logger.info("[App] Pi-hole handler invoked") + logger.info("[FastAPI] Pi-hole handler invoked") online_version = find_package_version_with_tag_dockerhub("pihole/pihole", "latest") - logger.debug(f"Fetched latest Pi-hole version from Docker Hub: {online_version}") + logger.debug(f"[FastAPI] Fetched latest Pi-hole version from Docker Hub: {online_version}") - cursor = db.cursor() - cursor.execute("SELECT latest_version FROM docker_repositories WHERE app='pihole'") - local_state = cursor.fetchone() - local_version = str(local_state[0]) if local_state and local_state[0] is not None else None - logger.debug(f"[App] Comparing versions: local='{local_version}' vs online='{online_version}'") + with db.cursor() as cursor: + cursor.execute("SELECT latest_version FROM docker_repositories WHERE app='pihole'") + local_state = cursor.fetchone() + local_version = str(local_state[0]) if local_state and local_state[0] is not None else None + logger.debug(f"[FastAPI] Comparing versions: local='{local_version}' vs online='{online_version}'") - if local_version != online_version: - logger.debug("[App] Version mismatch detected. Updating database.") - cursor.execute ("UPDATE docker_repositories SET latest_version=%s WHERE app='pihole'", - (online_version,)) - db.commit() - logger.info("[App] New Pi-hole version recorded in database") - send_notification("New Pi-hole version has been found") - logger.debug("[App] Notification sent for Pi-hole update") - return Response(status_code=200) + if local_version != online_version: + logger.debug("[FastAPI] Version mismatch detected. Updating database.") + cursor.execute ("UPDATE docker_repositories SET latest_version=%s WHERE app='pihole'", + (online_version,)) + db.commit() + logger.info("[FastAPI] New Pi-hole version recorded in database") + request.app.state.notifier.send( + title="New Pi-hole version has been found", + body=f"Version {online_version}", + category="utility" + ) + logger.debug("[FastAPI] Notification sent for Pi-hole update") + return Response(status_code=200) - logger.debug("[App] No new Pi-hole version found") + logger.debug("[FastAPI] No new Pi-hole version found") return Response(status_code=204) except Exception as e: - logger.error(f"[App] Error in Pi-hole handler: {e}", exc_info=True) + logger.error(f"[FastAPI] Error in Pi-hole handler: {e}", exc_info=True) raise HTTPException(status_code=500, detail=str(e)) from e async def start_servers(): - logger.info("Starting main and metrics servers") - config_main = uvicorn.Config("main:api", host="0.0.0.0", port=5000, log_level=LOG_LEVEL.lower(), log_config=LOGGING_CONFIG) - config_metrics = uvicorn.Config("metrics_server:metrics_api", host="0.0.0.0", port=9000, log_level=LOG_LEVEL.lower(), log_config=LOGGING_CONFIG) + logger.info("[Server] Starting main and metrics servers") + config_main = uvicorn.Config("main:api", host="0.0.0.0", port=5000, log_level=LOG_LEVEL.lower(), log_config=None) + config_metrics = uvicorn.Config("metrics_server:metrics_api", host="0.0.0.0", port=9000, log_level=LOG_LEVEL.lower(), log_config=None) server_main = uvicorn.Server(config_main) server_metrics = uvicorn.Server(config_metrics) - logger.debug("Launching servers concurrently") + await asyncio.gather(server_main.serve(), server_metrics.serve()) - logger.info("Both servers have stopped") + logger.info("[Server] Both servers have stopped") if __name__ == "__main__": asyncio.run(start_servers()) diff --git a/src/send_notification.py b/src/send_notification.py deleted file mode 100644 index d153aa5..0000000 --- a/src/send_notification.py +++ /dev/null @@ -1,65 +0,0 @@ -import os -import time - -import requests -from fastapi import HTTPException -from requests.exceptions import ConnectionError, HTTPError, RequestException, Timeout - -from secret_manager import return_credentials -from simple_logger_handler import setup_logger - -backend_api_url=os.getenv("BACKEND_API_URL","localhost:8101/internal/receive-notifications") -api_key= return_credentials("/etc/secrets/api_key") -logger = setup_logger(__name__) - - -def send_notification(notification:str,max_retries: int = 5,timeout: int = 5)->str: - """ - Sends a notification to the internal backend service when a new docker image is released. - - Parameters: - notification: Name of the docker repository. - """ - - headers = { - "X-API-Key-Internal": api_key, - "Content-Type": "application/json" - } - - data = { - "user_id": 1, - "message": { - "title": notification, - "body": "A new version is available.", - "link": None, - "category":"utility", - "timestamp": int(time.time()) - }} - - logger.debug(f"[Notify] Preparing to send notification: title='{notification}'") - - with requests.Session() as session: - for attempt in range(1, max_retries + 1): - try: - logger.debug(f"[Notify] Sending request to backend (attempt {attempt}/{max_retries})") - response = session.post(backend_api_url, headers=headers, json=data, timeout=timeout) - response.raise_for_status() - logger.info(f"[Notify] Notification sent successfully for '{notification}'") - return response.text - - except (Timeout, ConnectionError) as e: - logger.warning(f"[Notify] Attempt {attempt}/{max_retries} failed: {type(e).__name__}") - if attempt == max_retries: - logger.error(f"[Notify] All retry attempts failed for '{notification}'") - raise HTTPException(status_code=503, detail=f"Notification service unavailable: {type(e).__name__}") from e - sleep_time = 2 ** (attempt - 1) - logger.debug(f"[Notify] Retrying in {sleep_time} seconds...") - time.sleep(sleep_time) - - except HTTPError as e: - logger.error(f"[Notify] HTTP {e.response.status_code}: {e.response.text}") - raise HTTPException(status_code=e.response.status_code, detail=e.response.text) from e - - except RequestException as e: - logger.error(f"[Notify] Unexpected request failure: {e}") - raise HTTPException(status_code=500, detail=f"Request failed: {str(e)}") from e