fix(settings): harden PATCH/redeploy per code review
- Add infisicalsdk dependency - Narrow update→create fallback to NotFound errors only (no silent swallow) - Truncate Coolify error response text to 200 chars - Add 60s cooldown to redeploy endpoint - Move httpx to top-level import
This commit is contained in:
@@ -20,6 +20,7 @@ dependencies = [
|
|||||||
"fastapi>=0.115.0",
|
"fastapi>=0.115.0",
|
||||||
"uvicorn[standard]>=0.30.0",
|
"uvicorn[standard]>=0.30.0",
|
||||||
"httpx>=0.27.0",
|
"httpx>=0.27.0",
|
||||||
|
"infisicalsdk>=1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|||||||
47
web/app.py
47
web/app.py
@@ -26,6 +26,7 @@ from typing import Any
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
import asyncpg
|
import asyncpg
|
||||||
|
import httpx
|
||||||
|
|
||||||
from legal_mcp import config
|
from legal_mcp import config
|
||||||
from legal_mcp.services import chunker, db, embeddings, extractor, git_sync, processor, proofreader, research_md
|
from legal_mcp.services import chunker, db, embeddings, extractor, git_sync, processor, proofreader, research_md
|
||||||
@@ -2552,6 +2553,12 @@ async def api_post_agent_comment(case_number: str, req: AgentCommentRequest):
|
|||||||
|
|
||||||
# ── Settings: MCP Server Configuration ────────────────────────────
|
# ── Settings: MCP Server Configuration ────────────────────────────
|
||||||
|
|
||||||
|
# Module-level guard: minimum interval between redeploys (60 seconds).
|
||||||
|
# Prevents accidental double-clicks or automated retry loops from queueing
|
||||||
|
# multiple redundant Coolify builds.
|
||||||
|
_LAST_REDEPLOY_AT: float = 0.0
|
||||||
|
_REDEPLOY_MIN_INTERVAL_SEC: float = 60.0
|
||||||
|
|
||||||
|
|
||||||
def _infisical_client():
|
def _infisical_client():
|
||||||
"""Build Infisical SDK client, or return None if not configured."""
|
"""Build Infisical SDK client, or return None if not configured."""
|
||||||
@@ -2699,10 +2706,12 @@ async def api_mcp_env_update(key: str, req: McpEnvUpdateRequest):
|
|||||||
"false" if coerced is False else str(coerced)
|
"false" if coerced is False else str(coerced)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
# SDK pattern: try update, fall back to create if missing.
|
# SDK pattern: try update, fall back to create if the secret doesn't exist.
|
||||||
# NOTE: exact method may vary by infisical-python version. The
|
# The Infisical SDK's specific NotFound exception class isn't stable across
|
||||||
# canonical method is `update_secret_by_name`; if your version
|
# versions, so we inspect the error string. Network/auth errors (which DON'T
|
||||||
# uses `secrets.update`, replace accordingly.
|
# contain 'not found' / 404 / 'does not exist') are re-raised immediately
|
||||||
|
# so they reach the outer handler and surface as 502 with the real error,
|
||||||
|
# rather than being silently retried as a create-call.
|
||||||
try:
|
try:
|
||||||
client.update_secret_by_name(
|
client.update_secret_by_name(
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
@@ -2711,7 +2720,22 @@ async def api_mcp_env_update(key: str, req: McpEnvUpdateRequest):
|
|||||||
secret_name=key,
|
secret_name=key,
|
||||||
secret_value=str_value,
|
secret_value=str_value,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception as update_err:
|
||||||
|
err_text = str(update_err).lower()
|
||||||
|
looks_like_missing = (
|
||||||
|
"not found" in err_text
|
||||||
|
or "does not exist" in err_text
|
||||||
|
or "404" in err_text
|
||||||
|
)
|
||||||
|
if not looks_like_missing:
|
||||||
|
logger.warning(
|
||||||
|
"infisical_update_failed key=%s err=%s — not retrying as create",
|
||||||
|
key, update_err,
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
logger.info(
|
||||||
|
"infisical_secret_missing key=%s — falling back to create", key,
|
||||||
|
)
|
||||||
client.create_secret_by_name(
|
client.create_secret_by_name(
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
environment_slug=env,
|
environment_slug=env,
|
||||||
@@ -2740,7 +2764,14 @@ async def api_mcp_env_update(key: str, req: McpEnvUpdateRequest):
|
|||||||
@app.post("/api/settings/mcp/env/redeploy")
|
@app.post("/api/settings/mcp/env/redeploy")
|
||||||
async def api_mcp_env_redeploy():
|
async def api_mcp_env_redeploy():
|
||||||
"""Trigger Coolify redeploy of the legal-ai app."""
|
"""Trigger Coolify redeploy of the legal-ai app."""
|
||||||
import httpx
|
global _LAST_REDEPLOY_AT
|
||||||
|
now = time.time()
|
||||||
|
elapsed = now - _LAST_REDEPLOY_AT
|
||||||
|
if elapsed < _REDEPLOY_MIN_INTERVAL_SEC:
|
||||||
|
wait = int(_REDEPLOY_MIN_INTERVAL_SEC - elapsed)
|
||||||
|
raise HTTPException(
|
||||||
|
429, f"Redeploy בהמתנה: נסה שוב בעוד {wait} שניות."
|
||||||
|
)
|
||||||
coolify_url = os.environ.get("COOLIFY_URL", "http://158.178.131.193:8000")
|
coolify_url = os.environ.get("COOLIFY_URL", "http://158.178.131.193:8000")
|
||||||
coolify_token = os.environ.get("COOLIFY_API_TOKEN", "")
|
coolify_token = os.environ.get("COOLIFY_API_TOKEN", "")
|
||||||
app_uuid = os.environ.get("COOLIFY_APP_UUID", "gyjo0mtw2c42ej3xxvbz8zio")
|
app_uuid = os.environ.get("COOLIFY_APP_UUID", "gyjo0mtw2c42ej3xxvbz8zio")
|
||||||
@@ -2757,8 +2788,9 @@ async def api_mcp_env_redeploy():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(502, f"Coolify unreachable: {e}")
|
raise HTTPException(502, f"Coolify unreachable: {e}")
|
||||||
if resp.status_code >= 400:
|
if resp.status_code >= 400:
|
||||||
|
body_preview = (resp.text or "")[:200]
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
502, f"Coolify deploy failed: {resp.status_code} {resp.text}"
|
502, f"Coolify deploy failed: {resp.status_code} — {body_preview}"
|
||||||
)
|
)
|
||||||
data = resp.json() if resp.content else {}
|
data = resp.json() if resp.content else {}
|
||||||
deployment_uuid = (
|
deployment_uuid = (
|
||||||
@@ -2766,6 +2798,7 @@ async def api_mcp_env_redeploy():
|
|||||||
or (data.get("deployments") or [{}])[0].get("deployment_uuid")
|
or (data.get("deployments") or [{}])[0].get("deployment_uuid")
|
||||||
)
|
)
|
||||||
logger.info("mcp_env_redeploy triggered uuid=%s", deployment_uuid)
|
logger.info("mcp_env_redeploy triggered uuid=%s", deployment_uuid)
|
||||||
|
_LAST_REDEPLOY_AT = now
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"deployment_uuid": deployment_uuid,
|
"deployment_uuid": deployment_uuid,
|
||||||
|
|||||||
Reference in New Issue
Block a user