From 68bedebb1ce4e547e3c4d14134ca2f156ddb70f9 Mon Sep 17 00:00:00 2001 From: jungwoo choi Date: Mon, 31 Aug 2026 17:39:12 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20yakcloud=20domain=20check=20+=20?= =?UTF-8?q?=EC=8A=B9=EA=B2=A9/init=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EC=82=AC=EC=A0=84=EA=B2=80=EC=82=AC=20(v0.30.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- bin/yakcloud | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/bin/yakcloud b/bin/yakcloud index 870d767..f45000f 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.29.0" +VERSION="0.30.0" # 로컬 개발 클러스터(kind) — 개발 후 운영까지 '직선 배포'의 dev 구간. DEV_CTX="${YAK_DEV_CONTEXT:-kind-yak-dev}" DEV_CLUSTER="${YAK_DEV_CLUSTER:-yak-dev}" @@ -263,6 +263,14 @@ _init_wizard() { printf "검증 클러스터(관리형, 선택, 엔터=생략): "; read -r valc printf "운영 클러스터(관리형, 선택, 엔터=생략): "; read -r prodc printf "운영 도메인(선택, 예 app.example.com): "; read -r pdom + # 도메인 사전검사(로그인돼 있으면) — 예약·중복이면 지금 경고(빌드·배포 다 하고 나서 발견 방지). + if [ -n "$pdom" ]; then + local _dc; _dc="$(_domain_check "$pdom")" + case "$_dc" in + X*) echo " ⚠ '$pdom' 지금은 사용 불가 — ${_dc#X$'\t'}. 다른 도메인을 쓰거나 배포 전 'yakcloud domain check' 로 확인하세요." ;; + OK) echo " ✓ '$pdom' 사용 가능" ;; + esac + fi printf "컨테이너 포트 [8080]: "; read -r port; port="${port:-8080}" printf "헬스체크 경로 [/healthz]: "; read -r health; health="${health:-/healthz}" printf "CPU 요청 [25m]: "; read -r cpu; cpu="${cpu:-25m}" @@ -310,6 +318,56 @@ if v is not None: print(v) PY } +# 도메인 사용 가능 사전검사(부작용 없음) — 등록과 같은 규칙(형식·존·예약·쿼터·중복). +# stdout: "OK" | "X사유". 미로그인/네트워크 실패면 빈 출력(스킵 — 블록하지 않음). +_domain_check() { + local fqdn="$1" out + [ -n "${YAKCLOUD_URL:-}" ] && [ -n "${YAKCLOUD_TOKEN:-}" ] || return 0 + out="$(curl -fsS -H "Authorization: Bearer $YAKCLOUD_TOKEN" --max-time 15 "$YAKCLOUD_URL/api/v1/domains/check?fqdn=$fqdn" 2>/dev/null)" || return 0 + printf '%s' "$out" | python3 -c "import sys,json;d=json.load(sys.stdin);d=d.get('data',d);print('OK' if d.get('available') else 'X\t'+(d.get('reason') or '사용 불가'))" 2>/dev/null +} + +# 매니페스트 environments..domains 목록(줄바꿈 구분). +_env_domains() { + ensure_pyyaml >/dev/null 2>&1 || true + python3 - "$1" <<'PY' 2>/dev/null +import sys, yaml +try: m = yaml.safe_load(open("yakcloud.yaml")) or {} +except Exception: sys.exit(0) +e = ((m.get("environments") or {}).get(sys.argv[1]) or {}) +for d in (e.get("domains") or []): + if d: print(d) +PY +} + +# 환경 도메인 전부 사전검사. 사용 불가가 하나라도 있으면 1(비어있거나 전부 OK/스킵이면 0). +_precheck_env_domains() { + local bad=0 d r + while IFS= read -r d; do + [ -n "$d" ] || continue + r="$(_domain_check "$d")" + case "$r" in + X*) echo " ✗ 도메인 '$d' 사용 불가 — ${r#X$'\t'}"; bad=1 ;; + OK) echo " ✓ 도메인 '$d' 사용 가능" ;; + esac + done < — 단독 사전검사(등록 안 함). +cmd_domain_check() { + local fqdn="$1"; [ -n "$fqdn" ] || die "사용법: yakcloud domain check " + : "${YAKCLOUD_URL:?YAKCLOUD_URL 필요 — 'yakcloud login'}"; : "${YAKCLOUD_TOKEN:?YAKCLOUD_TOKEN 필요 — 'yakcloud login'}" + local r; r="$(_domain_check "$fqdn")" + case "$r" in + OK) echo "✓ '$fqdn' 사용 가능 — 등록/노출할 수 있습니다." ;; + X*) printf "✗ '%s' 사용 불가 — %s\n" "$fqdn" "${r#X$'\t'}"; return 1 ;; + *) die "확인 실패 — 네트워크/로그인 상태 확인('yakcloud login')" ;; + esac +} + # 로컬 kind 클러스터(yak-) 존재 여부. _dev_cluster_exists() { local n="$1" rt kenv=""; rt="$(_dev_runtime)"; [ "$rt" = podman ] && kenv="KIND_EXPERIMENTAL_PROVIDER=podman" @@ -385,6 +443,8 @@ cmd_project_check() { return fi : "${YAKCLOUD_URL:?YAKCLOUD_URL 필요}"; : "${YAKCLOUD_TOKEN:?YAKCLOUD_TOKEN 필요}" + echo "도메인 사전검사 ($env):" + _precheck_env_domains "$env" || echo " ⚠ 위 도메인은 지금 상태로 승격 시 실패합니다 — 배포 전에 해결하세요." YAKCLOUD_ENV="$env" python3 "$(_engine deploy)" yakcloud.yaml --dry-run } @@ -466,6 +526,10 @@ cmd_project_promote() { [ -n "$tag" ] || tag="v0.1.0" fi case "$tag" in v*) : ;; *) tag="v$tag" ;; esac + # 도메인 사전검사 — 예약·중복 등으로 못 쓰는 도메인이면 build/push 前에 빠르게 실패(kanban 사태 방지). + if ! _precheck_env_domains "$env"; then + die "도메인 사전검사 실패 — 위 도메인을 해결(예약 해제·다른 이름·소유자 확인)한 뒤 다시 승격하세요. 이미지 빌드/푸시 안 함." + fi # 운영 승격 게이트 — prod 는 개발계 검증 확정(그 버전)이 선행돼야 한다(val 은 자유). [ "$env" = prod ] && _require_dev_confirm "$tag" info "'$env' 승격: $tag → 관리형 '$env' 클러스터(이미지 build+push 후 리컨실; 이미 있으면 재빌드 없이 그 이미지)" @@ -910,6 +974,7 @@ yakcloud $VERSION — YakCloud 프로젝트 CLI (개발·검증·운영 모두 config 저장된 설정 표시(비밀 마스킹) logout 저장 자격 삭제 ── 배포환경 설정(앱별) ── + domain check 도메인 사용 가능 사전검사(등록 안 함) — 예약·중복·형식 확인. 도메인 정하기 前 권장 domain [wl] 도메인 등록 + 워크로드 할당(관리형=즉시/배포중이면 라이브) scale replicas 변경 set --image/--port/--health/--cpu/--mem/--path/--rewrite @@ -942,7 +1007,9 @@ case "${1:-help}" in config) cmd_config ;; logout) cmd_logout ;; datasource|ds) shift; ctl source "$@" ;; - domain|scale|set|env|source|bind|unbind) ctl "$@" ;; + domain) + if [ "${2:-}" = check ]; then shift 2; cmd_domain_check "${1:-}"; else ctl "$@"; fi ;; + scale|set|env|source|bind|unbind) ctl "$@" ;; upgrade|self-update) cmd_upgrade ;; version|-v|--version) echo "yakcloud $VERSION" ;; help|-h|--help|"") usage ;;