140 lines
4.7 KiB
Python
140 lines
4.7 KiB
Python
"""Turn apiGateway back off on the live Application to restore the chat.
|
|
|
|
## Why
|
|
|
|
Enabling apiGateway makes the Helm chart set `API_GATEWAY_URL` on the web pod
|
|
(infra/helm/medical-chatbot/templates/web.yaml). Every one of web's BFF routes
|
|
prefers that variable over `AI_SERVICE_URL`:
|
|
|
|
apps/web/app/api/chat/route.ts:7
|
|
apps/web/app/api/suggest/route.ts:5
|
|
apps/web/app/api/history/route.ts:5
|
|
apps/web/app/api/sections/route.ts:5
|
|
apps/web/app/api/section-text/route.ts:5
|
|
apps/web/app/api/feedback/route.ts:5
|
|
|
|
process.env.API_GATEWAY_URL ?? process.env.AI_SERVICE_URL ?? ...
|
|
|
|
but the gateway only proxies `/auth/*` (apps/api-gateway/src/proxy/ has a
|
|
single AuthProxyController). So all six start posting to a service with no
|
|
such route, and the UI shows "Dịch vụ AI Service đang khởi động hoặc gặp sự
|
|
cố tạm thời."
|
|
|
|
The bug shipped in PR #27 yesterday and sat dormant: the configuration that
|
|
triggers it was never actually rendered until the 2026-08-19 repair of the
|
|
folded-scalar corruption deployed it for the first time.
|
|
|
|
## What this does
|
|
|
|
Flips `enabled` to false under the `apiGateway` key only -- `authService`'s
|
|
own `enabled: true` is left alone -- so `API_GATEWAY_URL` stops being set and
|
|
all six routes fall back to `AI_SERVICE_URL`, exactly the configuration that
|
|
served traffic before today.
|
|
|
|
Cost: login stops working again (apps/web/app/api/auth/{login,me}/route.ts
|
|
read API_GATEWAY_URL with no fallback). That is the deliberate trade -- chat
|
|
is the product and affects every visitor; login is a day-old addition that
|
|
was not reachable before today anyway. The real fix is to stop routing RAG
|
|
through the gateway at all, which needs an image rebuild.
|
|
|
|
Refuses to act unless it finds `apiGateway:` followed by `enabled: true`, so
|
|
it cannot silently do something else.
|
|
|
|
Required env: ARGOCD_PRACTICE_URL, ARGOCD_PRACTICE_PASSWORD.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
APP_NAME = "medical-chatbot-app"
|
|
POLL_SECONDS = 15
|
|
POLL_ROUNDS = 8
|
|
|
|
|
|
def call(base: str, method: str, path: str, token: str | None = None, body=None):
|
|
req = urllib.request.Request(
|
|
f"{base}{path}",
|
|
data=json.dumps(body).encode() if body is not None else None,
|
|
method=method,
|
|
headers={"Content-Type": "application/json"},
|
|
)
|
|
if token:
|
|
req.add_header("Authorization", f"Bearer {token}")
|
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
raw = resp.read()
|
|
return json.loads(raw) if raw else {}
|
|
|
|
|
|
def disable_api_gateway(values: str) -> str:
|
|
"""Set `enabled: false` under the apiGateway key, and nowhere else."""
|
|
lines = values.splitlines()
|
|
out: list[str] = []
|
|
in_gateway = False
|
|
changed = 0
|
|
|
|
for line in lines:
|
|
stripped = line.strip()
|
|
# A non-indented key ends whatever block we were in.
|
|
if line and not line[0].isspace():
|
|
in_gateway = stripped == "apiGateway:"
|
|
elif in_gateway and stripped == "enabled: true":
|
|
indent = line[: len(line) - len(line.lstrip())]
|
|
line = f"{indent}enabled: false"
|
|
changed += 1
|
|
out.append(line)
|
|
|
|
if changed != 1:
|
|
raise SystemExit(
|
|
f"Expected exactly one `enabled: true` under apiGateway, changed {changed}. "
|
|
"Refusing to write."
|
|
)
|
|
return "\n".join(out) + "\n"
|
|
|
|
|
|
def main() -> int:
|
|
base = os.environ["ARGOCD_PRACTICE_URL"].rstrip("/")
|
|
password = os.environ["ARGOCD_PRACTICE_PASSWORD"]
|
|
|
|
session = call(base, "POST", "/api/v1/session", body={"username": "admin", "password": password})
|
|
token = session["token"]
|
|
|
|
app = call(base, "GET", f"/api/v1/applications/{APP_NAME}", token=token)
|
|
values = app["spec"]["source"]["helm"].get("values", "")
|
|
|
|
repaired = disable_api_gateway(values)
|
|
print("apiGateway.enabled -> false (authService untouched)")
|
|
|
|
app["spec"]["source"]["helm"]["values"] = repaired
|
|
call(base, "PUT", f"/api/v1/applications/{APP_NAME}", token=token, body=app)
|
|
print("PUT accepted.")
|
|
|
|
try:
|
|
call(base, "POST", f"/api/v1/applications/{APP_NAME}/sync", token=token, body={})
|
|
print("Sync triggered.")
|
|
except urllib.error.HTTPError as exc:
|
|
if exc.code == 400:
|
|
print("Explicit sync raced with autosync -- continuing.")
|
|
else:
|
|
raise
|
|
|
|
for i in range(POLL_ROUNDS):
|
|
time.sleep(POLL_SECONDS)
|
|
app = call(base, "GET", f"/api/v1/applications/{APP_NAME}", token=token)
|
|
status = app.get("status", {})
|
|
print(
|
|
f"poll {i + 1}/{POLL_ROUNDS}: sync={status.get('sync', {}).get('status')} "
|
|
f"health={status.get('health', {}).get('status')}"
|
|
)
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|