[wip] Support local repository

This commit is contained in:
ljf 2019-01-01 16:24:08 +01:00
parent 48f7e51dd8
commit a7af79d93e
2 changed files with 123 additions and 121 deletions

View file

@ -24,7 +24,6 @@
Manage backups Manage backups
""" """
import os import os
import re
import json import json
import time import time
import tarfile import tarfile
@ -52,6 +51,7 @@ from yunohost.monitor import binary_to_human
from yunohost.tools import tools_postinstall from yunohost.tools import tools_postinstall
from yunohost.service import service_regen_conf from yunohost.service import service_regen_conf
from yunohost.log import OperationLogger from yunohost.log import OperationLogger
from yunohost.repository import BackupRepository
from functools import reduce from functools import reduce
BACKUP_PATH = '/home/yunohost.backup' BACKUP_PATH = '/home/yunohost.backup'
@ -1425,7 +1425,9 @@ class BackupMethod(object):
BackupRepository object. If None, the default repo is used : BackupRepository object. If None, the default repo is used :
/home/yunohost.backup/archives/ /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 @property
def method_name(self): def method_name(self):
@ -1596,7 +1598,7 @@ class BackupMethod(object):
try: try:
subprocess.check_call(["mount", "--rbind", src, dest]) subprocess.check_call(["mount", "--rbind", src, dest])
subprocess.check_call(["mount", "-o", "remount,ro,bind", 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)) logger.warning(m18n.n("backup_couldnt_bind", src=src, dest=dest))
# To check if dest is mounted, use /proc/mounts that # To check if dest is mounted, use /proc/mounts that
# escape spaces as \040 # escape spaces as \040
@ -1697,6 +1699,7 @@ class CopyBackupMethod(BackupMethod):
def __init__(self, repo=None): def __init__(self, repo=None):
super(CopyBackupMethod, self).__init__(repo) super(CopyBackupMethod, self).__init__(repo)
filesystem.mkdir(self.repo.path, parent=True)
@property @property
def method_name(self): def method_name(self):
@ -1709,7 +1712,7 @@ class CopyBackupMethod(BackupMethod):
for path in self.manager.paths_to_backup: for path in self.manager.paths_to_backup:
source = path['source'] source = path['source']
dest = os.path.join(self.repo, path['dest']) dest = os.path.join(self.repo.path, path['dest'])
if source == dest: if source == dest:
logger.debug("Files already copyed") logger.debug("Files already copyed")
return return
@ -1735,18 +1738,18 @@ class CopyBackupMethod(BackupMethod):
# the ynh cli # the ynh cli
super(CopyBackupMethod, self).mount() 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') raise YunohostError('backup_no_uncompress_archive_dir')
filesystem.mkdir(self.work_dir, parent=True) 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]) self.work_dir])
if ret == 0: if ret == 0:
return return
else: else:
logger.warning(m18n.n("bind_mouting_disable")) logger.warning(m18n.n("bind_mouting_disable"))
subprocess.call(["mountpoint", "-q", dest, subprocess.call(["mountpoint", "-q", self.repo.path,
"&&", "umount", "-R", dest]) "&&", "umount", "-R", self.repo.path])
raise YunohostError('backup_cant_mount_uncompress_archive') raise YunohostError('backup_cant_mount_uncompress_archive')
@ -1758,6 +1761,7 @@ class TarBackupMethod(BackupMethod):
def __init__(self, repo=None): def __init__(self, repo=None):
super(TarBackupMethod, self).__init__(repo) super(TarBackupMethod, self).__init__(repo)
filesystem.mkdir(self.repo.path, parent=True)
@property @property
def method_name(self): def method_name(self):
@ -1766,7 +1770,7 @@ class TarBackupMethod(BackupMethod):
@property @property
def _archive_file(self): def _archive_file(self):
"""Return the compress archive path""" """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): def backup(self):
""" """
@ -1781,8 +1785,8 @@ class TarBackupMethod(BackupMethod):
compress archive compress archive
""" """
if not os.path.exists(self.repo): if not os.path.exists(self.repo.path):
filesystem.mkdir(self.repo, 0o750, parents=True, uid='admin') filesystem.mkdir(self.repo.path, 0o750, parents=True, uid='admin')
# Check free space in output # Check free space in output
self._check_is_enough_free_space() self._check_is_enough_free_space()
@ -2012,9 +2016,9 @@ def backup_create(name=None, description=None, repos=[],
if repos == []: if repos == []:
repos = ['/home/yunohost.backup/archives'] repos = ['/home/yunohost.backup/archives']
methods = []
for repo in repos: 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) # Add backup targets (system and apps)
backup_manager.set_system_targets(system) backup_manager.set_system_targets(system)

View file

@ -77,7 +77,8 @@ class BackupRepository(object):
try: try:
cls.repositories = read_json(REPOSITORIES_PATH) cls.repositories = read_json(REPOSITORIES_PATH)
except MoulinetteError as e: 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 return cls.repositories
@classmethod @classmethod
@ -98,7 +99,8 @@ class BackupRepository(object):
self.name = location if name is None else name self.name = location if name is None else name
if created and self.name in BackupMethod.repositories: 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.description = description
self.encryption = encryption self.encryption = encryption
@ -106,11 +108,7 @@ class BackupRepository(object):
if method is None: if method is None:
method = 'tar' if self.domain is None else 'borg' method = 'tar' if self.domain is None else 'borg'
if created: self.method = BackupMethod.get(method, self)
self.method = BackupMethod.create(method, self)
else:
self.method = BackupMethod.get(method, self)
def compute_space_used(self): def compute_space_used(self):
if self.used is None: if self.used is None:
@ -128,127 +126,127 @@ class BackupRepository(object):
repositories.pop(self.name) repositories.pop(self.name)
BackupRepository.save() BackupRepository.save()
if purge: if purge:
self.purge() self.purge()
def save(self): def save(self):
BackupRepository.reposirories[self.name] = self.__dict__ BackupRepository.reposirories[self.name] = self.__dict__
BackupRepository.save() BackupRepository.save()
def _split_location(self): def _split_location(self):
"""
Split a repository location into protocol, user, domain and path
"""
location_regex = r'^((?P<protocol>ssh://)?(?P<user>[^@ ]+)@(?P<domain>[^: ]+:))?(?P<path>[^\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'^((?P<protocol>ssh://)?(?P<user>[^@ ]+)@(?P<domain>[^: ]+:))?(?P<path>[^\0]+)$' repositories = BackupRepository.load()
location_match = re.match(location_regex, self.location)
if location_match is None: if full:
raise YunohostError('backup_repositories_invalid_location', return repositories
location=location) else:
return repositories.keys()
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): def backup_repository_info(name, human_readable=True, space_used=False):
""" """
List available repositories where put archives Show info about a repository
"""
repositories = BackupRepository.load()
if full: Keyword arguments:
return repositories name -- Name of the backup repository
else: """
return repositories.keys() 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): @is_unit_operation()
""" def backup_repository_add(operation_logger, location, name, description=None,
Show info about a repository methods=None, quota=None, encryption="passphrase"):
"""
Add a backup repository
Keyword arguments: Keyword arguments:
name -- Name of the backup repository location -- Location of the repository (could be a remote location)
""" name -- Name of the backup repository
repository = BackupRepository.get(name) 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: try:
repository.compute_space_used() repository.save()
except MoulinetteError:
raise YunohostError('backup_repository_add_failed',
repository=name, location=location)
repository = repository.__dict__ logger.success(m18n.n('backup_repository_added',
if human_readable: repository=name, location=location))
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
@is_unit_operation() @is_unit_operation()
def backup_repository_add(operation_logger, location, name, description=None, def backup_repository_update(operation_logger, name, description=None,
methods=None, quota=None, encryption="passphrase"): quota=None, password=None):
""" """
Add a backup repository Update a backup repository
Keyword arguments: Keyword arguments:
location -- Location of the repository (could be a remote location) name -- Name of the backup repository
name -- Name of the backup repository """
description -- An optionnal description repository = BackupRepository.get(name)
quota -- Maximum size quota of the repository
encryption -- If available, the kind of encryption to use
"""
repository = BackupRepository(
location, name, description, methods, quota, encryption)
try: if description is not None:
repository.save() repository.description = description
except MoulinetteError:
raise YunohostError('backup_repository_add_failed',
repository=name, location=location)
logger.success(m18n.n('backup_repository_added', if quota is not None:
repository=name, location=location)) 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() @is_unit_operation()
def backup_repository_update(operation_logger, name, description=None, def backup_repository_remove(operation_logger, name, purge=False):
quota=None, password=None): """
""" Remove a backup repository
Update a backup repository
Keyword arguments: Keyword arguments:
name -- Name of the backup repository name -- Name of the backup repository to remove
"""
repository = BackupRepository.get(name)
if description is not None: """
repository.description = description repository = BackupRepository.get(name)
repository.delete(purge)
if quota is not None: logger.success(m18n.n('backup_repository_removed', repository=name,
repository.quota = quota path=repository['path']))
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']))