"""Сохранение полного журнала развёртывания кластера в ``clusters/<имя>/provision_log.json``. Автор: Сергей Антропов Сайт: https://devops.org.ru """ from __future__ import annotations import json import logging from datetime import datetime, timezone from pathlib import Path from typing import Any from kind_k8s_paths import clusters_dir logger = logging.getLogger("kind_k8s.provision_log") PROVISION_LOG_FILENAME = "provision_log.json" PROVISION_LOG_VERSION = 1 def provision_log_file_path(cluster_name: str) -> Path: """Путь к JSON с журналом операции для кластера ``cluster_name``.""" return clusters_dir() / cluster_name.strip() / PROVISION_LOG_FILENAME def write_cluster_provision_log( *, cluster_name: str, job_id: str, job_kind: str, status: str, message: str | None, lines: list[str], result: dict[str, Any] | None, ) -> Path | None: """ Атомарно записать полный журнал (все строки без обрезки буфера задания). Возвращает путь к файлу или ``None``, если каталог кластера не существует. """ name = cluster_name.strip() cdir = clusters_dir() / name if not cdir.is_dir(): logger.debug("Каталог кластера отсутствует, provision_log не пишем: %s", cdir) return None path = cdir / PROVISION_LOG_FILENAME payload: dict[str, Any] = { "version": PROVISION_LOG_VERSION, "job_id": job_id, "kind": job_kind, "cluster_name": name, "finished_at_utc": datetime.now(timezone.utc).isoformat(), "status": status, "message": message, "lines": list(lines), "result": result, } tmp = path.with_suffix(path.suffix + ".tmp") try: text = json.dumps(payload, ensure_ascii=False, indent=2, default=str) tmp.write_text(text, encoding="utf-8") tmp.replace(path) logger.info("Сохранён журнал развёртывания: %s (%s строк)", path, len(lines)) return path except OSError as e: logger.warning("Не удалось записать %s: %s", path, e) try: tmp.unlink(missing_ok=True) except OSError: pass return None