diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index e39aa8d47..0374bce7b 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1345,14 +1345,6 @@ hook: help: Ordered list of arguments to pass to the script nargs: "*" - ### hook_check() - check: - action_help: Parse the script file and get arguments - api: GET /hook/check - arguments: - file: - help: File to check - ### hook_exec() exec: action_help: Execute hook from a file with arguments diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 325a68bb3..ccbed653f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -383,9 +383,14 @@ def app_upgrade(auth, app=[], url=None, file=None): for hook in os.listdir(app_tmp_folder +'/hooks'): hook_add(app_id, app_tmp_folder +'/hooks/'+ hook) + # Retrieve arguments list for upgrade script + # TODO: Allow to specify arguments + args_list = _parse_args_from_manifest(manifest, 'upgrade') + args_list.append(app_id) + # Execute App upgrade script os.system('chown -hR admin: %s' % install_tmp) - if hook_exec(app_tmp_folder +'/scripts/upgrade') != 0: + if hook_exec(app_tmp_folder +'/scripts/upgrade', args_list) != 0: raise MoulinetteError(errno.EIO, m18n.n('installation_failed')) else: now = int(time.time()) @@ -514,12 +519,11 @@ def app_install(auth, app, label=None, args=None): os.system('chown -R admin: '+ app_tmp_folder) - try: - if args is None: - args = '' - args_dict = dict(urlparse.parse_qsl(args, keep_blank_values=True)) - except: - args_dict = {} + # Retrieve arguments list for install script + args_dict = {} if not args else \ + dict(urlparse.parse_qsl(args, keep_blank_values=True)) + args_list = _parse_args_from_manifest(manifest, 'install', args_dict) + args_list.append(app_id) # Execute App install script os.system('chown -hR admin: %s' % install_tmp) @@ -527,7 +531,7 @@ def app_install(auth, app, label=None, args=None): os.system('cp %s/manifest.json %s' % (app_tmp_folder, app_setting_path)) os.system('cp -R %s/scripts %s' % (app_tmp_folder, app_setting_path)) try: - if hook_exec(app_tmp_folder + '/scripts/install', args_dict) == 0: + if hook_exec(app_tmp_folder + '/scripts/install', args_list) == 0: # Store app status with open(app_setting_path + '/status.json', 'w+') as f: json.dump(status, f) @@ -584,7 +588,9 @@ def app_remove(auth, app): os.system('chown -R admin: /tmp/yunohost_remove') os.system('chmod -R u+rX /tmp/yunohost_remove') - if hook_exec('/tmp/yunohost_remove/scripts/remove') == 0: + args_list = [app] + + if hook_exec('/tmp/yunohost_remove/scripts/remove', args_list) == 0: msignals.display(m18n.n('app_removed', app), 'success') if os.path.exists(app_setting_path): shutil.rmtree(app_setting_path) @@ -1337,6 +1343,57 @@ def _encode_string(value): return value +def _parse_args_from_manifest(manifest, action, args={}): + """Parse arguments needed for an action from the manifest + + Retrieve specified arguments for the action from the manifest, and parse + given args according to that. If some required arguments are not provided, + its values will be asked if interaction is possible. + Parsed arguments will be returned as a list of strings to pass directly + to the proper script. + + Keyword arguments: + manifest -- The app manifest to use + action -- The action to retrieve arguments for + args -- A dictionnary of arguments to parse + + """ + args_list = [] + try: + action_args = manifest['arguments'][action] + except KeyError: + logger.debug("no arguments found for '%s' in '%s'", action, path) + else: + for arg in action_args: + if arg['name'] in args: + if 'choices' in arg and args[arg['name']] not in arg['choices']: + raise MoulinetteError(errno.EINVAL, + m18n.n('hook_choice_invalid', args[arg['name']])) + args_list.append(args[arg['name']]) + else: + if os.isatty(1) and 'ask' in arg: + # Retrieve proper ask string + ask_string = _value_for_locale(arg['ask']) + + # Append extra strings + if 'choices' in arg: + ask_string += ' ({:s})'.format('|'.join(arg['choices'])) + if 'default' in arg: + ask_string += ' (default: {:s})'.format(arg['default']) + + input_string = msignals.prompt(ask_string) + if not input_string and 'default' in arg: + input_string = arg['default'] + + args_list.append(input_string) + elif 'default' in arg: + args_list.append(arg['default']) + else: + raise MoulinetteError(errno.EINVAL, + m18n.n('hook_argument_missing', arg['name'])) + return args_list + + def is_true(arg): """ Convert a string into a boolean diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 9c6d430e7..2b0902b6f 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -275,34 +275,13 @@ def hook_callback(action, hooks=[], args=None): return result -def hook_check(file): - """ - Parse the script file and get arguments - - Keyword argument: - file -- File to check - - """ - try: - with open(file[:file.index('scripts/')] + 'manifest.json') as f: - manifest = json.loads(str(f.read())) - except: - raise MoulinetteError(errno.EIO, m18n.n('app_manifest_invalid')) - - action = file[file.index('scripts/') + 8:] - if 'arguments' in manifest and action in manifest['arguments']: - return manifest['arguments'][action] - else: - return {} - - def hook_exec(path, args=None, raise_on_error=False, no_trace=False): """ Execute hook from a file with arguments Keyword argument: path -- Path of the script to execute - args -- Arguments to pass to the script + args -- A list of arguments to pass to the script raise_on_error -- Raise if the script returns a non-zero exit code no_trace -- Do not print each command that will be executed @@ -316,52 +295,15 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False): if not os.path.isfile(path): raise MoulinetteError(errno.EIO, m18n.g('file_not_exist')) - if isinstance(args, list): - arg_list = args - else: - required_args = hook_check(path) - if args is None: - args = {} - - arg_list = [] - for arg in required_args: - if arg['name'] in args: - if 'choices' in arg and args[arg['name']] not in arg['choices']: - raise MoulinetteError(errno.EINVAL, - m18n.n('hook_choice_invalid', args[arg['name']])) - arg_list.append(args[arg['name']]) - else: - if os.isatty(1) and 'ask' in arg: - # Retrieve proper ask string - ask_string = _value_for_locale(arg['ask']) - - # Append extra strings - if 'choices' in arg: - ask_string += ' ({:s})'.format('|'.join(arg['choices'])) - if 'default' in arg: - ask_string += ' (default: {:s})'.format(arg['default']) - - input_string = msignals.prompt(ask_string) - - if input_string == '' and 'default' in arg: - input_string = arg['default'] - - arg_list.append(input_string) - elif 'default' in arg: - arg_list.append(arg['default']) - else: - raise MoulinetteError(errno.EINVAL, - m18n.n('hook_argument_missing', arg['name'])) - # Construct command variables cmd_fdir, cmd_fname = os.path.split(path) cmd_fname = './{0}'.format(cmd_fname) cmd_args = '' - if arg_list: + if args and isinstance(args, list): # Concatenate arguments and escape them with double quotes to prevent # bash related issue if an argument is empty and is not the last - cmd_args = '"{:s}"'.format('" "'.join(str(s) for s in arg_list)) + cmd_args = '"{:s}"'.format('" "'.join(str(s) for s in args)) # Construct command to execute command = ['sudo', '-u', 'admin', '-H', 'sh', '-c']