From f31f51959e399217f554004618c43f47607cf3a9 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Thu, 16 Jul 2026 21:03:26 +0300 Subject: [PATCH] =?UTF-8?q?Invoke:=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B8?= =?UTF-8?q?=20=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=BE=D0=B1=D1=89=D0=B8=D1=85=20=D1=80?= =?UTF-8?q?=D0=BE=D0=BB=D0=B5=D0=B9=20(roles-pull/push/status)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tasks.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/tasks.py b/tasks.py index 1245bd7..83beadc 100644 --- a/tasks.py +++ b/tasks.py @@ -115,6 +115,92 @@ def edit_encrypted(ctx: Context, path: str) -> None: ctx.run(f"uv run ansible-vault edit {path}", pty=True) +# --- Синхронизация общих ролей с каноном ansible-roles --- +# Блок идентичен во всех репозиториях; различается только SYNCED_ROLES. +# Путь к канону — сосед по файловой системе (../ansible-roles/roles), +# поэтому не зависит от текущей директории и одинаков везде. + +SHARED_ROLES_DIR = os.path.normpath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), "..", "ansible-roles", "roles" + ) +) +SYNCED_ROLES = ["eget"] + + +def _require_canon() -> None: + if not os.path.isdir(SHARED_ROLES_DIR): + raise Exit(f"Канон не найден: {SHARED_ROLES_DIR}", code=1) + + +def _roles_to_sync() -> list[str]: + """Роли для синхронизации: из '-- ...' или все из SYNCED_ROLES.""" + requested = _rest_args() + if not requested: + return SYNCED_ROLES + unknown = [r for r in requested if r not in SYNCED_ROLES] + if unknown: + raise Exit(f"Роли не в подписке SYNCED_ROLES: {', '.join(unknown)}", code=1) + return requested + + +@task +def roles_status(ctx: Context) -> None: + """Сравнить подписанные роли с каноном ansible-roles""" + _require_canon() + drift = False + for role in SYNCED_ROLES: + canon = os.path.join(SHARED_ROLES_DIR, role) + if not os.path.isdir(canon): + print(f"{role}: missing in shared") + drift = True + elif subprocess.run(["diff", "-rq", f"roles/{role}", canon]).returncode != 0: + print(f"{role}: differs") + drift = True + else: + print(f"{role}: ok") + if drift: + raise Exit("Есть расхождения с каноном", code=1) + + +@task +def roles_pull(ctx: Context) -> None: + """Канон → репозиторий: inv roles-pull [-- ...]""" + _require_canon() + for role in _roles_to_sync(): + src = os.path.join(SHARED_ROLES_DIR, role) + if not os.path.isdir(src): + raise Exit(f"Роль '{role}' отсутствует в каноне: {src}", code=1) + os.makedirs(f"roles/{role}", exist_ok=True) + ctx.run(f"rsync -a -i --delete {src}/ roles/{role}/") + + +@task +def roles_push(ctx: Context) -> None: + """Репозиторий → канон, с подтверждением при расхождении: inv roles-push [-- ...]""" + _require_canon() + for role in _roles_to_sync(): + local = f"roles/{role}" + canon = os.path.join(SHARED_ROLES_DIR, role) + if not os.path.isdir(local): + raise Exit(f"Локальной роли нет: {local}", code=1) + differs = ( + os.path.isdir(canon) + and subprocess.run(["diff", "-rq", local, canon]).returncode != 0 + ) + if differs: + # Защита от затирания более свежего канона устаревшей копией. + subprocess.run(["diff", "-r", canon, local]) + answer = input( + f"Канон '{role}' отличается. Перезаписать его копией из этого репо? [y/N] " + ) + if answer.strip().lower() != "y": + print(f"{role}: пропущено") + continue + os.makedirs(canon, exist_ok=True) + ctx.run(f"rsync -a -i --delete {local}/ {canon}/") + + # --- Специфичные для этого репозитория задачи ---