From 9b66d3269576d3276de300d82a2231c85533ba58 Mon Sep 17 00:00:00 2001 From: jungwoo choi Date: Mon, 31 Aug 2026 11:42:09 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=9A=B4=EC=98=81=20=EC=8A=B9=EA=B2=A9?= =?UTF-8?q?=20=EA=B2=8C=EC=9D=B4=ED=8A=B8=20=E2=80=94=20project=20confirm(?= =?UTF-8?q?dev=20=EA=B2=80=EC=A6=9D=20=EC=82=AC=EC=9D=B8=EC=98=A4=ED=94=84?= =?UTF-8?q?)=20=EC=97=86=EC=9C=BC=EB=A9=B4=20prod=20=EC=B0=A8=EB=8B=A8=20(?= =?UTF-8?q?v0.27.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - project confirm [vX.Y.Z]: 개발계 검증 사인오프를 .yakcloud.confirm 에 기록(로컬·git 미포함) - promote --to prod: 확정 버전 일치 시에만 통과(val 자유). 불일치/없음 차단 - 스캐폴드: CLAUDE.md 운영 게이트 규칙 + 에이전트 임의 confirm 금지, .gitignore 에 .yakcloud.confirm - SKILL.md: confirm 실행 전 확인 + 운영 게이트 경고 Co-Authored-By: Claude Opus 4.8 --- .claude/skills/yakcloud-deploy/SKILL.md | 6 ++- bin/yakcloud | 69 +++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/.claude/skills/yakcloud-deploy/SKILL.md b/.claude/skills/yakcloud-deploy/SKILL.md index ab48748..2c9d2e1 100644 --- a/.claude/skills/yakcloud-deploy/SKILL.md +++ b/.claude/skills/yakcloud-deploy/SKILL.md @@ -22,10 +22,14 @@ Gitea Actions 가 이미지 빌드/푸시 + 콘솔 API 로 데이터소스 리 CLI 가 스스로 전제조건을 검사하고 오류를 안내하므로, 실패하면 그 출력을 그대로 전달하고 필요한 만큼만 후속 조치. **안전장치 — 아래 "되돌리기 어렵거나 외부로 나가는" 서브커맨드만 실행 전 1줄로 확인**하고 승인 시 실행: -`project deploy` · `project promote` · `domain` · `cluster create` · `dev down` · `source add` · `datasource migrate apply` +`project deploy` · `project confirm` · `project promote` · `domain` · `cluster create` · `dev down` · `source add` · `datasource migrate apply` (그 외 `project init/info/check` · `dev up/deploy/status` · `datasource ls`/`capture` · `cluster ls` · `config`/`login` 등 읽기·로컬·멱등 명령은 확인 없이 즉시 실행.) +**⚠ 운영 승격 게이트 — `project confirm` 은 사람이 dev 검증을 마쳤다는 사인오프다. 에이전트가 임의로 실행하지 마라.** +`project promote --to prod ` 는 그 버전의 `project confirm` 이 선행돼야 통과한다(val 은 자유). 사람의 명시적 검증 확인이 없으면 +confirm/prod-promote 를 실행하지 말고, "dev 에서 확인 후 confirm 하라"고 안내만 한다. + ## CLI (권장) 설치: `curl -fsSL https://gitea.yakenator.io/yakenator/yakcloud-starter/raw/branch/main/install.sh | sh` (전역 `/yakcloud` 슬래시 명령도 설치됨. `yakcloud upgrade` 로 최신화.) 자격 저장 = `yakcloud login`. diff --git a/bin/yakcloud b/bin/yakcloud index 16665ca..6b044b6 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.26.0" +VERSION="0.27.0" # 로컬 개발 클러스터(kind) — 개발 후 운영까지 '직선 배포'의 dev 구간. DEV_CTX="${YAK_DEV_CONTEXT:-kind-yak-dev}" DEV_CLUSTER="${YAK_DEV_CLUSTER:-yak-dev}" @@ -167,13 +167,16 @@ _scaffold_yakcloud_guardrails() { - **`docker compose` / `docker-compose` / `docker run` 으로 이 앱을 로컬 실행·검증하지 마라.** (`.claude/settings.json` 이 차단) - DB·캐시·큐 등 **데이터소스 컨테이너를 손으로 띄우지 마라** — `yakcloud.yaml` 의 `requires` 로 선언한다. - `Dockerfile` 은 **yakcloud 의 이미지 빌드 전용**. 로컬 구동 도구가 아니다. +- **사람의 명시적 dev 검증 확인 없이 `yakcloud project confirm` / `promote --to prod` 를 실행하지 마라.** 운영 승격은 사람이 개발계에서 검증한 뒤에만. ## 일상 개발 루프 (ALWAYS) 1. 코드를 고친다. 2. **`yakcloud project deploy`** — 로컬 kind 클러스터에 배포(없으면 자동 생성, 오프라인·무료). 관리형과 **동일한 바인딩 env·데이터소스** 로 뜬다. 3. 로컬 도메인 `*.localhost` 로 화면·API·WSS 확인. 반복 개발 = 2번 반복. -4. 확인되면 **`yakcloud project promote --to val|prod`** (같은 이미지, 재빌드 없이 관리형 승격). +4. (검증계 선택) `yakcloud project promote --to val`. +5. **운영 승격에는 게이트가 있다** — 사람이 dev 에서 검증한 뒤 `yakcloud project confirm ` 로 확정해야 + `yakcloud project promote --to prod ` 가 통과한다(확정은 그 버전에 묶임). 에이전트가 임의로 confirm 하지 말 것. ## 데이터소스 - `yakcloud.yaml` 의 `requires` 에 `{ name, type }` 선언 → 워크로드 `binds` 로 연결 → `_URL` 등 자동 주입. @@ -202,6 +205,9 @@ YCLAUDE } YSETTINGS fi + # dev 검증 확정 기록은 로컬·머신단위(git 미포함) — .gitignore 에 보장. + touch .gitignore + grep -qxF ".yakcloud.confirm" .gitignore 2>/dev/null || printf '\n# yakcloud dev 검증 확정(로컬 전용, 운영 승격 게이트)\n.yakcloud.confirm\n' >> .gitignore } cmd_project_init() { @@ -384,6 +390,57 @@ cmd_project_check() { # 승격 — 개발(로컬)에서 검증한 것을 **관리형** 검증/운영 클러스터로. 로컬 이미지는 레지스트리에 없으므로 # 태그 이미지를 build+push(이미 있으면 스킵=운영 재빌드 방지) 후 리컨실 + 도메인 부착. +# 운영 승격 게이트(Phase 1, CLI) — dev 검증 확정(.yakcloud.confirm)이 승격 버전과 일치해야 prod 통과. +# 확정은 정확히 그 버전(태그)에 묶인다 — 새 태그를 올리면 재검증·재확정 필요. (Phase 2: 백엔드 콘솔 휴먼 승인 추가) +_require_dev_confirm() { + local want="$1" have="" + [ -f .yakcloud.confirm ] && have="$(python3 -c 'import json;print((json.load(open(".yakcloud.confirm")) or {}).get("version",""))' 2>/dev/null || true)" + [ "$have" = "$want" ] && return 0 + die "운영(prod) 승격 차단 — '$want' 의 개발계 검증 확정이 없습니다(현재 확정: ${have:-<없음>}). + 1) 개발계 검증: yakcloud project deploy → 로컬(*.localhost)에서 확인 + 2) 검증 확정: yakcloud project confirm $want + 그다음에 다시: yakcloud project promote --to prod $want + (확정은 그 버전에 묶입니다. 코드가 바뀌어 새 태그를 올리면 재검증·재확정해야 합니다.)" +} + +# 개발계 검증 사인오프 — .yakcloud.confirm 에 '이 버전을 dev 에서 검증함'을 기록(로컬·머신단위, git 미포함). +# 운영 승격(promote --to prod)이 이 기록을 요구한다. 사람이 검증한 뒤 실행할 것(에이전트 임의 실행 금지 — CLAUDE.md). +cmd_project_confirm() { + [ -f yakcloud.yaml ] || die "'yakcloud project init' 먼저" + ensure_pyyaml + local tag="" yes=0 note="" + while [ "$#" -gt 0 ]; do case "$1" in + -y|--yes) yes=1; shift ;; + --note) note="${2:-}"; shift 2 || shift ;; + -*) shift ;; + *) [ -z "$tag" ] && tag="$1"; shift ;; + esac; done + if [ -z "$tag" ]; then + git fetch -q --tags origin 2>/dev/null || true + tag="$(git tag -l 'v*' --sort=-v:refname 2>/dev/null | head -1)" + [ -n "$tag" ] || die "확정할 버전을 지정하세요 — 'yakcloud project confirm vX.Y.Z' (project deploy 로 개발계에 올린 그 버전)" + fi + case "$tag" in v*) : ;; *) tag="v$tag" ;; esac + # 최소 증빙: 로컬 dev 클러스터가 있어야(= 개발계 배포·검증을 실제로 거쳤음). 없으면 확정 불가. + local devname; devname="$(_manifest_env dev cluster)"; [ -n "$devname" ] || devname="dev" + _dev_cluster_exists "$devname" || die "개발계('$devname') 로컬 클러스터가 없습니다 — 먼저 'yakcloud project deploy' 로 배포·검증 후 confirm." + if [ "$yes" != 1 ]; then + echo "개발계('$devname')에서 이 앱을 실제로 검증했습니까? 이 확정은 '$tag' 의 운영(prod) 승격을 허용합니다." + echo " (로컬 확인 호스트는 'yakcloud project info' 참고 — *.localhost)" + printf "검증 완료를 확정합니까? [y/N]: "; read -r a + case "$a" in y|Y|yes|YES) : ;; *) die "확정 취소 — 운영 승격은 계속 차단됩니다." ;; esac + fi + YC_TAG="$tag" YC_DEV="$devname" YC_NOTE="$note" python3 - > .yakcloud.confirm <<'PY' +import json,os,datetime,yaml +m=yaml.safe_load(open("yakcloud.yaml")) or {} +rec={"project":m.get("project",""),"version":os.environ["YC_TAG"],"cluster":os.environ["YC_DEV"], + "confirmedAt":datetime.datetime.now().astimezone().isoformat(timespec="seconds"), + "host":os.uname().nodename,"note":os.environ.get("YC_NOTE","")} +print(json.dumps(rec,ensure_ascii=False,indent=2)) +PY + echo "✓ 개발계 검증 확정: $tag. 이제 'yakcloud project promote --to prod $tag' 가능(그 버전 한정)." +} + cmd_project_promote() { [ -f yakcloud.yaml ] || die "'yakcloud project init' 먼저" : "${YAKCLOUD_URL:?YAKCLOUD_URL 필요 — 'yakcloud login'}"; : "${YAKCLOUD_TOKEN:?YAKCLOUD_TOKEN 필요 — 'yakcloud login'}" @@ -403,6 +460,8 @@ cmd_project_promote() { [ -n "$tag" ] || tag="v0.1.0" fi case "$tag" in v*) : ;; *) tag="v$tag" ;; esac + # 운영 승격 게이트 — prod 는 개발계 검증 확정(그 버전)이 선행돼야 한다(val 은 자유). + [ "$env" = prod ] && _require_dev_confirm "$tag" info "'$env' 승격: $tag → 관리형 '$env' 클러스터(이미지 build+push 후 리컨실; 이미 있으면 재빌드 없이 그 이미지)" _build_and_push "$tag" YAKCLOUD_ENV="$env" TAG="$tag" python3 "$(_engine deploy)" yakcloud.yaml @@ -832,7 +891,8 @@ yakcloud $VERSION — YakCloud 프로젝트 CLI (개발·검증·운영 모두 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 + project confirm [vX.Y.Z] 개발계 검증 확정(사인오프) — **운영 승격의 선행 게이트**. 그 버전 한정, 사람이 검증 후 실행 + project promote [vX.Y.Z] --to val|prod **관리형** 검증/운영으로 승격 — 이미지 build+push(이미 있으면 재빌드 없이) 후 리컨실. 기본 --to prod. **prod 는 project confirm 필요** project info 프로젝트 개괄(매니페스트 + 라이브 상태) project check [--env dev|val|prod] dry-run(직접 API 계획만) project update 이 프로젝트의 엔진·CI·스킬만 최신화(앱·매니페스트 보존) @@ -861,11 +921,12 @@ case "${1:-help}" in case "${1:-}" in init) shift; cmd_project_init "$@" ;; deploy) shift; cmd_project_deploy "$@" ;; + confirm) shift; cmd_project_confirm "$@" ;; promote) shift; cmd_project_promote "$@" ;; info) shift; ctl info "$@" ;; check|dry-run) shift; cmd_project_check "$@" ;; update) shift; cmd_project_update "$@" ;; - *) echo "yakcloud project "; exit 1 ;; + *) echo "yakcloud project "; exit 1 ;; esac ;; cluster) shift; cmd_cluster "$@" ;; dev) shift; cmd_dev "$@" ;;