From fba14e55df9c1bb54299c2d5e310bbd26ffbd0e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 8 Mar 2016 22:58:47 +0100 Subject: [PATCH] [enh] Introduce new 'requirements' manifest key (close #113) The 'requirements' key allows to specify the Debian packages which must be installed and their required version. It must be an array of the package name as the key and its version specifier - for its format, see yunohost.utils.packages.Specifier - as value. For example: "requirements": { "yunohost": ">= 2.4, << 2.5" } --- locales/en.json | 4 ++- src/yunohost/app.py | 50 ++++++++++++++++++++++++---------- src/yunohost/utils/packages.py | 17 ++++++++---- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/locales/en.json b/locales/en.json index 6852e9c6..4150f276 100644 --- a/locales/en.json +++ b/locales/en.json @@ -20,7 +20,9 @@ "app_not_installed" : "{app:s} is not installed", "app_not_correctly_installed" : "{app:s} seems to be not correctly installed", "custom_app_url_required" : "You must provide an URL to upgrade your custom app {app:s}", - "app_recent_version_required" : "{app:s} requires a more recent version of YunoHost", + "app_requirements_checking" : "Checking required packages...", + "app_requirements_unmeet" : "Requirements are not met, the package {pkgname} ({version}) must be {spec}", + "app_requirements_failed" : "Unable to meet requirements: {err}", "app_upgraded" : "{app:s} successfully upgraded", "app_upgrade_failed" : "Unable to upgrade {app:s}", "app_id_invalid" : "Invalid app id", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 6d688009..c5ea1bde 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -40,7 +40,7 @@ from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger from yunohost.service import service_log -from yunohost.utils.packages import meets_version_specifier +from yunohost.utils import packages logger = getActionLogger('yunohost.app') @@ -358,13 +358,8 @@ def app_upgrade(auth, app=[], url=None, file=None): else: continue - # Check min version - if 'min_version' in manifest \ - and not meets_version_specifier( - 'yunohost', '>> {0}'.format(manifest['min_version'])): - raise MoulinetteError(errno.EPERM, - m18n.n('app_recent_version_required', - app=app_id)) + # Check requirements + _check_manifest_requirements(manifest) app_setting_path = apps_setting_path +'/'+ app_id @@ -451,13 +446,8 @@ def app_install(auth, app, label=None, args=None): app_id = manifest['id'] - # Check min version - if 'min_version' in manifest \ - and not meets_version_specifier( - 'yunohost', '>> {0}'.format(manifest['min_version'])): - raise MoulinetteError(errno.EPERM, - m18n.n('app_recent_version_required', - app=app_id)) + # Check requirements + _check_manifest_requirements(manifest) # Check if app can be forked instance_number = _installed_instance_number(app_id, last=True) + 1 @@ -1371,6 +1361,36 @@ def _encode_string(value): return value +def _check_manifest_requirements(manifest): + """Check if required packages are met from the manifest""" + requirements = manifest.get('requirements', dict()) + # FIXME: Deprecate min_version key + if 'min_version' in manifest: + requirements['yunohost'] = '>> {0}'.format(manifest['min_version']) + logger.debug("the manifest key 'min_version' is deprecated, " + "use 'requirements' instead.") + if not requirements: + return + + logger.info(m18n.n('app_requirements_checking')) + + # Retrieve versions of each required package + try: + versions = packages.get_installed_version( + *requirements.keys(), strict=True, as_dict=True) + except packages.PackageException as e: + raise MoulinetteError(errno.EINVAL, + m18n.n('app_requirements_failed', err=str(e))) + + # Iterate over requirements + for pkgname, spec in requirements.items(): + version = versions[pkgname] + if version not in packages.SpecifierSet(spec): + raise MoulinetteError( + errno.EINVAL, m18n.n('app_requirements_unmeet', + pkgname=pkgname, version=version, + spec=spec)) + def _parse_args_from_manifest(manifest, action, args={}, auth=None): """Parse arguments needed for an action from the manifest diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index a2c447dc..edade553 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -250,14 +250,18 @@ def get_installed_version(*pkgnames, **kwargs): """Get the installed version of package(s) Retrieve one or more packages named `pkgnames` and return their installed - version as a dict or as a string if only one is requested. If `strict` is - `True`, an exception will be raised if a package is unknown or not - installed. + version as a dict or as a string if only one is requested and `as_dict` is + `False`. If `strict` is `True`, an exception will be raised if a package + is unknown or not installed. """ - cache = apt.Cache() - strict = kwargs.get('strict', False) versions = OrderedDict() + cache = apt.Cache() + + # Retrieve options + as_dict = kwargs.get('as_dict', False) + strict = kwargs.get('strict', False) + for pkgname in pkgnames: try: pkg = cache[pkgname] @@ -272,7 +276,8 @@ def get_installed_version(*pkgnames, **kwargs): raise UninstalledPackage(pkgname) version = None versions[pkgname] = version - if len(pkgnames) == 1: + + if len(pkgnames) == 1 and not as_dict: return versions[pkgnames[0]] return versions