Свой велосипед для бэкапов
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

196 lines
6.2 KiB

#!/usr/local/bin/python3.8
import argparse
import os
from dataclasses import dataclass
from datetime import datetime
from typing import List
import yaml
from loguru import logger
from rich import box, print
from rich.console import Console
from rich.padding import Padding
from rich.panel import Panel
from rich.prompt import Prompt
from rich.table import Table
from rich.text import Text
@dataclass(frozen=True)
class Action:
message: str
cmd: str
@property
def pretty_message(self) -> str:
_m = self.message
_m = _m.replace("{warn}", "[bold yellow]\[!][/bold yellow]")
_m = _m.replace("{process}", "[bold orange3]\[..][/bold orange3]")
return _m
@property
def pretty_cmd(self) -> str:
_date: str = datetime.today().strftime("%Y-%m-%d")
_time: str = datetime.now().strftime("%H%M")
return self.cmd.replace("{date}", _date).replace("{time}", _time)
@dataclass()
class Job:
name: str
dst_dir: str
actions: List[Action]
active: bool = False
def run_job(job: Job, console: Console) -> None:
"""Запусить задачу бэкапа.
Проходит по всем вложенным `actions` и, выводя сообщение
`pretty_message`, выполняет задачу из `pretyy_cmd`.
Notes
-----
Для удобства использования даты и времени, все это пишется с
помощью плейсхолдеров в скрипте, и преобразуется при вызове
`pretty_cmd`.
"""
date = datetime.today().strftime("%Y-%m-%d")
time = datetime.now().strftime("%H%M")
path = job.dst_dir.replace("{date}", date).replace("{time}", time)
if not os.path.isdir(path):
os.makedirs(path)
print(f"[bold yellow]\[..][/bold yellow] [bold]{job.name}[/bold] начинаю ...")
for action in job.actions:
with console.status(action.pretty_message):
os.system(action.pretty_cmd)
print(f"[bold green]\[OK][/bold green] [bold]{job.name}[/bold] выполнено")
def ask_about_backups(jobs: List[Job], force_all: bool) -> List[Job]:
"""Проверить список задач.
Проходит в цикле по всем задачам, спрашивая нужно ли ее выполнять.
Если нужно, ставит флаг `job.active` в True.
Parameters
----------
jobs : List[Job]
Список задач. Состоит из объектов Job.
force_all : bool
Если True, то всем задачам будет установлен флаг
`active`=True. Вопросы пользователю заданы не будут.
Returns
-------
jobs : List[Job]
Список задач после опроса пользователя по каждой из них. В
соответствии с решением пользователя поле `active` будет либо
изменено на True (задача активна), либо установленно False
(задача не активна).
"""
if force_all:
for job in jobs:
job.active = True
return jobs
print(Panel(Text("Составляю список задач", justify="center")))
for job in jobs:
answer = Prompt.ask(
f"Архивируем [bold]{job.name}[/bold]?",
choices=["y", "n"],
default="y",
).lower()
if answer == "y":
job.active = True
print("[bold green]\[+] Задача добавлена[/bold green]")
elif answer == "n":
job.active = False
print("[bold red]\[-] Пропуск задачи[/bold red]")
print(Panel(Text("Список задач составлен, начинаю бэкап", justify="center")))
return jobs
def create_actions_from_list(actions: list) -> List[Action]:
a: List[Action] = []
for com in actions:
a.append(Action(com["com"]["message"], com["com"]["cmd"]))
return a
def yaml_loader(file: str) -> List[Job]:
"""Парсинг yaml файла с задачами бэкапа.
See Also
--------
yamlchecker.com - сервис проверки yaml
"""
jobs: list = []
with open(file, "r") as conf:
for task in yaml.safe_load_all(conf):
if task:
jobs.append(
Job(
name=task["name"],
dst_dir=task["dst_dir"],
actions=create_actions_from_list(task["actions"]),
)
)
return jobs
def main() -> None:
parser = argparse.ArgumentParser(description="Скрипт для бэкапа")
required_args = parser.add_argument_group("Обязательные аргументы")
required_args.add_argument(
"--file", type=str, required=True, help="файл с задачами в формате YAML"
)
optional_args = parser.add_argument_group("Необязательные аргументы")
optional_args.add_argument(
"--forceall",
action="store_true",
help="выполнить все задачи из списка без запроса подтверждения",
)
args = parser.parse_args()
console = Console()
jobs = yaml_loader(os.path.abspath(args.file))
# logger.debug(jobs)
jobs = ask_about_backups(jobs, args.forceall)
for j in jobs:
if j.active:
run_job(j, console)
# table = Table(title="Результаты бэкапов", box=box.SIMPLE)
# table.add_column("Задача", justify="right", style="cyan", no_wrap=True)
# table.add_column("Архив", style="magenta")
# table.add_column("Статус", justify="right", style="green")
# table.add_row(
# "Archives криппи и прочее",
# "/back/Archives/Archives-{date}-{time}",
# "ОК",
# )
# table.add_row(
# "Archivebox-emacs",
# "/back/Archives/Archivebox-emacs",
# "Пропуск",
# )
# console.print(table, justify="center")
if __name__ == "__main__":
main()