feat: gated deploy with preview, audit log, rollback trail (stage 4)

- pulp_client: create_publication / update_distribution / wait_for_task
- GET /repos/{uuid}/deploy/confirm: 미리보기(현재→대상, 순 변화) + type-to-confirm 모달
- POST /repos/{uuid}/deploy: 검증 게이트(서버측 재확인) → publication 생성 →
  distribution 교체 → 감사 로그. 2단계 실패 시 '운영망 변경 여부' 명확화
- audit.record_deploy: 누가/언제/repo/이전버전→대상버전 JSONL (롤백 추적)
- 배포 버튼은 검증 통과 + 미배포 버전만 활성, 성공 시 버전목록 OOB 갱신
- 인증은 TODO (operator placeholder, 배포 비밀번호 재확인 예정)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 13:31:15 +09:00
parent 72c75252d3
commit 7ad6d65a11
17 changed files with 692 additions and 29 deletions

37
app/audit.py Normal file
View File

@@ -0,0 +1,37 @@
"""배포 확정 감사 로그 (스펙 §5-2).
누가 / 언제 / 어떤 repo 를 / 어떤 버전으로 배포했는지 append-only JSONL 로 남긴다.
이전 버전(from_version)도 기록해 사고 시 롤백 대상을 즉시 파악할 수 있게 한다.
"""
from __future__ import annotations
import json
from datetime import datetime, timezone
from typing import Any
from .config import get_settings
def record_deploy(
*,
repo_uuid: str,
repo_name: str,
from_version: int | None,
to_version: int | None,
publication_href: str,
operator: str = "operator", # TODO(auth): 로그인 도입 시 실제 사용자. 배포 시 비밀번호 재확인 예정.
) -> dict[str, Any]:
entry = {
"ts": datetime.now(timezone.utc).isoformat(),
"action": "deploy",
"operator": operator,
"repo_uuid": repo_uuid,
"repo_name": repo_name,
"from_version": from_version,
"to_version": to_version,
"publication": publication_href,
}
with open(get_settings().audit_log_path, "a", encoding="utf-8") as fh:
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
return entry