From 5da663d5c2fc90b225bf1466fbf81b00999277e6 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Tue, 9 Apr 2024 13:20:08 +0530 Subject: [PATCH 1/2] chore: cleanup `get_recent_backup()` Signed-off-by: Akhil Narang --- frappe/utils/backups.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index f14a989e81..3e8565399e 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -248,21 +248,19 @@ class BackupGenerator: def get_recent_backup(self, older_than, partial=False): backup_path = get_backup_path() + separator = suffix = "" + if partial: + separator = "*" - if not frappe.get_system_settings("encrypt_backup"): - file_type_slugs = { - "database": "*-{{}}-{}database.sql.gz".format("*" if partial else ""), - "public": "*-{}-files.tar", - "private": "*-{}-private-files.tar", - "config": "*-{}-site_config_backup.json", - } - else: - file_type_slugs = { - "database": "*-{{}}-{}database.enc.sql.gz".format("*" if partial else ""), - "public": "*-{}-files.enc.tar", - "private": "*-{}-private-files.enc.tar", - "config": "*-{}-site_config_backup.json", - } + if frappe.get_system_settings("encrypt_backup"): + suffix = "-enc" + + file_type_slugs = { + "database": f"*-{{}}-{separator}database{suffix}.sql.gz", + "public": f"*-{{}}-files{suffix}.tar", + "private": f"*-{{}}-private-files{suffix}.tar", + "config": f"*-{{}}-site_config_backup{suffix}.json", + } def backup_time(file_path): file_name = file_path.split(os.sep)[-1] From ea1c8910e674aef90800a8c7a923116e0fd5209c Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Tue, 9 Apr 2024 13:27:32 +0530 Subject: [PATCH 2/2] feat: delete failed backups Signed-off-by: Akhil Narang --- frappe/commands/site.py | 6 ++++++ frappe/utils/backups.py | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index e7165e3b30..281c85683c 100644 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -924,11 +924,13 @@ def backup( verbose = verbose or context.verbose exit_code = 0 + rollback_callback = None for site in context.sites: try: frappe.init(site=site) frappe.connect() + rollback_callback = CallbackManager() odb = scheduled_backup( ignore_files=not with_files, backup_path=backup_path, @@ -943,12 +945,16 @@ def backup( verbose=verbose, force=True, old_backup_metadata=old_backup_metadata, + rollback_callback=rollback_callback, ) except Exception: click.secho( f"Backup failed for Site {site}. Database or site_config.json may be corrupted", fg="red", ) + if rollback_callback: + rollback_callback.run() + rollback_callback = None if verbose: print(frappe.get_traceback(with_context=True)) exit_code = 1 diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index 3e8565399e..16922ccf7a 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -7,6 +7,7 @@ import gzip import os import sys from calendar import timegm +from collections.abc import Callable from datetime import datetime from glob import glob from shutil import which @@ -58,6 +59,7 @@ class BackupGenerator: exclude_doctypes="", verbose=False, old_backup_metadata=False, + rollback_callback=None, ): global _verbose self.compress_files = compress_files or compress @@ -78,6 +80,7 @@ class BackupGenerator: self.exclude_doctypes = exclude_doctypes self.partial = False self.old_backup_metadata = old_backup_metadata + self.rollback_callback = rollback_callback site = frappe.local.site or frappe.generate_hash(length=8) self.site_slug = site.replace(".", "_") @@ -186,9 +189,13 @@ class BackupGenerator: if not (last_db and last_file and last_private_file and site_config_backup_path): self.take_dump() + self.add_to_rollback(lambda: os.remove(self.backup_path_db)) self.copy_site_config() + self.add_to_rollback(lambda: os.remove(self.backup_path_conf)) if not ignore_files: self.backup_files() + self.add_to_rollback(lambda: os.remove(self.backup_path_files)) + self.add_to_rollback(lambda: os.remove(self.backup_path_private_files)) if frappe.get_system_settings("encrypt_backup"): self.backup_encryption() @@ -475,6 +482,16 @@ download only after 24 hours.""" frappe.sendmail(recipients=recipient_list, message=msg, subject=subject) return recipient_list + def add_to_rollback(self, func: Callable) -> None: + """ + Adds the given callable to the rollback CallbackManager stack + + :param func: The callable to add to the rollback stack + :return: Nothing + """ + if self.rollback_callback: + self.rollback_callback.add(func) + def _get_tables(doctypes: list[str], existing_tables: list[str]) -> list[str]: """Return a list of tables for the given doctypes that exist in the database.""" @@ -527,6 +544,7 @@ def scheduled_backup( force=False, verbose=False, old_backup_metadata=False, + rollback_callback=None, ): """this function is called from scheduler deletes backups older than 7 days @@ -546,6 +564,7 @@ def scheduled_backup( force=force, verbose=verbose, old_backup_metadata=old_backup_metadata, + rollback_callback=rollback_callback, ) @@ -564,6 +583,7 @@ def new_backup( force=False, verbose=False, old_backup_metadata=False, + rollback_callback=None, ): delete_temp_backups() odb = BackupGenerator( @@ -585,6 +605,7 @@ def new_backup( verbose=verbose, compress_files=compress, old_backup_metadata=old_backup_metadata, + rollback_callback=rollback_callback, ) odb.get_backup(older_than, ignore_files, force=force) return odb