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 <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2019-02-27 12:28:17 -08:00 committed by James Valleroy
parent 7862325bb6
commit ec68eb3d89
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
4 changed files with 56 additions and 32 deletions

View File

@ -172,35 +172,49 @@ def subcommand_filter_conffile_packages(arguments):
apt_pkg.init() # Read configuration that will be used later. apt_pkg.init() # Read configuration that will be used later.
packages = set(arguments.packages) 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) mismatched_hashes = _filter_matching_package_hashes(status_hashes)
downloaded_files = _download_packages(packages) 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) packages, downloaded_files, status_hashes, mismatched_hashes)
conffile_packages = [ packages_info = {}
package for package in packages if _is_conffile_prompt_pending( for package in packages:
modified_conffiles = _get_modified_conffiles(
status_hashes[package], mismatched_hashes[package], status_hashes[package], mismatched_hashes[package],
new_package_hashes[package]) new_package_hashes[package])
] if not modified_conffiles:
print(json.dumps(conffile_packages)) 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, def _get_modified_conffiles(status_hashes, mismatched_hashes,
new_package_hashes): new_package_hashes):
"""Return whether upgrading the package requires a conffile prompt.""" """Return list of conffiles that will cause prompts for a package."""
modified_conffiles = []
for conffile, hash_value in mismatched_hashes.items(): for conffile, hash_value in mismatched_hashes.items():
if conffile not in new_package_hashes: if conffile not in new_package_hashes:
# Modified configuration file not present new package # Modified configuration file not present new package
continue 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 # Configuration file is same in both versions of package. Conffile
# prompt is not triggered even if the file is modified on disk. # 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(): for conffile, hash_value in new_package_hashes.items():
if conffile in status_hashes: 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, # If existing file is a directory, unattended-upgrades allows it,
# we still treat it as a conffile prompt. This should be okay. # 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): 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_hashes = defaultdict(dict)
package_versions = defaultdict(lambda: None)
status_file = apt_pkg.config.find('Dir::State::status') status_file = apt_pkg.config.find('Dir::State::status')
with apt_pkg.TagFile(status_file) as tag_file: 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', '')) hashes = _parse_conffiles_value(section.get('Conffiles', ''))
package_hashes[package] = hashes package_hashes[package] = hashes
package_versions[package] = section.get('Version')
return package_hashes return package_hashes, package_versions
def _parse_conffiles_value(value): def _parse_conffiles_value(value):
@ -335,16 +351,19 @@ def _get_conffile_hashes_from_downloaded_files(
packages, downloaded_files, status_hashes, mismatched_hashes): packages, downloaded_files, status_hashes, mismatched_hashes):
"""Retrieve the conffile hashes from downloaded .deb files.""" """Retrieve the conffile hashes from downloaded .deb files."""
new_hashes = defaultdict(dict) new_hashes = defaultdict(dict)
new_versions = defaultdict(lambda: None)
for downloaded_file in downloaded_files: for downloaded_file in downloaded_files:
try: 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) packages, downloaded_file, status_hashes, mismatched_hashes)
except (LookupError, apt_pkg.Error, ValueError): except (LookupError, apt_pkg.Error, ValueError):
continue continue
new_hashes[package_name] = hashes 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( def _get_conffile_hashes_from_downloaded_file(
@ -359,6 +378,8 @@ def _get_conffile_hashes_from_downloaded_file(
if package_name not in packages: if package_name not in packages:
raise ValueError raise ValueError
new_version = section['Version']
conffiles = deb_file.control.extractdata('conffiles') conffiles = deb_file.control.extractdata('conffiles')
conffiles = conffiles.decode().strip().split() conffiles = conffiles.decode().strip().split()
@ -376,7 +397,7 @@ def _get_conffile_hashes_from_downloaded_file(
md5sum = apt_pkg.md5sum(conffile_data) md5sum = apt_pkg.md5sum(conffile_data)
hashes[conffile] = md5sum hashes[conffile] = md5sum
return package_name, hashes return package_name, hashes, new_version
def main(): def main():

View File

@ -104,7 +104,7 @@ def setup(helper, old_version=None):
helper.call('post', actions.superuser_run, 'bind', ['setup']) 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.""" """Force upgrade the managed packages to resolve conffile prompt."""
helper.install(managed_packages, force_configuration='old') helper.install(managed_packages, force_configuration='old')

View File

@ -186,8 +186,13 @@ def refresh_package_lists():
transaction.refresh_package_lists() transaction.refresh_package_lists()
def filter_conffile_prompts(packages): def filter_conffile_prompt_packages(packages):
"""Return a filtered list of packages that require conffile prompts.""" """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( response = actions.superuser_run(
'packages', 'packages',
['filter-conffile-packages', '--packages'] + list(packages)) ['filter-conffile-packages', '--packages'] + list(packages))

View File

@ -501,17 +501,13 @@ class ForceUpgrader():
apps = self._get_list_of_apps_to_force_upgrade() apps = self._get_list_of_apps_to_force_upgrade()
logger.info('Apps needing conffile upgrades: %s', 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 need_retry = False
for app in apps: for app, packages in apps.items():
try: try:
logger.info('Force upgrading app: %s', app.name) logger.info('Force upgrading app: %s', app.name)
# XXX: Try to send information about package versions being app.force_upgrade(app.setup_helper, packages)
# 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)
logger.info('Successfully force upgraded app: %s', app.name) logger.info('Successfully force upgraded app: %s', app.name)
except Exception as exception: except Exception as exception:
logger.exception('Error running force upgrade: %s', 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.""" """Return a list of app modules on which to run force upgrade."""
packages = self._get_list_of_upgradable_packages() packages = self._get_list_of_upgradable_packages()
if not packages: # No packages to upgrade if not packages: # No packages to upgrade
return [] return {}
package_names = [package.name for package in packages] package_names = [package.name for package in packages]
logger.info('Packages available for upgrade: %s', logger.info('Packages available for upgrade: %s',
@ -534,15 +530,17 @@ class ForceUpgrader():
managed_packages, package_apps_map = self._filter_managed_packages( managed_packages, package_apps_map = self._filter_managed_packages(
package_names) package_names)
if not managed_packages: 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', logger.info('Packages that need conffile upgrades: %s',
conffile_packages) conffile_packages)
apps = set() apps = defaultdict(list)
for package_name in conffile_packages: 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 return apps