линтеры python перенастроены: mypy заменён на pyrefly
- набор правил ruff расширен по канону rp-local-env (ANN, PTH, C90, G и др.), в lefthook добавлен job ruff check --fix - код приведён под новые правила: логирование через %s вместо f-строк, os.path заменён на pathlib, run_backup_process и initialize разбиты на функции по порогу цикломатической сложности
This commit is contained in:
@@ -5,6 +5,7 @@ import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from invoke.context import Context
|
||||
from invoke.exceptions import Exit
|
||||
@@ -37,7 +38,7 @@ def _remote_host() -> str:
|
||||
def _authelia_docker() -> str:
|
||||
"""Команда запуска authelia CLI на том же образе, что и задеплоенный сервис"""
|
||||
pattern = re.compile(r"""image:\s*["']?(\S*authelia/authelia:[^"'\s]+)""")
|
||||
with open(AUTHELIA_COMPOSE_FILE, encoding="utf-8") as compose:
|
||||
with Path(AUTHELIA_COMPOSE_FILE).open(encoding="utf-8") as compose:
|
||||
for line in compose:
|
||||
match = pattern.search(line)
|
||||
if match:
|
||||
@@ -56,7 +57,7 @@ def _rest_args() -> list[str]:
|
||||
def _resolve_playbook(name: str) -> str:
|
||||
candidates = [name, f"{name}.yml", f"playbook-{name}.yml"]
|
||||
for candidate in candidates:
|
||||
if os.path.isfile(candidate):
|
||||
if Path(candidate).is_file():
|
||||
return candidate
|
||||
raise Exit(
|
||||
f"Плейбук для '{name}' не найден. Проверял: {', '.join(candidates)}", code=1
|
||||
@@ -141,16 +142,12 @@ def edit_encrypted(ctx: Context, path: str) -> None:
|
||||
# Путь к канону — сосед по файловой системе (../ansible-roles/roles),
|
||||
# поэтому не зависит от текущей директории и одинаков везде.
|
||||
|
||||
SHARED_ROLES_DIR = os.path.normpath(
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "..", "ansible-roles", "roles"
|
||||
)
|
||||
)
|
||||
SHARED_ROLES_DIR = Path(__file__).resolve().parent.parent / "ansible-roles" / "roles"
|
||||
SYNCED_ROLES = ["eget", "app_image"]
|
||||
|
||||
|
||||
def _require_canon() -> None:
|
||||
if not os.path.isdir(SHARED_ROLES_DIR):
|
||||
if not SHARED_ROLES_DIR.is_dir():
|
||||
raise Exit(f"Канон не найден: {SHARED_ROLES_DIR}", code=1)
|
||||
|
||||
|
||||
@@ -171,11 +168,13 @@ def roles_status(ctx: Context) -> None:
|
||||
_require_canon()
|
||||
drift = False
|
||||
for role in SYNCED_ROLES:
|
||||
canon = os.path.join(SHARED_ROLES_DIR, role)
|
||||
if not os.path.isdir(canon):
|
||||
canon = SHARED_ROLES_DIR / role
|
||||
if not canon.is_dir():
|
||||
print(f"{role}: missing in shared")
|
||||
drift = True
|
||||
elif subprocess.run(["diff", "-rq", f"roles/{role}", canon]).returncode != 0:
|
||||
elif (
|
||||
subprocess.run(["diff", "-rq", f"roles/{role}", str(canon)]).returncode != 0
|
||||
):
|
||||
print(f"{role}: differs")
|
||||
drift = True
|
||||
else:
|
||||
@@ -189,10 +188,10 @@ def roles_pull(ctx: Context) -> None:
|
||||
"""Канон → репозиторий: inv roles-pull [-- <role> ...]"""
|
||||
_require_canon()
|
||||
for role in _roles_to_sync():
|
||||
src = os.path.join(SHARED_ROLES_DIR, role)
|
||||
if not os.path.isdir(src):
|
||||
src = SHARED_ROLES_DIR / role
|
||||
if not src.is_dir():
|
||||
raise Exit(f"Роль '{role}' отсутствует в каноне: {src}", code=1)
|
||||
os.makedirs(f"roles/{role}", exist_ok=True)
|
||||
Path("roles", role).mkdir(parents=True, exist_ok=True)
|
||||
ctx.run(f"rsync -a -i --delete {src}/ roles/{role}/")
|
||||
|
||||
|
||||
@@ -201,24 +200,24 @@ def roles_push(ctx: Context) -> None:
|
||||
"""Репозиторий → канон, с подтверждением при расхождении: inv roles-push [-- <role> ...]"""
|
||||
_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):
|
||||
local = Path("roles", role)
|
||||
canon = SHARED_ROLES_DIR / role
|
||||
if not local.is_dir():
|
||||
raise Exit(f"Локальной роли нет: {local}", code=1)
|
||||
differs = (
|
||||
os.path.isdir(canon)
|
||||
and subprocess.run(["diff", "-rq", local, canon]).returncode != 0
|
||||
canon.is_dir()
|
||||
and subprocess.run(["diff", "-rq", str(local), str(canon)]).returncode != 0
|
||||
)
|
||||
if differs:
|
||||
# Защита от затирания более свежего канона устаревшей копией.
|
||||
subprocess.run(["diff", "-r", canon, local])
|
||||
subprocess.run(["diff", "-r", str(canon), str(local)])
|
||||
answer = input(
|
||||
f"Канон '{role}' отличается. Перезаписать его копией из этого репо? [y/N] "
|
||||
)
|
||||
if answer.strip().lower() != "y":
|
||||
print(f"{role}: пропущено")
|
||||
continue
|
||||
os.makedirs(canon, exist_ok=True)
|
||||
canon.mkdir(parents=True, exist_ok=True)
|
||||
ctx.run(f"rsync -a -i --delete {local}/ {canon}/")
|
||||
|
||||
|
||||
@@ -267,7 +266,7 @@ def authelia_validate_config(ctx: Context) -> None:
|
||||
"""Отрендерить конфиг authelia из шаблона и проверить его"""
|
||||
dest = "temp/configuration.yml"
|
||||
# temp/ в .gitignore, на свежем клоне его нет — ansible сам директорию не создаёт.
|
||||
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
||||
Path(dest).parent.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
ctx.run(
|
||||
"uv run ansible localhost"
|
||||
|
||||
Reference in New Issue
Block a user