From a7af79d93e0b3e4971041c27219603dc22854296 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 1 Jan 2019 16:24:08 +0100 Subject: [PATCH] [wip] Support local repository --- src/yunohost/backup.py | 30 +++--- src/yunohost/repository.py | 214 ++++++++++++++++++------------------- 2 files changed, 123 insertions(+), 121 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 83e3168c1..687d02258 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -24,7 +24,6 @@ Manage backups """ import os -import re import json import time import tarfile @@ -52,6 +51,7 @@ from yunohost.monitor import binary_to_human from yunohost.tools import tools_postinstall from yunohost.service import service_regen_conf from yunohost.log import OperationLogger +from yunohost.repository import BackupRepository from functools import reduce BACKUP_PATH = '/home/yunohost.backup' @@ -1425,7 +1425,9 @@ class BackupMethod(object): BackupRepository object. If None, the default repo is used : /home/yunohost.backup/archives/ """ - self.repo = ARCHIVES_PATH if repo is None else repo + if not repo or isinstance(repo, basestring): + repo = BackupRepository.get_or_create(ARCHIVES_PATH) + self.repo = repo @property def method_name(self): @@ -1596,7 +1598,7 @@ class BackupMethod(object): try: subprocess.check_call(["mount", "--rbind", src, dest]) subprocess.check_call(["mount", "-o", "remount,ro,bind", dest]) - except Exception as e: + except Exception: logger.warning(m18n.n("backup_couldnt_bind", src=src, dest=dest)) # To check if dest is mounted, use /proc/mounts that # escape spaces as \040 @@ -1697,6 +1699,7 @@ class CopyBackupMethod(BackupMethod): def __init__(self, repo=None): super(CopyBackupMethod, self).__init__(repo) + filesystem.mkdir(self.repo.path, parent=True) @property def method_name(self): @@ -1709,7 +1712,7 @@ class CopyBackupMethod(BackupMethod): for path in self.manager.paths_to_backup: source = path['source'] - dest = os.path.join(self.repo, path['dest']) + dest = os.path.join(self.repo.path, path['dest']) if source == dest: logger.debug("Files already copyed") return @@ -1735,18 +1738,18 @@ class CopyBackupMethod(BackupMethod): # the ynh cli super(CopyBackupMethod, self).mount() - if not os.path.isdir(self.repo): + if not os.path.isdir(self.repo.path): raise YunohostError('backup_no_uncompress_archive_dir') filesystem.mkdir(self.work_dir, parent=True) - ret = subprocess.call(["mount", "-r", "--rbind", self.repo, + ret = subprocess.call(["mount", "-r", "--rbind", self.repo.path, self.work_dir]) if ret == 0: return else: logger.warning(m18n.n("bind_mouting_disable")) - subprocess.call(["mountpoint", "-q", dest, - "&&", "umount", "-R", dest]) + subprocess.call(["mountpoint", "-q", self.repo.path, + "&&", "umount", "-R", self.repo.path]) raise YunohostError('backup_cant_mount_uncompress_archive') @@ -1758,6 +1761,7 @@ class TarBackupMethod(BackupMethod): def __init__(self, repo=None): super(TarBackupMethod, self).__init__(repo) + filesystem.mkdir(self.repo.path, parent=True) @property def method_name(self): @@ -1766,7 +1770,7 @@ class TarBackupMethod(BackupMethod): @property def _archive_file(self): """Return the compress archive path""" - return os.path.join(self.repo, self.name + '.tar.gz') + return os.path.join(self.repo.path, self.name + '.tar.gz') def backup(self): """ @@ -1781,8 +1785,8 @@ class TarBackupMethod(BackupMethod): compress archive """ - if not os.path.exists(self.repo): - filesystem.mkdir(self.repo, 0o750, parents=True, uid='admin') + if not os.path.exists(self.repo.path): + filesystem.mkdir(self.repo.path, 0o750, parents=True, uid='admin') # Check free space in output self._check_is_enough_free_space() @@ -2012,9 +2016,9 @@ def backup_create(name=None, description=None, repos=[], if repos == []: repos = ['/home/yunohost.backup/archives'] - methods = [] for repo in repos: - backup_manager.add(BackupMethod.create(methods, repo)) + repo = BackupRepository.get(repo) + backup_manager.add(repo.method) # Add backup targets (system and apps) backup_manager.set_system_targets(system) diff --git a/src/yunohost/repository.py b/src/yunohost/repository.py index 56893c1a0..2b3fce6cd 100644 --- a/src/yunohost/repository.py +++ b/src/yunohost/repository.py @@ -77,7 +77,8 @@ class BackupRepository(object): try: cls.repositories = read_json(REPOSITORIES_PATH) except MoulinetteError as e: - raise YunohostError('backup_cant_open_repositories_file', reason=e) + raise YunohostError( + 'backup_cant_open_repositories_file', reason=e) return cls.repositories @classmethod @@ -98,7 +99,8 @@ class BackupRepository(object): self.name = location if name is None else name if created and self.name in BackupMethod.repositories: - raise YunohostError('backup_repository_already_exists', repositories=self.name) + raise YunohostError( + 'backup_repository_already_exists', repositories=self.name) self.description = description self.encryption = encryption @@ -106,11 +108,7 @@ class BackupRepository(object): if method is None: method = 'tar' if self.domain is None else 'borg' - if created: - self.method = BackupMethod.create(method, self) - else: - self.method = BackupMethod.get(method, self) - + self.method = BackupMethod.get(method, self) def compute_space_used(self): if self.used is None: @@ -128,127 +126,127 @@ class BackupRepository(object): repositories.pop(self.name) - BackupRepository.save() + BackupRepository.save() - if purge: - self.purge() + if purge: + self.purge() - def save(self): - BackupRepository.reposirories[self.name] = self.__dict__ - BackupRepository.save() + def save(self): + BackupRepository.reposirories[self.name] = self.__dict__ + BackupRepository.save() - def _split_location(self): + def _split_location(self): + """ + Split a repository location into protocol, user, domain and path + """ + location_regex = r'^((?Pssh://)?(?P[^@ ]+)@(?P[^: ]+:))?(?P[^\0]+)$' + location_match = re.match(location_regex, self.location) + + if location_match is None: + raise YunohostError('backup_repositories_invalid_location', + location=location) + + self.protocol = location_match.group('protocol') + self.user = location_match.group('user') + self.domain = location_match.group('domain') + self.path = location_match.group('path') + + + def backup_repository_list(name, full=False): """ - Split a repository location into protocol, user, domain and path + List available repositories where put archives """ - location_regex = r'^((?Pssh://)?(?P[^@ ]+)@(?P[^: ]+:))?(?P[^\0]+)$' - location_match = re.match(location_regex, self.location) + repositories = BackupRepository.load() - if location_match is None: - raise YunohostError('backup_repositories_invalid_location', - location=location) - - self.protocol = location_match.group('protocol') - self.user = location_match.group('user') - self.domain = location_match.group('domain') - self.path = location_match.group('path') + if full: + return repositories + else: + return repositories.keys() -def backup_repository_list(name, full=False): - """ - List available repositories where put archives - """ - repositories = BackupRepository.load() + def backup_repository_info(name, human_readable=True, space_used=False): + """ + Show info about a repository - if full: - return repositories - else: - return repositories.keys() + Keyword arguments: + name -- Name of the backup repository + """ + repository = BackupRepository.get(name) + + if space_used: + repository.compute_space_used() + + repository = repository.__dict__ + if human_readable: + if 'quota' in repository: + repository['quota'] = binary_to_human(repository['quota']) + if 'used' in repository and isinstance(repository['used'], int): + repository['used'] = binary_to_human(repository['used']) + + return repository -def backup_repository_info(name, human_readable=True, space_used=False): - """ - Show info about a repository + @is_unit_operation() + def backup_repository_add(operation_logger, location, name, description=None, + methods=None, quota=None, encryption="passphrase"): + """ + Add a backup repository - Keyword arguments: - name -- Name of the backup repository - """ - repository = BackupRepository.get(name) + Keyword arguments: + location -- Location of the repository (could be a remote location) + name -- Name of the backup repository + description -- An optionnal description + quota -- Maximum size quota of the repository + encryption -- If available, the kind of encryption to use + """ + repository = BackupRepository( + location, name, description, methods, quota, encryption) - if space_used: - repository.compute_space_used() + try: + repository.save() + except MoulinetteError: + raise YunohostError('backup_repository_add_failed', + repository=name, location=location) - repository = repository.__dict__ - if human_readable: - if 'quota' in repository: - repository['quota'] = binary_to_human(repository['quota']) - if 'used' in repository and isinstance(repository['used'], int): - repository['used'] = binary_to_human(repository['used']) - - return repository + logger.success(m18n.n('backup_repository_added', + repository=name, location=location)) -@is_unit_operation() -def backup_repository_add(operation_logger, location, name, description=None, - methods=None, quota=None, encryption="passphrase"): - """ - Add a backup repository + @is_unit_operation() + def backup_repository_update(operation_logger, name, description=None, + quota=None, password=None): + """ + Update a backup repository - Keyword arguments: - location -- Location of the repository (could be a remote location) - name -- Name of the backup repository - description -- An optionnal description - quota -- Maximum size quota of the repository - encryption -- If available, the kind of encryption to use - """ - repository = BackupRepository( - location, name, description, methods, quota, encryption) + Keyword arguments: + name -- Name of the backup repository + """ + repository = BackupRepository.get(name) - try: - repository.save() - except MoulinetteError: - raise YunohostError('backup_repository_add_failed', - repository=name, location=location) + if description is not None: + repository.description = description - logger.success(m18n.n('backup_repository_added', - repository=name, location=location)) + if quota is not None: + repository.quota = quota + + try: + repository.save() + except MoulinetteError: + raise YunohostError('backup_repository_update_failed', repository=name) + logger.success(m18n.n('backup_repository_updated', repository=name, + location=repository['location'])) -@is_unit_operation() -def backup_repository_update(operation_logger, name, description=None, - quota=None, password=None): - """ - Update a backup repository + @is_unit_operation() + def backup_repository_remove(operation_logger, name, purge=False): + """ + Remove a backup repository - Keyword arguments: - name -- Name of the backup repository - """ - repository = BackupRepository.get(name) + Keyword arguments: + name -- Name of the backup repository to remove - if description is not None: - repository.description = description - - if quota is not None: - repository.quota = quota - - try: - repository.save() - except MoulinetteError: - raise YunohostError('backup_repository_update_failed', repository=name) - logger.success(m18n.n('backup_repository_updated', repository=name, - location=repository['location'])) - - -@is_unit_operation() -def backup_repository_remove(operation_logger, name, purge=False): - """ - Remove a backup repository - - Keyword arguments: - name -- Name of the backup repository to remove - - """ - repository = BackupRepository.get(name) - repository.delete(purge) - logger.success(m18n.n('backup_repository_removed', repository=name, - path=repository['path'])) + """ + repository = BackupRepository.get(name) + repository.delete(purge) + logger.success(m18n.n('backup_repository_removed', repository=name, + path=repository['path']))