#!/usr/bin/env python3 """Интерактивное или пакетное удаление кластера kind и локальной папки с конфигами. Автор: Сергей Антропов Сайт: https://devops.org.ru Пакетный режим: ``--non-interactive --name ИМЯ [--yes]`` (``--yes`` пропускает подтверждение). """ from __future__ import annotations import argparse import logging import shutil import subprocess import sys from core.cluster_lifecycle import KindClusterError, delete_kind_cluster_and_data from kind_k8s_paths import clusters_dir def _configure_logging() -> None: if logging.root.handlers: return logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s") def _list_kind_clusters() -> list[str]: p = subprocess.run(["kind", "get", "clusters"], capture_output=True, text=True) if p.returncode != 0: return [] lines = [x.strip() for x in (p.stdout or "").splitlines() if x.strip()] return [x for x in lines if x.lower() not in ("no kind clusters found.", "no kind clusters found")] def _ask(prompt: str) -> str: return input(f"{prompt}: ").strip() def _interactive() -> None: print("=== Удаление кластера kind ===\n") CLUSTERS_DIR = clusters_dir() if not shutil.which("kind"): print("Не найден kind.", file=sys.stderr) print(" Через Docker: из корня репозитория «make docker up» и удаление в веб-интерфейсе.", file=sys.stderr) sys.exit(127) clusters = _list_kind_clusters() if not clusters: print("Нет зарегистрированных кластеров kind.") only_dirs = ( sorted(p.name for p in CLUSTERS_DIR.iterdir() if p.is_dir() and not p.name.startswith(".")) if CLUSTERS_DIR.is_dir() else [] ) if only_dirs: print("Есть локальные папки в clusters/: ", ", ".join(only_dirs)) name = _ask("Удалить только данные в clusters/<имя> (без kind delete)? Введите имя или пусто для выхода") if not name: return d = CLUSTERS_DIR / name if d.is_dir(): shutil.rmtree(d) print(f"Удалена папка {d}") else: print("Папка не найдена.") return print("Существующие кластеры kind:") for i, c in enumerate(clusters, 1): print(f" {i}) {c}") raw = _ask("\nВведите имя кластера или номер из списка") if not raw: print("Отмена.") return if raw.isdigit(): idx = int(raw, 10) if idx < 1 or idx > len(clusters): print("Неверный номер.") sys.exit(1) name = clusters[idx - 1] else: name = raw if name not in clusters: print(f"Кластер «{name}» не найден в kind. Отмена.") sys.exit(1) confirm = _ask(f"Удалить кластер «{name}» и папку clusters/{name}? (yes/no)") if confirm.lower() not in ("yes", "y", "да", "д"): print("Отмена.") return try: kind_ok, summary = delete_kind_cluster_and_data(name=name, log_to_stdout=True) except KindClusterError as e: print(str(e), file=sys.stderr) raise SystemExit(getattr(e, "exit_code", 1)) from e if not kind_ok: print("kind delete завершился с ошибкой; локальная папка удалена при наличии.", file=sys.stderr) print(summary) print("Готово.") def _parse_args() -> argparse.Namespace: p = argparse.ArgumentParser(description="Удаление кластера kind") p.add_argument("--non-interactive", action="store_true", help="Без диалогов") p.add_argument("--name", help="Имя кластера") p.add_argument("--yes", "-y", action="store_true", help="Не спрашивать подтверждение (только с --non-interactive)") return p.parse_args() def main() -> None: _configure_logging() args = _parse_args() if args.non_interactive: if not args.name: print("Нужен --name в режиме --non-interactive.", file=sys.stderr) raise SystemExit(2) if not args.yes: print("Добавьте --yes для подтверждения удаления в пакетном режиме.", file=sys.stderr) raise SystemExit(2) if not shutil.which("kind"): print("Не найден kind.", file=sys.stderr) sys.exit(127) try: kind_ok, summary = delete_kind_cluster_and_data(name=args.name.strip()) except KindClusterError as e: print(str(e), file=sys.stderr) raise SystemExit(getattr(e, "exit_code", 1)) from e print(summary) raise SystemExit(0 if kind_ok else 1) _interactive() if __name__ == "__main__": main()