fix: normalize host runtime service endpoints
This commit is contained in:
+56
-6
@@ -1,8 +1,57 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
import os
|
||||
from typing import Optional
|
||||
from urllib.parse import urlsplit, urlunsplit
|
||||
|
||||
from pydantic import model_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
_DOCKER_SERVICE_ALIASES = {"postgres", "redis", "minio"}
|
||||
|
||||
|
||||
def _is_running_in_container() -> bool:
|
||||
if os.path.exists("/.dockerenv"):
|
||||
return True
|
||||
try:
|
||||
with open("/proc/1/cgroup", "r", encoding="utf-8") as handle:
|
||||
return "docker" in handle.read() or "containerd" in handle.read()
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def _normalize_service_host(host: str) -> str:
|
||||
if _is_running_in_container():
|
||||
return host
|
||||
return "localhost" if host in _DOCKER_SERVICE_ALIASES else host
|
||||
|
||||
|
||||
def _normalize_service_url(url: str) -> str:
|
||||
parsed = urlsplit(url)
|
||||
if not parsed.hostname:
|
||||
return url
|
||||
|
||||
normalized_host = _normalize_service_host(parsed.hostname)
|
||||
if normalized_host == parsed.hostname:
|
||||
return url
|
||||
|
||||
netloc = normalized_host
|
||||
if parsed.username:
|
||||
auth = parsed.username
|
||||
if parsed.password:
|
||||
auth = f"{auth}:{parsed.password}"
|
||||
netloc = f"{auth}@{netloc}"
|
||||
if parsed.port:
|
||||
netloc = f"{netloc}:{parsed.port}"
|
||||
return urlunsplit((parsed.scheme, netloc, parsed.path, parsed.query, parsed.fragment))
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env",
|
||||
case_sensitive=False,
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
# Database
|
||||
postgres_db: str = "hartomat"
|
||||
postgres_user: str = "hartomat"
|
||||
@@ -27,6 +76,12 @@ class Settings(BaseSettings):
|
||||
# Redis / Celery
|
||||
redis_url: str = "redis://localhost:6379/0"
|
||||
|
||||
@model_validator(mode="after")
|
||||
def normalize_runtime_hosts(self) -> "Settings":
|
||||
self.postgres_host = _normalize_service_host(self.postgres_host)
|
||||
self.redis_url = _normalize_service_url(self.redis_url)
|
||||
return self
|
||||
|
||||
# JWT
|
||||
jwt_secret_key: str = "changeme"
|
||||
jwt_algorithm: str = "HS256"
|
||||
@@ -42,9 +97,4 @@ class Settings(BaseSettings):
|
||||
upload_dir: str = "/app/uploads"
|
||||
max_upload_size_mb: int = 500
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = False
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
from app import config as config_module
|
||||
|
||||
|
||||
def test_settings_normalize_docker_aliases_for_host_runtime(monkeypatch):
|
||||
monkeypatch.setattr(config_module, "_is_running_in_container", lambda: False)
|
||||
|
||||
settings = config_module.Settings(
|
||||
postgres_host="postgres",
|
||||
redis_url="redis://redis:6379/0",
|
||||
)
|
||||
|
||||
assert settings.postgres_host == "localhost"
|
||||
assert settings.redis_url == "redis://localhost:6379/0"
|
||||
assert settings.database_url == "postgresql+asyncpg://hartomat:hartomat@localhost:5432/hartomat"
|
||||
|
||||
|
||||
def test_settings_preserve_service_aliases_inside_container(monkeypatch):
|
||||
monkeypatch.setattr(config_module, "_is_running_in_container", lambda: True)
|
||||
|
||||
settings = config_module.Settings(
|
||||
postgres_host="postgres",
|
||||
redis_url="redis://redis:6379/0",
|
||||
)
|
||||
|
||||
assert settings.postgres_host == "postgres"
|
||||
assert settings.redis_url == "redis://redis:6379/0"
|
||||
|
||||
|
||||
def test_normalize_service_url_preserves_auth_and_query(monkeypatch):
|
||||
monkeypatch.setattr(config_module, "_is_running_in_container", lambda: False)
|
||||
|
||||
normalized = config_module._normalize_service_url(
|
||||
"redis://user:secret@redis:6380/1?ssl_cert_reqs=none"
|
||||
)
|
||||
|
||||
assert normalized == "redis://user:secret@localhost:6380/1?ssl_cert_reqs=none"
|
||||
@@ -152,6 +152,14 @@ Ergebnis:
|
||||
|
||||
## Letzte Verifikation
|
||||
|
||||
- `backend/.venv/bin/pytest backend/tests/test_config_runtime_resolution.py -q`
|
||||
- Ergebnis: 3 Tests grün; Host-Runtime normalisiert Docker-Service-Aliase (`postgres`, `redis`) außerhalb von Containern nun automatisch auf `localhost`, Container-Runtime bleibt unverändert
|
||||
- `backend/.venv/bin/pytest backend/tests/domains/test_workflow_runtime_services.py -q -x`
|
||||
- Ergebnis: 29 Tests grün; Root Cause für den Host-Testfehler war Celery/Redis-Zugriff über Docker-DNS aus dem Host-Kontext, der jetzt zentral im Config-Layer abgefangen wird
|
||||
- `curl -I -s http://localhost:5173`
|
||||
- Ergebnis: Frontend antwortet mit `HTTP/1.1 200 OK`
|
||||
- `curl -s http://localhost:8888/health`
|
||||
- Ergebnis: Backend antwortet mit `{"status":"ok","service":"hartomat-backend"}`
|
||||
- `python3 scripts/test_render_pipeline.py --workflow-still-smoke --execution-mode shadow`
|
||||
- Ergebnis: Live-Smoke erfolgreich; Shadow-Comparison stabilisiert auf `WARN` mit `mean_pixel_delta=0.000257`, Legacy bleibt dadurch weiterhin authoritative
|
||||
- `./backend/.venv/bin/pytest -q backend/tests/domains/test_workflow_runtime_services.py -k 'resolve_order_line_template_context_uses_exact_template_and_override or resolve_order_line_material_map_prefers_line_override_over_output_override or resolve_order_line_material_map_allows_node_override or prefers_authoritative_scene_manifest_assignments or keeps_legacy_source_name_fallback_without_scene_manifest'`
|
||||
|
||||
Reference in New Issue
Block a user