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,19 +568,87 @@ _dev_deregister() {
return 0 return 0
} }
# 로컬 dev 클러스터 목록 — kind 이름 / --name / 콘솔 등록명 매핑. 파괴적 명령(down) 전 대상 확인용.
# 콘솔에 보이는 '등록명'(예 imac192)이 아니라 여기 '--name'(예 dev) 으로 지정해야 한다.
cmd_dev_ls() {
command -v kind >/dev/null 2>&1 || die "kind 필요"
local rt kenv=""; rt="$(_dev_runtime)"; [ "$rt" = podman ] && kenv="KIND_EXPERIMENTAL_PROVIDER=podman"
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 "✓ 삭제 완료"
# 이 클러스터의 릴레이 에이전트만 정리(pidfile <name>.pid, 클러스터 단위). 다른 dev 클러스터는 건드리지 않음.
local agd="${YAK_CONFIG_DIR:-$HOME/.config/yakcloud}/agent"
local apf="$agd/$DEV_NAME.pid"
if [ -f "$apf" ]; then
local apid; apid="$(cat "$apf" 2>/dev/null)"
[ -n "$apid" ] && kill "$apid" 2>/dev/null
rm -f "$apf"
info "릴레이 에이전트 정리($DEV_NAME)"
fi
# 공유 소스 컨테이너(yak-dev-*)는 여러 클러스터가 공유 → '마지막' dev 클러스터 삭제 시에만 정리.
if env $kenv kind get clusters 2>/dev/null | grep -q '^yak-'; then
info "다른 로컬 dev 클러스터가 남아 공유 소스 컨테이너는 유지"
else
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; }
if [ -d "$agd" ]; then
for pf in "$agd"/*.pid; do
[ -f "$pf" ] || continue
local lp; lp="$(cat "$pf" 2>/dev/null)"; [ -n "$lp" ] && kill "$lp" 2>/dev/null
rm -f "$pf"
done
fi
fi
_dev_deregister
}
cmd_dev() { cmd_dev() {
local sub="${1:-}"; [ "$#" -gt 0 ] && shift local sub="${1:-}"; [ "$#" -gt 0 ] && shift
# --name 미지정 + 프로젝트 폴더면 매니페스트 environments.dev 값을 강제 주입(입력값이 클러스터·등록명에 반영). local name_given=0
# → 'yakcloud dev up/deploy' 를 이름 없이 실행해도 항상 environments.dev.cluster 로 뜬다(호스트명만 등록되던 문제 차단). case " $* " in *" --name"*|*" --cluster"*) name_given=1 ;; esac # 명시 지정 여부
case " $* " in # 프로젝트 폴더 + --name 미지정이면 매니페스트 environments.dev 값을 자동 주입 — 단, **비파괴적 프로젝트 명령만**
*" --name"*|*" --cluster"*) : ;; # 명시 지정이 있으면 존중 # (up/deploy/status/clean). down(클러스터 삭제)은 자동 선택 안 함 → 엉뚱한 삭제 방지.
*) case "$sub" in
if [ -f yakcloud.yaml ]; then 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)" local _mn _mp; _mn="$(_manifest_env dev cluster 2>/dev/null)"; _mp="$(_manifest_env dev port 2>/dev/null)"
if [ -n "$_mn" ]; then if [ -n "$_mn" ]; then
local _inj=(--name "$_mn") local _inj=(--name "$_mn")
{ [ -n "$_mp" ] && [ "$_mp" != "80" ]; } && _inj+=(--port "$_mp") { [ -n "$_mp" ] && [ "$_mp" != "80" ]; } && _inj+=(--port "$_mp")
set -- "${_inj[@]}" "$@" set -- "${_inj[@]}" "$@"; name_given=1
fi fi
fi fi
;; ;;
@ -589,6 +657,7 @@ cmd_dev() {
_podman_auth_fix "$(_dev_runtime)" # podman + Docker Desktop credHelpers(https://) 충돌 자동 우회(공개 이미지) _podman_auth_fix "$(_dev_runtime)" # podman + Docker Desktop credHelpers(https://) 충돌 자동 우회(공개 이미지)
case "$sub" in case "$sub" in
up) cmd_dev_up ;; up) cmd_dev_up ;;
ls|list) cmd_dev_ls ;;
deploy|status|clean) deploy|status|clean)
[ -f yakcloud.yaml ] || die "프로젝트 폴더가 아닙니다 — 'yakcloud project init' 먼저" [ -f yakcloud.yaml ] || die "프로젝트 폴더가 아닙니다 — 'yakcloud project init' 먼저"
ensure_pyyaml ensure_pyyaml
@ -600,38 +669,8 @@ cmd_dev() {
YAK_REG_NAME="$(_local_reg_name)" TAG="${tag:-${TAG:-dev}}" python3 "$(_engine dev)" "$pysub" yakcloud.yaml YAK_REG_NAME="$(_local_reg_name)" TAG="${tag:-${TAG:-dev}}" python3 "$(_engine dev)" "$pysub" yakcloud.yaml
[ "$sub" = deploy ] && _dev_register || true [ "$sub" = deploy ] && _dev_register || true
;; ;;
down) down) cmd_dev_down "$name_given" ;;
command -v kind >/dev/null 2>&1 || die "kind 필요" *) echo "yakcloud dev <up | deploy [tag] | status | clean | down | ls> [--name N --port P]"; exit 1 ;;
local rt kenv=""; rt="$(_dev_runtime)"; [ "$rt" = podman ] && kenv="KIND_EXPERIMENTAL_PROVIDER=podman"
info "kind 클러스터 '$DEV_CLUSTER' 삭제…"
env $kenv kind delete cluster --name "$DEV_CLUSTER" && echo "✓ 삭제 완료"
# 이 클러스터의 릴레이 에이전트만 정리(pidfile <name>.pid, 클러스터 단위). 다른 dev 클러스터는 건드리지 않음.
local agd="${YAK_CONFIG_DIR:-$HOME/.config/yakcloud}/agent"
local apf="$agd/$DEV_NAME.pid"
if [ -f "$apf" ]; then
local apid; apid="$(cat "$apf" 2>/dev/null)"
[ -n "$apid" ] && kill "$apid" 2>/dev/null
rm -f "$apf"
info "릴레이 에이전트 정리($DEV_NAME)"
fi
# 공유 소스 컨테이너(yak-dev-*)는 여러 클러스터가 공유 → '마지막' dev 클러스터 삭제 시에만 정리.
if env $kenv kind get clusters 2>/dev/null | grep -q '^yak-'; then
info "다른 로컬 dev 클러스터가 남아 공유 소스 컨테이너는 유지"
else
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; }
# 마지막 클러스터 → 남은 모든 에이전트 pidfile 정리(구 <ns>.pid 명명 포함 — 전부 고아).
if [ -d "$agd" ]; then
for pf in "$agd"/*.pid; do
[ -f "$pf" ] || continue
local lp; lp="$(cat "$pf" 2>/dev/null)"; [ -n "$lp" ] && kill "$lp" 2>/dev/null
rm -f "$pf"
done
fi
fi
_dev_deregister
;;
*) echo "yakcloud dev <up [--name N --port P] | deploy [--name N] [tag] | status [--name N] | clean | down [--name N]>"; 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