fix(dev): dev down 안전화 + dev ls 신설(엉뚱한 삭제 방지) — 0.21.3

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 18:09:14 +09:00
parent d6532ad988
commit 53881b85ae

View File

@ -16,7 +16,7 @@ set -uo pipefail # -e 미사용: 'test && action' 관용구가 값 없을 때
REPO="${YAKCLOUD_STARTER_REPO:-https://gitea.yakenator.io/yakenator/yakcloud-starter}" REPO="${YAKCLOUD_STARTER_REPO:-https://gitea.yakenator.io/yakenator/yakcloud-starter}"
BRANCH="${YAKCLOUD_STARTER_BRANCH:-main}" BRANCH="${YAKCLOUD_STARTER_BRANCH:-main}"
VERSION="0.21.2" VERSION="0.21.3"
# 로컬 개발 클러스터(kind) — 개발 후 운영까지 '직선 배포'의 dev 구간. # 로컬 개발 클러스터(kind) — 개발 후 운영까지 '직선 배포'의 dev 구간.
DEV_CTX="${YAK_DEV_CONTEXT:-kind-yak-dev}" DEV_CTX="${YAK_DEV_CONTEXT:-kind-yak-dev}"
DEV_CLUSTER="${YAK_DEV_CLUSTER:-yak-dev}" DEV_CLUSTER="${YAK_DEV_CLUSTER:-yak-dev}"
@ -568,42 +568,46 @@ _dev_deregister() {
return 0 return 0
} }
cmd_dev() { # 로컬 dev 클러스터 목록 — kind 이름 / --name / 콘솔 등록명 매핑. 파괴적 명령(down) 전 대상 확인용.
local sub="${1:-}"; [ "$#" -gt 0 ] && shift # 콘솔에 보이는 '등록명'(예 imac192)이 아니라 여기 '--name'(예 dev) 으로 지정해야 한다.
# --name 미지정 + 프로젝트 폴더면 매니페스트 environments.dev 값을 강제 주입(입력값이 클러스터·등록명에 반영). cmd_dev_ls() {
# → 'yakcloud dev up/deploy' 를 이름 없이 실행해도 항상 environments.dev.cluster 로 뜬다(호스트명만 등록되던 문제 차단).
case " $* " in
*" --name"*|*" --cluster"*) : ;; # 명시 지정이 있으면 존중
*)
if [ -f yakcloud.yaml ]; then
local _mn _mp; _mn="$(_manifest_env dev cluster 2>/dev/null)"; _mp="$(_manifest_env dev port 2>/dev/null)"
if [ -n "$_mn" ]; then
local _inj=(--name "$_mn")
{ [ -n "$_mp" ] && [ "$_mp" != "80" ]; } && _inj+=(--port "$_mp")
set -- "${_inj[@]}" "$@"
fi
fi
;;
esac
_dev_resolve "$@" # --name/--port 파싱 → DEV_NAME/DEV_CLUSTER/DEV_CTX/DEV_HTTP_PORT/DEV_ARGS
_podman_auth_fix "$(_dev_runtime)" # podman + Docker Desktop credHelpers(https://) 충돌 자동 우회(공개 이미지)
case "$sub" in
up) cmd_dev_up ;;
deploy|status|clean)
[ -f yakcloud.yaml ] || die "프로젝트 폴더가 아닙니다 — 'yakcloud project init' 먼저"
ensure_pyyaml
local rt tag=""; rt="$(_dev_runtime)"
for a in "${DEV_ARGS[@]+"${DEV_ARGS[@]}"}"; do case "$a" in -*) : ;; *) [ -z "$tag" ] && tag="$a" ;; esac; done
local pysub="$sub"; [ "$sub" = clean ] && pysub="down"
YAK_DEV_CONTEXT="$DEV_CTX" YAK_DEV_CLUSTER="$DEV_CLUSTER" YAK_DEV_NAME="$DEV_NAME" \
YAK_DEV_RUNTIME="${rt:-docker}" YAK_DEV_HTTP_PORT="$DEV_HTTP_PORT" \
YAK_REG_NAME="$(_local_reg_name)" TAG="${tag:-${TAG:-dev}}" python3 "$(_engine dev)" "$pysub" yakcloud.yaml
[ "$sub" = deploy ] && _dev_register || true
;;
down)
command -v kind >/dev/null 2>&1 || die "kind 필요" command -v kind >/dev/null 2>&1 || die "kind 필요"
local rt kenv=""; rt="$(_dev_runtime)"; [ "$rt" = podman ] && kenv="KIND_EXPERIMENTAL_PROVIDER=podman" local rt kenv=""; rt="$(_dev_runtime)"; [ "$rt" = podman ] && kenv="KIND_EXPERIMENTAL_PROVIDER=podman"
info "kind 클러스터 '$DEV_CLUSTER' 삭제…" local cs; cs="$(env $kenv kind get clusters 2>/dev/null | grep '^yak-' || true)"
[ -z "$cs" ] && { echo "로컬 dev 클러스터 없음 — 'yakcloud dev up' 으로 생성."; return 0; }
printf " %-18s %-14s %s\n" "kind 클러스터" "--name" "콘솔 등록명"
local c nm
while IFS= read -r c; do
[ -z "$c" ] && continue
nm="${c#yak-}"; DEV_NAME="$nm"
printf " %-18s %-14s %s\n" "$c" "$nm" "$(_local_reg_name)"
done <<EOF
$cs
EOF
}
# 로컬 dev 클러스터 삭제 — 파괴적. 대상을 안전 해석: --name 없으면 0/1/다수 판정, 없는 클러스터면 가짜 삭제 금지.
cmd_dev_down() {
local name_given="$1"
command -v kind >/dev/null 2>&1 || die "kind 필요"
local rt kenv=""; rt="$(_dev_runtime)"; [ "$rt" = podman ] && kenv="KIND_EXPERIMENTAL_PROVIDER=podman"
if [ "$name_given" = 0 ]; then
# --name 미지정: 정확히 하나면 그것, 여럿이면 지정 요구(엉뚱한 삭제 방지). 매니페스트 자동선택 안 함(파괴적).
local all n; all="$(env $kenv kind get clusters 2>/dev/null | grep '^yak-' || true)"
n="$(printf '%s\n' "$all" | grep -c . 2>/dev/null || echo 0)"
if [ "$n" -eq 0 ]; then die "로컬 dev 클러스터가 없습니다 — 'yakcloud dev ls'."; fi
if [ "$n" -gt 1 ]; then
echo "로컬 클러스터가 여러 개입니다 — 삭제할 것을 --name 으로 지정하세요:"; echo; cmd_dev_ls
die "예: yakcloud dev down --name <이름>"
fi
DEV_CLUSTER="$(printf '%s' "$all" | head -1)"; DEV_NAME="${DEV_CLUSTER#yak-}"; DEV_CTX="kind-$DEV_CLUSTER"
fi
# 존재 확인 — 없으면 가짜 삭제/해제 금지(오타·'등록명 vs --name' 혼동 방지).
if ! env $kenv kind get clusters 2>/dev/null | grep -qx "$DEV_CLUSTER"; then
echo "로컬 클러스터 '$DEV_CLUSTER' (--name $DEV_NAME) 이(가) 없습니다."; echo; cmd_dev_ls
die "콘솔 '등록명'(예 imac192)이 아니라 위 '--name' 값으로 지정하세요."
fi
info "kind 클러스터 '$DEV_CLUSTER' 삭제 (콘솔 등록명 '$(_local_reg_name)')…"
env $kenv kind delete cluster --name "$DEV_CLUSTER" && echo "✓ 삭제 완료" env $kenv kind delete cluster --name "$DEV_CLUSTER" && echo "✓ 삭제 완료"
# 이 클러스터의 릴레이 에이전트만 정리(pidfile <name>.pid, 클러스터 단위). 다른 dev 클러스터는 건드리지 않음. # 이 클러스터의 릴레이 에이전트만 정리(pidfile <name>.pid, 클러스터 단위). 다른 dev 클러스터는 건드리지 않음.
local agd="${YAK_CONFIG_DIR:-$HOME/.config/yakcloud}/agent" local agd="${YAK_CONFIG_DIR:-$HOME/.config/yakcloud}/agent"
@ -620,7 +624,6 @@ cmd_dev() {
else else
local scs; scs="$("${rt:-docker}" ps -aq -f label=yakcloud.dev/shared=1 2>/dev/null)" local scs; scs="$("${rt:-docker}" ps -aq -f label=yakcloud.dev/shared=1 2>/dev/null)"
[ -n "$scs" ] && { info "공유 소스 컨테이너 정리(마지막 dev 클러스터)…"; echo "$scs" | xargs "${rt:-docker}" rm -f >/dev/null 2>&1; } [ -n "$scs" ] && { info "공유 소스 컨테이너 정리(마지막 dev 클러스터)…"; echo "$scs" | xargs "${rt:-docker}" rm -f >/dev/null 2>&1; }
# 마지막 클러스터 → 남은 모든 에이전트 pidfile 정리(구 <ns>.pid 명명 포함 — 전부 고아).
if [ -d "$agd" ]; then if [ -d "$agd" ]; then
for pf in "$agd"/*.pid; do for pf in "$agd"/*.pid; do
[ -f "$pf" ] || continue [ -f "$pf" ] || continue
@ -630,8 +633,44 @@ cmd_dev() {
fi fi
fi fi
_dev_deregister _dev_deregister
}
cmd_dev() {
local sub="${1:-}"; [ "$#" -gt 0 ] && shift
local name_given=0
case " $* " in *" --name"*|*" --cluster"*) name_given=1 ;; esac # 명시 지정 여부
# 프로젝트 폴더 + --name 미지정이면 매니페스트 environments.dev 값을 자동 주입 — 단, **비파괴적 프로젝트 명령만**
# (up/deploy/status/clean). down(클러스터 삭제)은 자동 선택 안 함 → 엉뚱한 삭제 방지.
case "$sub" in
up|deploy|status|clean)
if [ "$name_given" = 0 ] && [ -f yakcloud.yaml ]; then
local _mn _mp; _mn="$(_manifest_env dev cluster 2>/dev/null)"; _mp="$(_manifest_env dev port 2>/dev/null)"
if [ -n "$_mn" ]; then
local _inj=(--name "$_mn")
{ [ -n "$_mp" ] && [ "$_mp" != "80" ]; } && _inj+=(--port "$_mp")
set -- "${_inj[@]}" "$@"; name_given=1
fi
fi
;; ;;
*) echo "yakcloud dev <up [--name N --port P] | deploy [--name N] [tag] | status [--name N] | clean | down [--name N]>"; exit 1 ;; esac
_dev_resolve "$@" # --name/--port 파싱 → DEV_NAME/DEV_CLUSTER/DEV_CTX/DEV_HTTP_PORT/DEV_ARGS
_podman_auth_fix "$(_dev_runtime)" # podman + Docker Desktop credHelpers(https://) 충돌 자동 우회(공개 이미지)
case "$sub" in
up) cmd_dev_up ;;
ls|list) cmd_dev_ls ;;
deploy|status|clean)
[ -f yakcloud.yaml ] || die "프로젝트 폴더가 아닙니다 — 'yakcloud project init' 먼저"
ensure_pyyaml
local rt tag=""; rt="$(_dev_runtime)"
for a in "${DEV_ARGS[@]+"${DEV_ARGS[@]}"}"; do case "$a" in -*) : ;; *) [ -z "$tag" ] && tag="$a" ;; esac; done
local pysub="$sub"; [ "$sub" = clean ] && pysub="down"
YAK_DEV_CONTEXT="$DEV_CTX" YAK_DEV_CLUSTER="$DEV_CLUSTER" YAK_DEV_NAME="$DEV_NAME" \
YAK_DEV_RUNTIME="${rt:-docker}" YAK_DEV_HTTP_PORT="$DEV_HTTP_PORT" \
YAK_REG_NAME="$(_local_reg_name)" TAG="${tag:-${TAG:-dev}}" python3 "$(_engine dev)" "$pysub" yakcloud.yaml
[ "$sub" = deploy ] && _dev_register || true
;;
down) cmd_dev_down "$name_given" ;;
*) echo "yakcloud dev <up | deploy [tag] | status | clean | down | ls> [--name N --port P]"; exit 1 ;;
esac esac
} }
@ -699,8 +738,9 @@ yakcloud $VERSION — YakCloud 프로젝트 CLI (개발·검증·운영 모두
── 로컬 개발 클러스터(kind) — 개발 후 운영까지 직선 배포의 dev 구간 ── ── 로컬 개발 클러스터(kind) — 개발 후 운영까지 직선 배포의 dev 구간 ──
dev up 로컬 kind 생성 + 인그레스(80/443) — 최초 1회 dev up 로컬 kind 생성 + 인그레스(80/443) — 최초 1회
dev deploy [tag] 매니페스트대로 로컬 리컨실(이미지 빌드+load, 소스/워크로드/인그레스; 콘솔 API 불필요) dev deploy [tag] 매니페스트대로 로컬 리컨실(이미지 빌드+load, 소스/워크로드/인그레스; 콘솔 API 불필요)
dev ls 로컬 클러스터 목록(kind 이름 / --name / 콘솔 등록명) — 삭제 전 대상 확인
dev status | dev clean 로컬 배포 상태 / 이 프로젝트 리소스 삭제 dev status | dev clean 로컬 배포 상태 / 이 프로젝트 리소스 삭제
dev down 로컬 kind 클러스터 삭제 dev down [--name N] 로컬 kind 클러스터 삭제(여럿이면 --name 필수). 대상은 콘솔 등록명이 아니라 'dev ls' 의 --name
project init [name] 빈 폴더에 스타터 스캐폴딩(대화형 마법사) + 자격 프롬프트 project init [name] 빈 폴더에 스타터 스캐폴딩(대화형 마법사) + 자격 프롬프트
project deploy [tag] **개발 배포 = 로컬 kind**(environments.dev). 로컬 클러스터 없으면 자동 생성 후 배포(오프라인·무료·CI 불필요) project deploy [tag] **개발 배포 = 로컬 kind**(environments.dev). 로컬 클러스터 없으면 자동 생성 후 배포(오프라인·무료·CI 불필요)
project promote [vX.Y.Z] --to val|prod **관리형** 검증/운영으로 승격 — 이미지 build+push(이미 있으면 재빌드 없이) 후 리컨실. 기본 --to prod project promote [vX.Y.Z] --to val|prod **관리형** 검증/운영으로 승격 — 이미지 build+push(이미 있으면 재빌드 없이) 후 리컨실. 기본 --to prod