From ec68eb3d89c917b4ae6fdb3da11cbae915eca61d Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Wed, 27 Feb 2019 12:28:17 -0800 Subject: [PATCH] setup: Make additional info available for force upgrading This includes list of packages for which conffile prompts will be shown. For each package current version of the package, new version of the package and list of configuration files that were modified. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- actions/packages | 55 +++++++++++++++++++++++---------- plinth/modules/bind/__init__.py | 2 +- plinth/package.py | 9 ++++-- plinth/setup.py | 22 ++++++------- 4 files changed, 56 insertions(+), 32 deletions(-) diff --git a/actions/packages b/actions/packages index 4f65d6882..b0072cbf3 100755 --- a/actions/packages +++ b/actions/packages @@ -172,35 +172,49 @@ def subcommand_filter_conffile_packages(arguments): apt_pkg.init() # Read configuration that will be used later. packages = set(arguments.packages) - status_hashes = _get_conffile_hashes_from_status_file(packages) + status_hashes, current_versions = _get_conffile_hashes_from_status_file( + packages) mismatched_hashes = _filter_matching_package_hashes(status_hashes) downloaded_files = _download_packages(packages) - new_package_hashes = _get_conffile_hashes_from_downloaded_files( + new_package_hashes, new_versions = _get_conffile_hashes_from_downloaded_files( packages, downloaded_files, status_hashes, mismatched_hashes) - conffile_packages = [ - package for package in packages if _is_conffile_prompt_pending( + packages_info = {} + for package in packages: + modified_conffiles = _get_modified_conffiles( status_hashes[package], mismatched_hashes[package], new_package_hashes[package]) - ] - print(json.dumps(conffile_packages)) + if not modified_conffiles: + continue + + package_info = { + 'current_version': current_versions[package], + 'new_version': new_versions[package], + 'modified_conffiles': modified_conffiles + } + packages_info[package] = package_info + + print(json.dumps(packages_info)) -def _is_conffile_prompt_pending(status_hashes, mismatched_hashes, - new_package_hashes): - """Return whether upgrading the package requires a conffile prompt.""" +def _get_modified_conffiles(status_hashes, mismatched_hashes, + new_package_hashes): + """Return list of conffiles that will cause prompts for a package.""" + modified_conffiles = [] for conffile, hash_value in mismatched_hashes.items(): if conffile not in new_package_hashes: # Modified configuration file not present new package continue - if status_hashes[conffile] != new_package_hashes[conffile]: + if status_hashes[conffile] == new_package_hashes[conffile]: # Configuration file is same in both versions of package. Conffile # prompt is not triggered even if the file is modified on disk. - return True + continue + + modified_conffiles.append(conffile) for conffile, hash_value in new_package_hashes.items(): if conffile in status_hashes: @@ -218,9 +232,9 @@ def _is_conffile_prompt_pending(status_hashes, mismatched_hashes, # # If existing file is a directory, unattended-upgrades allows it, # we still treat it as a conffile prompt. This should be okay. - return True + modified_conffiles.append(conffile) - return False + return modified_conffiles def _get_conffile_hashes_from_status_file(packages): @@ -231,6 +245,7 @@ def _get_conffile_hashes_from_status_file(packages): """ package_hashes = defaultdict(dict) + package_versions = defaultdict(lambda: None) status_file = apt_pkg.config.find('Dir::State::status') with apt_pkg.TagFile(status_file) as tag_file: @@ -241,8 +256,9 @@ def _get_conffile_hashes_from_status_file(packages): hashes = _parse_conffiles_value(section.get('Conffiles', '')) package_hashes[package] = hashes + package_versions[package] = section.get('Version') - return package_hashes + return package_hashes, package_versions def _parse_conffiles_value(value): @@ -335,16 +351,19 @@ def _get_conffile_hashes_from_downloaded_files( packages, downloaded_files, status_hashes, mismatched_hashes): """Retrieve the conffile hashes from downloaded .deb files.""" new_hashes = defaultdict(dict) + new_versions = defaultdict(lambda: None) for downloaded_file in downloaded_files: try: - package_name, hashes = _get_conffile_hashes_from_downloaded_file( + package_name, hashes, new_version = \ + _get_conffile_hashes_from_downloaded_file( packages, downloaded_file, status_hashes, mismatched_hashes) except (LookupError, apt_pkg.Error, ValueError): continue new_hashes[package_name] = hashes + new_versions[package_name] = new_version - return new_hashes + return new_hashes, new_versions def _get_conffile_hashes_from_downloaded_file( @@ -359,6 +378,8 @@ def _get_conffile_hashes_from_downloaded_file( if package_name not in packages: raise ValueError + new_version = section['Version'] + conffiles = deb_file.control.extractdata('conffiles') conffiles = conffiles.decode().strip().split() @@ -376,7 +397,7 @@ def _get_conffile_hashes_from_downloaded_file( md5sum = apt_pkg.md5sum(conffile_data) hashes[conffile] = md5sum - return package_name, hashes + return package_name, hashes, new_version def main(): diff --git a/plinth/modules/bind/__init__.py b/plinth/modules/bind/__init__.py index b29b76839..b5425701e 100644 --- a/plinth/modules/bind/__init__.py +++ b/plinth/modules/bind/__init__.py @@ -104,7 +104,7 @@ def setup(helper, old_version=None): helper.call('post', actions.superuser_run, 'bind', ['setup']) -def force_upgrade(helper): +def force_upgrade(helper, _packages): """Force upgrade the managed packages to resolve conffile prompt.""" helper.install(managed_packages, force_configuration='old') diff --git a/plinth/package.py b/plinth/package.py index a50a87092..a278a57dd 100644 --- a/plinth/package.py +++ b/plinth/package.py @@ -186,8 +186,13 @@ def refresh_package_lists(): transaction.refresh_package_lists() -def filter_conffile_prompts(packages): - """Return a filtered list of packages that require conffile prompts.""" +def filter_conffile_prompt_packages(packages): + """Return a filtered info on packages that require conffile prompts. + + Information for each package includes: current_version, new_version and + list of modified_conffiles. + + """ response = actions.superuser_run( 'packages', ['filter-conffile-packages', '--packages'] + list(packages)) diff --git a/plinth/setup.py b/plinth/setup.py index 5646e59d8..e1a2d2b37 100644 --- a/plinth/setup.py +++ b/plinth/setup.py @@ -501,17 +501,13 @@ class ForceUpgrader(): apps = self._get_list_of_apps_to_force_upgrade() logger.info('Apps needing conffile upgrades: %s', - ', '.join([app.name for app in apps])) + ', '.join([str(app.name) for app in apps])) need_retry = False - for app in apps: + for app, packages in apps.items(): try: logger.info('Force upgrading app: %s', app.name) - # XXX: Try to send information about package versions being - # upgraded and the configuration files that have lead to the - # force upgrade. This can be picked up from the 'packages - # filter-conffile-packages' that does all the relevant work. - app.force_upgrade(app.setup_helper) + app.force_upgrade(app.setup_helper, packages) logger.info('Successfully force upgraded app: %s', app.name) except Exception as exception: logger.exception('Error running force upgrade: %s', exception) @@ -525,7 +521,7 @@ class ForceUpgrader(): """Return a list of app modules on which to run force upgrade.""" packages = self._get_list_of_upgradable_packages() if not packages: # No packages to upgrade - return [] + return {} package_names = [package.name for package in packages] logger.info('Packages available for upgrade: %s', @@ -534,15 +530,17 @@ class ForceUpgrader(): managed_packages, package_apps_map = self._filter_managed_packages( package_names) if not managed_packages: - return [] + return {} - conffile_packages = package.filter_conffile_prompts(managed_packages) + conffile_packages = package.filter_conffile_prompt_packages( + managed_packages) logger.info('Packages that need conffile upgrades: %s', conffile_packages) - apps = set() + apps = defaultdict(list) for package_name in conffile_packages: - apps.update(package_apps_map[package_name]) + for app in package_apps_map[package_name]: + apps[app].append(conffile_packages[package_name]) return apps