mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
manifestv2: forget about webpath ressource, replace with permissions ressource
This commit is contained in:
parent
364a3bc70a
commit
dfc37a48c3
2 changed files with 119 additions and 92 deletions
69
src/app.py
69
src/app.py
|
@ -859,13 +859,13 @@ def app_install(
|
||||||
|
|
||||||
# If packaging_format v2+, save all install questions as settings
|
# If packaging_format v2+, save all install questions as settings
|
||||||
if packaging_format >= 2:
|
if packaging_format >= 2:
|
||||||
for arg_name, arg_value in args.items():
|
for question in questions:
|
||||||
|
|
||||||
# ... except is_public ....
|
# Except user-provider passwords
|
||||||
if arg_name == "is_public":
|
if question.type == "password":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
app_settings[arg_name] = arg_value
|
app_settings[question.name] = question.value
|
||||||
|
|
||||||
_set_app_settings(app_instance_name, app_settings)
|
_set_app_settings(app_instance_name, app_settings)
|
||||||
|
|
||||||
|
@ -878,36 +878,27 @@ def app_install(
|
||||||
recursive=True,
|
recursive=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize the main permission for the app
|
|
||||||
# The permission is initialized with no url associated, and with tile disabled
|
|
||||||
# For web app, the root path of the app will be added as url and the tile
|
|
||||||
# will be enabled during the app install. C.f. 'app_register_url()' below
|
|
||||||
# or the webpath resource
|
|
||||||
if packaging_format >= 2:
|
|
||||||
if args.get("init_permission_main"):
|
|
||||||
init_main_perm_allowed = args.get("init_permission_main")
|
|
||||||
else:
|
|
||||||
init_main_perm_allowed = ["visitors"] if not args.get("is_public") else ["all_users"]
|
|
||||||
|
|
||||||
else:
|
|
||||||
init_main_perm_allowed = ["all_users"]
|
|
||||||
|
|
||||||
permission_create(
|
|
||||||
app_instance_name + ".main",
|
|
||||||
allowed=init_main_perm_allowed,
|
|
||||||
label=label if label else manifest["name"],
|
|
||||||
show_tile=False,
|
|
||||||
protected=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
if packaging_format >= 2:
|
if packaging_format >= 2:
|
||||||
from yunohost.utils.resources import AppResourceManager
|
from yunohost.utils.resources import AppResourceManager
|
||||||
try:
|
try:
|
||||||
AppResourceManager(app_instance_name, wanted=manifest["resources"], current={}).apply()
|
AppResourceManager(app_instance_name, wanted=manifest, current={}).apply()
|
||||||
except Exception:
|
except Exception:
|
||||||
# FIXME : improve error handling ....
|
# FIXME : improve error handling ....
|
||||||
AppResourceManager(app_instance_name, wanted={}, current=manifest["resources"]).apply()
|
AppResourceManager(app_instance_name, wanted={}, current=manifest).apply()
|
||||||
raise
|
raise
|
||||||
|
else:
|
||||||
|
# Initialize the main permission for the app
|
||||||
|
# The permission is initialized with no url associated, and with tile disabled
|
||||||
|
# For web app, the root path of the app will be added as url and the tile
|
||||||
|
# will be enabled during the app install. C.f. 'app_register_url()' below
|
||||||
|
# or the webpath resource
|
||||||
|
permission_create(
|
||||||
|
app_instance_name + ".main",
|
||||||
|
allowed=["all_users"],
|
||||||
|
label=label if label else manifest["name"],
|
||||||
|
show_tile=False,
|
||||||
|
protected=False,
|
||||||
|
)
|
||||||
|
|
||||||
# Prepare env. var. to pass to script
|
# Prepare env. var. to pass to script
|
||||||
env_dict = _make_environment_for_app_script(
|
env_dict = _make_environment_for_app_script(
|
||||||
|
@ -1008,15 +999,15 @@ def app_install(
|
||||||
if packaging_format >= 2:
|
if packaging_format >= 2:
|
||||||
from yunohost.utils.resources import AppResourceManager
|
from yunohost.utils.resources import AppResourceManager
|
||||||
try:
|
try:
|
||||||
AppResourceManager(app_instance_name, wanted={}, current=manifest["resources"]).apply()
|
AppResourceManager(app_instance_name, wanted={}, current=manifest).apply()
|
||||||
except Exception:
|
except Exception:
|
||||||
# FIXME : improve error handling ....
|
# FIXME : improve error handling ....
|
||||||
raise
|
raise
|
||||||
|
else:
|
||||||
# Remove all permission in LDAP
|
# Remove all permission in LDAP
|
||||||
for permission_name in user_permission_list()["permissions"].keys():
|
for permission_name in user_permission_list()["permissions"].keys():
|
||||||
if permission_name.startswith(app_instance_name + "."):
|
if permission_name.startswith(app_instance_name + "."):
|
||||||
permission_delete(permission_name, force=True, sync_perm=False)
|
permission_delete(permission_name, force=True, sync_perm=False)
|
||||||
|
|
||||||
if remove_retcode != 0:
|
if remove_retcode != 0:
|
||||||
msg = m18n.n("app_not_properly_removed", app=app_instance_name)
|
msg = m18n.n("app_not_properly_removed", app=app_instance_name)
|
||||||
|
@ -1116,18 +1107,18 @@ def app_remove(operation_logger, app, purge=False):
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(tmp_workdir_for_app)
|
shutil.rmtree(tmp_workdir_for_app)
|
||||||
|
|
||||||
# Remove all permission in LDAP
|
|
||||||
for permission_name in user_permission_list(apps=[app])["permissions"].keys():
|
|
||||||
permission_delete(permission_name, force=True, sync_perm=False)
|
|
||||||
|
|
||||||
packaging_format = manifest["packaging_format"]
|
packaging_format = manifest["packaging_format"]
|
||||||
if packaging_format >= 2:
|
if packaging_format >= 2:
|
||||||
try:
|
try:
|
||||||
from yunohost.utils.resources import AppResourceManager
|
from yunohost.utils.resources import AppResourceManager
|
||||||
AppResourceManager(app, wanted={}, current=manifest["resources"]).apply()
|
AppResourceManager(app, wanted={}, current=manifest).apply()
|
||||||
except Exception:
|
except Exception:
|
||||||
# FIXME : improve error handling ....
|
# FIXME : improve error handling ....
|
||||||
raise
|
raise
|
||||||
|
else:
|
||||||
|
# Remove all permission in LDAP
|
||||||
|
for permission_name in user_permission_list(apps=[app])["permissions"].keys():
|
||||||
|
permission_delete(permission_name, force=True, sync_perm=False)
|
||||||
|
|
||||||
if os.path.exists(app_setting_path):
|
if os.path.exists(app_setting_path):
|
||||||
shutil.rmtree(app_setting_path)
|
shutil.rmtree(app_setting_path)
|
||||||
|
|
|
@ -41,29 +41,23 @@ class AppResourceManager:
|
||||||
def __init__(self, app: str, current: Dict, wanted: Dict):
|
def __init__(self, app: str, current: Dict, wanted: Dict):
|
||||||
|
|
||||||
self.app = app
|
self.app = app
|
||||||
self.current = current
|
self.current = current.get("resources", {})
|
||||||
self.wanted = wanted
|
self.wanted = wanted.get("resources", {})
|
||||||
|
|
||||||
|
# c.f. the permission ressources where we need the app label >_>
|
||||||
|
self.wanted_manifest = wanted
|
||||||
|
|
||||||
def apply(self, **context):
|
def apply(self, **context):
|
||||||
|
|
||||||
for name, infos in self.wanted.items():
|
|
||||||
resource = AppResourceClassesByType[name](infos, self.app)
|
|
||||||
# FIXME: not a great place to check this because here
|
|
||||||
# we already started an operation
|
|
||||||
# We should find a way to validate this before actually starting
|
|
||||||
# the install procedure / theoperation log
|
|
||||||
if name not in self.current.keys():
|
|
||||||
resource.validate_availability(context=context)
|
|
||||||
|
|
||||||
for name, infos in reversed(self.current.items()):
|
for name, infos in reversed(self.current.items()):
|
||||||
if name not in self.wanted.keys():
|
if name not in self.wanted.keys():
|
||||||
resource = AppResourceClassesByType[name](infos, self.app)
|
resource = AppResourceClassesByType[name](infos, self.app, self)
|
||||||
# FIXME : i18n, better info strings
|
# FIXME : i18n, better info strings
|
||||||
logger.info(f"Deprovisionning {name} ...")
|
logger.info(f"Deprovisionning {name} ...")
|
||||||
resource.deprovision(context=context)
|
resource.deprovision(context=context)
|
||||||
|
|
||||||
for name, infos in self.wanted.items():
|
for name, infos in self.wanted.items():
|
||||||
resource = AppResourceClassesByType[name](infos, self.app)
|
resource = AppResourceClassesByType[name](infos, self.app, self)
|
||||||
if name not in self.current.keys():
|
if name not in self.current.keys():
|
||||||
# FIXME : i18n, better info strings
|
# FIXME : i18n, better info strings
|
||||||
logger.info(f"Provisionning {name} ...")
|
logger.info(f"Provisionning {name} ...")
|
||||||
|
@ -75,9 +69,10 @@ class AppResourceManager:
|
||||||
|
|
||||||
class AppResource:
|
class AppResource:
|
||||||
|
|
||||||
def __init__(self, properties: Dict[str, Any], app: str):
|
def __init__(self, properties: Dict[str, Any], app: str, manager: str):
|
||||||
|
|
||||||
self.app = app
|
self.app = app
|
||||||
|
self.manager = manager
|
||||||
|
|
||||||
for key, value in self.default_properties.items():
|
for key, value in self.default_properties.items():
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
|
@ -101,9 +96,6 @@ class AppResource:
|
||||||
from yunohost.app import app_setting
|
from yunohost.app import app_setting
|
||||||
app_setting(self.app, key, delete=True)
|
app_setting(self.app, key, delete=True)
|
||||||
|
|
||||||
def validate_availability(self, context: Dict):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _run_script(self, action, script, env={}, user="root"):
|
def _run_script(self, action, script, env={}, user="root"):
|
||||||
|
|
||||||
from yunohost.app import _make_tmp_workdir_for_app, _make_environment_for_app_script
|
from yunohost.app import _make_tmp_workdir_for_app, _make_environment_for_app_script
|
||||||
|
@ -131,7 +123,7 @@ ynh_abort_if_errors
|
||||||
#print(ret)
|
#print(ret)
|
||||||
|
|
||||||
|
|
||||||
class WebpathResource(AppResource):
|
class PermissionsResource(AppResource):
|
||||||
"""
|
"""
|
||||||
is_provisioned -> main perm exists
|
is_provisioned -> main perm exists
|
||||||
is_available -> perm urls do not conflict
|
is_available -> perm urls do not conflict
|
||||||
|
@ -147,38 +139,98 @@ class WebpathResource(AppResource):
|
||||||
restore -> handled by the core, should be integrated in there (restore .ldif/yml?)
|
restore -> handled by the core, should be integrated in there (restore .ldif/yml?)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type = "webpath"
|
type = "permissions"
|
||||||
priority = 10
|
priority = 10
|
||||||
|
|
||||||
default_properties = {
|
default_properties = {
|
||||||
"full_domain": False,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate_availability(self, context):
|
default_perm_properties = {
|
||||||
|
"url": None,
|
||||||
|
"additional_urls": [],
|
||||||
|
"auth_header": True,
|
||||||
|
"allowed": None,
|
||||||
|
"show_tile": None, # To be automagically set to True by default if an url is defined and show_tile not provided
|
||||||
|
"protected": False,
|
||||||
|
}
|
||||||
|
|
||||||
from yunohost.app import _assert_no_conflicting_apps
|
def __init__(self, properties: Dict[str, Any], *args, **kwargs):
|
||||||
domain = self.get_setting("domain")
|
|
||||||
path = self.get_setting("path") if not self.full_domain else "/"
|
for perm, infos in properties.items():
|
||||||
_assert_no_conflicting_apps(domain, path, ignore_app=self.app)
|
properties[perm] = copy.copy(self.default_perm_properties)
|
||||||
|
properties[perm].update(infos)
|
||||||
|
if properties[perm]["show_tile"] is None:
|
||||||
|
properties[perm]["show_tile"] = bool(properties[perm]["url"])
|
||||||
|
|
||||||
|
if isinstance(properties["main"]["url"], str) and properties["main"]["url"] != "/":
|
||||||
|
raise YunohostError("URL for the 'main' permission should be '/' for webapps (or undefined/None for non-webapps). Note that / refers to the install url of the app")
|
||||||
|
|
||||||
|
super().__init__({"permissions": properties}, *args, **kwargs)
|
||||||
|
|
||||||
def provision_or_update(self, context: Dict):
|
def provision_or_update(self, context: Dict):
|
||||||
|
|
||||||
from yunohost.permission import (
|
from yunohost.permission import (
|
||||||
permission_url,
|
permission_create,
|
||||||
|
#permission_url,
|
||||||
|
permission_delete,
|
||||||
|
user_permission_list,
|
||||||
user_permission_update,
|
user_permission_update,
|
||||||
permission_sync_to_user,
|
permission_sync_to_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
if context.get("action") == "install":
|
# Delete legacy is_public setting if not already done
|
||||||
permission_url(f"{self.app}.main", url="/", sync_perm=False)
|
self.delete_setting(f"is_public")
|
||||||
user_permission_update(f"{self.app}.main", show_tile=True, sync_perm=False)
|
|
||||||
permission_sync_to_user()
|
existing_perms = user_permission_list(short=True, apps=[self.app])["permissions"]
|
||||||
|
for perm in existing_perms:
|
||||||
|
if perm.split(".") not in self.permissions.keys():
|
||||||
|
permission_delete(perm, force=True, sync_perm=False)
|
||||||
|
|
||||||
|
for perm, infos in self.permissions.items():
|
||||||
|
if f"{self.app}.{perm}" not in existing_perms:
|
||||||
|
# Use the 'allowed' key from the manifest,
|
||||||
|
# or use the 'init_{perm}_permission' from the install questions
|
||||||
|
# which is temporarily saved as a setting as an ugly hack to pass the info to this piece of code...
|
||||||
|
init_allowed = infos["allowed"] or self.get_setting(f"init_{perm}_permission") or []
|
||||||
|
permission_create(
|
||||||
|
f"{self.app}.{perm}",
|
||||||
|
allowed=init_allowed,
|
||||||
|
# This is why the ugly hack with self.manager and wanted_manifest exists >_>
|
||||||
|
label=self.manager.wanted_manifest["name"] if perm == "main" else perm,
|
||||||
|
url=infos["url"],
|
||||||
|
additional_urls=infos["additional_urls"],
|
||||||
|
auth_header=infos["auth_header"],
|
||||||
|
sync_perm=False,
|
||||||
|
)
|
||||||
|
self.delete_setting(f"init_{perm}_permission")
|
||||||
|
|
||||||
|
user_permission_update(
|
||||||
|
f"{self.app}.{perm}",
|
||||||
|
show_tile=infos["show_tile"],
|
||||||
|
protected=infos["protected"],
|
||||||
|
sync_perm=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
# FIXME : current implementation of permission_url is hell for
|
||||||
|
# easy declarativeness of additional_urls >_> ...
|
||||||
|
#permission_url(f"{self.app}.{perm}", url=infos["url"], auth_header=infos["auth_header"], sync_perm=False)
|
||||||
|
|
||||||
|
permission_sync_to_user()
|
||||||
|
|
||||||
def deprovision(self, context: Dict):
|
def deprovision(self, context: Dict):
|
||||||
self.delete_setting("domain")
|
|
||||||
self.delete_setting("path")
|
from yunohost.permission import (
|
||||||
# FIXME : theoretically here, should also remove the url in the main permission ?
|
permission_delete,
|
||||||
# but is that worth the trouble ?
|
user_permission_list,
|
||||||
|
permission_sync_to_user,
|
||||||
|
)
|
||||||
|
|
||||||
|
existing_perms = user_permission_list(short=True, apps=[self.app])["permissions"]
|
||||||
|
for perm in existing_perms:
|
||||||
|
permission_delete(perm, force=True, sync_perm=False)
|
||||||
|
|
||||||
|
permission_sync_to_user()
|
||||||
|
|
||||||
|
|
||||||
class SystemuserAppResource(AppResource):
|
class SystemuserAppResource(AppResource):
|
||||||
|
@ -205,19 +257,11 @@ class SystemuserAppResource(AppResource):
|
||||||
"allow_sftp": []
|
"allow_sftp": []
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate_availability(self, context):
|
|
||||||
pass
|
|
||||||
# FIXME : do we care if user already exists ? shouldnt we assume that user $app corresponds to the app ...?
|
|
||||||
|
|
||||||
# FIXME : but maybe we should at least check that no corresponding yunohost user exists
|
|
||||||
|
|
||||||
#if os.system(f"getent passwd {self.username} &>/dev/null") != 0:
|
|
||||||
# raise YunohostValidationError(f"User {self.username} already exists")
|
|
||||||
#if os.system(f"getent group {self.username} &>/dev/null") != 0:
|
|
||||||
# raise YunohostValidationError(f"Group {self.username} already exists")
|
|
||||||
|
|
||||||
def provision_or_update(self, context: Dict):
|
def provision_or_update(self, context: Dict):
|
||||||
|
|
||||||
|
# FIXME : validate that no yunohost user exists with that name?
|
||||||
|
# and/or that no system user exists during install ?
|
||||||
|
|
||||||
if not check_output(f"getent passwd {self.app} &>/dev/null || true").strip():
|
if not check_output(f"getent passwd {self.app} &>/dev/null || true").strip():
|
||||||
# FIXME: improve error handling ?
|
# FIXME: improve error handling ?
|
||||||
cmd = f"useradd --system --user-group {self.app}"
|
cmd = f"useradd --system --user-group {self.app}"
|
||||||
|
@ -291,9 +335,6 @@ class InstalldirAppResource(AppResource):
|
||||||
# FIXME: change default dir to /opt/stuff if app ain't a webapp ...
|
# FIXME: change default dir to /opt/stuff if app ain't a webapp ...
|
||||||
# FIXME: what do in a scenario where the location changed
|
# FIXME: what do in a scenario where the location changed
|
||||||
|
|
||||||
def validate_availability(self, context):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def provision_or_update(self, context: Dict):
|
def provision_or_update(self, context: Dict):
|
||||||
|
|
||||||
current_install_dir = self.get_setting("install_dir")
|
current_install_dir = self.get_setting("install_dir")
|
||||||
|
@ -354,11 +395,6 @@ class DatadirAppResource(AppResource):
|
||||||
"group": "__APP__:rx",
|
"group": "__APP__:rx",
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate_availability(self, context):
|
|
||||||
pass
|
|
||||||
# Nothing to do ? If datadir already exists then it may be legit data
|
|
||||||
# from a previous install
|
|
||||||
|
|
||||||
def provision_or_update(self, context: Dict):
|
def provision_or_update(self, context: Dict):
|
||||||
|
|
||||||
current_data_dir = self.get_setting("data_dir")
|
current_data_dir = self.get_setting("data_dir")
|
||||||
|
|
Loading…
Add table
Reference in a new issue