mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge pull request #526 from YunoHost/hook_return
[enh] Allow hooks to return data
This commit is contained in:
commit
cea5c81e92
5 changed files with 79 additions and 32 deletions
|
@ -219,6 +219,7 @@
|
||||||
"good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",
|
"good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",
|
||||||
"hook_exec_failed": "Script execution failed: {path:s}",
|
"hook_exec_failed": "Script execution failed: {path:s}",
|
||||||
"hook_exec_not_terminated": "Script execution did not finish properly: {path:s}",
|
"hook_exec_not_terminated": "Script execution did not finish properly: {path:s}",
|
||||||
|
"hook_json_return_error": "Failed to read return from hook {path:s}. Error: {msg:s}. Raw content: {raw_content}",
|
||||||
"hook_list_by_invalid": "Invalid property to list hook by",
|
"hook_list_by_invalid": "Invalid property to list hook by",
|
||||||
"hook_name_unknown": "Unknown hook name '{name:s}'",
|
"hook_name_unknown": "Unknown hook name '{name:s}'",
|
||||||
"installation_complete": "Installation complete",
|
"installation_complete": "Installation complete",
|
||||||
|
|
|
@ -523,7 +523,7 @@ def app_change_url(operation_logger, auth, app, domain, path):
|
||||||
os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts", "change_url")))
|
os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts", "change_url")))
|
||||||
|
|
||||||
if hook_exec(os.path.join(APP_TMP_FOLDER, 'scripts/change_url'),
|
if hook_exec(os.path.join(APP_TMP_FOLDER, 'scripts/change_url'),
|
||||||
args=args_list, env=env_dict) != 0:
|
args=args_list, env=env_dict)[0] != 0:
|
||||||
msg = "Failed to change '%s' url." % app
|
msg = "Failed to change '%s' url." % app
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
operation_logger.error(msg)
|
operation_logger.error(msg)
|
||||||
|
@ -654,7 +654,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
||||||
# Execute App upgrade script
|
# Execute App upgrade script
|
||||||
os.system('chown -hR admin: %s' % INSTALL_TMP)
|
os.system('chown -hR admin: %s' % INSTALL_TMP)
|
||||||
if hook_exec(extracted_app_folder + '/scripts/upgrade',
|
if hook_exec(extracted_app_folder + '/scripts/upgrade',
|
||||||
args=args_list, env=env_dict) != 0:
|
args=args_list, env=env_dict)[0] != 0:
|
||||||
msg = m18n.n('app_upgrade_failed', app=app_instance_name)
|
msg = m18n.n('app_upgrade_failed', app=app_instance_name)
|
||||||
not_upgraded_apps.append(app_instance_name)
|
not_upgraded_apps.append(app_instance_name)
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
|
@ -847,7 +847,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
|
||||||
install_retcode = hook_exec(
|
install_retcode = hook_exec(
|
||||||
os.path.join(extracted_app_folder, 'scripts/install'),
|
os.path.join(extracted_app_folder, 'scripts/install'),
|
||||||
args=args_list, env=env_dict
|
args=args_list, env=env_dict
|
||||||
)
|
)[0]
|
||||||
except (KeyboardInterrupt, EOFError):
|
except (KeyboardInterrupt, EOFError):
|
||||||
install_retcode = -1
|
install_retcode = -1
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -872,7 +872,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
|
||||||
remove_retcode = hook_exec(
|
remove_retcode = hook_exec(
|
||||||
os.path.join(extracted_app_folder, 'scripts/remove'),
|
os.path.join(extracted_app_folder, 'scripts/remove'),
|
||||||
args=[app_instance_name], env=env_dict_remove
|
args=[app_instance_name], env=env_dict_remove
|
||||||
)
|
)[0]
|
||||||
if remove_retcode != 0:
|
if remove_retcode != 0:
|
||||||
msg = m18n.n('app_not_properly_removed',
|
msg = m18n.n('app_not_properly_removed',
|
||||||
app=app_instance_name)
|
app=app_instance_name)
|
||||||
|
@ -963,7 +963,7 @@ def app_remove(operation_logger, auth, app):
|
||||||
operation_logger.flush()
|
operation_logger.flush()
|
||||||
|
|
||||||
if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list,
|
if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list,
|
||||||
env=env_dict) == 0:
|
env=env_dict)[0] == 0:
|
||||||
logger.success(m18n.n('app_removed', app=app))
|
logger.success(m18n.n('app_removed', app=app))
|
||||||
|
|
||||||
hook_callback('post_app_remove', args=args_list, env=env_dict)
|
hook_callback('post_app_remove', args=args_list, env=env_dict)
|
||||||
|
@ -1562,7 +1562,7 @@ def app_action_run(app, action, args=None):
|
||||||
env=env_dict,
|
env=env_dict,
|
||||||
chdir=cwd,
|
chdir=cwd,
|
||||||
user=action_declaration.get("user", "root"),
|
user=action_declaration.get("user", "root"),
|
||||||
)
|
)[0]
|
||||||
|
|
||||||
if retcode not in action_declaration.get("accepted_return_codes", [0]):
|
if retcode not in action_declaration.get("accepted_return_codes", [0]):
|
||||||
raise YunohostError("Error while executing action '%s' of app '%s': return code %s" % (action, app, retcode), raw_msg=True)
|
raise YunohostError("Error while executing action '%s' of app '%s': return code %s" % (action, app, retcode), raw_msg=True)
|
||||||
|
|
|
@ -593,8 +593,15 @@ class BackupManager():
|
||||||
env=env_dict,
|
env=env_dict,
|
||||||
chdir=self.work_dir)
|
chdir=self.work_dir)
|
||||||
|
|
||||||
if ret["succeed"] != []:
|
ret_succeed = {hook: {path:result["state"] for path, result in infos.items()}
|
||||||
self.system_return = ret["succeed"]
|
for hook, infos in ret.items()
|
||||||
|
if any(result["state"] == "succeed" for result in infos.values())}
|
||||||
|
ret_failed = {hook: {path:result["state"] for path, result in infos.items.items()}
|
||||||
|
for hook, infos in ret.items()
|
||||||
|
if any(result["state"] == "failed" for result in infos.values())}
|
||||||
|
|
||||||
|
if ret_succeed.keys() != []:
|
||||||
|
self.system_return = ret_succeed
|
||||||
|
|
||||||
# Add files from targets (which they put in the CSV) to the list of
|
# Add files from targets (which they put in the CSV) to the list of
|
||||||
# files to backup
|
# files to backup
|
||||||
|
@ -610,7 +617,7 @@ class BackupManager():
|
||||||
|
|
||||||
restore_hooks = hook_list("restore")["hooks"]
|
restore_hooks = hook_list("restore")["hooks"]
|
||||||
|
|
||||||
for part in ret['succeed'].keys():
|
for part in ret_succeed.keys():
|
||||||
if part in restore_hooks:
|
if part in restore_hooks:
|
||||||
part_restore_hooks = hook_info("restore", part)["hooks"]
|
part_restore_hooks = hook_info("restore", part)["hooks"]
|
||||||
for hook in part_restore_hooks:
|
for hook in part_restore_hooks:
|
||||||
|
@ -620,7 +627,7 @@ class BackupManager():
|
||||||
logger.warning(m18n.n('restore_hook_unavailable', hook=part))
|
logger.warning(m18n.n('restore_hook_unavailable', hook=part))
|
||||||
self.targets.set_result("system", part, "Warning")
|
self.targets.set_result("system", part, "Warning")
|
||||||
|
|
||||||
for part in ret['failed'].keys():
|
for part in ret_failed.keys():
|
||||||
logger.error(m18n.n('backup_system_part_failed', part=part))
|
logger.error(m18n.n('backup_system_part_failed', part=part))
|
||||||
self.targets.set_result("system", part, "Error")
|
self.targets.set_result("system", part, "Error")
|
||||||
|
|
||||||
|
@ -682,7 +689,7 @@ class BackupManager():
|
||||||
subprocess.call(['install', '-Dm555', app_script, tmp_script])
|
subprocess.call(['install', '-Dm555', app_script, tmp_script])
|
||||||
|
|
||||||
hook_exec(tmp_script, args=[tmp_app_bkp_dir, app],
|
hook_exec(tmp_script, args=[tmp_app_bkp_dir, app],
|
||||||
raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict)
|
raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict)[0]
|
||||||
|
|
||||||
self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"])
|
self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"])
|
||||||
except:
|
except:
|
||||||
|
@ -1177,16 +1184,21 @@ class RestoreManager():
|
||||||
env=env_dict,
|
env=env_dict,
|
||||||
chdir=self.work_dir)
|
chdir=self.work_dir)
|
||||||
|
|
||||||
for part in ret['succeed'].keys():
|
ret_succeed = [hook for hook, infos in ret.items()
|
||||||
|
if any(result["state"] == "succeed" for result in infos.values())]
|
||||||
|
ret_failed = [hook for hook, infos in ret.items()
|
||||||
|
if any(result["state"] == "failed" for result in infos.values())]
|
||||||
|
|
||||||
|
for part in ret_succeed:
|
||||||
self.targets.set_result("system", part, "Success")
|
self.targets.set_result("system", part, "Success")
|
||||||
|
|
||||||
error_part = []
|
error_part = []
|
||||||
for part in ret['failed'].keys():
|
for part in ret_failed:
|
||||||
logger.error(m18n.n('restore_system_part_failed', part=part))
|
logger.error(m18n.n('restore_system_part_failed', part=part))
|
||||||
self.targets.set_result("system", part, "Error")
|
self.targets.set_result("system", part, "Error")
|
||||||
error_part.append(part)
|
error_part.append(part)
|
||||||
|
|
||||||
if ret['failed']:
|
if ret_failed:
|
||||||
operation_logger.error(m18n.n('restore_system_part_failed', part=', '.join(error_part)))
|
operation_logger.error(m18n.n('restore_system_part_failed', part=', '.join(error_part)))
|
||||||
else:
|
else:
|
||||||
operation_logger.success()
|
operation_logger.success()
|
||||||
|
@ -1301,7 +1313,7 @@ class RestoreManager():
|
||||||
args=[app_backup_in_archive, app_instance_name],
|
args=[app_backup_in_archive, app_instance_name],
|
||||||
chdir=app_backup_in_archive,
|
chdir=app_backup_in_archive,
|
||||||
raise_on_error=True,
|
raise_on_error=True,
|
||||||
env=env_dict)
|
env=env_dict)[0]
|
||||||
except:
|
except:
|
||||||
msg = m18n.n('restore_app_failed', app=app_instance_name)
|
msg = m18n.n('restore_app_failed', app=app_instance_name)
|
||||||
logger.exception(msg)
|
logger.exception(msg)
|
||||||
|
@ -1326,7 +1338,7 @@ class RestoreManager():
|
||||||
# Execute remove script
|
# Execute remove script
|
||||||
# TODO: call app_remove instead
|
# TODO: call app_remove instead
|
||||||
if hook_exec(remove_script, args=[app_instance_name],
|
if hook_exec(remove_script, args=[app_instance_name],
|
||||||
env=env_dict_remove) != 0:
|
env=env_dict_remove)[0] != 0:
|
||||||
msg = m18n.n('app_not_properly_removed', app=app_instance_name)
|
msg = m18n.n('app_not_properly_removed', app=app_instance_name)
|
||||||
logger.warning(msg)
|
logger.warning(msg)
|
||||||
operation_logger.error(msg)
|
operation_logger.error(msg)
|
||||||
|
@ -1932,8 +1944,9 @@ class CustomBackupMethod(BackupMethod):
|
||||||
|
|
||||||
ret = hook_callback('backup_method', [self.method],
|
ret = hook_callback('backup_method', [self.method],
|
||||||
args=self._get_args('need_mount'))
|
args=self._get_args('need_mount'))
|
||||||
|
ret_succeed = [hook for hook, infos in ret.items()
|
||||||
self._need_mount = True if ret['succeed'] else False
|
if any(result["state"] == "succeed" for result in infos.values())]
|
||||||
|
self._need_mount = True if ret_succeed else False
|
||||||
return self._need_mount
|
return self._need_mount
|
||||||
|
|
||||||
def backup(self):
|
def backup(self):
|
||||||
|
@ -1946,7 +1959,10 @@ class CustomBackupMethod(BackupMethod):
|
||||||
|
|
||||||
ret = hook_callback('backup_method', [self.method],
|
ret = hook_callback('backup_method', [self.method],
|
||||||
args=self._get_args('backup'))
|
args=self._get_args('backup'))
|
||||||
if ret['failed']:
|
|
||||||
|
ret_failed = [hook for hook, infos in ret.items()
|
||||||
|
if any(result["state"] == "failed" for result in infos.values())]
|
||||||
|
if ret_failed:
|
||||||
raise YunohostError('backup_custom_backup_error')
|
raise YunohostError('backup_custom_backup_error')
|
||||||
|
|
||||||
def mount(self, restore_manager):
|
def mount(self, restore_manager):
|
||||||
|
@ -1959,7 +1975,10 @@ class CustomBackupMethod(BackupMethod):
|
||||||
super(CustomBackupMethod, self).mount(restore_manager)
|
super(CustomBackupMethod, self).mount(restore_manager)
|
||||||
ret = hook_callback('backup_method', [self.method],
|
ret = hook_callback('backup_method', [self.method],
|
||||||
args=self._get_args('mount'))
|
args=self._get_args('mount'))
|
||||||
if ret['failed']:
|
|
||||||
|
ret_failed = [hook for hook, infos in ret.items()
|
||||||
|
if any(result["state"] == "failed" for result in infos.values())]
|
||||||
|
if ret_failed:
|
||||||
raise YunohostError('backup_custom_mount_error')
|
raise YunohostError('backup_custom_mount_error')
|
||||||
|
|
||||||
def _get_args(self, action):
|
def _get_args(self, action):
|
||||||
|
|
|
@ -31,6 +31,7 @@ from glob import iglob
|
||||||
from moulinette import m18n
|
from moulinette import m18n
|
||||||
from yunohost.utils.error import YunohostError
|
from yunohost.utils.error import YunohostError
|
||||||
from moulinette.utils import log
|
from moulinette.utils import log
|
||||||
|
from moulinette.utils.filesystem import read_json
|
||||||
|
|
||||||
HOOK_FOLDER = '/usr/share/yunohost/hooks/'
|
HOOK_FOLDER = '/usr/share/yunohost/hooks/'
|
||||||
CUSTOM_HOOK_FOLDER = '/etc/yunohost/hooks.d/'
|
CUSTOM_HOOK_FOLDER = '/etc/yunohost/hooks.d/'
|
||||||
|
@ -228,7 +229,7 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None,
|
||||||
(name, priority, path, succeed) as arguments
|
(name, priority, path, succeed) as arguments
|
||||||
|
|
||||||
"""
|
"""
|
||||||
result = {'succeed': {}, 'failed': {}}
|
result = {}
|
||||||
hooks_dict = {}
|
hooks_dict = {}
|
||||||
|
|
||||||
# Retrieve hooks
|
# Retrieve hooks
|
||||||
|
@ -278,20 +279,20 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None,
|
||||||
try:
|
try:
|
||||||
hook_args = pre_callback(name=name, priority=priority,
|
hook_args = pre_callback(name=name, priority=priority,
|
||||||
path=path, args=args)
|
path=path, args=args)
|
||||||
hook_exec(path, args=hook_args, chdir=chdir, env=env,
|
hook_return = hook_exec(path, args=hook_args, chdir=chdir, env=env,
|
||||||
no_trace=no_trace, raise_on_error=True)
|
no_trace=no_trace, raise_on_error=True)[1]
|
||||||
except YunohostError as e:
|
except YunohostError as e:
|
||||||
state = 'failed'
|
state = 'failed'
|
||||||
|
hook_return = {}
|
||||||
logger.error(e.strerror, exc_info=1)
|
logger.error(e.strerror, exc_info=1)
|
||||||
post_callback(name=name, priority=priority, path=path,
|
post_callback(name=name, priority=priority, path=path,
|
||||||
succeed=False)
|
succeed=False)
|
||||||
else:
|
else:
|
||||||
post_callback(name=name, priority=priority, path=path,
|
post_callback(name=name, priority=priority, path=path,
|
||||||
succeed=True)
|
succeed=True)
|
||||||
try:
|
if not name in result:
|
||||||
result[state][name].append(path)
|
result[name] = {}
|
||||||
except KeyError:
|
result[name][path] = {'state' : state, 'stdreturn' : hook_return }
|
||||||
result[state][name] = [path]
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -339,6 +340,11 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
|
||||||
stdinfo = os.path.join(tempfile.mkdtemp(), "stdinfo")
|
stdinfo = os.path.join(tempfile.mkdtemp(), "stdinfo")
|
||||||
env['YNH_STDINFO'] = stdinfo
|
env['YNH_STDINFO'] = stdinfo
|
||||||
|
|
||||||
|
stdreturn = os.path.join(tempfile.mkdtemp(), "stdreturn")
|
||||||
|
with open(stdreturn, 'w') as f:
|
||||||
|
f.write('')
|
||||||
|
env['YNH_STDRETURN'] = stdreturn
|
||||||
|
|
||||||
# Construct command to execute
|
# Construct command to execute
|
||||||
if user == "root":
|
if user == "root":
|
||||||
command = ['sh', '-c']
|
command = ['sh', '-c']
|
||||||
|
@ -385,10 +391,27 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
|
||||||
raise YunohostError('hook_exec_not_terminated', path=path)
|
raise YunohostError('hook_exec_not_terminated', path=path)
|
||||||
else:
|
else:
|
||||||
logger.error(m18n.n('hook_exec_not_terminated', path=path))
|
logger.error(m18n.n('hook_exec_not_terminated', path=path))
|
||||||
return 1
|
return 1, {}
|
||||||
elif raise_on_error and returncode != 0:
|
elif raise_on_error and returncode != 0:
|
||||||
raise YunohostError('hook_exec_failed', path=path)
|
raise YunohostError('hook_exec_failed', path=path)
|
||||||
return returncode
|
|
||||||
|
raw_content = None
|
||||||
|
try:
|
||||||
|
with open(stdreturn, 'r') as f:
|
||||||
|
raw_content = f.read()
|
||||||
|
if raw_content != '':
|
||||||
|
returnjson = read_json(stdreturn)
|
||||||
|
else:
|
||||||
|
returnjson = {}
|
||||||
|
except Exception as e:
|
||||||
|
raise YunohostError('hook_json_return_error', path=path, msg=str(e),
|
||||||
|
raw_content=raw_content)
|
||||||
|
finally:
|
||||||
|
stdreturndir = os.path.split(stdreturn)[0]
|
||||||
|
os.remove(stdreturn)
|
||||||
|
os.rmdir(stdreturndir)
|
||||||
|
|
||||||
|
return returncode, returnjson
|
||||||
|
|
||||||
|
|
||||||
def _extract_filename_parts(filename):
|
def _extract_filename_parts(filename):
|
||||||
|
|
|
@ -493,12 +493,16 @@ def service_regen_conf(operation_logger, names=[], with_diff=False, force=False,
|
||||||
|
|
||||||
pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call)
|
pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call)
|
||||||
|
|
||||||
# Update the services name
|
# Keep only the hook names with at least one success
|
||||||
names = pre_result['succeed'].keys()
|
names = [hook for hook, infos in pre_result.items()
|
||||||
|
if any(result["state"] == "succeed" for result in infos.values())]
|
||||||
|
|
||||||
|
# FIXME : what do in case of partial success/failure ...
|
||||||
if not names:
|
if not names:
|
||||||
|
ret_failed = [hook for hook, infos in pre_result.items()
|
||||||
|
if any(result["state"] == "failed" for result in infos.values())]
|
||||||
raise YunohostError('service_regenconf_failed',
|
raise YunohostError('service_regenconf_failed',
|
||||||
services=', '.join(pre_result['failed']))
|
services=', '.join(ret_failed))
|
||||||
|
|
||||||
# Set the processing method
|
# Set the processing method
|
||||||
_regen = _process_regen_conf if not dry_run else lambda *a, **k: True
|
_regen = _process_regen_conf if not dry_run else lambda *a, **k: True
|
||||||
|
|
Loading…
Add table
Reference in a new issue