diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 67e77f033..05c5a6b6b 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -25,8 +25,11 @@ """ import os import re +import sys import tempfile +import mimetypes from glob import iglob +from importlib import import_module from moulinette import m18n, msettings from yunohost.utils.error import YunohostError @@ -179,7 +182,7 @@ def hook_list(action, list_by='name', show_info=False): def _append_folder(d, folder): # Iterate over and add hook from a folder for f in os.listdir(folder + action): - if f[0] == '.' or f[-1] == '~': + if f[0] == '.' or f[-1] == '~' or f.endswith(".pyc"): continue path = '%s%s/%s' % (folder, action, f) priority, name = _extract_filename_parts(f) @@ -311,7 +314,6 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, user -- User with which to run the command """ - from moulinette.utils.process import call_async_output # Validate hook path if path[0] != '/': @@ -319,6 +321,38 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, if not os.path.isfile(path): raise YunohostError('file_does_not_exist', path=path) + # Define output loggers and call command + loggers = ( + lambda l: logger.debug(l.rstrip()+"\r"), + lambda l: logger.warning(l.rstrip()), + lambda l: logger.info(l.rstrip()) + ) + + # Check the type of the hook (bash by default) + # For now we support only python and bash hooks. + hook_type = mimetypes.MimeTypes().guess_type(path)[0] + if hook_type == 'text/x-python': + returncode, returndata = _hook_exec_python(path, args, env, loggers) + else: + returncode, returndata = _hook_exec_bash(path, args, no_trace, chdir, env, user, return_format, loggers) + + # Check and return process' return code + if returncode is None: + if raise_on_error: + raise YunohostError('hook_exec_not_terminated', path=path) + else: + logger.error(m18n.n('hook_exec_not_terminated', path=path)) + return 1, {} + elif raise_on_error and returncode != 0: + raise YunohostError('hook_exec_failed', path=path) + + return returncode, returndata + + +def _hook_exec_bash(path, args, no_trace, chdir, env, user, return_format, loggers): + + from moulinette.utils.process import call_async_output + # Construct command variables cmd_args = '' if args and isinstance(args, list): @@ -369,33 +403,13 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, else: logger.debug(m18n.n('executing_script', script=path)) - # Define output callbacks and call command - callbacks = ( - lambda l: logger.debug(l.rstrip()+"\r"), - lambda l: logger.warning(l.rstrip()), - ) - - if stdinfo: - callbacks = (callbacks[0], callbacks[1], - lambda l: logger.info(l.rstrip())) - logger.debug("About to run the command '%s'" % command) returncode = call_async_output( - command, callbacks, shell=False, cwd=chdir, + command, loggers, shell=False, cwd=chdir, stdinfo=stdinfo ) - # Check and return process' return code - if returncode is None: - if raise_on_error: - raise YunohostError('hook_exec_not_terminated', path=path) - else: - logger.error(m18n.n('hook_exec_not_terminated', path=path)) - return 1, {} - elif raise_on_error and returncode != 0: - raise YunohostError('hook_exec_failed', path=path) - raw_content = None try: with open(stdreturn, 'r') as f: @@ -427,6 +441,25 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, return returncode, returncontent +def _hook_exec_python(path, args, env, loggers): + + dir_ = os.path.dirname(path) + name = os.path.splitext(os.path.basename(path))[0] + + if not dir_ in sys.path: + sys.path = [dir_] + sys.path + module = import_module(name) + + ret = module.main(args, env, loggers) + # # Assert that the return is a (int, dict) tuple + assert isinstance(ret, tuple) \ + and len(ret) == 2 \ + and isinstance(ret[0],int) \ + and isinstance(ret[1],dict), \ + "Module %s did not return a (int, dict) tuple !" % module + return ret + + def _extract_filename_parts(filename): """Extract hook parts from filename""" if '-' in filename: @@ -434,6 +467,9 @@ def _extract_filename_parts(filename): else: priority = '50' action = filename + + # Remove extension if there's one + action = os.path.splitext(action)[0] return priority, action