update checks for app requirements as generator

This commit is contained in:
axolotle 2022-11-14 19:30:25 +01:00
parent 727bef92e5
commit e202df4793

View file

@ -29,7 +29,7 @@ import subprocess
import tempfile import tempfile
import copy import copy
from collections import OrderedDict from collections import OrderedDict
from typing import List, Tuple, Dict, Any from typing import List, Tuple, Dict, Any, Iterator
from packaging import version from packaging import version
from moulinette import Moulinette, m18n from moulinette import Moulinette, m18n
@ -587,7 +587,11 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False
upgrade_type = "UPGRADE_FULL" upgrade_type = "UPGRADE_FULL"
# Check requirements # Check requirements
_check_manifest_requirements(manifest, action="upgrade") for _, check, values, err in _check_manifest_requirements(
manifest, action="upgrade"
):
if not check:
raise YunohostValidationError(err, **values)
if manifest["packaging_format"] >= 2: if manifest["packaging_format"] >= 2:
if no_safety_backup: if no_safety_backup:
@ -788,6 +792,18 @@ def app_manifest(app):
raw_questions = manifest.get("install", {}).values() raw_questions = manifest.get("install", {}).values()
manifest["install"] = hydrate_questions_with_choices(raw_questions) manifest["install"] = hydrate_questions_with_choices(raw_questions)
manifest["requirements"] = {}
for name, check, values, err in _check_manifest_requirements(
manifest, action="install"
):
if Moulinette.interface.type == "api":
manifest["requirements"][name] = {
"pass": check,
"values": values,
}
else:
manifest["requirements"][name] = "ok" if check else m18n.n(err, **values)
return manifest return manifest
@ -873,7 +889,17 @@ def app_install(
app_id = manifest["id"] app_id = manifest["id"]
# Check requirements # Check requirements
_check_manifest_requirements(manifest, action="install") for name, check, values, err in _check_manifest_requirements(
manifest, action="install"
):
if not check:
if name == "ram":
_ask_confirmation(
"confirm_app_install_insufficient_ram", params=values, force=force
)
else:
raise YunohostValidationError(err, **values)
_assert_system_is_sane_for_app(manifest, "pre") _assert_system_is_sane_for_app(manifest, "pre")
# Check if app can be forked # Check if app can be forked
@ -2351,71 +2377,97 @@ def _get_all_installed_apps_id():
return all_apps_ids_formatted return all_apps_ids_formatted
def _check_manifest_requirements(manifest: Dict, action: str): def _check_manifest_requirements(
manifest: Dict, action: str = ""
) -> Iterator[Tuple[str, bool, object, str]]:
"""Check if required packages are met from the manifest""" """Check if required packages are met from the manifest"""
if manifest["packaging_format"] not in [1, 2]:
raise YunohostValidationError("app_packaging_format_not_supported")
app_id = manifest["id"] app_id = manifest["id"]
logger.debug(m18n.n("app_requirements_checking", app=app_id)) logger.debug(m18n.n("app_requirements_checking", app=app_id))
# Yunohost version requirement # Packaging format
yield (
yunohost_requirement = version.parse(manifest["integration"]["yunohost"] or "4.3") "packaging_format",
yunohost_installed_version = version.parse( manifest["packaging_format"] in (1, 2),
get_ynh_package_version("yunohost")["version"] {},
"app_packaging_format_not_supported", # i18n: app_packaging_format_not_supported
) )
if yunohost_requirement > yunohost_installed_version:
# FIXME : i18n # Yunohost version
raise YunohostValidationError( required_yunohost_version = manifest["integration"].get("yunohost", "4.3")
f"This app requires Yunohost >= {yunohost_requirement} but current installed version is {yunohost_installed_version}" current_yunohost_version = get_ynh_package_version("yunohost")["version"]
yield (
"version",
version.parse(required_yunohost_version)
<= version.parse(current_yunohost_version),
{"current": current_yunohost_version, "required": required_yunohost_version},
"app_yunohost_version_not_supported", # i18n: app_yunohost_version_not_supported
) )
# Architectures # Architectures
arch_requirement = manifest["integration"]["architectures"] arch_requirement = manifest["integration"]["architectures"]
if arch_requirement != "all":
arch = system_arch() arch = system_arch()
if arch not in arch_requirement:
# FIXME: i18n yield (
raise YunohostValidationError( "arch",
f"This app can only be installed on architectures {', '.join(arch_requirement)} but your server architecture is {arch}" arch_requirement == "all" or arch in arch_requirement,
{"current": arch, "required": arch_requirement},
"app_arch_not_supported", # i18n: app_arch_not_supported
) )
# Multi-instance # Multi-instance
if action == "install" and manifest["integration"]["multi_instance"] == False: if action == "install":
multi_instance = manifest["integration"]["multi_instance"] == True
if not multi_instance:
apps = _installed_apps() apps = _installed_apps()
sibling_apps = [a for a in apps if a == app_id or a.startswith(f"{app_id}__")] sibling_apps = [
if len(sibling_apps) > 0: a for a in apps if a == app_id or a.startswith(f"{app_id}__")
raise YunohostValidationError("app_already_installed", app=app_id) ]
multi_instance = len(sibling_apps) > 0
yield (
"install",
multi_instance,
{},
"app_already_installed", # i18n: app_already_installed
)
# Disk # Disk
if action == "install": if action == "install":
disk_requirement = manifest["integration"]["disk"] disk_req_bin = human_to_binary(manifest["integration"]["disk"])
root_free_space = free_space_in_directory("/")
if free_space_in_directory("/") <= human_to_binary( var_free_space = free_space_in_directory("/var")
disk_requirement has_enough_disk = (
) or free_space_in_directory("/var") <= human_to_binary(disk_requirement): root_free_space > disk_req_bin and var_free_space > disk_req_bin
# FIXME : i18m )
raise YunohostValidationError( free_space = binary_to_human(
f"This app requires {disk_requirement} free space." root_free_space
if root_free_space == var_free_space
else root_free_space + var_free_space
) )
# Ram for build yield (
ram_build_requirement = manifest["integration"]["ram"]["build"] "disk",
# Is "include_swap" really useful ? We should probably decide wether to always include it or not instead has_enough_disk,
ram_include_swap = manifest["integration"]["ram"].get("include_swap", False) {"current": free_space, "required": manifest["integration"]["disk"]},
"app_not_enough_disk", # i18n: app_not_enough_disk
)
# Ram
ram_requirement = manifest["integration"]["ram"]
ram, swap = ram_available() ram, swap = ram_available()
if ram_include_swap: # Is "include_swap" really useful ? We should probably decide wether to always include it or not instead
if ram_requirement.get("include_swap", False):
ram += swap ram += swap
can_build = ram > human_to_binary(ram_requirement["build"])
can_run = ram > human_to_binary(ram_requirement["runtime"])
if ram < human_to_binary(ram_build_requirement): yield (
# FIXME : i18n "ram",
ram_human = binary_to_human(ram) can_build and can_run,
raise YunohostValidationError( {"current": binary_to_human(ram), "required": ram_requirement["build"]},
f"This app requires {ram_build_requirement} RAM to install/upgrade but only {ram_human} is available right now." "app_not_enough_ram", # i18n: app_not_enough_ram
) )