feat(cli): clusterName 환경별 소스명 매핑 + 운영 소스 3-모드 결정 조회(datasource options/ls) + 검증·문서 · v0.32.0
This commit is contained in:
@ -287,8 +287,13 @@ def _mint_oracle(cname: str, user: str, pw: str) -> None:
|
||||
|
||||
def provision_source(ns: str, project: str, req: dict) -> dict | None:
|
||||
"""requires 소스 → 공유 도커 컨테이너의 프로젝트별 격리 DB + no-selector Service/Endpoints. 접속정보 반환."""
|
||||
name = req["name"]
|
||||
logical = req["name"]
|
||||
# 실제 로컬 소스 이름 = clusterName(오버레이) 없으면 논리 이름. 서비스/격리DB/Secret 은 이 실제 이름 기준,
|
||||
# 앱 binds 는 논리 이름 고정(로컬↔운영 매핑). 호출부는 반환 conn 을 논리 이름으로 키.
|
||||
name = req.get("clusterName") or logical
|
||||
stype = req["type"].upper()
|
||||
if name != logical:
|
||||
log(f"source '{logical}' → 실제 소스 '{name}' (clusterName)")
|
||||
if req.get("mode") == "local":
|
||||
warn(f"source '{name}': mode=local(인클러스터)는 로컬 dev 에서 공유 컨테이너로 대체됩니다.")
|
||||
if stype not in SUPPORTED:
|
||||
@ -547,9 +552,10 @@ def _report_to_console(project: str, reqs: list, wls: list) -> None:
|
||||
"project": project,
|
||||
"context": CTX, # kubectl 컨텍스트(콘솔 연결·삭제 탭 안내용) — kind-yak-<name>
|
||||
|
||||
# secret = 노트북 kind 커넥션 Secret 이름 → 콘솔이 connSecretRef 로 저장, 에이전트가 이 이름으로 읽음.
|
||||
# name=논리 이름(binds 매칭·표시용), secret=실제 소스명(clusterName 우선)의 Secret 이름.
|
||||
# 콘솔이 secret 을 connSecretRef 로 저장 → 에이전트가 액션의 그 이름으로 조회(재계산 아님)하므로 정합 유지.
|
||||
"sources": [{"name": r["name"], "type": str(r.get("type", "")).upper(),
|
||||
"secret": f"yak-dev-src-{sanitize(r['name'])}"} for r in reqs],
|
||||
"secret": f"yak-dev-src-{sanitize(r.get('clusterName') or r['name'])}"} for r in reqs],
|
||||
"workloads": [{"name": sanitize(w["name"]), "image": w["image"].replace("${TAG}", TAG),
|
||||
"hosts": _disp_hosts(project, w), "port": int(w.get("port", 8080)),
|
||||
"path": (w.get("expose", {}) or {}).get("path", "/") or "/",
|
||||
@ -642,8 +648,8 @@ def _ensure_agent() -> None:
|
||||
|
||||
|
||||
# ── 환경 오버레이: base(requires/workloads) + environments.<env> 이름 매칭 병합 ─────────────────
|
||||
# 앱 binds 는 논리이름 고정 → 환경별로 소스 정체(type/mode/plan)·워크로드 필드만. 로컬은 항상 env="dev".
|
||||
# ※ deploy.py 의 동일 함수와 규약 일치(파리티).
|
||||
# 앱 binds 는 논리이름(name) 고정 → 환경별로 소스 정체(type/mode/plan)·실제 소스명(clusterName)·
|
||||
# 워크로드 필드만. 로컬은 항상 env="dev". ※ deploy.py 의 동일 함수와 규약 일치(파리티).
|
||||
def _merge_env_vars(base, ov):
|
||||
def norm(e):
|
||||
if isinstance(e, dict):
|
||||
@ -655,17 +661,40 @@ def _merge_env_vars(base, ov):
|
||||
return list(out.values())
|
||||
|
||||
|
||||
def _validate_requires(requires: list, env: str) -> None:
|
||||
"""병합된 requires 사전검증 — 프로비저닝 도중 KeyError 대신 친절한 오류로 조기 차단.
|
||||
★ 환경 오버레이는 base 하고만 병합되며 dev↔val↔prod 끼리 상속하지 않는다 →
|
||||
type 은 base(권장) 또는 그 환경에 반드시 있어야 한다. ※ deploy.py 와 규약 일치(파리티)."""
|
||||
for r in requires:
|
||||
name = r.get("name")
|
||||
t = str(r.get("type") or "").upper()
|
||||
if not t:
|
||||
die(f"소스 '{name}' 에 type 이 없습니다(환경 '{env}') — base.requires 또는 "
|
||||
f"environments.{env}.requires 에 type 을 지정하세요. 환경 오버레이는 base 하고만 "
|
||||
f"병합되고 dev↔val↔prod 끼리 상속하지 않습니다(공유 정체는 base 에 두는 걸 권장).")
|
||||
if t not in SUPPORTED:
|
||||
die(f"소스 '{name}' 의 type '{r.get('type')}' 은 미지원(환경 '{env}') — "
|
||||
f"{', '.join(sorted(s.lower() for s in SUPPORTED))} 중 하나여야 합니다.")
|
||||
|
||||
|
||||
def resolve_env(m: dict, env: str):
|
||||
ec = (m.get("environments") or {}).get(env) or {}
|
||||
by, order = {}, []
|
||||
for r in (m.get("requires") or []):
|
||||
by[r["name"]] = dict(r); order.append(r["name"])
|
||||
nm = r.get("name")
|
||||
if not nm:
|
||||
die("requires 항목에 name 이 없습니다(base.requires) — 각 소스는 논리 이름 name 이 필수입니다.")
|
||||
by[nm] = dict(r); order.append(nm)
|
||||
for r in (ec.get("requires") or []):
|
||||
if r["name"] in by:
|
||||
by[r["name"]].update(r)
|
||||
nm = r.get("name")
|
||||
if not nm:
|
||||
die(f"requires 항목에 name 이 없습니다(environments.{env}.requires) — name 은 필수입니다.")
|
||||
if nm in by:
|
||||
by[nm].update(r)
|
||||
else:
|
||||
by[r["name"]] = dict(r); order.append(r["name"])
|
||||
by[nm] = dict(r); order.append(nm)
|
||||
requires = [by[n] for n in order]
|
||||
_validate_requires(requires, env) # 병합 후 사전검증(type 누락·미지원 등)
|
||||
wov = ec.get("workloads") or {}
|
||||
workloads = []
|
||||
for w in (m.get("workloads") or []):
|
||||
@ -688,9 +717,10 @@ def cmd_deploy(m: dict) -> None:
|
||||
reqs, wls = resolve_env(m, "dev") # base + environments.dev 오버레이(로컬은 항상 dev 환경)
|
||||
|
||||
# 충돌 가드 — 서로 다른 소스명/워크로드명이 같은 서비스명으로 뭉개지면 자격 오배선 → 거부.
|
||||
# 서비스명은 '실제 소스 이름'(clusterName 우선)으로 만들어지므로 그 기준으로 검사.
|
||||
svc_owner: dict[str, str] = {}
|
||||
for r in reqs:
|
||||
s = sanitize(r["name"])
|
||||
s = sanitize(r.get("clusterName") or r["name"])
|
||||
if s in svc_owner and svc_owner[s] != r["name"]:
|
||||
die(f"소스명 충돌 — '{svc_owner[s]}' 와 '{r['name']}' 가 같은 서비스명 '{s}' 로 뭉개집니다. 이름을 구분하세요.")
|
||||
svc_owner[s] = r["name"]
|
||||
@ -709,7 +739,8 @@ def cmd_deploy(m: dict) -> None:
|
||||
results = [deploy_workload(project, ns, w, src_conns) for w in wls]
|
||||
|
||||
# 매니페스트에서 빠진 소스/워크로드 정리(고아 방지 — add-only 가 아니라 리컨실).
|
||||
_prune(ns, "source", "yakcloud.dev/source", {sanitize(r["name"]) for r in reqs})
|
||||
# 소스 라벨(yakcloud.dev/source)=실제 소스명(clusterName 우선)의 sanitize → desired 도 그 기준.
|
||||
_prune(ns, "source", "yakcloud.dev/source", {sanitize(r.get("clusterName") or r["name"]) for r in reqs})
|
||||
_prune(ns, "workload", "app", {sanitize(w["name"]) for w in wls})
|
||||
|
||||
_report_to_console(project, reqs, wls) # 콘솔 LOCAL 클러스터 탭(데이터소스·앱) 리포트
|
||||
|
||||
Reference in New Issue
Block a user