feat(relay): 에이전트 kube 핸들러(pods/describe/workloads) — 로컬 앱 라이브 조회
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -261,6 +261,57 @@ def _h_redis(sec: dict, action: str, params: dict) -> dict:
|
|||||||
raise DsErr("FORBIDDEN_ACTION")
|
raise DsErr("FORBIDDEN_ACTION")
|
||||||
|
|
||||||
|
|
||||||
|
# ── 앱(워크로드) 라이브 조회 — 로컬 kind 에 kubectl(호스트). 원격 앱 화면 파리티(P2). ───────────
|
||||||
|
def _kubectl(argv: list[str]) -> str:
|
||||||
|
r = subprocess.run(["kubectl", "--context", CTX, "-n", NS, *argv], capture_output=True, text=True)
|
||||||
|
if r.returncode != 0:
|
||||||
|
err = (r.stderr or "").lower()
|
||||||
|
if "notfound" in err.replace(" ", "") or "not found" in err:
|
||||||
|
raise DsErr("NOT_FOUND")
|
||||||
|
raise DsErr("UNREACHABLE")
|
||||||
|
return r.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def _h_kube(action: str, params: dict) -> dict:
|
||||||
|
if action == "pods":
|
||||||
|
app = params.get("app") or ""
|
||||||
|
sel = ["-l", f"app={app}"] if app else []
|
||||||
|
items = json.loads(_kubectl(["get", "pods", *sel, "-o", "json"]) or "{}").get("items", [])
|
||||||
|
pods = []
|
||||||
|
for p in items:
|
||||||
|
st = p.get("status", {}) or {}
|
||||||
|
cs = st.get("containerStatuses", []) or []
|
||||||
|
pods.append({"name": p["metadata"]["name"], "phase": st.get("phase", ""),
|
||||||
|
"ready": bool(cs) and all(c.get("ready") for c in cs),
|
||||||
|
"restarts": sum(int(c.get("restartCount", 0)) for c in cs),
|
||||||
|
"node": (p.get("spec", {}) or {}).get("nodeName", ""),
|
||||||
|
"ip": st.get("podIP", ""), "startedAt": st.get("startTime", "")})
|
||||||
|
return {"app": app, "pods": pods[:100]}
|
||||||
|
if action == "describe":
|
||||||
|
pod = params["pod"]
|
||||||
|
p = json.loads(_kubectl(["get", "pod", pod, "-o", "json"]) or "{}")
|
||||||
|
st = p.get("status", {}) or {}
|
||||||
|
containers = [{"name": c.get("name", ""), "image": c.get("image", ""),
|
||||||
|
"ready": bool(c.get("ready")), "restarts": int(c.get("restartCount", 0)),
|
||||||
|
"state": next(iter((c.get("state", {}) or {}).keys()), "")}
|
||||||
|
for c in (st.get("containerStatuses", []) or [])]
|
||||||
|
conds = [{"type": c.get("type", ""), "status": c.get("status", "")} for c in (st.get("conditions", []) or [])]
|
||||||
|
return {"name": pod, "phase": st.get("phase", ""), "node": (p.get("spec", {}) or {}).get("nodeName", ""),
|
||||||
|
"ip": st.get("podIP", ""), "startedAt": st.get("startTime", ""),
|
||||||
|
"containers": containers, "conditions": conds}
|
||||||
|
if action == "workloads":
|
||||||
|
items = json.loads(_kubectl(["get", "deploy", "-o", "json"]) or "{}").get("items", [])
|
||||||
|
wls = []
|
||||||
|
for d in items:
|
||||||
|
sp = d.get("spec", {}) or {}
|
||||||
|
conts = (sp.get("template", {}).get("spec", {}) or {}).get("containers", []) or []
|
||||||
|
wls.append({"name": d["metadata"]["name"], "replicas": sp.get("replicas", 0) or 0,
|
||||||
|
"ready": (d.get("status", {}) or {}).get("readyReplicas", 0) or 0,
|
||||||
|
"image": conts[0]["image"] if conts else ""})
|
||||||
|
return {"workloads": wls}
|
||||||
|
raise DsErr("FORBIDDEN_ACTION")
|
||||||
|
|
||||||
|
|
||||||
def dispatch(a: dict) -> dict:
|
def dispatch(a: dict) -> dict:
|
||||||
"""ds-actions 액션 1건 실행 → {ok, data}|{ok:false, error:{code}} + nonce."""
|
"""ds-actions 액션 1건 실행 → {ok, data}|{ok:false, error:{code}} + nonce."""
|
||||||
nonce = a.get("nonce")
|
nonce = a.get("nonce")
|
||||||
@ -269,6 +320,8 @@ def dispatch(a: dict) -> dict:
|
|||||||
service = a.get("service", "")
|
service = a.get("service", "")
|
||||||
action = a.get("action", "")
|
action = a.get("action", "")
|
||||||
params = a.get("params") or {}
|
params = a.get("params") or {}
|
||||||
|
if stype == "KUBE": # 앱(워크로드) 라이브 조회 — 소스 Secret 불필요
|
||||||
|
return {"nonce": nonce, "ok": True, "data": _h_kube(action, params)}
|
||||||
sec = _read_secret(service)
|
sec = _read_secret(service)
|
||||||
if stype in ("POSTGRESQL", "MYSQL", "MARIADB"):
|
if stype in ("POSTGRESQL", "MYSQL", "MARIADB"):
|
||||||
data = _h_sql(stype, sec, action, params)
|
data = _h_sql(stype, sec, action, params)
|
||||||
|
|||||||
Reference in New Issue
Block a user