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.
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():

View File

@ -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')

View File

@ -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))

View File

@ -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