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:
2026-05-04 06:33:01 +00:00
parent 2fe73fcce1
commit 69bdf7b30a
2 changed files with 41 additions and 7 deletions

View File

@@ -26,6 +26,7 @@ from typing import Any
from pydantic import BaseModel
import asyncpg
import httpx
from legal_mcp import config
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 ────────────────────────────
# 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():
"""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)
)
try:
# SDK pattern: try update, fall back to create if missing.
# NOTE: exact method may vary by infisical-python version. The
# canonical method is `update_secret_by_name`; if your version
# uses `secrets.update`, replace accordingly.
# SDK pattern: try update, fall back to create if the secret doesn't exist.
# The Infisical SDK's specific NotFound exception class isn't stable across
# versions, so we inspect the error string. Network/auth errors (which DON'T
# 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:
client.update_secret_by_name(
project_id=project_id,
@@ -2711,7 +2720,22 @@ async def api_mcp_env_update(key: str, req: McpEnvUpdateRequest):
secret_name=key,
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(
project_id=project_id,
environment_slug=env,
@@ -2740,7 +2764,14 @@ async def api_mcp_env_update(key: str, req: McpEnvUpdateRequest):
@app.post("/api/settings/mcp/env/redeploy")
async def api_mcp_env_redeploy():
"""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_token = os.environ.get("COOLIFY_API_TOKEN", "")
app_uuid = os.environ.get("COOLIFY_APP_UUID", "gyjo0mtw2c42ej3xxvbz8zio")
@@ -2757,8 +2788,9 @@ async def api_mcp_env_redeploy():
except Exception as e:
raise HTTPException(502, f"Coolify unreachable: {e}")
if resp.status_code >= 400:
body_preview = (resp.text or "")[:200]
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 {}
deployment_uuid = (
@@ -2766,6 +2798,7 @@ async def api_mcp_env_redeploy():
or (data.get("deployments") or [{}])[0].get("deployment_uuid")
)
logger.info("mcp_env_redeploy triggered uuid=%s", deployment_uuid)
_LAST_REDEPLOY_AT = now
return {
"ok": True,
"deployment_uuid": deployment_uuid,