diff --git a/bin/yakcloud b/bin/yakcloud index ff9419d..d8ab664 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.21.2" +VERSION="0.21.3" # 로컬 개발 클러스터(kind) — 개발 후 운영까지 '직선 배포'의 dev 구간. DEV_CTX="${YAK_DEV_CONTEXT:-kind-yak-dev}" DEV_CLUSTER="${YAK_DEV_CLUSTER:-yak-dev}" @@ -568,19 +568,87 @@ _dev_deregister() { 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 </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 .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() { local sub="${1:-}"; [ "$#" -gt 0 ] && shift - # --name 미지정 + 프로젝트 폴더면 매니페스트 environments.dev 값을 강제 주입(입력값이 클러스터·등록명에 반영). - # → 'yakcloud dev up/deploy' 를 이름 없이 실행해도 항상 environments.dev.cluster 로 뜬다(호스트명만 등록되던 문제 차단). - case " $* " in - *" --name"*|*" --cluster"*) : ;; # 명시 지정이 있으면 존중 - *) - if [ -f yakcloud.yaml ]; then + 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[@]}" "$@" + set -- "${_inj[@]}" "$@"; name_given=1 fi fi ;; @@ -589,6 +657,7 @@ cmd_dev() { _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 @@ -600,38 +669,8 @@ cmd_dev() { 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 필요" - 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 .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 정리(구 .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 "; exit 1 ;; + down) cmd_dev_down "$name_given" ;; + *) echo "yakcloud dev [--name N --port P]"; exit 1 ;; esac } @@ -699,8 +738,9 @@ yakcloud $VERSION — YakCloud 프로젝트 CLI (개발·검증·운영 모두 ── 로컬 개발 클러스터(kind) — 개발 후 운영까지 직선 배포의 dev 구간 ── dev up 로컬 kind 생성 + 인그레스(80/443) — 최초 1회 dev deploy [tag] 매니페스트대로 로컬 리컨실(이미지 빌드+load, 소스/워크로드/인그레스; 콘솔 API 불필요) + dev ls 로컬 클러스터 목록(kind 이름 / --name / 콘솔 등록명) — 삭제 전 대상 확인 dev status | dev clean 로컬 배포 상태 / 이 프로젝트 리소스 삭제 - dev down 로컬 kind 클러스터 삭제 + dev down [--name N] 로컬 kind 클러스터 삭제(여럿이면 --name 필수). 대상은 콘솔 등록명이 아니라 'dev ls' 의 --name project init [name] 빈 폴더에 스타터 스캐폴딩(대화형 마법사) + 자격 프롬프트 project deploy [tag] **개발 배포 = 로컬 kind**(environments.dev). 로컬 클러스터 없으면 자동 생성 후 배포(오프라인·무료·CI 불필요) project promote [vX.Y.Z] --to val|prod **관리형** 검증/운영으로 승격 — 이미지 build+push(이미 있으면 재빌드 없이) 후 리컨실. 기본 --to prod