From 71500ea9dfe0b65d6542b8b7929fbbfb1307412d Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Fri, 6 Sep 2024 11:42:13 -0400 Subject: [PATCH] upgrades: Add repair step for held packages Package holds are only expected when apps are being installed or uninstalled, or during distribution upgrade process. At any other time, package holds are not expected and should be released. Tests: - Place a hold on one package. Run the upgrades diagnostics, which will have a failure. Try to repair the failure, and confirm that the package is no longer held. - Repeat with two or three packages being held. [sunil] - When the package 'needsrestart' is outdated and another package is held, running repair unholds the package as well as runs setup() on the upgrades app leading to 'needsrestart' package getting upgrade. - When only failed diagnostic is for package holds. Running repair unholds the packages but does not rung setup(). Helps: #2347 Signed-off-by: James Valleroy Reviewed-by: Sunil Mohan Adapa --- plinth/modules/upgrades/__init__.py | 15 ++++++++++++++- plinth/modules/upgrades/privileged.py | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/plinth/modules/upgrades/__init__.py b/plinth/modules/upgrades/__init__.py index 56f1553ec..22a046f84 100644 --- a/plinth/modules/upgrades/__init__.py +++ b/plinth/modules/upgrades/__init__.py @@ -46,6 +46,8 @@ BACKPORTS_REQUESTED_KEY = 'upgrades_backports_requested' DIST_UPGRADE_ENABLED_KEY = 'upgrades_dist_upgrade_enabled' +PKG_HOLD_DIAG_CHECK_ID = 'upgrades-package-holds' + logger = logging.getLogger(__name__) @@ -166,6 +168,17 @@ class UpgradesApp(app_module.App): results.append(_diagnose_held_packages()) return results + def repair(self, failed_checks: list) -> bool: + """Handle repair for custom diagnostic.""" + remaining_checks = [] + for check in failed_checks: + if check.check_id == PKG_HOLD_DIAG_CHECK_ID: + privileged.release_held_packages() + else: + remaining_checks.append(check) + + return super().repair(remaining_checks) + def setup_repositories(_): """Setup apt repositories for backports.""" @@ -307,7 +320,7 @@ def test_dist_upgrade(): def _diagnose_held_packages(): """Check if any packages have holds.""" - check = DiagnosticCheck('upgrades-package-holds', + check = DiagnosticCheck(PKG_HOLD_DIAG_CHECK_ID, gettext_noop('Check for package holds'), Result.NOT_DONE) if (package.is_package_manager_busy() diff --git a/plinth/modules/upgrades/privileged.py b/plinth/modules/upgrades/privileged.py index 53521de41..37f21a804 100644 --- a/plinth/modules/upgrades/privileged.py +++ b/plinth/modules/upgrades/privileged.py @@ -19,6 +19,8 @@ from plinth.modules.snapshot import is_apt_snapshots_enabled from plinth.modules.snapshot import is_supported as snapshot_is_supported from plinth.modules.snapshot import load_augeas as snapshot_load_augeas +logger = logging.getLogger(__name__) + SOURCES_LIST = '/etc/apt/sources.list' BACKPORTS_SOURCES_LIST = '/etc/apt/sources.list.d/freedombox2.list' @@ -94,6 +96,25 @@ def _release_held_freedombox(): apt_unhold_freedombox() +@privileged +def release_held_packages(): + """Release any packages that are being held.""" + if is_package_manager_busy(): + logger.warning('Package manager is busy, skipping releasing holds.') + return + + if service_is_running('freedombox-dist-upgrade'): + logger.warning('Distribution upgrade in progress, skipping releasing ' + 'holds.') + return + + output = subprocess.check_output(['apt-mark', 'showhold']).decode().strip() + holds = output.split('\n') + logger.info('Releasing package holds: %s', holds) + subprocess.run(['apt-mark', 'unhold', *holds], stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, check=True) + + @privileged def run(): """Run unattended-upgrades."""