From 42b41aa934c8ca88c21795d4976d690c463cf9a1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Feb 2019 17:00:46 +0100 Subject: [PATCH 01/14] Add new regen-conf API in tools category --- data/actionsmap/yunohost.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index cbe959b55..fabdcb923 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1623,6 +1623,32 @@ tools: full: --force action: store_true + ### tools_regen_conf() + regen-conf: + action_help: Regenerate the configuration file(s) + api: PUT /tools/regenconf + arguments: + names: + help: Categories to regenerate configuration of (all by default) + nargs: "*" + metavar: NAME + -d: + full: --with-diff + help: Show differences in case of configuration changes + action: store_true + -f: + full: --force + help: Override all manual modifications in configuration files + action: store_true + -n: + full: --dry-run + help: Show what would have been regenerated + action: store_true + -p: + full: --list-pending + help: List pending configuration files and exit + action: store_true + subcategories: migrations: From 739bf8e559af5e52cc0c55bf614d649b8737c7ee Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Feb 2019 17:23:44 +0100 Subject: [PATCH 02/14] Brutally move regenconf stuff to a new regenconf.py file --- src/yunohost/regenconf.py | 546 ++++++++++++++++++++++++++++++++++++++ src/yunohost/service.py | 475 --------------------------------- 2 files changed, 546 insertions(+), 475 deletions(-) create mode 100644 src/yunohost/regenconf.py diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py new file mode 100644 index 000000000..0330db508 --- /dev/null +++ b/src/yunohost/regenconf.py @@ -0,0 +1,546 @@ +# -*- coding: utf-8 -*- + +""" License + + Copyright (C) 2019 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 + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, see http://www.gnu.org/licenses + +""" + +import os +import yaml +import json +import subprocess +import shutil +import hashlib + +from difflib import unified_diff +from datetime import datetime + +from moulinette import m18n +from moulinette.utils import log, filesystem + +from yunohost.utils.error import YunohostError +from yunohost.log import is_unit_operation +from yunohost.hook import hook_callback, hook_list + +BASE_CONF_PATH = '/home/yunohost.conf' +BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup') +PENDING_CONF_DIR = os.path.join(BASE_CONF_PATH, 'pending') + +logger = log.getActionLogger('yunohost.regenconf') + + +@is_unit_operation([('names', 'service')]) +def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run=False, + list_pending=False): + """ + Regenerate the configuration file(s) for a service + + Keyword argument: + names -- Services name to regenerate configuration of + with_diff -- Show differences in case of configuration changes + force -- Override all manual modifications in configuration files + dry_run -- Show what would have been regenerated + list_pending -- List pending configuration files and exit + + """ + result = {} + + # Return the list of pending conf + if list_pending: + pending_conf = _get_pending_conf(names) + + if not with_diff: + return pending_conf + + for service, conf_files in pending_conf.items(): + for system_path, pending_path in conf_files.items(): + + pending_conf[service][system_path] = { + 'pending_conf': pending_path, + 'diff': _get_files_diff( + system_path, pending_path, True), + } + + return pending_conf + + if not dry_run: + operation_logger.related_to = [('service', x) for x in names] + if not names: + operation_logger.name_parameter_override = 'all' + elif len(names) != 1: + operation_logger.name_parameter_override = str(len(operation_logger.related_to)) + '_services' + operation_logger.start() + + # Clean pending conf directory + if os.path.isdir(PENDING_CONF_DIR): + if not names: + shutil.rmtree(PENDING_CONF_DIR, ignore_errors=True) + else: + for name in names: + shutil.rmtree(os.path.join(PENDING_CONF_DIR, name), + ignore_errors=True) + else: + filesystem.mkdir(PENDING_CONF_DIR, 0o755, True) + + # Format common hooks arguments + common_args = [1 if force else 0, 1 if dry_run else 0] + + # Execute hooks for pre-regen + pre_args = ['pre', ] + common_args + + def _pre_call(name, priority, path, args): + # create the pending conf directory for the service + service_pending_path = os.path.join(PENDING_CONF_DIR, name) + filesystem.mkdir(service_pending_path, 0o755, True, uid='root') + + # return the arguments to pass to the script + return pre_args + [service_pending_path, ] + + # Don't regen SSH if not specifically specified + if not names: + names = hook_list('conf_regen', list_by='name', + show_info=False)['hooks'] + names.remove('ssh') + + pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call) + + # Update the services name + names = pre_result['succeed'].keys() + + if not names: + raise YunohostError('service_regenconf_failed', + services=', '.join(pre_result['failed'])) + + # Set the processing method + _regen = _process_regen_conf if not dry_run else lambda *a, **k: True + + operation_logger.related_to = [] + + # Iterate over services and process pending conf + for service, conf_files in _get_pending_conf(names).items(): + if not dry_run: + operation_logger.related_to.append(('service', service)) + + logger.debug(m18n.n( + 'service_regenconf_pending_applying' if not dry_run else + 'service_regenconf_dry_pending_applying', + service=service)) + + conf_hashes = _get_conf_hashes(service) + succeed_regen = {} + failed_regen = {} + + for system_path, pending_path in conf_files.items(): + logger.debug("processing pending conf '%s' to system conf '%s'", + pending_path, system_path) + conf_status = None + regenerated = False + + # Get the diff between files + conf_diff = _get_files_diff( + system_path, pending_path, True) if with_diff else None + + # Check if the conf must be removed + to_remove = True if os.path.getsize(pending_path) == 0 else False + + # Retrieve and calculate hashes + system_hash = _calculate_hash(system_path) + saved_hash = conf_hashes.get(system_path, None) + new_hash = None if to_remove else _calculate_hash(pending_path) + + # -> system conf does not exists + if not system_hash: + if to_remove: + logger.debug("> system conf is already removed") + os.remove(pending_path) + continue + if not saved_hash or force: + if force: + logger.debug("> system conf has been manually removed") + conf_status = 'force-created' + else: + logger.debug("> system conf does not exist yet") + conf_status = 'created' + regenerated = _regen( + system_path, pending_path, save=False) + else: + logger.info(m18n.n( + 'service_conf_file_manually_removed', + conf=system_path)) + conf_status = 'removed' + + # -> system conf is not managed yet + elif not saved_hash: + logger.debug("> system conf is not managed yet") + if system_hash == new_hash: + logger.debug("> no changes to system conf has been made") + conf_status = 'managed' + regenerated = True + elif not to_remove: + # If the conf exist but is not managed yet, and is not to be removed, + # we assume that it is safe to regen it, since the file is backuped + # anyway (by default in _regen), as long as we warn the user + # appropriately. + logger.info(m18n.n('service_conf_now_managed_by_yunohost', + conf=system_path)) + regenerated = _regen(system_path, pending_path) + conf_status = 'new' + elif force: + regenerated = _regen(system_path) + conf_status = 'force-removed' + else: + logger.info(m18n.n('service_conf_file_kept_back', + conf=system_path, service=service)) + conf_status = 'unmanaged' + + # -> system conf has not been manually modified + elif system_hash == saved_hash: + if to_remove: + regenerated = _regen(system_path) + conf_status = 'removed' + elif system_hash != new_hash: + regenerated = _regen(system_path, pending_path) + conf_status = 'updated' + else: + logger.debug("> system conf is already up-to-date") + os.remove(pending_path) + continue + + else: + logger.debug("> system conf has been manually modified") + if system_hash == new_hash: + logger.debug("> new conf is as current system conf") + conf_status = 'managed' + regenerated = True + elif force: + regenerated = _regen(system_path, pending_path) + conf_status = 'force-updated' + else: + logger.warning(m18n.n( + 'service_conf_file_manually_modified', + conf=system_path)) + conf_status = 'modified' + + # Store the result + conf_result = {'status': conf_status} + if conf_diff is not None: + conf_result['diff'] = conf_diff + if regenerated: + succeed_regen[system_path] = conf_result + conf_hashes[system_path] = new_hash + if os.path.isfile(pending_path): + os.remove(pending_path) + else: + failed_regen[system_path] = conf_result + + # Check for service conf changes + if not succeed_regen and not failed_regen: + logger.debug(m18n.n('service_conf_up_to_date', service=service)) + continue + elif not failed_regen: + logger.success(m18n.n( + 'service_conf_updated' if not dry_run else + 'service_conf_would_be_updated', + service=service)) + + if succeed_regen and not dry_run: + _update_conf_hashes(service, conf_hashes) + + # Append the service results + result[service] = { + 'applied': succeed_regen, + 'pending': failed_regen + } + + # Return in case of dry run + if dry_run: + return result + + # Execute hooks for post-regen + post_args = ['post', ] + common_args + + def _pre_call(name, priority, path, args): + # append coma-separated applied changes for the service + if name in result and result[name]['applied']: + regen_conf_files = ','.join(result[name]['applied'].keys()) + else: + regen_conf_files = '' + return post_args + [regen_conf_files, ] + + hook_callback('conf_regen', names, pre_callback=_pre_call) + + operation_logger.success() + + return result + + +def _get_services(): + """ + Get a dict of managed services with their parameters + + """ + try: + with open('/etc/yunohost/services.yml', 'r') as f: + services = yaml.load(f) + except: + return {} + else: + # some services are marked as None to remove them from YunoHost + # filter this + for key, value in services.items(): + if value is None: + del services[key] + + return services + + +def _save_services(services): + """ + Save managed services to files + + Keyword argument: + services -- A dict of managed services with their parameters + + """ + try: + with open('/etc/yunohost/services.yml', 'w') as f: + yaml.safe_dump(services, f, default_flow_style=False) + except Exception as e: + logger.warning('Error while saving services, exception: %s', e, exc_info=1) + raise + + +def _get_files_diff(orig_file, new_file, as_string=False, skip_header=True): + """Compare two files and return the differences + + Read and compare two files. The differences are returned either as a delta + in unified diff format or a formatted string if as_string is True. The + header can also be removed if skip_header is True. + + """ + + if os.path.exists(orig_file): + with open(orig_file, 'r') as orig_file: + orig_file = orig_file.readlines() + else: + orig_file = [] + + if os.path.exists(new_file): + with open(new_file, 'r') as new_file: + new_file = new_file.readlines() + else: + new_file = [] + + # Compare files and format output + diff = unified_diff(orig_file, new_file) + + if skip_header: + try: + next(diff) + next(diff) + except: + pass + + if as_string: + return ''.join(diff).rstrip() + + return diff + + +def _calculate_hash(path): + """Calculate the MD5 hash of a file""" + + if not os.path.exists(path): + return None + + hasher = hashlib.md5() + + try: + with open(path, 'rb') as f: + hasher.update(f.read()) + return hasher.hexdigest() + + except IOError as e: + logger.warning("Error while calculating file '%s' hash: %s", path, e, exc_info=1) + return None + + +def _get_pending_conf(services=[]): + """Get pending configuration for service(s) + + Iterate over the pending configuration directory for given service(s) - or + all if empty - and look for files inside. Each file is considered as a + pending configuration file and therefore must be in the same directory + tree than the system file that it replaces. + The result is returned as a dict of services with pending configuration as + key and a dict of `system_conf_path` => `pending_conf_path` as value. + + """ + result = {} + + if not os.path.isdir(PENDING_CONF_DIR): + return result + + if not services: + services = os.listdir(PENDING_CONF_DIR) + + for name in services: + service_pending_path = os.path.join(PENDING_CONF_DIR, name) + + if not os.path.isdir(service_pending_path): + continue + + path_index = len(service_pending_path) + service_conf = {} + + for root, dirs, files in os.walk(service_pending_path): + for filename in files: + pending_path = os.path.join(root, filename) + service_conf[pending_path[path_index:]] = pending_path + + if service_conf: + result[name] = service_conf + else: + # remove empty directory + shutil.rmtree(service_pending_path, ignore_errors=True) + + return result + + +def _get_conf_hashes(service): + """Get the registered conf hashes for a service""" + + services = _get_services() + + if service not in services: + logger.debug("Service %s is not in services.yml yet.", service) + return {} + + elif services[service] is None or 'conffiles' not in services[service]: + logger.debug("No configuration files for service %s.", service) + return {} + + else: + return services[service]['conffiles'] + + +def _update_conf_hashes(service, hashes): + """Update the registered conf hashes for a service""" + logger.debug("updating conf hashes for '%s' with: %s", + service, hashes) + services = _get_services() + service_conf = services.get(service, {}) + + # Handle the case where services[service] is set to null in the yaml + if service_conf is None: + service_conf = {} + + service_conf['conffiles'] = hashes + services[service] = service_conf + _save_services(services) + + +def _process_regen_conf(system_conf, new_conf=None, save=True): + """Regenerate a given system configuration file + + Replace a given system configuration file by a new one or delete it if + new_conf is None. A backup of the file - keeping its directory tree - will + be done in the backup conf directory before any operation if save is True. + + """ + if save: + backup_path = os.path.join(BACKUP_CONF_DIR, '{0}-{1}'.format( + system_conf.lstrip('/'), datetime.utcnow().strftime("%Y%m%d.%H%M%S"))) + backup_dir = os.path.dirname(backup_path) + + if not os.path.isdir(backup_dir): + filesystem.mkdir(backup_dir, 0o755, True) + + shutil.copy2(system_conf, backup_path) + logger.debug(m18n.n('service_conf_file_backed_up', + conf=system_conf, backup=backup_path)) + + try: + if not new_conf: + os.remove(system_conf) + logger.debug(m18n.n('service_conf_file_removed', + conf=system_conf)) + else: + system_dir = os.path.dirname(system_conf) + + if not os.path.isdir(system_dir): + filesystem.mkdir(system_dir, 0o755, True) + + shutil.copyfile(new_conf, system_conf) + logger.debug(m18n.n('service_conf_file_updated', + conf=system_conf)) + except Exception as e: + logger.warning("Exception while trying to regenerate conf '%s': %s", system_conf, e, exc_info=1) + if not new_conf and os.path.exists(system_conf): + logger.warning(m18n.n('service_conf_file_remove_failed', + conf=system_conf), + exc_info=1) + return False + + elif new_conf: + try: + # From documentation: + # Raise an exception if an os.stat() call on either pathname fails. + # (os.stats returns a series of information from a file like type, size...) + copy_succeed = os.path.samefile(system_conf, new_conf) + except: + copy_succeed = False + finally: + if not copy_succeed: + logger.warning(m18n.n('service_conf_file_copy_failed', + conf=system_conf, new=new_conf), + exc_info=1) + return False + + return True + + +def manually_modified_files(): + + # We do this to have --quiet, i.e. don't throw a whole bunch of logs + # just to fetch this... + # Might be able to optimize this by looking at what service_regenconf does + # and only do the part that checks file hashes... + cmd = "yunohost service regen-conf --dry-run --output-as json --quiet" + j = json.loads(subprocess.check_output(cmd.split())) + + # j is something like : + # {"postfix": {"applied": {}, "pending": {"/etc/postfix/main.cf": {"status": "modified"}}} + + output = [] + for app, actions in j.items(): + for action, files in actions.items(): + for filename, infos in files.items(): + if infos["status"] == "modified": + output.append(filename) + + return output + + +def manually_modified_files_compared_to_debian_default(): + + # from https://serverfault.com/a/90401 + r = subprocess.check_output("dpkg-query -W -f='${Conffiles}\n' '*' \ + | awk 'OFS=\" \"{print $2,$1}' \ + | md5sum -c 2>/dev/null \ + | awk -F': ' '$2 !~ /OK/{print $1}'", shell=True) + return r.strip().split("\n") diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 60729053b..ab0e791d8 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -26,12 +26,8 @@ import os import time import yaml -import json import subprocess -import shutil -import hashlib -from difflib import unified_diff from datetime import datetime from moulinette import m18n @@ -39,11 +35,7 @@ from yunohost.utils.error import YunohostError from moulinette.utils import log, filesystem from yunohost.log import is_unit_operation -from yunohost.hook import hook_callback, hook_list -BASE_CONF_PATH = '/home/yunohost.conf' -BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup') -PENDING_CONF_DIR = os.path.join(BASE_CONF_PATH, 'pending') MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" logger = log.getActionLogger('yunohost.service') @@ -418,251 +410,6 @@ def service_log(name, number=50): return result -@is_unit_operation([('names', 'service')]) -def service_regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run=False, - list_pending=False): - """ - Regenerate the configuration file(s) for a service - - Keyword argument: - names -- Services name to regenerate configuration of - with_diff -- Show differences in case of configuration changes - force -- Override all manual modifications in configuration files - dry_run -- Show what would have been regenerated - list_pending -- List pending configuration files and exit - - """ - result = {} - - # Return the list of pending conf - if list_pending: - pending_conf = _get_pending_conf(names) - - if not with_diff: - return pending_conf - - for service, conf_files in pending_conf.items(): - for system_path, pending_path in conf_files.items(): - - pending_conf[service][system_path] = { - 'pending_conf': pending_path, - 'diff': _get_files_diff( - system_path, pending_path, True), - } - - return pending_conf - - if not dry_run: - operation_logger.related_to = [('service', x) for x in names] - if not names: - operation_logger.name_parameter_override = 'all' - elif len(names) != 1: - operation_logger.name_parameter_override = str(len(operation_logger.related_to)) + '_services' - operation_logger.start() - - # Clean pending conf directory - if os.path.isdir(PENDING_CONF_DIR): - if not names: - shutil.rmtree(PENDING_CONF_DIR, ignore_errors=True) - else: - for name in names: - shutil.rmtree(os.path.join(PENDING_CONF_DIR, name), - ignore_errors=True) - else: - filesystem.mkdir(PENDING_CONF_DIR, 0o755, True) - - # Format common hooks arguments - common_args = [1 if force else 0, 1 if dry_run else 0] - - # Execute hooks for pre-regen - pre_args = ['pre', ] + common_args - - def _pre_call(name, priority, path, args): - # create the pending conf directory for the service - service_pending_path = os.path.join(PENDING_CONF_DIR, name) - filesystem.mkdir(service_pending_path, 0o755, True, uid='root') - - # return the arguments to pass to the script - return pre_args + [service_pending_path, ] - - # Don't regen SSH if not specifically specified - if not names: - names = hook_list('conf_regen', list_by='name', - show_info=False)['hooks'] - names.remove('ssh') - - pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call) - - # Update the services name - names = pre_result['succeed'].keys() - - if not names: - raise YunohostError('service_regenconf_failed', - services=', '.join(pre_result['failed'])) - - # Set the processing method - _regen = _process_regen_conf if not dry_run else lambda *a, **k: True - - operation_logger.related_to = [] - - # Iterate over services and process pending conf - for service, conf_files in _get_pending_conf(names).items(): - if not dry_run: - operation_logger.related_to.append(('service', service)) - - logger.debug(m18n.n( - 'service_regenconf_pending_applying' if not dry_run else - 'service_regenconf_dry_pending_applying', - service=service)) - - conf_hashes = _get_conf_hashes(service) - succeed_regen = {} - failed_regen = {} - - for system_path, pending_path in conf_files.items(): - logger.debug("processing pending conf '%s' to system conf '%s'", - pending_path, system_path) - conf_status = None - regenerated = False - - # Get the diff between files - conf_diff = _get_files_diff( - system_path, pending_path, True) if with_diff else None - - # Check if the conf must be removed - to_remove = True if os.path.getsize(pending_path) == 0 else False - - # Retrieve and calculate hashes - system_hash = _calculate_hash(system_path) - saved_hash = conf_hashes.get(system_path, None) - new_hash = None if to_remove else _calculate_hash(pending_path) - - # -> system conf does not exists - if not system_hash: - if to_remove: - logger.debug("> system conf is already removed") - os.remove(pending_path) - continue - if not saved_hash or force: - if force: - logger.debug("> system conf has been manually removed") - conf_status = 'force-created' - else: - logger.debug("> system conf does not exist yet") - conf_status = 'created' - regenerated = _regen( - system_path, pending_path, save=False) - else: - logger.info(m18n.n( - 'service_conf_file_manually_removed', - conf=system_path)) - conf_status = 'removed' - - # -> system conf is not managed yet - elif not saved_hash: - logger.debug("> system conf is not managed yet") - if system_hash == new_hash: - logger.debug("> no changes to system conf has been made") - conf_status = 'managed' - regenerated = True - elif not to_remove: - # If the conf exist but is not managed yet, and is not to be removed, - # we assume that it is safe to regen it, since the file is backuped - # anyway (by default in _regen), as long as we warn the user - # appropriately. - logger.info(m18n.n('service_conf_now_managed_by_yunohost', - conf=system_path)) - regenerated = _regen(system_path, pending_path) - conf_status = 'new' - elif force: - regenerated = _regen(system_path) - conf_status = 'force-removed' - else: - logger.info(m18n.n('service_conf_file_kept_back', - conf=system_path, service=service)) - conf_status = 'unmanaged' - - # -> system conf has not been manually modified - elif system_hash == saved_hash: - if to_remove: - regenerated = _regen(system_path) - conf_status = 'removed' - elif system_hash != new_hash: - regenerated = _regen(system_path, pending_path) - conf_status = 'updated' - else: - logger.debug("> system conf is already up-to-date") - os.remove(pending_path) - continue - - else: - logger.debug("> system conf has been manually modified") - if system_hash == new_hash: - logger.debug("> new conf is as current system conf") - conf_status = 'managed' - regenerated = True - elif force: - regenerated = _regen(system_path, pending_path) - conf_status = 'force-updated' - else: - logger.warning(m18n.n( - 'service_conf_file_manually_modified', - conf=system_path)) - conf_status = 'modified' - - # Store the result - conf_result = {'status': conf_status} - if conf_diff is not None: - conf_result['diff'] = conf_diff - if regenerated: - succeed_regen[system_path] = conf_result - conf_hashes[system_path] = new_hash - if os.path.isfile(pending_path): - os.remove(pending_path) - else: - failed_regen[system_path] = conf_result - - # Check for service conf changes - if not succeed_regen and not failed_regen: - logger.debug(m18n.n('service_conf_up_to_date', service=service)) - continue - elif not failed_regen: - logger.success(m18n.n( - 'service_conf_updated' if not dry_run else - 'service_conf_would_be_updated', - service=service)) - - if succeed_regen and not dry_run: - _update_conf_hashes(service, conf_hashes) - - # Append the service results - result[service] = { - 'applied': succeed_regen, - 'pending': failed_regen - } - - # Return in case of dry run - if dry_run: - return result - - # Execute hooks for post-regen - post_args = ['post', ] + common_args - - def _pre_call(name, priority, path, args): - # append coma-separated applied changes for the service - if name in result and result[name]['applied']: - regen_conf_files = ','.join(result[name]['applied'].keys()) - else: - regen_conf_files = '' - return post_args + [regen_conf_files, ] - - hook_callback('conf_regen', names, pre_callback=_pre_call) - - operation_logger.success() - - return result - - def _run_service_command(action, service): """ Run services management command (start, stop, enable, disable, restart, reload) @@ -860,231 +607,9 @@ def _find_previous_log_file(file): return None -def _get_files_diff(orig_file, new_file, as_string=False, skip_header=True): - """Compare two files and return the differences - - Read and compare two files. The differences are returned either as a delta - in unified diff format or a formatted string if as_string is True. The - header can also be removed if skip_header is True. - - """ - - if os.path.exists(orig_file): - with open(orig_file, 'r') as orig_file: - orig_file = orig_file.readlines() - else: - orig_file = [] - - if os.path.exists(new_file): - with open(new_file, 'r') as new_file: - new_file = new_file.readlines() - else: - new_file = [] - - # Compare files and format output - diff = unified_diff(orig_file, new_file) - - if skip_header: - try: - next(diff) - next(diff) - except: - pass - - if as_string: - return ''.join(diff).rstrip() - - return diff - - -def _calculate_hash(path): - """Calculate the MD5 hash of a file""" - - if not os.path.exists(path): - return None - - hasher = hashlib.md5() - - try: - with open(path, 'rb') as f: - hasher.update(f.read()) - return hasher.hexdigest() - - except IOError as e: - logger.warning("Error while calculating file '%s' hash: %s", path, e, exc_info=1) - return None - - -def _get_pending_conf(services=[]): - """Get pending configuration for service(s) - - Iterate over the pending configuration directory for given service(s) - or - all if empty - and look for files inside. Each file is considered as a - pending configuration file and therefore must be in the same directory - tree than the system file that it replaces. - The result is returned as a dict of services with pending configuration as - key and a dict of `system_conf_path` => `pending_conf_path` as value. - - """ - result = {} - - if not os.path.isdir(PENDING_CONF_DIR): - return result - - if not services: - services = os.listdir(PENDING_CONF_DIR) - - for name in services: - service_pending_path = os.path.join(PENDING_CONF_DIR, name) - - if not os.path.isdir(service_pending_path): - continue - - path_index = len(service_pending_path) - service_conf = {} - - for root, dirs, files in os.walk(service_pending_path): - for filename in files: - pending_path = os.path.join(root, filename) - service_conf[pending_path[path_index:]] = pending_path - - if service_conf: - result[name] = service_conf - else: - # remove empty directory - shutil.rmtree(service_pending_path, ignore_errors=True) - - return result - - -def _get_conf_hashes(service): - """Get the registered conf hashes for a service""" - - services = _get_services() - - if service not in services: - logger.debug("Service %s is not in services.yml yet.", service) - return {} - - elif services[service] is None or 'conffiles' not in services[service]: - logger.debug("No configuration files for service %s.", service) - return {} - - else: - return services[service]['conffiles'] - - -def _update_conf_hashes(service, hashes): - """Update the registered conf hashes for a service""" - logger.debug("updating conf hashes for '%s' with: %s", - service, hashes) - services = _get_services() - service_conf = services.get(service, {}) - - # Handle the case where services[service] is set to null in the yaml - if service_conf is None: - service_conf = {} - - service_conf['conffiles'] = hashes - services[service] = service_conf - _save_services(services) - - -def _process_regen_conf(system_conf, new_conf=None, save=True): - """Regenerate a given system configuration file - - Replace a given system configuration file by a new one or delete it if - new_conf is None. A backup of the file - keeping its directory tree - will - be done in the backup conf directory before any operation if save is True. - - """ - if save: - backup_path = os.path.join(BACKUP_CONF_DIR, '{0}-{1}'.format( - system_conf.lstrip('/'), datetime.utcnow().strftime("%Y%m%d.%H%M%S"))) - backup_dir = os.path.dirname(backup_path) - - if not os.path.isdir(backup_dir): - filesystem.mkdir(backup_dir, 0o755, True) - - shutil.copy2(system_conf, backup_path) - logger.debug(m18n.n('service_conf_file_backed_up', - conf=system_conf, backup=backup_path)) - - try: - if not new_conf: - os.remove(system_conf) - logger.debug(m18n.n('service_conf_file_removed', - conf=system_conf)) - else: - system_dir = os.path.dirname(system_conf) - - if not os.path.isdir(system_dir): - filesystem.mkdir(system_dir, 0o755, True) - - shutil.copyfile(new_conf, system_conf) - logger.debug(m18n.n('service_conf_file_updated', - conf=system_conf)) - except Exception as e: - logger.warning("Exception while trying to regenerate conf '%s': %s", system_conf, e, exc_info=1) - if not new_conf and os.path.exists(system_conf): - logger.warning(m18n.n('service_conf_file_remove_failed', - conf=system_conf), - exc_info=1) - return False - - elif new_conf: - try: - # From documentation: - # Raise an exception if an os.stat() call on either pathname fails. - # (os.stats returns a series of information from a file like type, size...) - copy_succeed = os.path.samefile(system_conf, new_conf) - except: - copy_succeed = False - finally: - if not copy_succeed: - logger.warning(m18n.n('service_conf_file_copy_failed', - conf=system_conf, new=new_conf), - exc_info=1) - return False - - return True - - -def manually_modified_files(): - - # We do this to have --quiet, i.e. don't throw a whole bunch of logs - # just to fetch this... - # Might be able to optimize this by looking at what service_regenconf does - # and only do the part that checks file hashes... - cmd = "yunohost service regen-conf --dry-run --output-as json --quiet" - j = json.loads(subprocess.check_output(cmd.split())) - - # j is something like : - # {"postfix": {"applied": {}, "pending": {"/etc/postfix/main.cf": {"status": "modified"}}} - - output = [] - for app, actions in j.items(): - for action, files in actions.items(): - for filename, infos in files.items(): - if infos["status"] == "modified": - output.append(filename) - - return output - - def _get_journalctl_logs(service, number="all"): try: return subprocess.check_output("journalctl -xn -u {0} -n{1}".format(service, number), shell=True) except: import traceback return "error while get services logs from journalctl:\n%s" % traceback.format_exc() - - -def manually_modified_files_compared_to_debian_default(): - - # from https://serverfault.com/a/90401 - r = subprocess.check_output("dpkg-query -W -f='${Conffiles}\n' '*' \ - | awk 'OFS=\" \"{print $2,$1}' \ - | md5sum -c 2>/dev/null \ - | awk -F': ' '$2 !~ /OK/{print $1}'", shell=True) - return r.strip().split("\n") From 3067e8e8975e77a3309a3038e0ba614647a381b6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 Feb 2019 12:03:17 +0100 Subject: [PATCH 03/14] Misc renaming for consistency, basically service->category --- locales/en.json | 30 ++++---- src/yunohost/regenconf.py | 152 +++++++++++++++++++------------------- 2 files changed, 92 insertions(+), 90 deletions(-) diff --git a/locales/en.json b/locales/en.json index 8528c2576..34517a036 100644 --- a/locales/en.json +++ b/locales/en.json @@ -377,6 +377,21 @@ "port_available": "Port {port:d} is available", "port_unavailable": "Port {port:d} is not available", "recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create' or the admin interface.", + "regenconf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", + "regenconf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'", + "regenconf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by regen-conf (category {category}) but has been kept back.", + "regenconf_file_manually_modified": "The configuration file '{conf}' has been manually modified and will not be updated", + "regenconf_file_manually_removed": "The configuration file '{conf}' has been manually removed and will not be created", + "regenconf_file_remove_failed": "Unable to remove the configuration file '{conf}'", + "regenconf_file_removed": "The configuration file '{conf}' has been removed", + "regenconf_file_updated": "The configuration file '{conf}' has been updated", + "regenconf_now_managed_by_yunohost": "The configuration file '{conf}' is now managed by YunoHost (category {category}).", + "regenconf_up_to_date": "The configuration is already up-to-date for category '{category}'", + "regenconf_updated": "The configuration has been updated for category '{category}'", + "regenconf_would_be_updated": "The configuration would have been updated for category '{category}'", + "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…", + "regenconf_failed": "Unable to regenerate the configuration for category(s): {categories}", + "regenconf_pending_applying": "Applying pending configuration for category '{category}'…", "restore_action_required": "You must specify something to restore", "restore_already_installed_app": "An app is already installed with the id '{app:s}'", "restore_app_failed": "Unable to restore the app '{app:s}'", @@ -405,18 +420,6 @@ "service_already_started": "Service '{service:s}' has already been started", "service_already_stopped": "Service '{service:s}' has already been stopped", "service_cmd_exec_failed": "Unable to execute command '{command:s}'", - "service_conf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", - "service_conf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'", - "service_conf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by service {service} but has been kept back.", - "service_conf_file_manually_modified": "The configuration file '{conf}' has been manually modified and will not be updated", - "service_conf_file_manually_removed": "The configuration file '{conf}' has been manually removed and will not be created", - "service_conf_file_remove_failed": "Unable to remove the configuration file '{conf}'", - "service_conf_file_removed": "The configuration file '{conf}' has been removed", - "service_conf_file_updated": "The configuration file '{conf}' has been updated", - "service_conf_now_managed_by_yunohost": "The configuration file '{conf}' is now managed by YunoHost.", - "service_conf_up_to_date": "The configuration is already up-to-date for service '{service}'", - "service_conf_updated": "The configuration has been updated for service '{service}'", - "service_conf_would_be_updated": "The configuration would have been updated for service '{service}'", "service_description_avahi-daemon": "allows to reach your server using yunohost.local on your local network", "service_description_dnsmasq": "handles domain name resolution (DNS)", "service_description_dovecot": "allows e-mail client to access/fetch email (via IMAP and POP3)", @@ -440,9 +443,6 @@ "service_enable_failed": "Unable to enable service '{service:s}'\n\nRecent service logs:{logs:s}", "service_enabled": "The service '{service:s}' has been enabled", "service_no_log": "No log to display for service '{service:s}'", - "service_regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for service '{service}'…", - "service_regenconf_failed": "Unable to regenerate the configuration for service(s): {services}", - "service_regenconf_pending_applying": "Applying pending configuration for service '{service}'…", "service_remove_failed": "Unable to remove service '{service:s}'", "service_removed": "The service '{service:s}' has been removed", "service_reload_failed": "Unable to reload service '{service:s}'\n\nRecent service logs:{logs:s}", diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 0330db508..4104e2491 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -43,14 +43,16 @@ PENDING_CONF_DIR = os.path.join(BASE_CONF_PATH, 'pending') logger = log.getActionLogger('yunohost.regenconf') +# FIXME : those ain't just services anymore ... what are we supposed to do with this ... +# FIXME : check for all reference of 'service' close to operation_logger stuff @is_unit_operation([('names', 'service')]) def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run=False, list_pending=False): """ - Regenerate the configuration file(s) for a service + Regenerate the configuration file(s) Keyword argument: - names -- Services name to regenerate configuration of + names -- Categories to regenerate configuration of with_diff -- Show differences in case of configuration changes force -- Override all manual modifications in configuration files dry_run -- Show what would have been regenerated @@ -66,10 +68,10 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run if not with_diff: return pending_conf - for service, conf_files in pending_conf.items(): + for category, conf_files in pending_conf.items(): for system_path, pending_path in conf_files.items(): - pending_conf[service][system_path] = { + pending_conf[category][system_path] = { 'pending_conf': pending_path, 'diff': _get_files_diff( system_path, pending_path, True), @@ -103,12 +105,12 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run pre_args = ['pre', ] + common_args def _pre_call(name, priority, path, args): - # create the pending conf directory for the service - service_pending_path = os.path.join(PENDING_CONF_DIR, name) - filesystem.mkdir(service_pending_path, 0o755, True, uid='root') + # create the pending conf directory for the category + category_pending_path = os.path.join(PENDING_CONF_DIR, name) + filesystem.mkdir(category_pending_path, 0o755, True, uid='root') # return the arguments to pass to the script - return pre_args + [service_pending_path, ] + return pre_args + [category_pending_path, ] # Don't regen SSH if not specifically specified if not names: @@ -118,29 +120,29 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call) - # Update the services name + # Update the categorys name names = pre_result['succeed'].keys() if not names: - raise YunohostError('service_regenconf_failed', - services=', '.join(pre_result['failed'])) + raise YunohostError('regenconf_failed', + categories=', '.join(pre_result['failed'])) # Set the processing method _regen = _process_regen_conf if not dry_run else lambda *a, **k: True operation_logger.related_to = [] - # Iterate over services and process pending conf - for service, conf_files in _get_pending_conf(names).items(): + # Iterate over categorys and process pending conf + for category, conf_files in _get_pending_conf(names).items(): if not dry_run: - operation_logger.related_to.append(('service', service)) + operation_logger.related_to.append(('service', category)) logger.debug(m18n.n( - 'service_regenconf_pending_applying' if not dry_run else - 'service_regenconf_dry_pending_applying', - service=service)) + 'regenconf_pending_applying' if not dry_run else + 'regenconf_dry_pending_applying', + category=category)) - conf_hashes = _get_conf_hashes(service) + conf_hashes = _get_conf_hashes(category) succeed_regen = {} failed_regen = {} @@ -179,7 +181,7 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run system_path, pending_path, save=False) else: logger.info(m18n.n( - 'service_conf_file_manually_removed', + 'regenconf_file_manually_removed', conf=system_path)) conf_status = 'removed' @@ -195,16 +197,16 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run # we assume that it is safe to regen it, since the file is backuped # anyway (by default in _regen), as long as we warn the user # appropriately. - logger.info(m18n.n('service_conf_now_managed_by_yunohost', - conf=system_path)) + logger.info(m18n.n('regenconf_now_managed_by_yunohost', + conf=system_path, category=category)) regenerated = _regen(system_path, pending_path) conf_status = 'new' elif force: regenerated = _regen(system_path) conf_status = 'force-removed' else: - logger.info(m18n.n('service_conf_file_kept_back', - conf=system_path, service=service)) + logger.info(m18n.n('regenconf_file_kept_back', + conf=system_path, category=category)) conf_status = 'unmanaged' # -> system conf has not been manually modified @@ -231,7 +233,7 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run conf_status = 'force-updated' else: logger.warning(m18n.n( - 'service_conf_file_manually_modified', + 'regenconf_file_manually_modified', conf=system_path)) conf_status = 'modified' @@ -247,21 +249,21 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run else: failed_regen[system_path] = conf_result - # Check for service conf changes + # Check for category conf changes if not succeed_regen and not failed_regen: - logger.debug(m18n.n('service_conf_up_to_date', service=service)) + logger.debug(m18n.n('regenconf_up_to_date', category=category)) continue elif not failed_regen: logger.success(m18n.n( - 'service_conf_updated' if not dry_run else - 'service_conf_would_be_updated', - service=service)) + 'regenconf_updated' if not dry_run else + 'regenconf_would_be_updated', + category=category)) if succeed_regen and not dry_run: - _update_conf_hashes(service, conf_hashes) + _update_conf_hashes(category, conf_hashes) - # Append the service results - result[service] = { + # Append the category results + result[category] = { 'applied': succeed_regen, 'pending': failed_regen } @@ -274,7 +276,7 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run post_args = ['post', ] + common_args def _pre_call(name, priority, path, args): - # append coma-separated applied changes for the service + # append coma-separated applied changes for the category if name in result and result[name]['applied']: regen_conf_files = ','.join(result[name]['applied'].keys()) else: @@ -379,14 +381,14 @@ def _calculate_hash(path): return None -def _get_pending_conf(services=[]): - """Get pending configuration for service(s) +def _get_pending_conf(categories=[]): + """Get pending configuration for categories - Iterate over the pending configuration directory for given service(s) - or + Iterate over the pending configuration directory for given categories - or all if empty - and look for files inside. Each file is considered as a pending configuration file and therefore must be in the same directory tree than the system file that it replaces. - The result is returned as a dict of services with pending configuration as + The result is returned as a dict of categories with pending configuration as key and a dict of `system_conf_path` => `pending_conf_path` as value. """ @@ -395,63 +397,63 @@ def _get_pending_conf(services=[]): if not os.path.isdir(PENDING_CONF_DIR): return result - if not services: - services = os.listdir(PENDING_CONF_DIR) + if not categories: + categories = os.listdir(PENDING_CONF_DIR) - for name in services: - service_pending_path = os.path.join(PENDING_CONF_DIR, name) + for name in categories: + category_pending_path = os.path.join(PENDING_CONF_DIR, name) - if not os.path.isdir(service_pending_path): + if not os.path.isdir(category_pending_path): continue - path_index = len(service_pending_path) - service_conf = {} + path_index = len(category_pending_path) + category_conf = {} - for root, dirs, files in os.walk(service_pending_path): + for root, dirs, files in os.walk(category_pending_path): for filename in files: pending_path = os.path.join(root, filename) - service_conf[pending_path[path_index:]] = pending_path + category_conf[pending_path[path_index:]] = pending_path - if service_conf: - result[name] = service_conf + if category_conf: + result[name] = category_conf else: # remove empty directory - shutil.rmtree(service_pending_path, ignore_errors=True) + shutil.rmtree(category_pending_path, ignore_errors=True) return result -def _get_conf_hashes(service): - """Get the registered conf hashes for a service""" +def _get_conf_hashes(category): + """Get the registered conf hashes for a category""" - services = _get_services() + categories = _get_categories() - if service not in services: - logger.debug("Service %s is not in services.yml yet.", service) + if category not in categories: + logger.debug("category %s is not in categories.yml yet.", category) return {} - elif services[service] is None or 'conffiles' not in services[service]: - logger.debug("No configuration files for service %s.", service) + elif categories[category] is None or 'conffiles' not in categories[category]: + logger.debug("No configuration files for category %s.", category) return {} else: - return services[service]['conffiles'] + return categories[category]['conffiles'] -def _update_conf_hashes(service, hashes): - """Update the registered conf hashes for a service""" +def _update_conf_hashes(category, hashes): + """Update the registered conf hashes for a category""" logger.debug("updating conf hashes for '%s' with: %s", - service, hashes) - services = _get_services() - service_conf = services.get(service, {}) + category, hashes) + categories = _get_categories() + category_conf = categories.get(category, {}) - # Handle the case where services[service] is set to null in the yaml - if service_conf is None: - service_conf = {} + # Handle the case where categories[category] is set to null in the yaml + if category_conf is None: + category_conf = {} - service_conf['conffiles'] = hashes - services[service] = service_conf - _save_services(services) + category_conf['conffiles'] = hashes + categories[category] = category_conf + _save_categories(categories) def _process_regen_conf(system_conf, new_conf=None, save=True): @@ -471,13 +473,13 @@ def _process_regen_conf(system_conf, new_conf=None, save=True): filesystem.mkdir(backup_dir, 0o755, True) shutil.copy2(system_conf, backup_path) - logger.debug(m18n.n('service_conf_file_backed_up', + logger.debug(m18n.n('regenconf_file_backed_up', conf=system_conf, backup=backup_path)) try: if not new_conf: os.remove(system_conf) - logger.debug(m18n.n('service_conf_file_removed', + logger.debug(m18n.n('regenconf_file_removed', conf=system_conf)) else: system_dir = os.path.dirname(system_conf) @@ -486,12 +488,12 @@ def _process_regen_conf(system_conf, new_conf=None, save=True): filesystem.mkdir(system_dir, 0o755, True) shutil.copyfile(new_conf, system_conf) - logger.debug(m18n.n('service_conf_file_updated', + logger.debug(m18n.n('regenconf_file_updated', conf=system_conf)) except Exception as e: logger.warning("Exception while trying to regenerate conf '%s': %s", system_conf, e, exc_info=1) if not new_conf and os.path.exists(system_conf): - logger.warning(m18n.n('service_conf_file_remove_failed', + logger.warning(m18n.n('regenconf_file_remove_failed', conf=system_conf), exc_info=1) return False @@ -506,7 +508,7 @@ def _process_regen_conf(system_conf, new_conf=None, save=True): copy_succeed = False finally: if not copy_succeed: - logger.warning(m18n.n('service_conf_file_copy_failed', + logger.warning(m18n.n('regenconf_file_copy_failed', conf=system_conf, new=new_conf), exc_info=1) return False @@ -518,9 +520,9 @@ def manually_modified_files(): # We do this to have --quiet, i.e. don't throw a whole bunch of logs # just to fetch this... - # Might be able to optimize this by looking at what service_regenconf does + # Might be able to optimize this by looking at what the regen conf does # and only do the part that checks file hashes... - cmd = "yunohost service regen-conf --dry-run --output-as json --quiet" + cmd = "yunohost tools regen-conf --dry-run --output-as json --quiet" j = json.loads(subprocess.check_output(cmd.split())) # j is something like : From e6f3a99269927a17829977dfa9682eb22827eae6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 17:44:09 +0100 Subject: [PATCH 04/14] Change binding from service_regen_conf to tools_regen_conf --- debian/postinst | 2 +- locales/en.json | 1 + src/yunohost/backup.py | 4 ++-- src/yunohost/certificate.py | 7 +++--- ...0007_ssh_conf_managed_by_yunohost_step1.py | 6 ++--- ...0008_ssh_conf_managed_by_yunohost_step2.py | 6 ++--- src/yunohost/domain.py | 6 ++--- src/yunohost/service.py | 13 +++++++++++ src/yunohost/tools.py | 22 ++++++++++++------- 9 files changed, 44 insertions(+), 23 deletions(-) diff --git a/debian/postinst b/debian/postinst index df7112b9d..83220ae0b 100644 --- a/debian/postinst +++ b/debian/postinst @@ -12,7 +12,7 @@ do_configure() { bash /usr/share/yunohost/hooks/conf_regen/15-nginx init else echo "Regenerating configuration, this might take a while..." - yunohost service regen-conf --output-as none + yunohost tools regen-conf --output-as none echo "Launching migrations.." yunohost tools migrations migrate --auto diff --git a/locales/en.json b/locales/en.json index 34517a036..5ab2a749b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -443,6 +443,7 @@ "service_enable_failed": "Unable to enable service '{service:s}'\n\nRecent service logs:{logs:s}", "service_enabled": "The service '{service:s}' has been enabled", "service_no_log": "No log to display for service '{service:s}'", + "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is depracted! Please use 'yunohost tools regen-conf' instead.", "service_remove_failed": "Unable to remove service '{service:s}'", "service_removed": "The service '{service:s}' has been removed", "service_reload_failed": "Unable to reload service '{service:s}'\n\nRecent service logs:{logs:s}", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index ed7799fc1..9f48700a2 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -50,7 +50,7 @@ from yunohost.hook import ( ) from yunohost.monitor import binary_to_human from yunohost.tools import tools_postinstall -from yunohost.service import service_regen_conf +from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger from functools import reduce @@ -1191,7 +1191,7 @@ class RestoreManager(): else: operation_logger.success() - service_regen_conf() + regen_conf() def _restore_apps(self): """Restore all apps targeted""" diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 855910b8a..d7e8c0157 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -43,7 +43,8 @@ from yunohost.utils.network import get_public_ip from moulinette import m18n from yunohost.app import app_ssowatconf -from yunohost.service import _run_service_command, service_regen_conf +from yunohost.service import _run_service_command +from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger logger = getActionLogger('yunohost.certmanager') @@ -806,7 +807,7 @@ def _enable_certificate(domain, new_cert_folder): if os.path.isfile('/etc/yunohost/installed'): # regen nginx conf to be sure it integrates OCSP Stapling # (We don't do this yet if postinstall is not finished yet) - service_regen_conf(names=['nginx']) + regen_conf(names=['nginx']) _run_service_command("reload", "nginx") @@ -924,7 +925,7 @@ def _regen_dnsmasq_if_needed(): break if do_regen: - service_regen_conf(["dnsmasq"]) + regen_conf(["dnsmasq"]) def _name_self_CA(): diff --git a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py index 080cc0163..39a7d34e3 100644 --- a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py +++ b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py @@ -8,10 +8,10 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import mkdir, rm from yunohost.tools import Migration -from yunohost.service import service_regen_conf, \ - _get_conf_hashes, \ +from yunohost.service import _get_conf_hashes, \ _calculate_hash, \ _run_service_command +from yunohost.regen_conf import regen_conf from yunohost.settings import settings_set from yunohost.utils.error import YunohostError @@ -64,7 +64,7 @@ class MyMigration(Migration): if os.path.exists('/etc/yunohost/from_script'): rm('/etc/yunohost/from_script') copyfile(SSHD_CONF, '/etc/ssh/sshd_config.bkp') - service_regen_conf(names=['ssh'], force=True) + regen_conf(names=['ssh'], force=True) copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF) # Restart ssh and backward if it fail diff --git a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py index 0976f1354..fce44298d 100644 --- a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py +++ b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py @@ -6,9 +6,9 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import chown from yunohost.tools import Migration -from yunohost.service import service_regen_conf, \ - _get_conf_hashes, \ +from yunohost.service import _get_conf_hashes, \ _calculate_hash +from yunohost.regenconf import regen_conf from yunohost.settings import settings_set, settings_get from yunohost.utils.error import YunohostError from yunohost.backup import ARCHIVES_PATH @@ -36,7 +36,7 @@ class MyMigration(Migration): def migrate(self): settings_set("service.ssh.allow_deprecated_dsa_hostkey", False) - service_regen_conf(names=['ssh'], force=True) + regen_conf(names=['ssh'], force=True) # Update local archives folder permissions, so that # admin can scp archives out of the server diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 3d46691f8..54ed81dfb 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -34,7 +34,7 @@ from moulinette.utils.log import getActionLogger import yunohost.certificate -from yunohost.service import service_regen_conf +from yunohost.regenconf import regen_conf from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation @@ -111,7 +111,7 @@ def domain_add(operation_logger, auth, domain, dyndns=False): # Don't regen these conf if we're still in postinstall if os.path.exists('/etc/yunohost/installed'): - service_regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix', 'rspamd']) + regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix', 'rspamd']) app_ssowatconf(auth) except Exception: @@ -164,7 +164,7 @@ def domain_remove(operation_logger, auth, domain, force=False): else: raise YunohostError('domain_deletion_failed') - service_regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix']) + regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix']) app_ssowatconf(auth) hook_callback('post_domain_remove', args=[domain]) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index ab0e791d8..d088f0029 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -410,6 +410,19 @@ def service_log(name, number=50): return result +def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False, + list_pending=False): + + services = _get_services() + for name in names: + if name not in services: + raise YunohostError('service_unknown', service=service) + + logger.warning(m18n.n("service_regen_conf_is_deprecated")) + + return regen_conf(names, with_diff, force, dry_run, list_pending) + + def _run_service_command(action, service): """ Run services management command (start, stop, enable, disable, restart, reload) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index b2fbf380c..deec0746f 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -47,7 +47,8 @@ from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, a from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain from yunohost.dyndns import _dyndns_available, _dyndns_provides from yunohost.firewall import firewall_upnp -from yunohost.service import service_status, service_regen_conf, service_log, service_start, service_enable +from yunohost.service import service_status, service_log, service_start, service_enable +from yunohost.regenconf import regen_conf from yunohost.monitor import monitor_disk, monitor_system from yunohost.utils.packages import ynh_packages_version from yunohost.utils.network import get_public_ip @@ -207,7 +208,7 @@ def tools_maindomain(operation_logger, auth, new_domain=None): # Regen configurations try: with open('/etc/yunohost/installed', 'r'): - service_regen_conf() + regen_conf() except IOError: pass @@ -325,7 +326,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, operation_logger.start() logger.info(m18n.n('yunohost_installing')) - service_regen_conf(['nslcd', 'nsswitch'], force=True) + regen_conf(['nslcd', 'nsswitch'], force=True) # Initialize LDAP for YunoHost # TODO: Improve this part by integrate ldapinit into conf_regen hook @@ -376,7 +377,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, os.system('chmod 644 /etc/ssowat/conf.json.persistent') # Create SSL CA - service_regen_conf(['ssl'], force=True) + regen_conf(['ssl'], force=True) ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' # (Update the serial so that it's specific to this very instance) os.system("openssl rand -hex 19 > %s/serial" % ssl_dir) @@ -405,7 +406,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, logger.success(m18n.n('yunohost_ca_creation_success')) # New domain config - service_regen_conf(['nsswitch'], force=True) + regen_conf(['nsswitch'], force=True) domain_add(auth, domain, dyndns) tools_maindomain(auth, domain) @@ -433,7 +434,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, service_enable("yunohost-firewall") service_start("yunohost-firewall") - service_regen_conf(force=True) + regen_conf(force=True) # Restore original ssh conf, as chosen by the # admin during the initial install @@ -450,13 +451,18 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, else: # We need to explicitly ask the regen conf to regen ssh # (by default, i.e. first argument = None, it won't because it's too touchy) - service_regen_conf(names=["ssh"], force=True) + regen_conf(names=["ssh"], force=True) logger.success(m18n.n('yunohost_configured')) logger.warning(m18n.n('recommend_to_add_first_user')) +def tools_regen_conf(names=[], with_diff=False, force=False, dry_run=False, + list_pending=False): + return regen_conf(names, with_diff, force, dry_run, list_pending) + + def tools_update(ignore_apps=False, ignore_packages=False): """ Update apps & package cache, then display changelog @@ -693,7 +699,7 @@ def tools_diagnosis(auth, private=False): # Domains diagnosis['private']['domains'] = domain_list(auth)['domains'] - diagnosis['private']['regen_conf'] = service_regen_conf(with_diff=True, dry_run=True) + diagnosis['private']['regen_conf'] = regen_conf(with_diff=True, dry_run=True) try: diagnosis['security'] = { From 39891b228c393986101cdbf5093eaa9f1f2227c4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 17:54:24 +0100 Subject: [PATCH 05/14] Inteface with regenconf.yml instead of services.yml --- src/yunohost/regenconf.py | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 4104e2491..783b50c4a 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -39,6 +39,7 @@ from yunohost.hook import hook_callback, hook_list BASE_CONF_PATH = '/home/yunohost.conf' BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup') PENDING_CONF_DIR = os.path.join(BASE_CONF_PATH, 'pending') +REGEN_CONF_FILE = '/etc/yunohost/regenconf.yml' logger = log.getActionLogger('yunohost.regenconf') @@ -290,39 +291,28 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run return result -def _get_services(): +def _get_regenconf_infos(): """ - Get a dict of managed services with their parameters - + Get a dict of regen conf informations """ try: - with open('/etc/yunohost/services.yml', 'r') as f: - services = yaml.load(f) + with open(REGEN_CONF_FILE, 'r') as f: + return yaml.load(f) except: return {} - else: - # some services are marked as None to remove them from YunoHost - # filter this - for key, value in services.items(): - if value is None: - del services[key] - - return services -def _save_services(services): +def _save_regenconf_infos(infos): """ - Save managed services to files - + Save the regen conf informations Keyword argument: - services -- A dict of managed services with their parameters - + categories -- A dict containing the regenconf infos """ try: - with open('/etc/yunohost/services.yml', 'w') as f: - yaml.safe_dump(services, f, default_flow_style=False) + with open(REGEN_CONF_FILE, 'w') as f: + yaml.safe_dump(infos, f, default_flow_style=False) except Exception as e: - logger.warning('Error while saving services, exception: %s', e, exc_info=1) + logger.warning('Error while saving regenconf infos, exception: %s', e, exc_info=1) raise @@ -426,7 +416,7 @@ def _get_pending_conf(categories=[]): def _get_conf_hashes(category): """Get the registered conf hashes for a category""" - categories = _get_categories() + categories = _get_regenconf_infos() if category not in categories: logger.debug("category %s is not in categories.yml yet.", category) @@ -444,7 +434,8 @@ def _update_conf_hashes(category, hashes): """Update the registered conf hashes for a category""" logger.debug("updating conf hashes for '%s' with: %s", category, hashes) - categories = _get_categories() + + categories = _get_regenconf_infos() category_conf = categories.get(category, {}) # Handle the case where categories[category] is set to null in the yaml @@ -453,7 +444,7 @@ def _update_conf_hashes(category, hashes): category_conf['conffiles'] = hashes categories[category] = category_conf - _save_categories(categories) + _save_regenconf_infos(categories) def _process_regen_conf(system_conf, new_conf=None, save=True): From 96bd6f8deb5683475bb5daa23ad2489419e909cc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 18:50:46 +0100 Subject: [PATCH 06/14] Fix a few things for service_regen_conf backward compatibility --- src/yunohost/service.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index d088f0029..56cac7a55 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -414,12 +414,20 @@ def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False, list_pending=False): services = _get_services() + + if isinstance(names, str): + names = [names] + for name in names: - if name not in services: - raise YunohostError('service_unknown', service=service) + if name not in services.keys(): + raise YunohostError('service_unknown', service=name) + + if names is []: + names = services.keys() logger.warning(m18n.n("service_regen_conf_is_deprecated")) + from yunohost.regenconf import regen_conf return regen_conf(names, with_diff, force, dry_run, list_pending) From 0ebbb83191273ca716ecaf1c128ece0705b84c82 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 19:03:43 +0100 Subject: [PATCH 07/14] Add migration for services.yml on existing instance --- data/templates/yunohost/services.yml | 9 ++-- .../0009_decouple_regenconf_from_services.py | 42 +++++++++++++++++++ src/yunohost/regenconf.py | 12 ++++++ 3 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 62509e1e9..0d79b182f 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -20,8 +20,6 @@ mysql: glances: {} ssh: log: /var/log/auth.log -ssl: - status: null metronome: log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err] slapd: @@ -34,10 +32,9 @@ yunohost-firewall: need_lock: true nslcd: log: /var/log/syslog -nsswitch: - status: null -yunohost: - status: null +nsswitch: null +ssl: null +yunohost: null bind9: null tahoe-lafs: null memcached: null diff --git a/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py b/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py new file mode 100644 index 000000000..e65aadfdf --- /dev/null +++ b/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py @@ -0,0 +1,42 @@ +import os + +from moulinette import m18n +from moulinette.utils.log import getActionLogger + +from moulinette.utils.filesystem import read_file +from yunohost.service import _get_services, _save_services +from yunohost.regenconf import _update_conf_hashes + +from yunohost.tools import Migration + +logger = getActionLogger('yunohost.migration') + + +class MyMigration(Migration): + """ + Decouple the regen conf mechanism from the concept of services + """ + + def migrate(self): + + if "conffiles" not in read_file("/etc/yunohost/services.yml") \ + or os.path.exists("/etc/yunohost/regenconf.yml"): + logger.warning(m18n.n("migration_0009_not_needed")) + return + + # For all services + services = _get_services() + for service, infos in services.items(): + # If there are some conffiles (file hashes) + if "conffiles" in infos.keys(): + # Save them using the new regen conf thingy + _update_conf_hashes(service, infos["conffiles"]) + # And delete the old conffile key from the service infos + del services[service]["conffiles"] + + # (Actually save the modification of services) + _save_services(services) + + def backward(self): + + pass diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 783b50c4a..3ea8ccb6d 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -31,6 +31,7 @@ from datetime import datetime from moulinette import m18n from moulinette.utils import log, filesystem +from moulinette.utils.filesystem import read_file from yunohost.utils.error import YunohostError from yunohost.log import is_unit_operation @@ -60,6 +61,17 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run list_pending -- List pending configuration files and exit """ + + # Legacy code to automatically run the migration + # This is required because regen_conf is called before the migration call + # in debian's postinst script + if os.path.exists("/etc/yunohost/installed") \ + and ("conffiles" in read_file("/etc/yunohost/services.yml") \ + or not os.path.exists("/etc/yunohost/regenconf.yml")): + from yunohost.tools import _get_migration_by_name + migration = _get_migration_by_name("decouple_regenconf_from_services") + migration.migrate() + result = {} # Return the list of pending conf From d7d224286237f7bb8733bdccb19ad348674f1331 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 19:10:01 +0100 Subject: [PATCH 08/14] Fix imports in some migrations --- src/yunohost/data_migrations/0003_migrate_to_stretch.py | 6 +++--- .../0007_ssh_conf_managed_by_yunohost_step1.py | 7 ++----- .../0008_ssh_conf_managed_by_yunohost_step2.py | 3 +-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/yunohost/data_migrations/0003_migrate_to_stretch.py b/src/yunohost/data_migrations/0003_migrate_to_stretch.py index 438393216..0db719e15 100644 --- a/src/yunohost/data_migrations/0003_migrate_to_stretch.py +++ b/src/yunohost/data_migrations/0003_migrate_to_stretch.py @@ -10,9 +10,9 @@ from moulinette.utils.filesystem import read_file from yunohost.tools import Migration from yunohost.app import unstable_apps -from yunohost.service import (_run_service_command, - manually_modified_files, - manually_modified_files_compared_to_debian_default) +from yunohost.service import _run_service_command +from yunohost.regenconf import (manually_modified_files, + manually_modified_files_compared_to_debian_default) from yunohost.utils.filesystem import free_space_in_directory from yunohost.utils.packages import get_installed_version from yunohost.utils.network import get_network_interfaces diff --git a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py index 39a7d34e3..959b17fb5 100644 --- a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py +++ b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py @@ -3,15 +3,12 @@ import re from shutil import copyfile -from moulinette import m18n from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import mkdir, rm from yunohost.tools import Migration -from yunohost.service import _get_conf_hashes, \ - _calculate_hash, \ - _run_service_command -from yunohost.regen_conf import regen_conf +from yunohost.service import _run_service_command +from yunohost.regenconf import regen_conf from yunohost.settings import settings_set from yunohost.utils.error import YunohostError diff --git a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py index fce44298d..8984440bd 100644 --- a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py +++ b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py @@ -6,8 +6,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import chown from yunohost.tools import Migration -from yunohost.service import _get_conf_hashes, \ - _calculate_hash +from yunohost.regenconf import _get_conf_hashes, _calculate_hash from yunohost.regenconf import regen_conf from yunohost.settings import settings_set, settings_get from yunohost.utils.error import YunohostError From 984646bb3cd6866ce36e146620ad62fe3361e048 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 19:22:19 +0100 Subject: [PATCH 09/14] Implement strings for migration --- locales/en.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/en.json b/locales/en.json index 5ab2a749b..a606a13dc 100644 --- a/locales/en.json +++ b/locales/en.json @@ -286,6 +286,7 @@ "migration_description_0006_sync_admin_and_root_passwords": "Synchronize admin and root passwords", "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Let the SSH configuration be managed by YunoHost (step 1, automatic)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)", + "migration_description_0009_decouple_regenconf_from_services": "Decouple the regen-conf mechanism from services", "migration_0003_backward_impossible": "The stretch migration cannot be reverted.", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", @@ -311,6 +312,7 @@ "migration_0008_dsa": " - the DSA key will be disabled. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server;", "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", + "migration_0009_not_needed": "This migration already happened somehow ? Skipping.", "migrations_backward": "Migrating backward.", "migrations_bad_value_for_target": "Invalid number for target argument, available migrations numbers are 0 or {}", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", From 69b8f7294b91360f3e937a2b3ab5cf7bd9515331 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 19:36:06 +0100 Subject: [PATCH 10/14] Fix log handling --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index a606a13dc..c10e8c393 100644 --- a/locales/en.json +++ b/locales/en.json @@ -249,7 +249,7 @@ "log_selfsigned_cert_install": "Install self signed certificate on '{}' domain", "log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate", "log_service_enable": "Enable '{}' service", - "log_service_regen_conf": "Regenerate system configurations '{}'", + "log_regen_conf": "Regenerate system configurations '{}'", "log_user_create": "Add '{}' user", "log_user_delete": "Delete '{}' user", "log_user_update": "Update information of '{}' user", From 8e7684c7d5f614faa02ed824e1403ecb0d3e424d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 18 Apr 2019 15:53:16 +0200 Subject: [PATCH 11/14] Use REGEN_CONF_FILE global instead of hard-coded filename --- .../data_migrations/0009_decouple_regenconf_from_services.py | 4 ++-- src/yunohost/regenconf.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py b/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py index e65aadfdf..d552d7c9c 100644 --- a/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py +++ b/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py @@ -5,7 +5,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file from yunohost.service import _get_services, _save_services -from yunohost.regenconf import _update_conf_hashes +from yunohost.regenconf import _update_conf_hashes, REGEN_CONF_FILE from yunohost.tools import Migration @@ -20,7 +20,7 @@ class MyMigration(Migration): def migrate(self): if "conffiles" not in read_file("/etc/yunohost/services.yml") \ - or os.path.exists("/etc/yunohost/regenconf.yml"): + or os.path.exists(REGEN_CONF_FILE): logger.warning(m18n.n("migration_0009_not_needed")) return diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 46add4c00..f4163d27c 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -67,7 +67,7 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run # in debian's postinst script if os.path.exists("/etc/yunohost/installed") \ and ("conffiles" in read_file("/etc/yunohost/services.yml") \ - or not os.path.exists("/etc/yunohost/regenconf.yml")): + or not os.path.exists(REGEN_CONF_FILE)): from yunohost.tools import _get_migration_by_name migration = _get_migration_by_name("decouple_regenconf_from_services") migration.migrate() From 845a7796bedcaa550d7f122fcbdf6d28cec58711 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 18 Apr 2019 16:38:54 +0200 Subject: [PATCH 12/14] Remove old piece of stale code (c.f. previous lines) --- src/yunohost/regenconf.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index f4163d27c..0b574e054 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -144,13 +144,6 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run raise YunohostError('regenconf_failed', categories=', '.join(ret_failed)) - # Update the categorys name - names = pre_result['succeed'].keys() - - if not names: - raise YunohostError('regenconf_failed', - categories=', '.join(pre_result['failed'])) - # Set the processing method _regen = _process_regen_conf if not dry_run else lambda *a, **k: True From e43fcac478fdc718f907d252253a4557f7509917 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 18 Apr 2019 16:42:10 +0200 Subject: [PATCH 13/14] Typo --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index f0fe731b4..9c4183adb 100644 --- a/locales/en.json +++ b/locales/en.json @@ -459,7 +459,7 @@ "service_enable_failed": "Unable to enable service '{service:s}'\n\nRecent service logs:{logs:s}", "service_enabled": "The service '{service:s}' has been enabled", "service_no_log": "No log to display for service '{service:s}'", - "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is depracted! Please use 'yunohost tools regen-conf' instead.", + "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is deprecated! Please use 'yunohost tools regen-conf' instead.", "service_remove_failed": "Unable to remove service '{service:s}'", "service_removed": "The service '{service:s}' has been removed", "service_reload_failed": "Unable to reload service '{service:s}'\n\nRecent service logs:{logs:s}", From 2694a44ee75e6def6e2b119f6fe8a76ee83335de Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 18 Apr 2019 17:05:41 +0200 Subject: [PATCH 14/14] Fix naming for operation logger --- src/yunohost/regenconf.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 0b574e054..48129634a 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -47,7 +47,7 @@ logger = log.getActionLogger('yunohost.regenconf') # FIXME : those ain't just services anymore ... what are we supposed to do with this ... # FIXME : check for all reference of 'service' close to operation_logger stuff -@is_unit_operation([('names', 'service')]) +@is_unit_operation([('names', 'configuration')]) def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run=False, list_pending=False): """ @@ -93,11 +93,11 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run return pending_conf if not dry_run: - operation_logger.related_to = [('service', x) for x in names] + operation_logger.related_to = [('configuration', x) for x in names] if not names: operation_logger.name_parameter_override = 'all' elif len(names) != 1: - operation_logger.name_parameter_override = str(len(operation_logger.related_to)) + '_services' + operation_logger.name_parameter_override = str(len(operation_logger.related_to)) + '_categories' operation_logger.start() # Clean pending conf directory @@ -149,10 +149,10 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run operation_logger.related_to = [] - # Iterate over categorys and process pending conf + # Iterate over categories and process pending conf for category, conf_files in _get_pending_conf(names).items(): if not dry_run: - operation_logger.related_to.append(('service', category)) + operation_logger.related_to.append(('configuration', category)) logger.debug(m18n.n( 'regenconf_pending_applying' if not dry_run else