feat(console): 로컬 앱 설정·재배포·삭제·스케일·연결표시(에이전트 릴레이) — 0.23.0

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 19:39:58 +09:00
parent e848547319
commit f3a70227ba
2 changed files with 45 additions and 1 deletions

View File

@ -460,6 +460,50 @@ def _h_kube(action: str, params: dict) -> dict:
"ready": (d.get("status", {}) or {}).get("readyReplicas", 0) or 0,
"image": conts[0]["image"] if conts else ""})
return {"workloads": wls}
# ── 쓰기 액션(콘솔 앱 카드 설정/재배포/삭제 릴레이). 로컬 dev: ns=프로젝트명, 라벨 app=<name> ──
if action in ("app_scale", "app_restart", "app_delete", "app_edit"):
app = params.get("app") or ""
if not app:
raise DsErr("BAD_REQUEST")
items = json.loads(_kubectl(["get", "deploy", "-A", "-l", f"app={app}", "-o", "json"]) or "{}").get("items", [])
if not items:
raise DsErr("NOT_FOUND")
d = items[0]
ns = d["metadata"]["namespace"]
conts = (d.get("spec", {}).get("template", {}).get("spec", {}) or {}).get("containers", []) or []
cont = conts[0]["name"] if conts else app
if action == "app_scale":
n = int(params.get("replicas", 1))
_kubectl(["-n", ns, "scale", f"deploy/{app}", f"--replicas={n}"])
return {"app": app, "replicas": n}
if action == "app_restart":
_kubectl(["-n", ns, "rollout", "restart", f"deploy/{app}"])
return {"app": app, "restarted": True}
if action == "app_delete":
# 앱 리소스만 제거(라벨 app=<name> = workload). 공유 소스(role=source)는 건드리지 않음.
_kubectl(["-n", ns, "delete", "deploy,svc,ingress", "-l", f"app={app}", "--ignore-not-found"])
return {"app": app, "deleted": True}
# app_edit — 런타임 조정만 라이브 반영(replicas/image/resources/health/env). 구조변경(포트/도메인/경로)은
# 매니페스트+dev deploy 소관(로컬은 매니페스트가 진실). 각 필드는 있을 때만 적용.
applied = []
if params.get("replicas") is not None:
_kubectl(["-n", ns, "scale", f"deploy/{app}", f"--replicas={int(params['replicas'])}"]); applied.append("replicas")
if params.get("image"):
_kubectl(["-n", ns, "set", "image", f"deploy/{app}", f"{cont}={params['image']}"]); applied.append("image")
cpu, mem = params.get("cpu"), params.get("mem")
if cpu or mem:
req = ",".join([x for x in [f"cpu={cpu}" if cpu else "", f"memory={mem}" if mem else ""] if x])
_kubectl(["-n", ns, "set", "resources", f"deploy/{app}", f"--requests={req}"]); applied.append("resources")
if params.get("health"):
hp = params["health"]
patch = json.dumps({"spec": {"template": {"spec": {"containers": [
{"name": cont, "readinessProbe": {"httpGet": {"path": hp}}, "livenessProbe": {"httpGet": {"path": hp}}}]}}}})
_kubectl(["-n", ns, "patch", f"deploy/{app}", "--type=strategic", "-p", patch]); applied.append("health")
env = params.get("env") or []
kv = [f"{e['key']}={e.get('value', '')}" for e in env if isinstance(e, dict) and e.get("key")]
if kv:
_kubectl(["-n", ns, "set", "env", f"deploy/{app}", *kv]); applied.append("env")
return {"app": app, "applied": applied}
raise DsErr("FORBIDDEN_ACTION")

View File

@ -16,7 +16,7 @@ set -uo pipefail # -e 미사용: 'test && action' 관용구가 값 없을 때
REPO="${YAKCLOUD_STARTER_REPO:-https://gitea.yakenator.io/yakenator/yakcloud-starter}"
BRANCH="${YAKCLOUD_STARTER_BRANCH:-main}"
VERSION="0.22.1"
VERSION="0.23.0"
# 로컬 개발 클러스터(kind) — 개발 후 운영까지 '직선 배포'의 dev 구간.
DEV_CTX="${YAK_DEV_CONTEXT:-kind-yak-dev}"
DEV_CLUSTER="${YAK_DEV_CLUSTER:-yak-dev}"