cli v0.13.0: 공유 외부 소스 CLI 선언(source add --shared, mode:shared) + 문서 3계층·cluster create 정리

source add --shared / requires[].mode:shared 로 공유 외부 소스를 매니페스트에 선언 → 배포 시 백엔드가 클러스터별 격리 DB/계정 민팅(인클러스터 StatefulSet 없음, 소용량 클러스터용). reconcile_source 가 mode 를 POST /services 로 전달(local 하드코딩 제거). 문서(슬래시명령·매니페스트 템플릿·DATA-SOURCES·SKILL·bin help): 로컬 dev/migrate 제거·dev/val/prod 3계층 승격(promote --to)·cluster create/ls·project check --env 반영.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jungwoo choi
2026-08-28 12:08:11 +09:00
parent 2281039c1b
commit 89fca872c0
7 changed files with 56 additions and 32 deletions

View File

@ -232,10 +232,17 @@ def cmd_source(a) -> None:
if a.action == "add":
if any(r.get("name") == a.name for r in reqs):
sys.exit(" ✗ 이미 있음: %s" % a.name)
reqs.append({"name": a.name, "type": a.type, "plan": a.plan})
save_manifest(m)
print(" ✓ requires += {name=%s, type=%s, plan=%s}" % (a.name, a.type, a.plan))
print(" · 프로비저닝/바인딩은 'yakcloud bind <workload> %s <alias>''yakcloud deploy'" % a.name)
if getattr(a, "shared", False): # 공유 외부 소스 — 배포 시 백엔드가 클러스터별 격리 DB/계정 민팅
reqs.append({"name": a.name, "type": a.type, "mode": "shared"})
save_manifest(m)
print(" ✓ requires += {name=%s, type=%s, mode=shared}" % (a.name, a.type))
print(" · 공유 외부 소스 — 배포 시 백엔드가 클러스터별 격리 DB/계정 민팅(인클러스터 StatefulSet 없음).")
print(" · 바인딩: 'yakcloud bind <workload> %s <alias>''yakcloud deploy'" % a.name)
else:
reqs.append({"name": a.name, "type": a.type, "plan": a.plan})
save_manifest(m)
print(" ✓ requires += {name=%s, type=%s, plan=%s}" % (a.name, a.type, a.plan))
print(" · 프로비저닝/바인딩은 'yakcloud bind <workload> %s <alias>''yakcloud deploy'" % a.name)
else: # rm
m["requires"] = [r for r in reqs if r.get("name") != a.name]
# 관련 바인딩도 정리
@ -362,7 +369,10 @@ def build_parser() -> argparse.ArgumentParser:
sosub = so.add_subparsers(dest="action", required=True)
sol = sosub.add_parser("ls", help="클러스터에서 이용 가능한 소스 목록"); sol.set_defaults(fn=cmd_source_ls)
soa = sosub.add_parser("add"); soa.add_argument("name"); soa.add_argument("type")
soa.add_argument("plan", nargs="?", default="small"); soa.set_defaults(fn=cmd_source)
soa.add_argument("plan", nargs="?", default="small")
soa.add_argument("--shared", action="store_true",
help="공유 외부 소스(mode:shared) — 백엔드가 클러스터별 격리 DB/계정 민팅(소용량 클러스터용, 현재 mongodb)")
soa.set_defaults(fn=cmd_source)
sor = sosub.add_parser("rm"); sor.add_argument("name"); sor.set_defaults(fn=cmd_source)
b = sub.add_parser("bind", help="워크로드에 소스 바인딩")

View File

@ -99,18 +99,23 @@ def reconcile_source(req: dict) -> str | None:
if match and match.get("status") == "READY":
log(f"source '{name}' ({stype}) 이미 READY → 스킵 (id={match['id']})")
return match["id"]
mode = req.get("mode", "local") # local=인클러스터 프로비저닝 · shared=공유 외부(백엔드가 클러스터별 격리 DB 민팅)
if DRY:
if match:
log(f"source '{name}' 상태={match.get('status')} — READY 대기 필요")
else:
log(f"source '{name}' ({stype}, {plan}) 없음 → 프로비저닝 예정(POST /services). 현재 READY: {_ready_sources()}")
plan_or_mode = "shared" if mode == "shared" else plan
log(f"source '{name}' ({stype}, {plan_or_mode}) 없음 → 프로비저닝 예정(POST /services, mode={mode}). 현재 READY: {_ready_sources()}")
return None
if not match:
log(f"source '{name}' ({stype}, {plan}) 프로비저닝 시도…")
log(f"source '{name}' ({stype}, {'shared' if mode=='shared' else plan}) 프로비저닝 시도… (mode={mode})")
try:
# 소스 생성 = POST /services (clusterId 는 body). /clusters/{id}/services 는 GET 전용.
api("POST", "/services",
{"clusterId": CLUSTER, "type": stype, "name": name, "mode": "local", "size": plan})
# local=인클러스터 프로비저닝(size 사용) · shared=공유 외부(size 무관, 백엔드가 격리 DB/계정 민팅).
body = {"clusterId": CLUSTER, "type": stype, "name": name, "mode": mode}
if mode == "local":
body["size"] = plan
api("POST", "/services", body)
except SystemExit as e:
raise SystemExit(
f"소스 '{name}' 생성 실패 — 콘솔에서 소스를 만든 뒤 requires[].name·binds[].source 를 그 소스명으로 "