- pulp_client: list_versions / list_distributions / get_publication
- GET /repos/{uuid}/versions: 버전 목록 페이지(최신순)
- 현재 운영 배포 버전 판별: Distribution→publication→repository_version 역추적
- views.build_version_view: vN, 생성일시, 패키지 수, 증감(+/-), 검증 배지, 배포 여부
- views.verification_detail: gpgcheck/repo-gpgcheck/checksum 상세 (화면4)
- 대시보드 저장소명 → 버전 페이지 링크, [이 버전 배포] 버튼은 단계4용 placeholder
- 단계1 TODO(배포 버전 역추적) 해소
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9.8 KiB
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를 프록시해 "연결됨/끊김"을 보여준다.
작업:
- 의존성 고정:
requirements.txt(fastapi,uvicorn[standard],httpx,jinja2,python-multipart,pydantic-settings). 폐쇄망 반입 위해 버전 핀. - 디렉터리 구조 생성:
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 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 대응을 처음부터 넣는다.pulp_client.py골격:get/post/patch(Basic Auth 포함, base_url 결합),status().GET /healthz:pulp_client.status()호출 결과를 JSON 또는 작은 HTML 배지로 반환..env.example작성. 실제.env는 커밋 금지(.gitignore).
검증: uvicorn app.main:app --reload → /healthz가 Pulp 연결 상태 반영.
(Pulp 미연결 환경이면 httpx 예외를 잡아 "연결 실패" 표시까지 확인.)
[x] 단계 1 — 대시보드 (화면 1, 읽기 전용)
목표: 저장소 목록을 표/카드로 렌더. 조작 버튼은 아직 동작 안 해도 됨(자리만).
작업:
pulp_client.list_repos()—GET /pulp/api/v3/repositories/rpm/rpm/결과 파싱.- 각 저장소에 대해 표시용 모델로 가공: 이름, 현재 운영 배포 버전(vN), 마지막 동기화 시각,
패키지 수, GPG 검증 배지 근거(
repo_config의gpgcheck/repo-gpgcheck).- 현재 배포 버전은 Distribution → publication → repository_version 역추적이 필요할 수 있음. 이 단계에선 가능한 범위까지만 표시하고 TODO 주석으로 남긴다.
GET /→base.html+ 대시보드 본문(전체 페이지).GET /repos→ 저장소 목록 조각(새로고침/폴링용 partial)./는 이 조각을 include.- 상단 요약 카드: 전체 저장소 수 / 동기화 중 수 / GPG 통과 수 / 총 패키지 수.
- 검증 배지 컴포넌트: ✅ 통과 / ⚠️ 경고 / ⚪ 미설정 3단계(화면 4 로직을 여기서 함수로).
검증: /에서 실제 저장소 목록과 배지가 보인다. /repos 조각만 단독 요청해도 렌더된다.
[x] 단계 2 — 동기화 실행 + 진행률 폴링 (화면 2)
목표: [동기화] 클릭 → sync task 시작 → 3초 폴링으로 진행률 갱신 → 완료/실패 표시.
작업:
pulp_client.sync_repo(uuid, remote_href)—POST .../repositories/rpm/rpm/{uuid}/sync/body{"remote": remote_href}→ 반환된task_href보관.- remote_href는 저장소에 연결된 remote를 조회해 결정(또는 repo 객체의
remote필드).
- remote_href는 저장소에 연결된 remote를 조회해 결정(또는 repo 객체의
POST /repos/{uuid}/sync→ sync 시작 후 진행률 바 조각 반환. 이 조각은hx-get="/repos/{uuid}/progress" hx-trigger="every 3s"를 포함.- task_href를 클라이언트로 어떻게 넘길지: 진행률 조각/폴링 URL에 task_href를 쿼리로 싣거나,
서버측에
{uuid: task_href}임시 매핑(메모리 dict)으로 보관. 폐쇄망 단일 인스턴스이므로 메모리 매핑으로 시작하고, 다중 워커 시 한계는 주석으로 명시.
- task_href를 클라이언트로 어떻게 넘길지: 진행률 조각/폴링 URL에 task_href를 쿼리로 싣거나,
서버측에
pulp_client.get_task(task_href)—state(waiting/running/completed/failed),progress_reports[](done/total) 파싱.GET /repos/{uuid}/progress→ 진행률 바 조각:- running:
n/m 패키지, NN%+ 진행률 바, 계속 폴링. - completed: "동기화 완료, 새 버전 vN 생성됨" 배지로 교체(폴링 중단 —
hx-trigger제거). - failed: 빨간 에러 + task의
error필드 사유.
- running:
- 동기화는 비교적 안전(스펙 §5-1) → 확인 모달 없이 바로 실행 허용.
검증: 실제 sync 실행 시 진행률이 3초마다 갱신되고, 완료 시 새 버전 안내가 뜬다. 폴링 종료(완료/실패 후 더 이상 요청 안 감) 확인.
[x] 단계 3 — 버전(스냅샷) 목록 + 검증 배지 (화면 3 전반)
목표: 저장소별 RepositoryVersion 목록을 최신순으로, 검증/배포 상태와 함께 표시.
작업:
pulp_client.list_versions(uuid)—GET .../repositories/rpm/rpm/{uuid}/versions/.GET /repos/{uuid}/versions→ 버전 목록 조각/페이지:- 각 버전: vN, 생성일시, 패키지 수(가능하면 증감), 검증 상태, 현재 운영 배포 여부 배지.
- 현재 배포 버전 판별: Distribution의 publication → repository_version 과 비교.
- 검증 상태(화면 4): GPG 서명, checksum 타입(sha256 등),
repo_config.gpgcheck값을 ✅/⚠️/⚪ 로. 단계 1의 배지 함수를 재사용. - 배포 버튼 자리만 둔다(다음 단계에서 활성화 로직 연결).
검증: 한 저장소의 버전들이 최신순으로, 검증/배포 배지와 함께 보인다.
[ ] 단계 4 — 배포 확정 + 감사 로그 (화면 3 후반, 위험 동작)
목표: 검증 완료 + 미배포 버전만 배포 가능. 확인 모달 → publication 생성 → distribution PATCH → task 폴링 → 배지 갱신 → 감사 로그 기록.
작업:
- 배포 버튼 활성화 게이트(스펙 §5-2):
- "검증 완료(GPG/checksum 통과)"가 아닌 버전 → 버튼 비활성화.
- 이미 현재 배포 중인 버전 → 버튼 숨김/비활성.
- 확인 단계:
hx-confirm또는 별도 확인 모달 조각("이 버전을 운영망에 배포합니다"). POST /repos/{uuid}/deploybody: 선택한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을 조회해 결정.
- 완료 시: 결과 조각 반환, 해당 버전에 "현재 운영 배포" 배지 갱신.
- 감사 로그(스펙 §5-2): 누가 / 언제 / 어떤 repo / 어떤 버전 → 으로 배포.
- 최소 구현: append-only 파일(JSONL) 또는 SQLite. 시각은 서버 시각.
- "누가"는 인증 도입 전까지 placeholder(예: 단일 운영자) — §5-3 권한 분리 시 확장.
검증: 미검증 버전은 배포 버튼이 막혀 있다. 검증된 버전을 확인 모달 거쳐 배포하면 운영 배포 배지가 그 버전으로 옮겨가고, 감사 로그에 1줄이 남는다.
[ ] 단계 5 — 스타일 마감 + 폐쇄망 패키징
목표: 운영툴로서 보기 좋고, 인터넷 없이 그대로 구동/반입 가능.
작업:
- Pico.css(권장) 또는 Tailwind standalone으로 표·버튼·배지·진행률 바 정돈.
모든 CSS/JS는
static/에 동봉, 템플릿은 로컬 경로만 참조(CDN 링크 0개 확인). - 컨테이너화:
Dockerfile(또는 단순 venv + 실행 스크립트). 의존성은 사전 다운로드한 wheel/오프라인 인덱스로 설치 가능하게(litellm 반입 방식 참고, 스펙 §9). - 실행 문서화:
README또는 CLAUDE.md "Conventions"에 기동 명령/환경변수 정리. - (선택, §5-3) 읽기전용 / 배포권한 계정 분리 여지를 남긴 인증 훅 위치 표시.
- 디자인 가이드는 ref 하위의 markdown 파일들을 참고.
검증: 네트워크 차단 상태에서 컨테이너/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 전까지는 운영망에 영향 없는 안전 구간.