docs: add project spec, build plan, and contributor/design guidance
- pulp-console-spec.md: 실행 스펙 (확정 스택, Pulp API, 안전 모델) - PLAN.md: 단계별 구현 계획 (체크박스 진행 추적) - CLAUDE.md: future Claude 세션용 아키텍처/제약 안내 - ref/design-ref.md: Toss 스타일 디자인 레퍼런스 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
173
PLAN.md
Normal file
173
PLAN.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# PLAN.md — Pulp 패치 관리 콘솔 구현 계획
|
||||
|
||||
`pulp-console-spec.md`를 근거로 한 단계별 작업 계획. 각 단계는 **"동작하는 화면/엔드포인트"가
|
||||
나오는 단위**로 끊는다. 한 번에 다 만들지 않는다. 각 단계 끝에 검증 방법과 커밋 포인트를 둔다.
|
||||
|
||||
원칙(스펙 §2, §5 재확인):
|
||||
- 브라우저는 Pulp API를 직접 호출하지 않는다. 모든 호출은 FastAPI(BFF) 경유.
|
||||
- 프론트 빌드 도구(React/Vite) 금지. HTMX + Jinja2만.
|
||||
- 정적 자산(Pico.css, htmx.min.js)은 `static/`에 동봉 — CDN 의존 금지(폐쇄망).
|
||||
- 화면용 라우트는 JSON이 아니라 **HTML(전체 페이지 또는 조각)** 을 반환.
|
||||
- 위험 동작(배포 확정)은 확인 + 검증 게이트 + 감사 로그.
|
||||
|
||||
---
|
||||
|
||||
## [x] 단계 0 — 프로젝트 골격 + 설정 + 헬스체크
|
||||
|
||||
**목표:** 서버가 뜨고 `/healthz`가 Pulp `status`를 프록시해 "연결됨/끊김"을 보여준다.
|
||||
|
||||
작업:
|
||||
1. 의존성 고정: `requirements.txt` (`fastapi`, `uvicorn[standard]`, `httpx`, `jinja2`,
|
||||
`python-multipart`, `pydantic-settings`). 폐쇄망 반입 위해 버전 핀.
|
||||
2. 디렉터리 구조 생성:
|
||||
```
|
||||
app/
|
||||
main.py # FastAPI 인스턴스, 라우터 등록, 정적/템플릿 마운트
|
||||
config.py # pydantic-settings로 환경변수 로드 (§8)
|
||||
pulp_client.py # Pulp 호출 래퍼 (스텁)
|
||||
routes/
|
||||
__init__.py
|
||||
dashboard.py
|
||||
templates/
|
||||
base.html
|
||||
partials/
|
||||
static/
|
||||
pico.min.css
|
||||
htmx.min.js
|
||||
tests/
|
||||
.env.example
|
||||
```
|
||||
3. `config.py`: `PULP_BASE_URL`, `PULP_USERNAME`, `PULP_PASSWORD`, `PULP_VERIFY_TLS`,
|
||||
`PULP_CA_FILE` 로드. `httpx` 클라이언트 생성 시 `verify=` 에 `PULP_CA_FILE`(있으면 경로,
|
||||
없고 VERIFY_TLS=false면 False) 적용 — 사내 self-signed CA 대응을 **처음부터** 넣는다.
|
||||
4. `pulp_client.py` 골격: `get/post/patch` (Basic Auth 포함, base_url 결합), `status()`.
|
||||
5. `GET /healthz`: `pulp_client.status()` 호출 결과를 JSON 또는 작은 HTML 배지로 반환.
|
||||
6. `.env.example` 작성. 실제 `.env`는 커밋 금지(`.gitignore`).
|
||||
|
||||
**검증:** `uvicorn app.main:app --reload` → `/healthz`가 Pulp 연결 상태 반영.
|
||||
(Pulp 미연결 환경이면 httpx 예외를 잡아 "연결 실패" 표시까지 확인.)
|
||||
|
||||
---
|
||||
|
||||
## [x] 단계 1 — 대시보드 (화면 1, 읽기 전용)
|
||||
|
||||
**목표:** 저장소 목록을 표/카드로 렌더. 조작 버튼은 아직 동작 안 해도 됨(자리만).
|
||||
|
||||
작업:
|
||||
1. `pulp_client.list_repos()` — `GET /pulp/api/v3/repositories/rpm/rpm/` 결과 파싱.
|
||||
2. 각 저장소에 대해 표시용 모델로 가공: 이름, 현재 운영 배포 버전(vN), 마지막 동기화 시각,
|
||||
패키지 수, GPG 검증 배지 근거(`repo_config`의 `gpgcheck`/`repo-gpgcheck`).
|
||||
- 현재 배포 버전은 Distribution → publication → repository_version 역추적이 필요할 수 있음.
|
||||
이 단계에선 가능한 범위까지만 표시하고 TODO 주석으로 남긴다.
|
||||
3. `GET /` → `base.html` + 대시보드 본문(전체 페이지).
|
||||
4. `GET /repos` → 저장소 목록 **조각**(새로고침/폴링용 partial). `/`는 이 조각을 include.
|
||||
5. 상단 요약 카드: 전체 저장소 수 / 동기화 중 수 / GPG 통과 수 / 총 패키지 수.
|
||||
6. 검증 배지 컴포넌트: ✅ 통과 / ⚠️ 경고 / ⚪ 미설정 3단계(화면 4 로직을 여기서 함수로).
|
||||
|
||||
**검증:** `/`에서 실제 저장소 목록과 배지가 보인다. `/repos` 조각만 단독 요청해도 렌더된다.
|
||||
|
||||
---
|
||||
|
||||
## [ ] 단계 2 — 동기화 실행 + 진행률 폴링 (화면 2)
|
||||
|
||||
**목표:** [동기화] 클릭 → sync task 시작 → 3초 폴링으로 진행률 갱신 → 완료/실패 표시.
|
||||
|
||||
작업:
|
||||
1. `pulp_client.sync_repo(uuid, remote_href)` — `POST .../repositories/rpm/rpm/{uuid}/sync/`
|
||||
body `{"remote": remote_href}` → 반환된 `task_href` 보관.
|
||||
- remote_href는 저장소에 연결된 remote를 조회해 결정(또는 repo 객체의 `remote` 필드).
|
||||
2. `POST /repos/{uuid}/sync` → sync 시작 후 **진행률 바 조각** 반환.
|
||||
이 조각은 `hx-get="/repos/{uuid}/progress" hx-trigger="every 3s"` 를 포함.
|
||||
- task_href를 클라이언트로 어떻게 넘길지: 진행률 조각/폴링 URL에 task_href를 쿼리로 싣거나,
|
||||
서버측에 `{uuid: task_href}` 임시 매핑(메모리 dict)으로 보관. 폐쇄망 단일 인스턴스이므로
|
||||
메모리 매핑으로 시작하고, 다중 워커 시 한계는 주석으로 명시.
|
||||
3. `pulp_client.get_task(task_href)` — `state`(waiting/running/completed/failed),
|
||||
`progress_reports[]`(done/total) 파싱.
|
||||
4. `GET /repos/{uuid}/progress` → 진행률 바 조각:
|
||||
- running: `n/m 패키지, NN%` + 진행률 바, 계속 폴링.
|
||||
- completed: "동기화 완료, 새 버전 vN 생성됨" 배지로 교체(폴링 중단 — `hx-trigger` 제거).
|
||||
- failed: 빨간 에러 + task의 `error` 필드 사유.
|
||||
5. 동기화는 비교적 안전(스펙 §5-1) → 확인 모달 없이 바로 실행 허용.
|
||||
|
||||
**검증:** 실제 sync 실행 시 진행률이 3초마다 갱신되고, 완료 시 새 버전 안내가 뜬다.
|
||||
폴링 종료(완료/실패 후 더 이상 요청 안 감) 확인.
|
||||
|
||||
---
|
||||
|
||||
## [ ] 단계 3 — 버전(스냅샷) 목록 + 검증 배지 (화면 3 전반)
|
||||
|
||||
**목표:** 저장소별 RepositoryVersion 목록을 최신순으로, 검증/배포 상태와 함께 표시.
|
||||
|
||||
작업:
|
||||
1. `pulp_client.list_versions(uuid)` — `GET .../repositories/rpm/rpm/{uuid}/versions/`.
|
||||
2. `GET /repos/{uuid}/versions` → 버전 목록 조각/페이지:
|
||||
- 각 버전: vN, 생성일시, 패키지 수(가능하면 증감), 검증 상태, **현재 운영 배포 여부 배지**.
|
||||
- 현재 배포 버전 판별: Distribution의 publication → repository_version 과 비교.
|
||||
3. 검증 상태(화면 4): GPG 서명, checksum 타입(sha256 등), `repo_config.gpgcheck` 값을
|
||||
✅/⚠️/⚪ 로. 단계 1의 배지 함수를 재사용.
|
||||
4. 배포 버튼 자리만 둔다(다음 단계에서 활성화 로직 연결).
|
||||
|
||||
**검증:** 한 저장소의 버전들이 최신순으로, 검증/배포 배지와 함께 보인다.
|
||||
|
||||
---
|
||||
|
||||
## [ ] 단계 4 — 배포 확정 + 감사 로그 (화면 3 후반, 위험 동작)
|
||||
|
||||
**목표:** 검증 완료 + 미배포 버전만 배포 가능. 확인 모달 → publication 생성 →
|
||||
distribution PATCH → task 폴링 → 배지 갱신 → 감사 로그 기록.
|
||||
|
||||
작업:
|
||||
1. 배포 버튼 활성화 게이트(스펙 §5-2):
|
||||
- "검증 완료(GPG/checksum 통과)"가 아닌 버전 → 버튼 **비활성화**.
|
||||
- 이미 현재 배포 중인 버전 → 버튼 숨김/비활성.
|
||||
2. 확인 단계: `hx-confirm` 또는 별도 확인 모달 조각("이 버전을 운영망에 배포합니다").
|
||||
3. `POST /repos/{uuid}/deploy` body: 선택한 `version_href`:
|
||||
- `pulp_client.create_publication(version_href)` → `POST /publications/rpm/rpm/`
|
||||
body `{"repository_version": version_href}` → task → 폴링으로 publication_href 확보.
|
||||
- `pulp_client.update_distribution(dist_href, publication_href)` →
|
||||
`PATCH {dist_href}` body `{"publication": publication_href}` → task 폴링.
|
||||
- 대상 distribution_href는 저장소에 매핑되는 distribution을 조회해 결정.
|
||||
4. 완료 시: 결과 조각 반환, 해당 버전에 "현재 운영 배포" 배지 갱신.
|
||||
5. **감사 로그**(스펙 §5-2): 누가 / 언제 / 어떤 repo / 어떤 버전 → 으로 배포.
|
||||
- 최소 구현: append-only 파일(JSONL) 또는 SQLite. 시각은 서버 시각.
|
||||
- "누가"는 인증 도입 전까지 placeholder(예: 단일 운영자) — §5-3 권한 분리 시 확장.
|
||||
|
||||
**검증:** 미검증 버전은 배포 버튼이 막혀 있다. 검증된 버전을 확인 모달 거쳐 배포하면
|
||||
운영 배포 배지가 그 버전으로 옮겨가고, 감사 로그에 1줄이 남는다.
|
||||
|
||||
---
|
||||
|
||||
## [ ] 단계 5 — 스타일 마감 + 폐쇄망 패키징
|
||||
|
||||
**목표:** 운영툴로서 보기 좋고, 인터넷 없이 그대로 구동/반입 가능.
|
||||
|
||||
작업:
|
||||
1. Pico.css(권장) 또는 Tailwind standalone으로 표·버튼·배지·진행률 바 정돈.
|
||||
모든 CSS/JS는 `static/`에 동봉, 템플릿은 로컬 경로만 참조(CDN 링크 0개 확인).
|
||||
2. 컨테이너화: `Dockerfile`(또는 단순 venv + 실행 스크립트). 의존성은 사전 다운로드한
|
||||
wheel/오프라인 인덱스로 설치 가능하게(litellm 반입 방식 참고, 스펙 §9).
|
||||
3. 실행 문서화: `README` 또는 CLAUDE.md "Conventions"에 기동 명령/환경변수 정리.
|
||||
4. (선택, §5-3) 읽기전용 / 배포권한 계정 분리 여지를 남긴 인증 훅 위치 표시.
|
||||
|
||||
**검증:** 네트워크 차단 상태에서 컨테이너/venv만으로 앱이 뜨고 모든 정적 자산이 로드된다.
|
||||
|
||||
---
|
||||
|
||||
## 교차 관심사 (전 단계 공통)
|
||||
|
||||
- **에러 처리:** httpx 타임아웃/연결 실패/4xx·5xx를 잡아 화면에 사람이 읽을 수 있는
|
||||
메시지로. Pulp task `failed` 시 `error` 필드 노출.
|
||||
- **href 기준:** Pulp 객체는 이름이 아니라 `pulp_href`로 참조(스펙 §4). 코드 전반 href 우선.
|
||||
- **비동기 task:** sync/deploy는 task 반환 → 반드시 폴링. 폴링 주체는 **화면(HTMX)**,
|
||||
`pulp_client`는 단발 조회만(스펙 §7).
|
||||
- **DEB 지원:** RPM 경로 `rpm/rpm` 자리를 `deb/apt`로. 1차 MVP는 RPM에 집중하되,
|
||||
`pulp_client`에서 content-type을 파라미터화해 확장 여지를 둔다.
|
||||
- **테스트:** `pulp_client`는 httpx mock(`respx` 등)으로 단위 테스트. 라우트는
|
||||
`TestClient`로 조각 HTML에 기대 요소(배지/진행률/버튼 비활성)가 있는지 검사.
|
||||
|
||||
## 권장 진행 순서 요약
|
||||
|
||||
0 골격·헬스체크 → 1 대시보드(읽기) → 2 동기화·폴링 → 3 버전목록·검증배지 →
|
||||
4 배포확정·감사로그 → 5 스타일·폐쇄망 패키징.
|
||||
|
||||
각 단계 완료 시 동작 확인 후 커밋. 단계 4 전까지는 운영망에 영향 없는 안전 구간.
|
||||
Reference in New Issue
Block a user