From 04f85eb860ee20eca3553210ce8bb93ec447f918 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 11 Oct 2022 02:24:05 +0200 Subject: [PATCH] [fix] Repository list, add, remove --- locales/en.json | 5 ++++- share/actionsmap.yml | 9 ++++----- src/backup.py | 4 ++-- src/repositories/borg.py | 41 ++++++++++++++++++++++++++-------------- src/repository.py | 22 +++++++++++---------- 5 files changed, 49 insertions(+), 32 deletions(-) diff --git a/locales/en.json b/locales/en.json index 5b8fb91b2..a3aba5dd3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -90,7 +90,8 @@ "backup_archive_system_part_not_available": "System part '{part}' unavailable in this backup", "backup_archive_writing_error": "Could not add the files '{source}' (named in the archive '{dest}') to be backed up into the compressed archive '{archive}'", "backup_ask_for_copying_if_needed": "Do you want to perform the backup using {size}MB temporarily? (This way is used since some files could not be prepared using a more efficient method.)", - "backup_borg_init_error": "Unable initialize the borg repository", + "backup_borg_init_error": "Unable initialize the borg repository: {error}", + "backup_borg_already_initialized": "The borg repository '{repository}' already exists, it has been properly added to repositories managed by YunoHost cli.", "backup_cant_mount_uncompress_archive": "Could not mount the uncompressed archive as write protected", "backup_cleaning_failed": "Could not clean up the temporary backup folder", "backup_copying_to_organize_the_archive": "Copying {size}MB to organize the archive", @@ -116,6 +117,8 @@ "backup_output_directory_required": "You must provide an output directory for the backup", "backup_output_symlink_dir_broken": "Your archive directory '{path}' is a broken symlink. Maybe you forgot to re/mount or plug in the storage medium it points to.", "backup_permission": "Backup permission for {app}", + "backup_repository_exists": "Backup repository '{backup_repository}' already exists", + "backup_repository_unknown": "Backup repository '{backup_repository}' unknown", "backup_running_hooks": "Running backup hooks...", "backup_system_part_failed": "Could not backup the '{part}' system part", "backup_unable_to_organize_files": "Could not use the quick method to organize files in the archive", diff --git a/share/actionsmap.yml b/share/actionsmap.yml index d4fadf1d5..0b3024c43 100644 --- a/share/actionsmap.yml +++ b/share/actionsmap.yml @@ -1099,6 +1099,9 @@ backup: --full: help: Show more details action: store_true + --space-used: + help: Display size used + action: store_true ### backup_repository_info() info: @@ -1111,13 +1114,9 @@ backup: pattern: &pattern_backup_repository_shortname - !!str ^[a-zA-Z0-9-_\.]+$ - "pattern_backup_repository_shortname" - -H: - full: --human-readable - help: Print sizes in human readable format - action: store_true --space-used: help: Display size used - action: store_false + action: store_true ### backup_repository_add() add: diff --git a/src/backup.py b/src/backup.py index 42fdc2416..d0bbc89d4 100644 --- a/src/backup.py +++ b/src/backup.py @@ -1871,12 +1871,12 @@ def backup_delete(name, repository): # -def backup_repository_list(full=False): +def backup_repository_list(space_used=False, full=False): """ List available repositories where put archives """ - return {"repositories": BackupRepository.list(full)} + return {"repositories": BackupRepository.list(space_used, full)} def backup_repository_info(shortname, space_used=False): diff --git a/src/repositories/borg.py b/src/repositories/borg.py index e5869113f..894d0b7ca 100644 --- a/src/repositories/borg.py +++ b/src/repositories/borg.py @@ -24,6 +24,7 @@ import json from datetime import datetime, timedelta +from moulinette import m18n from moulinette.utils.log import getActionLogger from yunohost.utils.error import YunohostError @@ -37,7 +38,7 @@ class BorgBackupRepository(LocalBackupRepository): method_name = "borg" # TODO logs - def _run_borg_command(self, cmd, stdout=None): + def _run_borg_command(self, cmd, stdout=None, stderr=None): """ Call a submethod of borg with the good context """ env = dict(os.environ) @@ -59,15 +60,16 @@ class BorgBackupRepository(LocalBackupRepository): # Authorize to move the repository (borgbase do this) env["BORG_RELOCATED_REPO_ACCESS_IS_OK"] = "yes" - return subprocess.Popen(cmd, env=env, stdout=stdout) + return subprocess.Popen(cmd, env=env, + stdout=stdout, stderr=stderr) def _call(self, action, cmd, json_output=False): - borg = self._run_borg_command(cmd) - return_code = borg.wait() - if return_code: - raise YunohostError(f"backup_borg_{action}_error") + borg = self._run_borg_command(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = borg.communicate() + if borg.returncode: + raise YunohostError(f"backup_borg_{action}_error", error=err) - out, _ = borg.communicate() if json_output: try: return json.loads(out) @@ -117,8 +119,20 @@ class BorgBackupRepository(LocalBackupRepository): if "quota" in self.future_values and self.future_values["quota"]: cmd += ['--storage-quota', self.quota] + try: + self._call('init', cmd) + except YunohostError as e: + if e.key != "backup_borg_init_error": + raise + else: + # Check if it's possible to read the borg repo with current settings + try: + cmd = ["borg", "info", self.location] + self._call('info', cmd) + except YunohostError: + raise e - self._call('init', cmd) + logger.info(m18n.n("backup_borg_already_initialized", repository=self.location)) def update(self): raise NotImplementedError() @@ -148,12 +162,11 @@ class BorgBackupRepository(LocalBackupRepository): return [archive["name"] for archive in response['archives']] def compute_space_used(self): - if not self.is_remote: - return super().purge() - else: - cmd = ["borg", "info", "--json", self.location] - response = self._call('info', cmd) - return response["cache"]["stats"]["unique_size"] + """ Return the size of this repo on the disk""" + # FIXME this size could be unrelevant, comparison between du and borg sizes doesn't match ! + cmd = ["borg", "info", "--json", self.location] + response = self._call('info', cmd, json_output=True) + return response["cache"]["stats"]["unique_size"] def prune(self, prefix=None, **kwargs): diff --git a/src/repository.py b/src/repository.py index 7b1820853..baa764ad2 100644 --- a/src/repository.py +++ b/src/repository.py @@ -104,13 +104,14 @@ class BackupRepository(ConfigPanel): if not full: return repositories + full_repositories = {} for repo in repositories: try: - repositories[repo] = BackupRepository(repo).info(space_used) - except Exception: - logger.error(f"Unable to open repository {repo}") + full_repositories.update(BackupRepository(repo).info(space_used)) + except Exception as e: + logger.error(f"Unable to open repository {repo}: {e}") - return repositories + return full_repositories # ================================================= # Config Panel Hooks @@ -232,18 +233,19 @@ class BackupRepository(ConfigPanel): self.purge() rm(self.save_path, force=True) - logger.success(m18n.n("repository_removed", repository=self.shortname)) + logger.success(m18n.n("repository_removed", repository=self.entity)) def info(self, space_used=False): result = super().get(mode="export") - if self.__class__ == BackupRepository and space_used is True: + if space_used is True: result["space_used"] = self.compute_space_used() - return {self.shortname: result} + return {self.entity: result} def list_archives(self, with_info): - archives = self.list_archive_name() + self._cast_by_method() + archives = self.list_archives_names() if with_info: d = {} for archive in archives: @@ -267,7 +269,7 @@ class BackupRepository(ConfigPanel): # List archives with creation date archives = {} - for archive_name in self.list_archive_name(prefix): + for archive_name in self.list_archives_names(prefix): archive = BackupArchive(repo=self, name=archive_name) created_at = archive.info()["created_at"] archives[created_at] = archive @@ -314,7 +316,7 @@ class BackupRepository(ConfigPanel): def purge(self): raise NotImplementedError() - def list_archives_names(self): + def list_archives_names(self, prefix=None): raise NotImplementedError() def compute_space_used(self):