From a51e395bcaaecd91caa253ade9388c8115b799cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 5 Oct 2015 17:43:48 +0200 Subject: [PATCH] [fix] Validate backup/restore hooks and show restoration result --- lib/yunohost/backup.py | 106 ++++++++++++++++++++++++++++++++--------- locales/en.json | 9 +++- 2 files changed, 90 insertions(+), 25 deletions(-) diff --git a/lib/yunohost/backup.py b/lib/yunohost/backup.py index 7a911ddec..30911e8fc 100644 --- a/lib/yunohost/backup.py +++ b/lib/yunohost/backup.py @@ -62,7 +62,7 @@ def backup_create(name=None, description=None, output_directory=None, """ # TODO: Add a 'clean' argument to clean output directory - from yunohost.hook import hook_callback, hook_exec + from yunohost.hook import hook_list, hook_callback, hook_exec tmp_dir = None @@ -136,9 +136,24 @@ def backup_create(name=None, description=None, output_directory=None, # Run system hooks if not ignore_hooks: - msignals.display(m18n.n('backup_running_hooks')) - hooks_ret = hook_callback('backup', hooks, args=[tmp_dir]) - info['hooks'] = hooks_ret['succeed'] + # Check hooks availibility + hooks_available = hook_list('backup')['hooks'] + hooks_filtered = set() + if hooks: + for hook in hooks: + if hook not in hooks_available: + logger.exception("backup hook '%s' not found", hook) + msignals.display(m18n.n('backup_hook_unknown', hook=hook), + 'error') + else: + hooks_filtered.add(hook) + else: + hooks_filtered = hooks_available + + if hooks_filtered: + msignals.display(m18n.n('backup_running_hooks')) + ret = hook_callback('backup', hooks_filtered, args=[tmp_dir]) + info['hooks'] = ret['succeed'] # Backup apps if not ignore_apps: @@ -205,10 +220,10 @@ def backup_create(name=None, description=None, output_directory=None, finally: filesystem.rm(tmp_script, force=True) - # Check if something has been saved - if ignore_hooks and not info['apps']: - _clean_tmp_dir(1) - raise MoulinetteError(errno.EINVAL, m18n.n('backup_nothings_done')) + # Check if something has been saved + if not info['hooks'] and not info['apps']: + _clean_tmp_dir(1) + raise MoulinetteError(errno.EINVAL, m18n.n('backup_nothings_done')) # Create backup info file with open("%s/info.json" % tmp_dir, 'w') as f: @@ -270,9 +285,12 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals force -- Force restauration on an already installed system """ - from yunohost.hook import hook_add - from yunohost.hook import hook_callback - from yunohost.hook import hook_exec + from yunohost.hook import hook_add, hook_list, hook_callback, hook_exec + + # Validate what to restore + if ignore_hooks and ignore_apps: + raise MoulinetteError(errno.EINVAL, + m18n.n('restore_action_required')) # Retrieve and open the archive info = backup_info(name) @@ -291,6 +309,13 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals tmp_dir) os.system('rm -rf %s' % tmp_dir) + def _clean_tmp_dir(retcode=0): + ret = hook_callback('post_backup_restore', args=[tmp_dir, retcode]) + if not ret['failed']: + filesystem.rm(tmp_dir, True, True) + else: + msignals.display(m18n.n('restore_cleaning_failed'), 'warning') + # Extract the tarball msignals.display(m18n.n('backup_extracting_archive')) tar.extractall(tmp_dir) @@ -308,6 +333,12 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals logger.info("restoring from backup '%s' created on %s", name, time.ctime(info['created_at'])) + # Initialize restauration summary result + result = { + 'apps': [], + 'hooks': {}, + } + # Check if YunoHost is installed if os.path.isfile('/etc/yunohost/installed'): msignals.display(m18n.n('yunohost_already_installed'), 'warning') @@ -338,18 +369,40 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals logger.info("executing the post-install...") tools_postinstall(domain, 'yunohost', True) - # Run hooks + # Run system hooks if not ignore_hooks: - if hooks is None or len(hooks)==0: - hooks=info['hooks'].keys() + # Filter hooks to execute + hooks_list = set(info['hooks'].keys()) + _is_hook_in_backup = lambda h: True + if hooks: + def _is_hook_in_backup(h): + if h in hooks_list: + return True + logger.warning("hook '%s' not executed in the backup '%s'", + h, archive_file) + msignals.display(m18n.n('backup_archive_hook_not_exec', hook=h), + 'error') + return False + else: + hooks = hooks_list - hooks_filtered=list(set(hooks) & set(info['hooks'].keys())) - hooks_unexecuted=set(hooks) - set(info['hooks'].keys()) - for hook in hooks_unexecuted: - logger.warning("hook '%s' not in this backup", hook) - msignals.display(m18n.n('backup_hook_unavailable', hook), 'warning') - msignals.display(m18n.n('restore_running_hooks')) - hook_callback('restore', hooks_filtered, args=[tmp_dir]) + # Check hooks availibility + hooks_available = hook_list('restore')['hooks'] + hooks_filtered = set() + for hook in hooks: + if not _is_hook_in_backup(hook): + continue + if hook not in hooks_available: + logger.exception("restoration hook '%s' not found", hook) + msignals.display(m18n.n('restore_hook_unavailable', hook=hook), + 'error') + continue + hooks_filtered.add(hook) + + if hooks_filtered: + msignals.display(m18n.n('restore_running_hooks')) + ret = hook_callback('restore', hooks_filtered, args=[tmp_dir]) + result['hooks'] = ret['succeed'] # Add apps restore hook if not ignore_apps: @@ -409,14 +462,21 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals 'error') # Cleaning app directory shutil.rmtree(app_setting_path, ignore_errors=True) + else: + result['apps'].append(app_id) finally: filesystem.rm(tmp_script, force=True) - # Remove temporary directory - os.system('rm -rf %s' % tmp_dir) + # Check if something has been restored + if not result['hooks'] and not result['apps']: + _clean_tmp_dir(1) + raise MoulinetteError(errno.EINVAL, m18n.n('restore_nothings_done')) + _clean_tmp_dir() msignals.display(m18n.n('restore_complete'), 'success') + return result + def backup_list(with_info=False, human_readable=False): """ diff --git a/locales/en.json b/locales/en.json index 1890f5c73..02ddc02a2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -144,6 +144,7 @@ "backup_output_directory_required" : "You must provide an output directory for the backup", "backup_output_directory_forbidden" : "Forbidden output directory", "backup_output_directory_not_empty" : "Output directory is not empty", + "backup_hook_unknown" : "Backup hook '{hook:s}' unknown", "backup_running_hooks" : "Running backup hooks...", "backup_running_app_script" : "Running backup script of app '{:s}'...", "backup_creating_archive" : "Creating the backup archive...", @@ -151,18 +152,22 @@ "backup_archive_open_failed" : "Unable to open the backup archive", "backup_archive_name_unknown" : "Unknown local backup archive named '{:s}'", "backup_archive_name_exists" : "Backup archive name already exists", + "backup_archive_hook_not_exec" : "Hook '{hook:s}' not executed in this backup", "backup_archive_app_not_found" : "App '{app:s}' not found in the backup archive", "backup_app_failed" : "Unable to back up the app '{app:s}'", "backup_nothings_done" : "There is nothing to save", - "backup_cleaning_failed" : "Unable to clean backup directory", + "backup_cleaning_failed" : "Unable to clean backup temporary directory", "backup_complete" : "Backup complete", "backup_invalid_archive" : "Invalid backup archive", - "backup_hook_unavailable" : "The hook '{:s}' is not in this backup", + "restore_action_required" : "You must specify something to restore", "restore_confirm_yunohost_installed" : "Do you really want to restore an already installed system? [{answers:s}]", + "restore_hook_unavailable" : "Restauration hook '{hook:s}' not available on your system", "restore_app_failed" : "Unable to restore the app '{app:s}'", "restore_running_hooks" : "Running restoration hooks...", "restore_running_app_script" : "Running restore script of app '{app:s}'...", "restore_failed" : "Unable to restore the system", + "restore_nothings_done" : "Nothing has been restored", + "restore_cleaning_failed" : "Unable to clean restoration temporary directory", "restore_complete" : "Restore complete", "restore_already_installed_app": "An app is already installed with the id '{app:s}'", "unbackup_app" : "App '{app:s}' will not be saved",