diff --git a/.yakcloud/agent.py b/.yakcloud/agent.py index c141446..d0e5dfd 100644 --- a/.yakcloud/agent.py +++ b/.yakcloud/agent.py @@ -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= ── + 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= = 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") diff --git a/bin/yakcloud b/bin/yakcloud index bddba98..1c413c9 100755 --- a/bin/yakcloud +++ b/bin/yakcloud @@ -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}"