From 2e6932928b80c4a5f01638a68b57a31954aa0696 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 5 Jul 2019 22:24:48 +0200 Subject: [PATCH 1/6] Split handling of hook_exec according to hook type --- src/yunohost/hook.py | 71 ++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 67e77f033..e9cabcbc3 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -311,7 +311,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 +318,49 @@ 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) + # Check the type of the hook (bash by default) + hook_type = "bash" + # (non-bash hooks shall start with something like "#!/usr/bin/env language") + hook_ext = os.path.splitext(path)[1] + if hook_ext == ".py": + hook_type = "python" + else: + # TODO / FIXME : if needed in the future, implement support for other + # languages... + assert hook_ext == "", "hook_exec only supports bash and python hooks for now" + + # 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()) + ) + + if hook_type == "bash": + returncode, returndata = _hook_exec_bash(path, args, no_trace, chdir, env, user, return_format, loggers) + elif hook_type == "python": + returncode, returndata = _hook_exec_python(path, args, env, loggers) + else: + # Doesn't happen ... c.f. previous assertion + returncode, returndata = None, {} + + # 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 +411,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 +449,11 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, return returncode, returncontent +def _hook_exec_python(path, args, env, loggers): + + pass + + def _extract_filename_parts(filename): """Extract hook parts from filename""" if '-' in filename: From 491959f16e87c1b411c1c3a01483ca755ee3f13d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 5 Jul 2019 22:27:53 +0200 Subject: [PATCH 2/6] Implement Python hooks handling --- src/yunohost/hook.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index e9cabcbc3..8c679b601 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -25,8 +25,10 @@ """ import os import re +import sys import tempfile from glob import iglob +from importlib import import_module from moulinette import m18n, msettings from yunohost.utils.error import YunohostError @@ -179,7 +181,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) @@ -451,7 +453,16 @@ def _hook_exec_bash(path, args, no_trace, chdir, env, user, return_format, logge def _hook_exec_python(path, args, env, loggers): - pass + 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) + + # TODO : We might want to check here that it's a tuple + # containing an int + a dict ? + return module.main(args, env, loggers) def _extract_filename_parts(filename): @@ -461,6 +472,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 From 8c4d136a7f30e823b09345c1eb0b2912722024f2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Jul 2019 18:29:37 +0200 Subject: [PATCH 3/6] Check python hook did return the expected format --- src/yunohost/hook.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 8c679b601..a9fd3186c 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -462,7 +462,13 @@ def _hook_exec_python(path, args, env, loggers): # TODO : We might want to check here that it's a tuple # containing an int + a dict ? - return module.main(args, env, loggers) + ret = module.main(args, env, loggers) + 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): From 81843b2b957ff88c4c1b7b8f897bed567510c6bf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 3 Aug 2019 20:56:36 +0200 Subject: [PATCH 4/6] Support .sh as an exception for bash hooks --- src/yunohost/hook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index a9fd3186c..64b4cc2ee 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -329,7 +329,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, else: # TODO / FIXME : if needed in the future, implement support for other # languages... - assert hook_ext == "", "hook_exec only supports bash and python hooks for now" + assert hook_ext in ["", ".sh"], "hook_exec only supports bash and python hooks for now" # Define output loggers and call command loggers = ( From 2ffb413a46142e97e976ff4e4be9cbb56760dfbf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 24 Aug 2019 15:49:25 +0200 Subject: [PATCH 5/6] This TODO is implemented now --- src/yunohost/hook.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 64b4cc2ee..8831370bd 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -460,9 +460,8 @@ def _hook_exec_python(path, args, env, loggers): sys.path = [dir_] + sys.path module = import_module(name) - # TODO : We might want to check here that it's a tuple - # containing an int + a dict ? 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) \ From ff2caae18300449c0dcb3264e9bb9635dcc2af45 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 24 Aug 2019 15:56:52 +0200 Subject: [PATCH 6/6] Now guess hook type using mimetypes lib --- src/yunohost/hook.py | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 8831370bd..05c5a6b6b 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -27,6 +27,7 @@ import os import re import sys import tempfile +import mimetypes from glob import iglob from importlib import import_module @@ -320,17 +321,6 @@ 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) - # Check the type of the hook (bash by default) - hook_type = "bash" - # (non-bash hooks shall start with something like "#!/usr/bin/env language") - hook_ext = os.path.splitext(path)[1] - if hook_ext == ".py": - hook_type = "python" - else: - # TODO / FIXME : if needed in the future, implement support for other - # languages... - assert hook_ext in ["", ".sh"], "hook_exec only supports bash and python hooks for now" - # Define output loggers and call command loggers = ( lambda l: logger.debug(l.rstrip()+"\r"), @@ -338,13 +328,13 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, lambda l: logger.info(l.rstrip()) ) - if hook_type == "bash": - returncode, returndata = _hook_exec_bash(path, args, no_trace, chdir, env, user, return_format, loggers) - elif hook_type == "python": + # 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: - # Doesn't happen ... c.f. previous assertion - returncode, returndata = None, {} + 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: