Files
KindClustersDashboard/scripts/setup_env_interactive.py
T
2026-04-04 05:15:54 +03:00

175 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""Интерактивное создание ``.env`` по шаблону ``env.example`` в корне kind-k8s-develop.
Запуск из корня репозитория: ``python3 scripts/setup_env_interactive.py``
или ``make setup``.
Опционально: ``--template`` / ``--output`` для других путей.
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from __future__ import annotations
import argparse
import logging
import re
import secrets
from pathlib import Path
logger = logging.getLogger("setup_env_interactive")
# Корень репозитория kind-k8s-develop (родитель каталога scripts/)
REPO_ROOT = Path(__file__).resolve().parents[1]
# Переменные, для которых предлагается сгенерировать секрет по вводу «g»
_SECRET_KEYS = frozenset(
{
"SECRET_KEY",
"CONNECTORS_ENCRYPTION_KEY",
"K8S_KUBECONFIG_ENCRYPTION_KEY",
"POSTGRES_PASSWORD",
"MINIO_SECRET_KEY",
"ROOT_PASSWORD",
},
)
def _configure_logging() -> None:
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
def _is_comment_or_blank(line: str) -> bool:
s = line.strip()
return not s or s.startswith("#")
def _parse_assignment(line: str) -> tuple[str, str] | None:
"""Строка ``KEY=значение`` без ведущего ``#``; иначе ``None``."""
raw = line.rstrip("\n\r")
if raw.lstrip().startswith("#"):
return None
if "=" not in raw:
return None
key, _, value = raw.partition("=")
k = key.strip()
if not k or not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", k):
return None
return k, value
def _ask_line(
key: str,
default: str,
*,
collected: dict[str, str],
) -> str:
"""Спросить одну переменную; ``g`` — сгенерировать секрет (для известных ключей)."""
extra = ""
if key in _SECRET_KEYS:
extra = " [g] — сгенерировать случайное значение"
hint = default.replace("\n", " ")[:72]
if len(default) > 72:
hint += ""
print(f"\n{key}")
print(f" По умолчанию из env.example: {hint!r}{extra}")
raw = input(" Значение (Enter — по умолчанию): ").strip()
if not raw:
return default
if raw.lower() == "g" and key in _SECRET_KEYS:
generated = secrets.token_urlsafe(32)
print(f" Сгенерировано ({len(generated)} символов).")
return generated
return raw
def _default_database_url(collected: dict[str, str]) -> str | None:
u = collected.get("POSTGRES_USER")
p = collected.get("POSTGRES_PASSWORD")
d = collected.get("POSTGRES_DB")
if u is not None and p is not None and d is not None:
return f"postgresql://{u}:{p}@postgres:5432/{d}"
return None
def run(
*,
template: Path,
output: Path,
force: bool = False,
) -> int:
template = template.resolve()
output = output.resolve()
if not template.is_file():
logger.error("Не найден шаблон: %s", template)
return 1
if output.exists() and not force:
print(f"Файл уже существует: {output}")
ans = input("Перезаписать? [y/N]: ").strip().lower()
if ans not in ("y", "yes", "д", "да"):
print("Выход без изменений.")
return 0
lines_in = template.read_text(encoding="utf-8").splitlines(keepends=True)
collected: dict[str, str] = {}
out_chunks: list[str] = []
for line in lines_in:
if _is_comment_or_blank(line.rstrip("\n\r")):
out_chunks.append(line if line.endswith("\n") else line + "\n")
continue
parsed = _parse_assignment(line)
if parsed is None:
out_chunks.append(line if line.endswith("\n") else line + "\n")
continue
key, template_default = parsed
default = template_default
if key == "DATABASE_URL":
built = _default_database_url(collected)
if built is not None:
default = built
print("\n--- DATABASE_URL: можно собрать из учётки PostgreSQL выше ---")
value = _ask_line(key, default, collected=collected)
collected[key] = value
out_chunks.append(f"{key}={value}\n")
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text("".join(out_chunks), encoding="utf-8")
print(f"\nГотово: записан файл {output}")
return 0
def main() -> None:
_configure_logging()
parser = argparse.ArgumentParser(description="Интерактивное заполнение .env по env.example")
parser.add_argument(
"--template",
type=Path,
default=REPO_ROOT / "env.example",
help="Путь к шаблону (по умолчанию: env.example в корне репозитория)",
)
parser.add_argument(
"--output",
type=Path,
default=REPO_ROOT / ".env",
help="Куда записать .env (по умолчанию: .env в корне репозитория)",
)
parser.add_argument("-f", "--force", action="store_true", help="Не спрашивать подтверждение перезаписи")
args = parser.parse_args()
raise SystemExit(run(template=args.template, output=args.output, force=args.force))
if __name__ == "__main__":
main()