Files
pulp-console/app/views.py
Hyemin Lee 72c75252d3 feat: version list with verification + deployed badge (stage 3)
- 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>
2026-06-17 11:15:31 +09:00

156 lines
5.6 KiB
Python

"""Pulp JSON → 화면용 표시 모델 변환 (순수 함수, httpx 의존 없음).
GPG 검증 배지 로직(✅/⚠️/⚪)은 화면 4 에서도 재사용하므로 여기 함수로 둔다(스펙 §6 화면4).
"""
from __future__ import annotations
from typing import Any
def gpg_status(repo: dict[str, Any]) -> dict[str, str]:
"""repo_config 의 gpgcheck / repo-gpgcheck 로 3단계 검증 배지 산출.
- pass(✅): 패키지 서명 + repo 메타데이터 서명 모두 검증
- warn(⚠️): 둘 중 하나만 검증
- unset(⚪): 검증 미설정
"""
cfg = repo.get("repo_config") or {}
pkg = bool(cfg.get("gpgcheck", 0))
meta = bool(cfg.get("repo-gpgcheck", 0))
# level 은 색상 칩(badge-{level})으로 렌더 — 장식 이모지는 쓰지 않는다(design-ref §7).
if pkg and meta:
return {"level": "pass", "label": "검증됨"}
if pkg or meta:
return {"level": "warn", "label": "부분 검증"}
return {"level": "unset", "label": "미설정"}
def version_number(version_href: str | None) -> int | None:
"""latest_version_href 끝의 정수(vN) 추출. 예: .../versions/3/ → 3"""
if not version_href:
return None
parts = [p for p in version_href.split("/") if p]
try:
return int(parts[-1])
except (ValueError, IndexError):
return None
def _content_count(version: dict[str, Any], section: str) -> int:
"""content_summary[section] (present/added/removed) 의 카운트 합계."""
data = (version.get("content_summary") or {}).get(section) or {}
return sum((info or {}).get("count", 0) for info in data.values())
def package_count(version: dict[str, Any] | None) -> int | None:
"""RepositoryVersion 의 content_summary.present 카운트 합계."""
if version is None:
return None
return _content_count(version, "present")
def _uuid_from_href(href: str) -> str:
parts = [p for p in href.split("/") if p]
return parts[-1] if parts else ""
def build_repo_view(
repo: dict[str, Any], version: dict[str, Any] | None = None
) -> dict[str, Any]:
"""저장소 1건 + (선택) 최신 버전 상세 → 대시보드 행 모델."""
return {
"name": repo.get("name", ""),
"uuid": _uuid_from_href(repo.get("pulp_href", "")),
"href": repo.get("pulp_href", ""),
"latest_version": version_number(repo.get("latest_version_href")),
"package_count": package_count(version),
# 최신 스냅샷 생성 시각 ≈ 마지막 동기화 시각
"last_sync": (version or {}).get("pulp_created"),
"gpg": gpg_status(repo),
# TODO(단계3/4): 현재 운영 배포 버전은 Distribution→publication→version 역추적 필요.
# 단계1에선 최신 버전만 표시한다.
"deployed_version": None,
}
def build_version_view(
version: dict[str, Any],
deployed_number: int | None = None,
gpg: dict[str, str] | None = None,
) -> dict[str, Any]:
"""RepositoryVersion → 버전 목록 행 모델.
gpg 검증은 저장소 단위 설정(repo_config)이라 모든 버전이 같은 값을 공유한다 —
호출부에서 repo 의 gpg_status() 를 주입한다.
"""
number = version_number(version.get("pulp_href"))
return {
"number": number,
"created": version.get("pulp_created"),
"package_count": _content_count(version, "present"),
"added": _content_count(version, "added"),
"removed": _content_count(version, "removed"),
"gpg": gpg,
"is_deployed": number is not None and number == deployed_number,
}
def verification_detail(repo: dict[str, Any]) -> dict[str, Any]:
"""검증 상태 상세 (화면 4): GPG/checksum 설정값 + 종합 배지."""
cfg = repo.get("repo_config") or {}
return {
"status": gpg_status(repo),
"gpgcheck": int(bool(cfg.get("gpgcheck", 0))),
"repo_gpgcheck": int(bool(cfg.get("repo-gpgcheck", 0))),
"checksum_type": repo.get("metadata_checksum_type")
or cfg.get("checksum_type")
or "",
}
def summarize(repo_views: list[dict[str, Any]], syncing: int = 0) -> dict[str, int]:
"""상단 요약 카드 수치. syncing 은 task_store.active_count() 에서 주입."""
return {
"total": len(repo_views),
"syncing": syncing,
"gpg_pass": sum(1 for r in repo_views if r["gpg"]["level"] == "pass"),
"total_packages": sum(r["package_count"] or 0 for r in repo_views),
}
def task_progress(task: dict[str, Any]) -> dict[str, Any]:
"""Pulp task → 진행률 표시 모델.
progress_reports[] 의 done/total 을 합산해 퍼센트를 낸다. 완료 시
created_resources 에서 새 RepositoryVersion 번호(vN)를 추출한다.
"""
state = task.get("state", "waiting")
reports = task.get("progress_reports") or []
done = sum((r or {}).get("done") or 0 for r in reports)
total = sum((r or {}).get("total") or 0 for r in reports)
percent = int(done / total * 100) if total else 0
new_version = None
for href in task.get("created_resources") or []:
if "/versions/" in href:
new_version = version_number(href)
break
error = None
if state == "failed":
err = task.get("error") or {}
error = err.get("description") or "동기화 작업이 실패했습니다."
return {
"state": state,
"done": done,
"total": total,
"percent": percent,
"new_version": new_version,
"error": error,
"completed": state == "completed",
"failed": state == "failed",
"running": state in ("waiting", "running"),
}