setup: Try force upgrade before running app setup

Avoid running setup if it would bypass a needed force upgrade.

Fixes: #2397

Tests:

- Rerun setup on an app and see that there are no errors.

- Install modified freedombox on bookworm and perform dist-upgrade to
  testing. Then rerun setup on Firewall app. It fails with the message "App
  firewall failed to force upgrade." firewalld package is not upgraded.

- Modify Firewall app to allow force upgrade to latest version. Then rerun
  setup on Firewall app. firewalld is successfully force upgraded.

  NOTE: In this case, Firewall setup is run twice, once by force upgrade, and
  again by rerun setup.

Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
James Valleroy 2024-02-10 16:44:28 -05:00
parent 7b2a65d449
commit 29d48e86b7
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808

View File

@ -54,10 +54,19 @@ def run_setup_on_app(app_id, allow_install=True, rerun=False):
def _run_setup_on_app(app, current_version):
"""Execute the setup process."""
global _force_upgrader
logger.info('Setup run: %s', app.app_id)
exception_to_update = None
message = None
try:
if not _force_upgrader:
_force_upgrader = ForceUpgrader()
# Check if this app needs force_upgrade. If it is needed, but not yet
# supported for the new version of the package, then an exception will
# be raised, so that we do not run setup.
_force_upgrader.attempt_upgrade_for_app(app.app_id)
current_version = app.get_setup_version()
app.setup(old_version=current_version)
app.set_setup_version(app.info.version)
@ -448,6 +457,50 @@ class ForceUpgrader():
if need_retry:
raise self.TemporaryFailure('Some apps failed to force upgrade.')
def attempt_upgrade_for_app(self, app_id):
"""Attempt to perform an upgrade for specified app.
Raise TemporaryFailure if upgrade can't be performed now.
Raise PermanentFailure if upgrade can't be performed until something
with the system state changes. We don't want to try again until
notified of further package cache changes.
Return True if upgrade was performed successfully.
Return False if upgrade is not needed.
"""
if _is_shutting_down:
raise self.PermanentFailure('Service is shutting down')
if packages_privileged.is_package_manager_busy():
raise self.TemporaryFailure('Package manager is busy')
apps = self._get_list_of_apps_to_force_upgrade()
if app_id not in apps:
logger.info('App %s does not need force upgrade', app_id)
return False
packages = apps[app_id]
app = app_module.App.get(app_id)
try:
logger.info('Force upgrading app: %s', app.info.name)
if app.force_upgrade(packages):
logger.info('Successfully force upgraded app: %s',
app.info.name)
return True
else:
logger.warning('Ignored force upgrade for app: %s',
app.info.name)
raise self.TemporaryFailure(
'Force upgrade is needed, but not yet implemented for new '
f'version of app: {app_id}')
except Exception as exception:
logger.exception('Error running force upgrade: %s', exception)
raise self.TemporaryFailure(
f'App {app_id} failed to force upgrade.')
def _run_force_upgrade_as_operation(self, app, packages):
"""Start an operation for force upgrading."""
name = gettext_noop('Updating app packages')