diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 745291fb1..83e3168c1 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -71,7 +71,6 @@ class BackupRestoreTargetsManager(object): """ def __init__(self): - self.targets = {} self.results = { "system": {}, @@ -1969,8 +1968,7 @@ class CustomBackupMethod(BackupMethod): # "Front-end" # # -def backup_create(name=None, description=None, methods=[], - output_directory=None, no_compress=False, +def backup_create(name=None, description=None, repos=[], system=[], apps=[]): """ Create a backup local archive @@ -1995,30 +1993,6 @@ def backup_create(name=None, description=None, methods=[], if name and name in backup_list()['archives']: raise YunohostError('backup_archive_name_exists') - # Validate output_directory option - if output_directory: - output_directory = os.path.abspath(output_directory) - - # Check for forbidden folders - if output_directory.startswith(ARCHIVES_PATH) or \ - re.match(r'^/(|(bin|boot|dev|etc|lib|root|run|sbin|sys|usr|var)(|/.*))$', - output_directory): - raise YunohostError('backup_output_directory_forbidden') - - # Check that output directory is empty - if os.path.isdir(output_directory) and no_compress and \ - os.listdir(output_directory): - raise YunohostError('backup_output_directory_not_empty') - elif no_compress: - raise YunohostError('backup_output_directory_required') - - # Define methods (retro-compat) - if not methods: - if no_compress: - methods = ['copy'] - else: - methods = ['tar'] # In future, borg will be the default actions - # If no --system or --apps given, backup everything if system is None and apps is None: system = [] @@ -2032,20 +2006,15 @@ def backup_create(name=None, description=None, methods=[], _create_archive_dir() # Prepare files to backup - if no_compress: - backup_manager = BackupManager(name, description, - work_dir=output_directory) - else: - backup_manager = BackupManager(name, description) + backup_manager = BackupManager(name, description) # Add backup methods - if output_directory: - methods = BackupMethod.create(methods, output_directory) - else: - methods = BackupMethod.create(methods) + if repos == []: + repos = ['/home/yunohost.backup/archives'] - for method in methods: - backup_manager.add(method) + methods = [] + for repo in repos: + backup_manager.add(BackupMethod.create(methods, repo)) # 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 21be1d92e..348262907 100644 --- a/src/yunohost/repository.py +++ b/src/yunohost/repository.py @@ -2,7 +2,7 @@ """ License - Copyright (C) 2013 YunoHost + Copyright (C) 2013 Yunohost This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -25,36 +25,39 @@ """ import os import re -import json -import errno import time -import tarfile -import shutil import subprocess from moulinette import msignals, m18n from moulinette.core import MoulinetteError from moulinette.utils import filesystem from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_file +from moulinette.utils.filesystem import read_file, read_json, write_to_json + +from yunohost.utils.error import YunohostError from yunohost.monitor import binary_to_human -from yunohost.log import OperationLogger +from yunohost.log import OperationLogger, is_unit_operation +from yunohost.backup import BackupMethod logger = getActionLogger('yunohost.repository') REPOSITORIES_PATH = '/etc/yunohost/repositories.yml' + class BackupRepository(object): + """ + BackupRepository manage all repository the admin added to the instance + """ + repositories = {} @classmethod def get(cls, name): cls.load() if name not in cls.repositories: - raise MoulinetteError(errno.EINVAL, m18n.n( - 'backup_repository_doesnt_exists', name=name)) + raise YunohostError('backup_repository_doesnt_exists', name=name) - return BackupRepository(**repositories[name]) + return BackupRepository(**cls.repositories[name]) @classmethod def load(cls): @@ -67,10 +70,7 @@ class BackupRepository(object): try: cls.repositories = read_json(REPOSITORIES_PATH) except MoulinetteError as e: - raise MoulinetteError(1, - m18n.n('backup_cant_open_repositories_file', - reason=e), - exc_info=1) + raise YunohostError('backup_cant_open_repositories_file', reason=e) return cls.repositories @classmethod @@ -81,34 +81,41 @@ class BackupRepository(object): try: write_to_json(REPOSITORIES_PATH, cls.repositories) except Exception as e: - raise MoulinetteError(1, m18n.n('backup_cant_save_repositories_file', - reason=e), - exc_info=1) + raise YunohostError('backup_cant_save_repositories_file', reason=e) - - def __init__(self, location, name, description=None, method=None, + def __init__(self, location, name=None, description=None, method=None, encryption=None, quota=None): - self.location = None + self.location = location self._split_location() self.name = location if name is None else name - if self.name in repositories: - raise MoulinetteError(errno.EIO, m18n.n('backup_repository_already_exists', repositories=name)) + if self.name in BackupMethod.repositories: + raise YunohostError('backup_repository_already_exists', repositories=name) - self.description = None - self.encryption = None - self.quota = None + self.description = description + self.encryption = encryption + self.quota = quota if method is None: method = 'tar' if self.domain is None else 'borg' self.method = BackupMethod.create(method, self) + + # Check for forbidden folders + if self.path.startswith(ARCHIVES_PATH) or \ + re.match(r'^/(|(bin|boot|dev|etc|lib|root|run|sbin|sys|usr|var)(|/.*))$', + self.path): + raise YunohostError('backup_output_directory_forbidden') + + # Check that output directory is empty + if os.path.isdir(location) and os.listdir(location): + raise YunohostError('backup_output_directory_not_empty') def compute_space_used(self): if self.used is None: try: self.used = self.method.compute_space_used() - except NotYetImplemented as e: + except (AttributeError, NotImplementedError): self.used = 'unknown' return self.used @@ -116,9 +123,9 @@ class BackupRepository(object): self.method.purge() def delete(self, purge=False): - repositories = BackupRepositories.repositories + repositories = BackupRepository.repositories - repository = repositories.pop(name) + repositories.pop(self.name) BackupRepository.save() @@ -137,13 +144,15 @@ class BackupRepository(object): location_match = re.match(location_regex, self.location) if location_match is None: - raise MoulinetteError(errno.EIO, m18n.n('backup_repositories_invalid_location', location=location)) + 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): """ List available repositories where put archives @@ -155,6 +164,7 @@ def backup_repository_list(name, full=False): else: return repositories.keys() + def backup_repository_info(name, human_readable=True, space_used=False): """ Show info about a repository @@ -164,7 +174,6 @@ def backup_repository_info(name, human_readable=True, space_used=False): """ repository = BackupRepository.get(name) - if space_used: repository.compute_space_used() @@ -172,11 +181,12 @@ def backup_repository_info(name, human_readable=True, space_used=False): if human_readable: if 'quota' in repository: repository['quota'] = binary_to_human(repository['quota']) - if 'used' in repository and isinstance(repository['used', int): + if 'used' in repository and isinstance(repository['used'], int): repository['used'] = binary_to_human(repository['used']) return repository + @is_unit_operation() def backup_repository_add(operation_logger, location, name, description=None, methods=None, quota=None, encryption="passphrase"): @@ -184,17 +194,24 @@ def backup_repository_add(operation_logger, location, name, description=None, Add 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) + repository = BackupRepository( + location, name, description, methods, quota, encryption) try: repository.save() - except MoulinetteError as e: - raise MoulinetteError(errno.EIO, m18n.n('backup_repository_add_failed', - repository=name, location=location)) + except MoulinetteError: + raise YunohostError('backup_repository_add_failed', + repository=name, location=location) + + logger.success(m18n.n('backup_repository_added', + repository=name, location=location)) - logger.success(m18n.n('backup_repository_added', repository=name, location=location)) @is_unit_operation() def backup_repository_update(operation_logger, name, description=None, @@ -215,12 +232,12 @@ def backup_repository_update(operation_logger, name, description=None, try: repository.save() - except MoulinetteError as e: - raise MoulinetteError(errno.EIO, m18n.n('backup_repository_update_failed', - repository=name)) + 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): """ @@ -234,4 +251,3 @@ def backup_repository_remove(operation_logger, name, purge=False): repository.delete(purge) logger.success(m18n.n('backup_repository_removed', repository=name, path=repository['path'])) -