- 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>
204 lines
10 KiB
Markdown
204 lines
10 KiB
Markdown
# Pulp 패치 관리 콘솔 — 개발 스펙 (Claude Code용)
|
|
|
|
> 이 문서는 Claude Code 세션에 그대로 넣어 개발을 시작하기 위한 실행 스펙이다.
|
|
> 읽는 대상은 "사람"이 아니라 "코드를 짜는 AI"다. 따라서 모호한 표현 대신
|
|
> 구체적인 동작·경로·데이터 형태를 명시한다.
|
|
|
|
---
|
|
|
|
## 0. 한 줄 정의
|
|
|
|
공식 관리 UI가 없는 Pulp 3을, 운영자가 명령어 없이 클릭만으로 다룰 수 있게 해주는
|
|
**사내 폐쇄망용 리눅스 패치 저장소 관리 콘솔**.
|
|
|
|
핵심 가치: **"검증이 끝난 특정 버전(스냅샷)만 운영 서버에 배포되도록 사람이 통제하는 화면."**
|
|
|
|
---
|
|
|
|
## 1. 배경 / 우리 팀 컨텍스트
|
|
|
|
- 한국은행 IT전략국 클라우드팀. 폐쇄망(air-gapped) 환경, 퍼블릭 클라우드 MSP 미사용.
|
|
- 인터넷/내부 서버관리망에 Pulp 기반 리눅스 패치 Repo 서버를 구축하는 인프라 개혁의 일부.
|
|
- Pulp는 외부 미러(RHEL/CentOS/Rocky/Ubuntu)에서 패키지를 미러링하고, GPG 서명·checksum으로
|
|
변조를 판별하며, repository version(스냅샷)으로 버전을 고정·배포하는 백엔드 역할을 이미 수행한다.
|
|
- 문제: **Pulp에는 쓸 만한 공식 웹 UI가 없다.** 커뮤니티 `pulp-ui`는 사실상 방치 상태.
|
|
반면 REST API(`/pulp/api/v3/`)는 전부 공개되어 있고 OpenAPI 스키마로 문서화되어 있다.
|
|
- 따라서 우리 요구에 딱 맞는 얇은 관리 콘솔을 자체 제작한다.
|
|
|
|
---
|
|
|
|
## 2. 기술 스택 (확정)
|
|
|
|
| 영역 | 선택 | 이유 |
|
|
|---|---|---|
|
|
| 백엔드/BFF | **FastAPI (Python)** | Pulp 인증을 서버가 쥐고 프록시. 폐쇄망 반입 간단(pip). |
|
|
| 화면 | **HTMX + Jinja2 템플릿** | 빌드 단계 없음. "버튼→부분 갱신", "주기적 폴링"이 HTML 속성만으로 됨. |
|
|
| 스타일 | **Tailwind CSS (CDN/standalone) 또는 Pico.css** | 운영툴은 깔끔한 표·버튼·상태표시면 충분. 폐쇄망이면 Pico.css가 더 편함. |
|
|
| 비동기 작업 추적 | HTMX `hx-trigger="every 3s"` 폴링 | Pulp의 task를 주기적으로 조회해 진행률 갱신. |
|
|
|
|
**금지/주의:**
|
|
- 브라우저가 Pulp API를 직접 호출하지 않는다. 반드시 FastAPI를 경유한다(인증정보·CORS 보호).
|
|
- React/Vite 등 무거운 프론트 빌드 도구 사용하지 않는다.
|
|
- 폐쇄망 반입을 전제로, CDN 의존은 최소화하고 정적 자산은 로컬에 둘 수 있게 한다.
|
|
|
|
---
|
|
|
|
## 3. 아키텍처
|
|
|
|
```
|
|
[브라우저: HTMX + Jinja 템플릿]
|
|
│ (HTTPS, 폼/버튼 → 부분 HTML 응답)
|
|
▼
|
|
[FastAPI BFF]
|
|
- Pulp 인증정보(Basic Auth) 보관
|
|
- Pulp REST API 호출 → 결과를 가공 → HTML 조각 렌더
|
|
- 위험한 동작(배포 확정)에 확인·권한·로깅 적용
|
|
│ (HTTP, Basic Auth)
|
|
▼
|
|
[Pulp REST API /pulp/api/v3/]
|
|
```
|
|
|
|
- BFF는 Pulp API 응답(JSON)을 받아 Jinja 부분 템플릿(HTML 조각)으로 렌더해서 돌려준다.
|
|
HTMX가 그 조각을 화면 일부에 끼워넣는다.
|
|
|
|
---
|
|
|
|
## 4. Pulp API — 알아야 할 핵심 (실제 경로)
|
|
|
|
Pulp의 객체 모델: **Remote(소스 정의) → Repository(그릇) → RepositoryVersion(스냅샷) →
|
|
Publication(배포가능 형태) → Distribution(공개 URL)**.
|
|
|
|
운영자가 `yum/dnf`의 `baseurl`로 바라보는 주소가 바로 Distribution이다.
|
|
|
|
자주 쓰는 엔드포인트(RPM 기준; deb는 `rpm/rpm` 자리를 `deb/apt`로):
|
|
|
|
| 동작 | 메서드 / 경로 | 비고 |
|
|
|---|---|---|
|
|
| 상태 확인 | `GET /pulp/api/v3/status/` | 헬스체크 |
|
|
| 저장소 목록 | `GET /pulp/api/v3/repositories/rpm/rpm/` | |
|
|
| 저장소 동기화(스냅샷 생성) | `POST /pulp/api/v3/repositories/rpm/rpm/{uuid}/sync/` body: `{"remote": "<remote_href>"}` | 새 RepositoryVersion을 만든다. 비동기 → task 반환 |
|
|
| 버전 목록 | `GET /pulp/api/v3/repositories/rpm/rpm/{uuid}/versions/` | 스냅샷 v1, v2, ... |
|
|
| 작업 상태 | `GET {task_href}` | state: waiting/running/completed/failed, progress_reports[] |
|
|
| Publication 생성 | `POST /pulp/api/v3/publications/rpm/rpm/` body: `{"repository_version": "<version_href>"}` | 특정 버전을 배포가능 형태로 |
|
|
| Distribution 목록 | `GET /pulp/api/v3/distributions/rpm/rpm/` | 공개 URL들 |
|
|
| Distribution 업데이트(배포 확정) | `PATCH {distribution_href}` body: `{"publication": "<publication_href>"}` | **이게 "이 버전 배포" 동작.** 운영망이 보는 URL을 특정 publication으로 교체 |
|
|
|
|
- Pulp 객체는 `pulp_href`(경로 문자열)로 서로를 참조한다. 이름과 href를 혼용 가능하나 코드에선 href 기준.
|
|
- 동기화/배포는 **비동기 task**를 반환한다. 반드시 task_href를 받아 폴링으로 완료를 확인한다.
|
|
- GPG 검증: Remote에 `gpgkey`/`tls_validation` 설정 시 sync 단계에서 서명을 검증한다.
|
|
Repository의 `repo_config`에 `gpgcheck: 1, repo-gpgcheck: 1`이 들어간다 → UI에서 "검증됨" 배지 근거.
|
|
|
|
---
|
|
|
|
## 5. 권한·안전 모델 (풀 기능이므로 필수)
|
|
|
|
운영자가 "실제 조작"까지 하므로 배포 사고 방지가 설계의 일부다.
|
|
|
|
1. **동기화(sync)**: 비교적 안전. 외부 미러에서 받아 새 스냅샷을 만들 뿐, 운영망 배포는 아님.
|
|
버튼 누르면 바로 실행 가능.
|
|
2. **배포 확정(Distribution 교체)**: 위험. 운영 서버가 실제로 받게 되는 버전이 바뀐다.
|
|
- 반드시 **확인 모달**(hx-confirm 또는 별도 확인 단계)을 거친다.
|
|
- "검증 완료(GPG/checksum 통과) 상태"가 아닌 버전은 배포 버튼 자체를 비활성화한다.
|
|
- 모든 배포 확정 행위는 **감사 로그**로 남긴다(누가/언제/어떤 repo를/어떤 버전으로).
|
|
3. (선택) 향후 읽기전용 계정 / 배포권한 계정 분리 여지를 남긴다.
|
|
|
|
---
|
|
|
|
## 6. 화면 명세 (MVP — 4개)
|
|
|
|
### 화면 1. 대시보드 (`GET /`)
|
|
- 상단 요약 카드: 전체 저장소 수 / 동기화 중 수 / GPG 검증 통과 수 / 총 패키지 수
|
|
- 저장소 목록(카드 또는 표). 각 행:
|
|
- 저장소 이름, GPG 검증 배지(✅/⚠️), 현재 운영 배포 버전(vN), 마지막 동기화 시각, 패키지 수
|
|
- [동기화] 버튼
|
|
- 동기화 중인 항목은 진행률 바 + "n/m 패키지, NN%" 표시
|
|
- 진행률 영역은 `hx-get="/repos/{uuid}/progress" hx-trigger="every 3s"`로 자동 폴링.
|
|
|
|
### 화면 2. 동기화 실행 + 진행률 (부분 갱신)
|
|
- [동기화] 클릭 → `POST /repos/{uuid}/sync` → BFF가 Pulp sync 호출 → task_href 보관
|
|
- 응답으로 진행률 바 조각 반환, 이후 3초마다 폴링하여 갱신
|
|
- 완료 시: "동기화 완료, 새 버전 vN 생성됨" 배지로 교체
|
|
- 실패 시: 빨간 에러 메시지 + 사유(task의 error 필드)
|
|
|
|
### 화면 3. 버전(스냅샷) 목록 + 배포 확정 (`GET /repos/{uuid}/versions`)
|
|
- 해당 저장소의 RepositoryVersion 목록을 최신순으로
|
|
- 각 버전: vN, 생성일시, 패키지 수(증감), 검증 상태, 현재 운영 배포 여부 배지
|
|
- 검증 완료 + 미배포 버전에만 [이 버전 배포] 버튼 활성화
|
|
- 클릭 → 확인 모달 → `POST /repos/{uuid}/deploy` body: 선택 version_href
|
|
→ BFF가 (publication 생성 → distribution PATCH) 수행 → task 폴링 → 완료 시 배지 갱신
|
|
|
|
### 화면 4. 검증 상태 상세 (화면 1/3에 인라인으로 표시해도 됨)
|
|
- GPG 서명 검증 결과, checksum 타입(sha256 등), repo_config의 gpgcheck 값 표시
|
|
- 통과 ✅ / 경고 ⚠️ / 미설정 ⚪ 3단계
|
|
|
|
---
|
|
|
|
## 7. FastAPI 라우트 설계 (BFF)
|
|
|
|
화면용 라우트는 "전체 페이지" 또는 "HTML 조각"을 반환한다(JSON 아님, HTMX가 HTML을 받기 때문).
|
|
|
|
```
|
|
GET / → 대시보드 전체 페이지
|
|
GET /repos → 저장소 목록 조각 (새로고침용)
|
|
POST /repos/{uuid}/sync → 동기화 시작, 진행률 바 조각 반환
|
|
GET /repos/{uuid}/progress → 진행률 바 조각 (폴링 대상)
|
|
GET /repos/{uuid}/versions → 버전 목록 조각/페이지
|
|
POST /repos/{uuid}/deploy → 배포 확정(publication+distribution), 결과 조각
|
|
GET /healthz → Pulp status 프록시
|
|
```
|
|
|
|
내부 헬퍼:
|
|
```
|
|
pulp_client.py
|
|
- get(path) / post(path, json) / patch(href, json) # Basic Auth 포함
|
|
- wait_or_poll_task(task_href) # 단발 조회(폴링은 화면이 함)
|
|
- list_repos() / sync_repo(uuid, remote_href)
|
|
- list_versions(uuid) / create_publication(version_href)
|
|
- update_distribution(dist_href, publication_href)
|
|
```
|
|
|
|
---
|
|
|
|
## 8. 설정 / 환경변수
|
|
|
|
```
|
|
PULP_BASE_URL = http://repo.bok.or.kr:8080 # 또는 내부 주소
|
|
PULP_USERNAME = admin
|
|
PULP_PASSWORD = (시크릿; 환경변수/시크릿 파일)
|
|
PULP_VERIFY_TLS = true/false # 사내 CA면 cafile 경로 지정
|
|
PULP_CA_FILE = /path/boknet-ca.pem # 사내 TLS 검사 환경 대응
|
|
```
|
|
|
|
- 사내망 TLS(self-signed CA) 이슈가 있을 수 있으므로, httpx 클라이언트에 `verify=PULP_CA_FILE`
|
|
지정 옵션을 처음부터 넣어둔다. (LiteLLM 세팅 때와 동일한 boknet-SMSCENTER-CA 계열)
|
|
|
|
---
|
|
|
|
## 9. 폐쇄망 배포 방식
|
|
|
|
- 최종 산출물: FastAPI 앱(+Jinja 템플릿) + 정적 자산(Pico.css 등 로컬 포함)을 하나의
|
|
컨테이너 이미지 또는 단순 pip 가상환경으로 패키징.
|
|
- Python 의존성(pip)·정적 CSS는 인터넷 가능 PC에서 받아 반입하거나 사내 프록시(litellm 방식) 활용.
|
|
- CDN 직접 의존을 피하고 CSS/HTMX 스크립트는 `static/`에 동봉한다.
|
|
|
|
---
|
|
|
|
## 10. 개발 순서 (Claude Code에게)
|
|
|
|
1. 프로젝트 골격: FastAPI + Jinja2 + httpx, `pulp_client.py` 스텁, `/healthz`로 Pulp status 확인
|
|
2. 대시보드(화면 1) — 저장소 목록 읽어 표/카드 렌더 (읽기만)
|
|
3. 동기화(화면 2) — sync POST → task_href → 진행률 폴링 조각
|
|
4. 버전 목록(화면 3 전반) — versions 읽어 목록 렌더 + 검증 배지
|
|
5. 배포 확정(화면 3 후반) — 확인 모달 → publication 생성 → distribution PATCH → 폴링 → 감사 로그
|
|
6. 스타일 마감(Pico.css/Tailwind), 폐쇄망 정적 자산 동봉, 컨테이너화
|
|
|
|
각 단계는 "동작하는 화면"이 나오는 단위로 끊는다. 한 번에 다 만들지 않는다.
|
|
|
|
---
|
|
|
|
## 11. 참고
|
|
|
|
- Pulp RPM 튜토리얼(sync→publication→distribution 흐름): pulpproject.org pulp_rpm 문서
|
|
- 커뮤니티 UI(참고용, 베이스로 쓰지 않음): github.com/pulp/pulp-ui
|
|
- 우리는 pulp-ui 코드를 fork하지 않는다. API 호출 흐름만 참고하고, 위 스택으로 새로 만든다.
|