From 24f7ffe3cfe1698c26b8da89bf4baae4022ff05c Mon Sep 17 00:00:00 2001 From: Fioddor Superconcentrado Date: Tue, 31 Aug 2021 00:04:09 +0200 Subject: [PATCH] package: Add functions for removing packages Functions needed to spot and remove installed conflicting packages before installation of apps. - Remove all packages in a single operation as this way apt can search for solutions to conflicts more easily. - Use type hints rather than a lot of type checking. Type hints shall later be enforced using offline checking (with mypy) or at runtime (with enforce, etc.). Signed-off-by: Fioddor Superconcentrado [sunil: Run single remove operation on all packages] [sunil: Use type hints instead of extensive type checking] [sunil: Trim down the test case as it would only succeed after install] Reviewed-by: Sunil Mohan Adapa package --- actions/packages | 13 ++++++++++++- plinth/package.py | 30 ++++++++++++++++++++++++++++++ plinth/tests/test_package.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 plinth/tests/test_package.py diff --git a/actions/packages b/actions/packages index 53d2afa7d..1a907d86c 100755 --- a/actions/packages +++ b/actions/packages @@ -48,6 +48,11 @@ def parse_arguments(): 'module', help='name of module for which package is being installed') subparser.add_argument('packages', nargs='+', help='list of packages to install') + + subparser = subparsers.add_parser('remove', help='remove the package(s)') + subparser.add_argument('--packages', required=True, + help='List of packages to remove', nargs='+') + subparsers.add_parser('is-package-manager-busy', help='Return whether package manager is busy') subparser = subparsers.add_parser( @@ -100,6 +105,11 @@ def subcommand_install(arguments): sys.exit(returncode) +def subcommand_remove(arguments): + """Remove apt package(s).""" + sys.exit(run_apt_command(['remove'] + arguments.packages)) + + def _assert_managed_packages(module, packages): """Check that list of packages are in fact managed by module.""" cfg.read() @@ -116,7 +126,8 @@ def _assert_managed_packages(module, packages): def subcommand_is_package_manager_busy(_): """Check whether package manager is busy. - An exit code of zero indicates that package manager is busy.""" + An exit code of zero indicates that package manager is busy. + """ if not is_package_manager_busy(): sys.exit(-1) diff --git a/plinth/package.py b/plinth/package.py index c6221a0c9..58314d630 100644 --- a/plinth/package.py +++ b/plinth/package.py @@ -7,11 +7,14 @@ import json import logging import subprocess import threading +from typing import Union +import apt.cache from django.utils.translation import gettext as _ from django.utils.translation import gettext_lazy from plinth import actions +from plinth.errors import ActionError, PlinthError from plinth.utils import format_lazy logger = logging.getLogger(__name__) @@ -195,3 +198,30 @@ def filter_conffile_prompt_packages(packages): 'packages', ['filter-conffile-packages', '--packages'] + list(packages)) return json.loads(response) + + +def packages_installed(candidates: Union[list, tuple]) -> list: + """Check which candidates are installed on the system. + + :param candidates: A list of package names. + :return: A list of installed Debian package names. + """ + cache = apt.cache.Cache() + installed_packages = [] + for package_name in candidates: + try: + package = cache[package_name] + if package.is_installed: + installed_packages.append(package_name) + except KeyError: + pass + + return installed_packages + + +def remove(packages: Union[list, tuple]) -> None: + """Remove packages.""" + try: + actions.superuser_run('packages', ['remove', '--packages'] + packages) + except ActionError: + pass diff --git a/plinth/tests/test_package.py b/plinth/tests/test_package.py new file mode 100644 index 000000000..3339172ca --- /dev/null +++ b/plinth/tests/test_package.py @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +Test module for package module. +""" + +from unittest.mock import call, patch + +from plinth.errors import ActionError +from plinth.package import packages_installed, remove + + +def test_packages_installed(): + """Test packages_installed().""" + # list as input + assert len(packages_installed([])) == 0 + assert len(packages_installed(['unknown-package'])) == 0 + assert len(packages_installed(['python3'])) == 1 + # tuples as input + assert len(packages_installed(())) == 0 + assert len(packages_installed(('unknown-package', ))) == 0 + assert len(packages_installed(('python3', ))) == 1 + + +@patch('plinth.actions.superuser_run') +def test_remove(run): + """Test removing packages.""" + remove(['package1', 'package2']) + run.assert_has_calls( + [call('packages', ['remove', '--packages', 'package1', 'package2'])]) + + run.reset_mock() + run.side_effect = ActionError() + remove(['package1']) + run.assert_has_calls( + [call('packages', ['remove', '--packages', 'package1'])])